diff options
Diffstat (limited to 'src/geo_json.cc')
| -rw-r--r-- | src/geo_json.cc | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/src/geo_json.cc b/src/geo_json.cc new file mode 100644 index 0000000..3c1431b --- /dev/null +++ b/src/geo_json.cc @@ -0,0 +1,345 @@ +#include "common.hh" + +#include "geo_json.hh" +#include "logger.hh" + +#include <errno.h> +#include <rapidjson/document.h> +#include <rapidjson/filereadstream.h> +#include <string.h> +#include <vector> + +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<Point> 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<Point> const& poly) { + return wn_pnpoly(pt, poly) != 0; +} + +class GeoJsonImpl : public GeoJson { +public: + GeoJsonImpl(std::shared_ptr<Logger> logger, std::filesystem::path db) + : logger_(std::move(logger)), db_(std::move(db)) {} + + std::optional<std::string> 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<rapidjson::UTF8<>, + 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<uint32_t>::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<std::string> 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<Point> get_polygon(std::vector<std::vector<double>> const& in) { + std::vector<Point> 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<std::string> 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<std::string> key_value_; + std::optional<std::string> geometry_type_; + std::vector<std::vector<std::vector<double>>> polygons_; + std::vector<std::vector<double>> coords_; + std::vector<double> coord_; + }; + + std::shared_ptr<Logger> logger_; + std::filesystem::path db_; +}; + +} // namespace + +std::unique_ptr<GeoJson> GeoJson::create(std::shared_ptr<Logger> logger, + std::filesystem::path db) { + return std::make_unique<GeoJsonImpl>(std::move(logger), std::move(db)); +} |
