feat: implement spatial boundary checks for Bari municipal area in GeoJSON handling
This commit is contained in:
@@ -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 = ?",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user