summaryrefslogtreecommitdiff
path: root/src/geo_json.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/geo_json.cc')
-rw-r--r--src/geo_json.cc345
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));
+}