#include "common.hh" #include "geo_json.hh" #include "logger.hh" #include #include #include #include #include namespace { struct Point { double x; double y; Point(double x, double y) : x(x), y(y) {} bool operator==(Point const& pt) const { return x == pt.x && y == pt.y; } bool operator!=(Point const& pt) const { return x != pt.x || y != pt.y; } }; // Copyright 2000 softSurfer, 2012 Dan Sunday // This code may be freely used and modified for any purpose // providing that this copyright notice is included with it. // SoftSurfer makes no warranty for this code, and cannot be held // liable for any real or imagined damage resulting from its use. // Users of this code must verify correctness for their application. inline double is_left(Point p0, Point p1, Point p2) { return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y); } int wn_pnpoly(Point pt, std::vector const& poly) { int wn = 0; // the winding number counter assert(poly.size() >= 2); assert(poly.front() == poly.back()); // loop through all edges of the polygon // edge from poly[i] to poly[i+1]. poly[n] == poly[0]. for (size_t i = 0; i < poly.size() -1 ; ++i) { if (poly[i].y <= pt.y) { // start y <= pt.y if (poly[i + 1].y > pt.y) // an upward crossing if (is_left(poly[i], poly[i + 1], pt) > 0) // P left of edge ++wn; // have a valid up intersect } else { // start y > P.y (no test needed) if (poly[i + 1].y <= pt.y) // a downward crossing if (is_left(poly[i], poly[i + 1], pt) < 0) // P right of edge --wn; // have a valid down intersect } } return wn; } bool in_polygon(Point pt, std::vector const& poly) { return wn_pnpoly(pt, poly) != 0; } class GeoJsonImpl : public GeoJson { public: GeoJsonImpl(std::shared_ptr logger, std::filesystem::path db) : logger_(std::move(logger)), db_(std::move(db)) {} std::optional get_data(double lat, double lng, std::string_view data) const override { if (!db_.empty()) { FILE* fh = fopen(db_.c_str(), "rb"); if (fh) { rapidjson::Reader reader; char buffer[1024 * 1024]; rapidjson::FileReadStream in(fh, buffer, sizeof(buffer)); Handler handler(logger_.get(), lat, lng, data); reader.Parse(in, handler); fclose(fh); return handler.data(); } else { logger_->warn("Unable to open %s for reading: %s", db_.c_str(), strerror(errno)); } } return std::nullopt; } private: class Handler : public rapidjson::BaseReaderHandler, Handler> { enum class Expect { NONE, FEATURES, GEOMETRY, PROPERTIES, KEY_VALUE, GEOMETRY_TYPE, GEOMETRY_COORDINATES, }; public: Handler(Logger* logger, double lat, double lng, std::string_view key) : logger_(logger), pt_(lng, lat), key_(key) {} bool StartObject() { if (depth_ == std::numeric_limits::max()) return false; ++depth_; switch (expect_) { case Expect::GEOMETRY: geometry_ = true; break; case Expect::PROPERTIES: properties_ = true; break; default: break; } return Default(); } bool Key(const char* str, rapidjson::SizeType len, bool /* copy */) { expect_ = Expect::NONE; auto key = std::string_view(str, len); if (depth_ == 1) { if (key == "features") { expect_ = Expect::FEATURES; } } else if (depth_ == 2) { if (features_) { if (key == "properties") { expect_ = Expect::PROPERTIES; } else if (key == "geometry") { expect_ = Expect::GEOMETRY; } } } else if (depth_ == 3) { if (properties_) { if (key == key_) { expect_ = Expect::KEY_VALUE; } } else if (geometry_) { if (key == "type") { expect_ = Expect::GEOMETRY_TYPE; } else if (key == "coordinates") { expect_ = Expect::GEOMETRY_COORDINATES; } } } return true; } bool Default() { expect_ = Expect::NONE; return true; } bool StartArray() { if (depth_ == 1) { if (expect_ == Expect::FEATURES) { features_ = true; } } else if (depth_ == 3) { if (polygon_coordinates_) { polygon_coordinate_ = true; } else if (list_of_polygons_) { polygon_coordinates_ = true; } else if (expect_ == Expect::GEOMETRY_COORDINATES) { list_of_polygons_ = true; } } return Default(); } bool Int(int value) { return Double(value); } bool Uint(unsigned value) { return Double(value); } bool Int64(int64_t value) { return Double(value); } bool Uint64(uint64_t value) { return Double(value); } bool Double(double value) { if (depth_ == 3 && polygon_coordinate_) { coord_.push_back(value); } return Default(); } bool String(const char* data, rapidjson::SizeType len, bool /* copy */) { std::string_view str(data, len); if (depth_ == 3) { if (expect_ == Expect::GEOMETRY_TYPE) { geometry_type_ = str; } else if (expect_ == Expect::KEY_VALUE) { key_value_ = str; } } return Default(); } bool EndArray(rapidjson::SizeType /* count */) { if (depth_ == 1) { if (features_) { features_ = false; } } else if (depth_ == 3) { if (polygon_coordinate_) { coords_.emplace_back(std::move(coord_)); coord_.clear(); polygon_coordinate_ = false; } else if (polygon_coordinates_) { polygons_.emplace_back(std::move(coords_)); coords_.clear(); polygon_coordinates_ = false; } else if (list_of_polygons_) { list_of_polygons_ = false; } } return Default(); } bool EndObject(rapidjson::SizeType /* members */) { if (depth_ == 0) return false; --depth_; if (depth_ == 2) { if (geometry_) { geometry_ = false; } else if (properties_) { properties_ = false; } } if (depth_ == 1) { if (features_) { if (check_if_done()) return false; key_value_.reset(); geometry_type_.reset(); coord_.clear(); coords_.clear(); polygons_.clear(); } } return Default(); } std::optional data() const { return data_; } private: bool check_if_done() { if (geometry_type_.has_value() && geometry_type_.value() == "Polygon") { if (polygons_.empty()) { logger_->dbg("No polygons in Polygon"); return true; } auto exterior = get_polygon(polygons_[0]); if (exterior.empty()) return false; if (in_polygon(pt_, exterior)) { bool in_hole = false; for (size_t i = 1; i < polygons_.size(); ++i) { auto hole = get_polygon(polygons_[i]); if (hole.empty()) return false; if (in_polygon(pt_, hole)) { in_hole = true; break; } } if (!in_hole) { data_ = key_value_; return true; } } } return false; } std::vector get_polygon(std::vector> const& in) { std::vector out; for (auto const& pair : in) { if (pair.size() != 2) { logger_->dbg("Coordinate of size != 2"); return {}; } out.emplace_back(pair[0], pair[1]); } if (out.empty()) { logger_->dbg("Empty LineString"); return {}; } if (out.size() < 2 || out.front() != out.back()) { logger_->dbg("Polygon does not start and end in same point."); return {}; } return out; } Logger* const logger_; Point const pt_; std::string_view const key_; std::optional data_; uint32_t depth_{0}; Expect expect_{Expect::NONE}; bool features_{false}; bool geometry_{false}; bool properties_{false}; bool list_of_polygons_{false}; bool polygon_coordinates_{false}; bool polygon_coordinate_{false}; std::optional key_value_; std::optional geometry_type_; std::vector>> polygons_; std::vector> coords_; std::vector coord_; }; std::shared_ptr logger_; std::filesystem::path db_; }; } // namespace std::unique_ptr GeoJson::create(std::shared_ptr logger, std::filesystem::path db) { return std::make_unique(std::move(logger), std::move(db)); }