feat: implement spatial boundary checks for Bari municipal area in GeoJSON handling

This commit is contained in:
gabdevele
2026-06-15 14:45:38 +02:00 Unverified
parent 088a2ed866
commit aab7420706
13 changed files with 310 additions and 15 deletions
+114 -10
View File
@@ -1,5 +1,6 @@
#include "common/GeoJson.h"
#include <cmath>
#include <vector>
namespace visione::geojson
@@ -11,17 +12,12 @@ struct Point
double x;
double y;
};
} // namespace
bool pointInPolygon(double latitude, double longitude, const Json::Value &geojson)
bool pointInRing(double latitude,
double longitude,
const Json::Value &ring,
bool *onBoundary = nullptr)
{
if (!geojson.isObject() || geojson["type"].asString() != "Polygon" ||
!geojson["coordinates"].isArray() || geojson["coordinates"].empty())
{
return false;
}
const auto &ring = geojson["coordinates"][0];
if (!ring.isArray() || ring.size() < 4)
{
return false;
@@ -41,10 +37,27 @@ bool pointInPolygon(double latitude, double longitude, const Json::Value &geojso
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);
@@ -55,5 +68,96 @@ bool pointInPolygon(double latitude, double longitude, const Json::Value &geojso
}
return inside;
}
} // namespace visione::geojson
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