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
+39
View File
@@ -1,5 +1,6 @@
#include "services/AdminService.h"
#include "common/GeoJson.h"
#include "common/Validation.h"
#include "repositories/Sql.h"
#include "services/UserService.h"
@@ -7,6 +8,7 @@
#include <drogon/drogon.h>
#include <algorithm>
#include <sstream>
namespace visione::services
{
@@ -87,6 +89,35 @@ bool isActiveField(const Json::Value &body)
{
return body.isMember("active") && body["active"].isBool();
}
std::optional<Json::Value> loadAllowedBoundary(const std::string &code)
{
const auto rows = drogon::app().getDbClient()->execSqlSync(
"SELECT JSON_UNQUOTE(JSON_EXTRACT(boundary_geojson, '$')) AS geojson "
"FROM allowed_boundaries WHERE code = ? AND active = TRUE LIMIT 1",
code);
if (rows.empty())
{
return std::nullopt;
}
Json::CharReaderBuilder builder;
Json::Value geojson;
std::string errors;
std::istringstream stream(rows[0]["geojson"].as<std::string>());
if (!Json::parseFromStream(builder, stream, &geojson, &errors))
{
return std::nullopt;
}
return geojson;
}
bool isAreaInsideAllowedBoundary(const Json::Value &boundaryGeojson)
{
const auto allowed = loadAllowedBoundary("BARI");
return allowed &&
geojson::polygonInsideGeometry(boundaryGeojson, *allowed);
}
} // namespace
Json::Value AdminService::createCategory(const auth::UserContext &user,
@@ -119,6 +150,10 @@ Json::Value AdminService::createArea(const auth::UserContext &user,
{
return failure("invalid_area");
}
if (!isAreaInsideAllowedBoundary(body["boundary_geojson"]))
{
return failure("area_outside_allowed_boundary");
}
Json::StreamWriterBuilder writer;
drogon::app().getDbClient()->execSqlSync(
@@ -410,6 +445,10 @@ Json::Value AdminService::updateArea(const auth::UserContext &user,
}
if (body["boundary_geojson"].isObject())
{
if (!isAreaInsideAllowedBoundary(body["boundary_geojson"]))
{
return failure("area_outside_allowed_boundary");
}
Json::StreamWriterBuilder writer;
db->execSqlSync(
"UPDATE areas SET boundary_geojson = CAST(? AS JSON) WHERE id = ?",
+47 -1
View File
@@ -75,6 +75,34 @@ std::string maskedLastName(const std::string &lastName)
return std::string(1, static_cast<char>(std::toupper(lastName.front()))) + ".";
}
std::optional<Json::Value> loadAllowedBoundary(const std::string &code)
{
const auto rows = drogon::app().getDbClient()->execSqlSync(
"SELECT JSON_UNQUOTE(JSON_EXTRACT(boundary_geojson, '$')) AS geojson "
"FROM allowed_boundaries WHERE code = ? AND active = TRUE LIMIT 1",
code);
if (rows.empty())
{
return std::nullopt;
}
Json::CharReaderBuilder builder;
Json::Value geojson;
std::string errors;
std::istringstream stream(rows[0]["geojson"].as<std::string>());
if (!Json::parseFromStream(builder, stream, &geojson, &errors))
{
return std::nullopt;
}
return geojson;
}
bool insideAllowedBoundary(double latitude, double longitude)
{
const auto boundary = loadAllowedBoundary("BARI");
return boundary && geojson::pointInGeometry(latitude, longitude, *boundary);
}
std::optional<long long> findArea(double latitude, double longitude)
{
const auto result = drogon::app().getDbClient()->execSqlSync(
@@ -89,7 +117,7 @@ std::optional<long long> findArea(double latitude, double longitude)
const auto raw = row["geojson"].as<std::string>();
std::istringstream stream(raw);
if (Json::parseFromStream(builder, stream, &geojson, &errors) &&
geojson::pointInPolygon(latitude, longitude, geojson))
geojson::pointInGeometry(latitude, longitude, geojson))
{
return row["id"].as<long long>();
}
@@ -397,6 +425,10 @@ Json::Value ReportService::createReport(const auth::UserContext &user,
const auto latitude = body["latitude"].asDouble();
const auto longitude = body["longitude"].asDouble();
if (!insideAllowedBoundary(latitude, longitude))
{
return failure("coordinates_outside_allowed_boundary");
}
const auto areaId = findArea(latitude, longitude);
auto db = drogon::app().getDbClient();
@@ -1215,6 +1247,20 @@ Json::Value ReportService::openDataReports(const std::optional<std::string> &pag
perPageValue,
offset),
openDataReportSchema());
if (const auto boundary = loadAllowedBoundary("BARI"))
{
Json::Value filtered(Json::arrayValue);
for (const auto &report : response["reports"])
{
if (geojson::pointInGeometry(report["latitude"].asDouble(),
report["longitude"].asDouble(),
*boundary))
{
filtered.append(report);
}
}
response["reports"] = std::move(filtered);
}
return response;
}