164 lines
4.2 KiB
C++
164 lines
4.2 KiB
C++
#include "common/GeoJson.h"
|
|
|
|
#include <cmath>
|
|
#include <vector>
|
|
|
|
namespace visione::geojson
|
|
{
|
|
namespace
|
|
{
|
|
struct Point
|
|
{
|
|
double x;
|
|
double y;
|
|
};
|
|
|
|
bool pointInRing(double latitude,
|
|
double longitude,
|
|
const Json::Value &ring,
|
|
bool *onBoundary = nullptr)
|
|
{
|
|
if (!ring.isArray() || ring.size() < 4)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::vector<Point> points;
|
|
points.reserve(ring.size());
|
|
for (const auto &coordinate : ring)
|
|
{
|
|
if (!coordinate.isArray() || coordinate.size() < 2)
|
|
{
|
|
return false;
|
|
}
|
|
points.push_back({coordinate[0].asDouble(), coordinate[1].asDouble()});
|
|
}
|
|
|
|
const auto x = longitude;
|
|
const auto y = latitude;
|
|
bool inside = false;
|
|
constexpr double epsilon = 1e-9;
|
|
for (size_t i = 0, j = points.size() - 1; i < points.size(); j = i++)
|
|
{
|
|
const auto &pi = points[i];
|
|
const auto &pj = points[j];
|
|
const auto cross =
|
|
(x - pi.x) * (pj.y - pi.y) - (y - pi.y) * (pj.x - pi.x);
|
|
const auto minX = std::min(pi.x, pj.x) - epsilon;
|
|
const auto maxX = std::max(pi.x, pj.x) + epsilon;
|
|
const auto minY = std::min(pi.y, pj.y) - epsilon;
|
|
const auto maxY = std::max(pi.y, pj.y) + epsilon;
|
|
if (std::abs(cross) <= epsilon && x >= minX && x <= maxX && y >= minY &&
|
|
y <= maxY)
|
|
{
|
|
if (onBoundary)
|
|
{
|
|
*onBoundary = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const bool intersects =
|
|
((pi.y > y) != (pj.y > y)) &&
|
|
(x < (pj.x - pi.x) * (y - pi.y) / (pj.y - pi.y) + pi.x);
|
|
if (intersects)
|
|
{
|
|
inside = !inside;
|
|
}
|
|
}
|
|
return inside;
|
|
}
|
|
|
|
bool polygonContainsPoint(double latitude,
|
|
double longitude,
|
|
const Json::Value &polygon)
|
|
{
|
|
if (!polygon.isObject() || polygon["type"].asString() != "Polygon" ||
|
|
!polygon["coordinates"].isArray() || polygon["coordinates"].empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool onBoundary = false;
|
|
if (!pointInRing(latitude, longitude, polygon["coordinates"][0], &onBoundary))
|
|
{
|
|
return false;
|
|
}
|
|
if (onBoundary)
|
|
{
|
|
return true;
|
|
}
|
|
for (Json::ArrayIndex i = 1; i < polygon["coordinates"].size(); ++i)
|
|
{
|
|
if (pointInRing(latitude, longitude, polygon["coordinates"][i]))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
} // namespace
|
|
|
|
bool pointInPolygon(double latitude, double longitude, const Json::Value &geojson)
|
|
{
|
|
return polygonContainsPoint(latitude, longitude, geojson);
|
|
}
|
|
|
|
bool pointInGeometry(double latitude, double longitude, const Json::Value &geojson)
|
|
{
|
|
if (!geojson.isObject() || !geojson.isMember("type"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const auto type = geojson["type"].asString();
|
|
if (type == "Polygon")
|
|
{
|
|
return polygonContainsPoint(latitude, longitude, geojson);
|
|
}
|
|
if (type == "MultiPolygon" && geojson["coordinates"].isArray())
|
|
{
|
|
for (const auto &polygonCoords : geojson["coordinates"])
|
|
{
|
|
Json::Value polygon(Json::objectValue);
|
|
polygon["type"] = "Polygon";
|
|
polygon["coordinates"] = polygonCoords;
|
|
if (polygonContainsPoint(latitude, longitude, polygon))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool polygonInsideGeometry(const Json::Value &candidate, const Json::Value &container)
|
|
{
|
|
if (!candidate.isObject() || candidate["type"].asString() != "Polygon" ||
|
|
!candidate["coordinates"].isArray() || candidate["coordinates"].empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const auto &ring = candidate["coordinates"][0];
|
|
if (!ring.isArray() || ring.size() < 4)
|
|
{
|
|
return false;
|
|
}
|
|
for (const auto &coordinate : ring)
|
|
{
|
|
if (!coordinate.isArray() || coordinate.size() < 2)
|
|
{
|
|
return false;
|
|
}
|
|
if (!pointInGeometry(coordinate[1].asDouble(),
|
|
coordinate[0].asDouble(),
|
|
container))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
} // namespace visione::geojson
|