From f42ff046ba37d0cf2c59bae336a89151bd833a92 Mon Sep 17 00:00:00 2001 From: TANG ZhiXiong Date: Sat, 9 Sep 2023 23:58:41 +0800 Subject: [PATCH] rapidjson io (#11) * test json * not ready * rapidjson * rapidjson io * not ready * update * rapidjson io * has from to rapidjson * more bindings * lint * why offset not inited? * fix * add rapidjson * update * add geometry * good * indexer * add indexer * not ready * loading geojson * fix * fix * fix --------- Co-authored-by: TANG ZHIXIONG --- nano_fmm/converter.py | 57 ++++ src/bindings/pybind11_network.cpp | 87 ++++-- src/nano_fmm/config.hpp | 15 +- src/nano_fmm/indexer.hpp | 100 +++++++ src/nano_fmm/network.cpp | 66 +++-- src/nano_fmm/network.hpp | 26 +- src/nano_fmm/network/projected_point.hpp | 21 +- src/nano_fmm/network/ubodt.hpp | 50 ++-- src/nano_fmm/rapidjson.cpp | 331 +++++++++++++++++++++++ src/nano_fmm/types.hpp | 11 + tests/test_basic.py | 121 ++++++++- 11 files changed, 808 insertions(+), 77 deletions(-) create mode 100644 src/nano_fmm/indexer.hpp create mode 100644 src/nano_fmm/rapidjson.cpp diff --git a/nano_fmm/converter.py b/nano_fmm/converter.py index e69de29..e5ed2b7 100755 --- a/nano_fmm/converter.py +++ b/nano_fmm/converter.py @@ -0,0 +1,57 @@ +import os +from typing import Union + +from loguru import logger + +from nano_fmm import Indexer, rapidjson + + +def remap_network_with_string_id( + network: Union[str, rapidjson], + *, + export: str = None, +): + """ + network.json normally has: + { + "type": "Feature", + "geometry": {...}, + "properties": { + "id": 244, + "nexts": [326, 452], + "prevs": [5241, 563], + ... + }, + } + + if you have: + { + "id": "road1", + "nexts": ["road2", "road3"], + "prevs": ["road4", "road5"], + } + """ + if isinstance(network, str): + path = network + network = rapidjson() + network.load(path) + indexer = Indexer() + features = network["features"] + for i in range(len(features)): + f = features[i] + props = f["properties"] + if "id" not in props or "nexts" not in props and "prevs" not in props: + continue + props["id"] = indexer.id(props["id"]()) + props["nexts"] = [indexer.id(n) for n in props["nexts"]()] + props["prevs"] = [indexer.id(n) for n in props["prevs"]()] + if "folds" in props: + props["folds"][0] = [indexer.id(i) for i in props["folds"][0]()] + props["type"] = "road" + if export: + network["index"] = indexer.to_rapidjson() + export = os.path.abspath(export) + os.makedirs(os.path.dirname(export), exist_ok=True) + network.dump(export, indent=True) + logger.info(f"wrote to {export}") + return network, indexer diff --git a/src/bindings/pybind11_network.cpp b/src/bindings/pybind11_network.cpp index 68b88ac..97755e2 100644 --- a/src/bindings/pybind11_network.cpp +++ b/src/bindings/pybind11_network.cpp @@ -5,6 +5,7 @@ #include #include "nano_fmm/network.hpp" +#include "nano_fmm/indexer.hpp" #include "spdlog/spdlog.h" namespace nano_fmm @@ -30,19 +31,38 @@ void bind_network(py::module &m) // .def_property_readonly( "position", - [](const ProjectedPoint &self) { return self.position_; }) + [](const ProjectedPoint &self) -> const Eigen::Vector3d { + return self.position(); + }) .def_property_readonly( "direction", - [](const ProjectedPoint &self) { return self.direction_; }) + [](const ProjectedPoint &self) -> const Eigen::Vector3d { + return self.direction(); + }) .def_property_readonly( "distance", - [](const ProjectedPoint &self) { return self.distance_; }) + [](const ProjectedPoint &self) { return self.distance(); }) .def_property_readonly( - "road_id", [](const ProjectedPoint &self) { return self.road_id_; }) + "road_id", + [](const ProjectedPoint &self) { return self.road_id(); }) .def_property_readonly( - "offset", [](const ProjectedPoint &self) { return self.offset_; }) + "offset", [](const ProjectedPoint &self) { return self.offset(); }) // - ; + .def("from_rapidjson", &ProjectedPoint::from_rapidjson, "json"_a) + .def("to_rapidjson", + py::overload_cast<>(&ProjectedPoint::to_rapidjson, py::const_)) + // + .def("__repr__", [](const ProjectedPoint &self) { + auto &p = self.position(); + auto &d = self.direction(); + return fmt::format("ProjectedPoint(pos=[{},{},{}],dir=[{},{},{}]," + "dist={},road={},offset={})", + p[0], p[1], p[2], // + d[0], d[1], d[2], // + self.distance(), self.road_id(), self.offset()); + }); + // + ; py::class_(m, "UbodtRecord", py::module_local()) // .def(py::init(), @@ -55,41 +75,43 @@ void bind_network(py::module &m) // .def_property_readonly( "source_road", - [](const UbodtRecord &self) { return self.source_road_; }) + [](const UbodtRecord &self) { return self.source_road(); }) .def_property_readonly( "target_road", - [](const UbodtRecord &self) { return self.target_road_; }) + [](const UbodtRecord &self) { return self.target_road(); }) .def_property_readonly( "source_next", - [](const UbodtRecord &self) { return self.source_next_; }) + [](const UbodtRecord &self) { return self.source_next(); }) .def_property_readonly( "target_prev", - [](const UbodtRecord &self) { return self.target_prev_; }) - .def_property_readonly( - "cost", [](const UbodtRecord &self) { return self.cost_; }) + [](const UbodtRecord &self) { return self.target_prev(); }) .def_property_readonly( - "next", [](const UbodtRecord &self) { return self.next_; }, - rvp::reference_internal) + "cost", [](const UbodtRecord &self) { return self.cost(); }) // .def(py::self == py::self) .def(py::self < py::self) // + .def("from_rapidjson", &UbodtRecord::from_rapidjson, "json"_a) + .def("to_rapidjson", + py::overload_cast<>(&UbodtRecord::to_rapidjson, py::const_)) + // .def("__repr__", [](const UbodtRecord &self) { return fmt::format( "UbodtRecord(s->t=[{}->{}], cost:{}, sn:{},tp:{})", - self.source_road_, self.target_road_, // - self.cost_, // - self.source_next_, self.target_prev_); + self.source_road(), self.target_road(), // + self.cost(), // + self.source_next(), self.target_prev()); }); // ; py::class_(m, "Network", py::module_local()) // // - .def(py::init(), py::kw_only(), "is_wgs84"_a = false) + .def(py::init(), py::kw_only(), "is_wgs84"_a) // .def("add_road", &Network::add_road, "geom"_a, py::kw_only(), "id"_a) - .def("add_link", &Network::add_link, "source_road"_a, "target_road"_a) + .def("add_link", &Network::add_link, "source_road"_a, "target_road"_a, + py::kw_only(), "check_road"_a = false) .def("remove_road", &Network::remove_road, "id"_a) .def("remove_link", &Network::remove_link, // "source_road"_a, "target_road"_a) @@ -120,8 +142,8 @@ void bind_network(py::module &m) py::call_guard()) // .def_static("load", &Network::load, "path"_a) - .def("dump", &Network::dump, "path"_a, py::kw_only(), - "with_config"_a = true) + .def("dump", &Network::dump, "path"_a, py::kw_only(), "indent"_a = true, + "as_geojson"_a = true) // .def("build_ubodt", py::overload_cast>(&Network::build_ubodt, @@ -145,6 +167,29 @@ void bind_network(py::module &m) // .def("to_2d", &Network::to_2d) // + .def("from_geojson", &Network::from_geojson, "json"_a) + .def("to_geojson", + py::overload_cast<>(&Network::to_geojson, py::const_)) + .def("from_rapidjson", &Network::from_rapidjson, "json"_a) + .def("to_rapidjson", + py::overload_cast<>(&Network::to_rapidjson, py::const_)) + // + ; + + py::class_(m, "Indexer", py::module_local()) // + .def(py::init<>()) + .def("id", py::overload_cast(&Indexer::id), "id"_a) + .def("id", py::overload_cast(&Indexer::id), "id"_a) + .def("index", + py::overload_cast(&Indexer::index), + "str_id"_a, "int_id"_a) + .def("index", py::overload_cast<>(&Indexer::index, py::const_)) + // + .def("from_rapidjson", &Indexer::from_rapidjson, "json"_a) + .def("to_rapidjson", + py::overload_cast<>(&Indexer::to_rapidjson, py::const_)) + // + // ; } } // namespace nano_fmm diff --git a/src/nano_fmm/config.hpp b/src/nano_fmm/config.hpp index 9724534..767e941 100644 --- a/src/nano_fmm/config.hpp +++ b/src/nano_fmm/config.hpp @@ -1,9 +1,22 @@ #pragma once +#include "nano_fmm/types.hpp" + namespace nano_fmm { struct Config { - double ubodt_thresh = 3000.0; + + SETUP_FLUENT_API(Config, double, ubodt_thresh) + Config &from_rapidjson(const RapidjsonValue &json); + RapidjsonValue to_rapidjson(RapidjsonAllocator &allocator) const; + RapidjsonValue to_rapidjson() const + { + RapidjsonAllocator allocator; + return to_rapidjson(allocator); + } + + private: + double ubodt_thresh_ = 3000.0; }; } // namespace nano_fmm diff --git a/src/nano_fmm/indexer.hpp b/src/nano_fmm/indexer.hpp new file mode 100644 index 0000000..9462183 --- /dev/null +++ b/src/nano_fmm/indexer.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include +#include "nano_fmm/types.hpp" + +namespace nano_fmm +{ +struct Indexer +{ + // get str id (with auto setup) + std::string id(int64_t id) + { + auto itr = int2str_.find(id); + if (itr != int2str_.end()) { + return itr->second; + } + int round = 0; + auto id_str = std::to_string(id); + auto str_id = id_str; + while (str2int_.count(str_id)) { + ++round; + str_id = id_str + "/" + std::to_string(round); + } + index(str_id, id); + return str_id; + } + // get int id (with auto setup) + int64_t id(const std::string &id) + { + auto itr = str2int_.find(id); + if (itr != str2int_.end()) { + return itr->second; + } + try { + // '44324' -> 44324 + // 'w44324' -> 44324 + int64_t ii = + id[0] == 'w' ? std::stoll(id.substr(1)) : std::stoll(id); + if (index(id, ii)) { + return ii; + } + } catch (...) { + } + while (!index(id, id_cursor_)) { + ++id_cursor_; + } + return id_cursor_++; + } + // setup str/int id, returns true (setup) or false (skip) + bool index(const std::string &str_id, int64_t int_id) + { + if (str2int_.count(str_id) || int2str_.count(int_id)) { + return false; + } + str2int_.emplace(str_id, int_id); + int2str_.emplace(int_id, str_id); + return true; + } + std::map index() const + { + return {str2int_.begin(), str2int_.end()}; + } + + Indexer &from_rapidjson(const RapidjsonValue &json) + { + // for (auto &m: json.GetMe) + for (auto &m : json.GetObject()) { + index(std::string(m.name.GetString(), m.name.GetStringLength()), + m.value.GetInt64()); + } + return *this; + } + RapidjsonValue to_rapidjson(RapidjsonAllocator &allocator) const + { + RapidjsonValue json(rapidjson::kObjectType); + for (auto &pair : str2int_) { + auto &str = pair.first; + json.AddMember(RapidjsonValue(str.data(), str.size(), allocator), + RapidjsonValue(pair.second), allocator); + } + std::sort( + json.MemberBegin(), json.MemberEnd(), [](auto &lhs, auto &rhs) { + return strcmp(lhs.name.GetString(), rhs.name.GetString()) < 0; + }); + return json; + } + RapidjsonValue to_rapidjson() const + { + RapidjsonAllocator allocator; + return to_rapidjson(allocator); + } + + private: + std::unordered_map str2int_; + std::unordered_map int2str_; + int64_t id_cursor_{1000000}; +}; +} // namespace nano_fmm diff --git a/src/nano_fmm/network.cpp b/src/nano_fmm/network.cpp index e23faeb..1edd422 100644 --- a/src/nano_fmm/network.cpp +++ b/src/nano_fmm/network.cpp @@ -3,6 +3,8 @@ #include "nano_fmm/heap.hpp" #include "spdlog/spdlog.h" +#include "nano_fmm/rapidjson_helpers.hpp" + #include namespace nano_fmm @@ -24,15 +26,18 @@ bool Network::add_road(const Eigen::Ref &geom, int64_t road_id) rtree_.reset(); return true; } -bool Network::add_link(int64_t source_road, int64_t target_road) +bool Network::add_link(int64_t source_road, int64_t target_road, + bool check_road) { - if (roads_.find(source_road) == roads_.end()) { - spdlog::error("source_road={} not in network", source_road); - return false; - } - if (roads_.find(target_road) == roads_.end()) { - spdlog::error("target_road={} not in network", target_road); - return false; + if (check_road) { + if (roads_.find(source_road) == roads_.end()) { + spdlog::error("source_road={} not in network", source_road); + return false; + } + if (roads_.find(target_road) == roads_.end()) { + spdlog::error("target_road={} not in network", target_road); + return false; + } } nexts_[source_road].insert(target_road); prevs_[target_road].insert(source_road); @@ -140,7 +145,7 @@ Network::query(const Eigen::Vector3d &position, double radius, pair.first, poly.range(s, t)); } std::sort(nearests.begin(), nearests.end(), - [](auto &n1, auto &n2) { return n1.distance_ < n2.distance_; }); + [](auto &n1, auto &n2) { return n1.distance() < n2.distance(); }); if (k && nearests.size() > *k) { nearests.resize(*k); } @@ -221,13 +226,40 @@ void Network::reset() const { rtree_.reset(); } std::unique_ptr Network::load(const std::string &path) { - // - return {}; + auto json = load_json(path); + if (!json.IsObject()) { + SPDLOG_ERROR("invalid network file: {}", path); + return {}; + } + bool is_wgs84 = true; + auto itr = json.FindMember("is_wgs84"); + if (itr != json.MemberEnd() && itr->value.IsBool()) { + is_wgs84 = itr->value.GetBool(); + } + auto type = json.FindMember("type"); + if (type != json.MemberEnd() && type->value.IsString() && + std::string(type->value.GetString(), type->value.GetStringLength()) == + "FeatureCollection") { + SPDLOG_CRITICAL("loading geojson {}", path); + auto ret = std::make_unique(is_wgs84); + ret->from_geojson(json); + return ret; + } + + if (!json.HasMember("roads") || !json.HasMember("nexts") || + !json.HasMember("prevs")) { + SPDLOG_ERROR("network file should at least have roads/nexts/prevs"); + return {}; + } + + auto ret = std::make_unique(is_wgs84); + ret->from_rapidjson(json); + return ret; } -bool Network::dump(const std::string &path, bool with_config) const + +bool Network::dump(const std::string &path, bool indent, bool as_geojson) const { - // - return false; + return dump_json(path, as_geojson ? to_geojson() : to_rapidjson(), indent); } std::vector @@ -246,7 +278,7 @@ Network::build_ubodt(const std::vector &roads, std::optional thresh) const { if (!thresh) { - thresh = config_.ubodt_thresh; + thresh = config_.ubodt_thresh(); } auto records = std::vector(); for (auto s : roads) { @@ -264,7 +296,7 @@ Network::build_ubodt(const std::vector &roads, while ((u = pmap[succ]) != s) { succ = u; } - records.push_back({s, curr, succ, prev, dmap[curr], nullptr}); + records.push_back({s, curr, succ, prev, dmap[curr]}); } } return records; @@ -282,7 +314,7 @@ size_t Network::load_ubodt(const std::vector &rows) // ubodt_; size_t count = 0; for (auto &row : rows) { - if (ubodt_.emplace(IndexIJ(row.source_road_, row.target_road_), row) + if (ubodt_.emplace(IndexIJ(row.source_road(), row.target_road()), row) .second) { ++count; } diff --git a/src/nano_fmm/network.hpp b/src/nano_fmm/network.hpp index 8f111bc..eea39b4 100644 --- a/src/nano_fmm/network.hpp +++ b/src/nano_fmm/network.hpp @@ -21,11 +21,12 @@ namespace nano_fmm { struct Network { - Network(bool is_wgs84 = false) : is_wgs84_(is_wgs84) {} + Network(bool is_wgs84) : is_wgs84_(is_wgs84) {} // road network bool add_road(const Eigen::Ref &geom, int64_t road_id); - bool add_link(int64_t source_road, int64_t target_road); + bool add_link(int64_t source_road, int64_t target_road, + bool check_road = false); bool remove_road(int64_t road_id); bool remove_link(int64_t source_road, int64_t target_road); std::unordered_set prev_roads(int64_t road_id) const; @@ -63,7 +64,8 @@ struct Network // load&dump static std::unique_ptr load(const std::string &path); - bool dump(const std::string &path, bool with_config = true) const; + bool dump(const std::string &path, bool indent = true, + bool as_geojson = true) const; // ubodt std::vector build_ubodt(std::optional thresh = std::nullopt) const; @@ -79,8 +81,24 @@ struct Network // to 2d, z will be set to zero Network to_2d() const; + Network &from_geojson(const RapidjsonValue &json); + RapidjsonValue to_geojson(RapidjsonAllocator &allocator) const; + RapidjsonValue to_geojson() const + { + RapidjsonAllocator allocator; + return to_geojson(allocator); + } + + Network &from_rapidjson(const RapidjsonValue &json); + RapidjsonValue to_rapidjson(RapidjsonAllocator &allocator) const; + RapidjsonValue to_rapidjson() const + { + RapidjsonAllocator allocator; + return to_rapidjson(allocator); + } + private: - const bool is_wgs84_{false}; + const bool is_wgs84_; // roads (id -> geom) std::unordered_map roads_; // links (id -> {nexts}, id -> {prevs}) diff --git a/src/nano_fmm/network/projected_point.hpp b/src/nano_fmm/network/projected_point.hpp index 0fc8eb1..2627416 100644 --- a/src/nano_fmm/network/projected_point.hpp +++ b/src/nano_fmm/network/projected_point.hpp @@ -8,16 +8,31 @@ struct ProjectedPoint { ProjectedPoint() {} ProjectedPoint(const Eigen::Vector3d &position, - const Eigen::Vector3d &direction, double distance, - int64_t road_id, + const Eigen::Vector3d &direction, // + double distance, int64_t road_id, double offset) : position_(position), // direction_(direction), // distance_(distance), // road_id_(road_id), // - offset_(offset_) + offset_(offset) { } + + SETUP_FLUENT_API(ProjectedPoint, Eigen::Vector3d, position) + SETUP_FLUENT_API(ProjectedPoint, Eigen::Vector3d, direction) + SETUP_FLUENT_API(ProjectedPoint, double, distance) + SETUP_FLUENT_API(ProjectedPoint, int64_t, road_id) + SETUP_FLUENT_API(ProjectedPoint, double, offset) + ProjectedPoint &from_rapidjson(const RapidjsonValue &json); + RapidjsonValue to_rapidjson(RapidjsonAllocator &allocator) const; + RapidjsonValue to_rapidjson() const + { + RapidjsonAllocator allocator; + return to_rapidjson(allocator); + } + + private: Eigen::Vector3d position_{0.0, 0.0, 0.0}; Eigen::Vector3d direction_{0.0, 0.0, 1.0}; double distance_{0.0}; diff --git a/src/nano_fmm/network/ubodt.hpp b/src/nano_fmm/network/ubodt.hpp index 97f620d..d50e1f6 100644 --- a/src/nano_fmm/network/ubodt.hpp +++ b/src/nano_fmm/network/ubodt.hpp @@ -7,27 +7,28 @@ namespace nano_fmm struct UbodtRecord { UbodtRecord() {} - UbodtRecord(int64_t source_road, int64_t target_road, int64_t source_next, - int64_t target_prev, double cost, UbodtRecord *next) - : source_road_(source_road), target_road_(target_road), - source_next_(source_next), target_prev_(target_prev), cost_(cost), - next_(next) + UbodtRecord(int64_t source_road, int64_t target_road, // + int64_t source_next, int64_t target_prev, // + double cost) + : source_road_(source_road), target_road_(target_road), // + source_next_(source_next), target_prev_(target_prev), // + cost_(cost) { } - UbodtRecord(int64_t source_road, int64_t target_road, int64_t source_next, - int64_t target_prev, double cost) - : UbodtRecord(source_road, target_road, source_next, target_prev, cost, - nullptr) + + SETUP_FLUENT_API(UbodtRecord, int64_t, source_road) + SETUP_FLUENT_API(UbodtRecord, int64_t, target_road) + SETUP_FLUENT_API(UbodtRecord, int64_t, source_next) + SETUP_FLUENT_API(UbodtRecord, int64_t, target_prev) + SETUP_FLUENT_API(UbodtRecord, double, cost) + UbodtRecord &from_rapidjson(const RapidjsonValue &json); + RapidjsonValue to_rapidjson(RapidjsonAllocator &allocator) const; + RapidjsonValue to_rapidjson() const { + RapidjsonAllocator allocator; + return to_rapidjson(allocator); } - int64_t source_road_{0}; - int64_t target_road_{0}; - int64_t source_next_{0}; - int64_t target_prev_{0}; - double cost_{0.0}; - UbodtRecord *next_{nullptr}; - bool operator<(const UbodtRecord &rhs) const { if (source_road_ != rhs.source_road_) { @@ -36,19 +37,24 @@ struct UbodtRecord if (cost_ != rhs.cost_) { return cost_ < rhs.cost_; } - if (source_next_ != rhs.source_next_) { - return source_next_ < rhs.source_next_; - } - return std::make_tuple(target_prev_, target_road_, next_) < - std::make_tuple(rhs.target_prev_, rhs.target_road_, rhs.next_); + return std::make_tuple(source_next_, target_prev_, target_road_) < + std::make_tuple(rhs.source_next_, rhs.target_prev_, + rhs.target_road_); } bool operator==(const UbodtRecord &rhs) const { return source_road_ == rhs.source_road_ && target_road_ == rhs.target_road_ && source_next_ == rhs.source_next_ && - target_prev_ == rhs.target_prev_ && next_ == rhs.next_; + target_prev_ == rhs.target_prev_; } + + private: + int64_t source_road_{0}; + int64_t target_road_{0}; + int64_t source_next_{0}; + int64_t target_prev_{0}; + double cost_{0.0}; }; } // namespace nano_fmm diff --git a/src/nano_fmm/rapidjson.cpp b/src/nano_fmm/rapidjson.cpp new file mode 100644 index 0000000..2b53f16 --- /dev/null +++ b/src/nano_fmm/rapidjson.cpp @@ -0,0 +1,331 @@ +#include "nano_fmm/rapidjson_helpers.hpp" +#include "nano_fmm/network/projected_point.hpp" +#include "nano_fmm/network/ubodt.hpp" +#include "nano_fmm/network.hpp" + +#include "spdlog/spdlog.h" + +namespace nano_fmm +{ +template struct HAS_FROM_RAPIDJSON +{ + template struct SFINAE + { + }; + template static char Test(SFINAE *); + template static int Test(...); + static const bool Has = sizeof(Test(0)) == sizeof(char); +}; + +template struct HAS_TO_RAPIDJSON +{ + template + struct SFINAE + { + }; + template static char Test(SFINAE *); + template static int Test(...); + static const bool Has = sizeof(Test(0)) == sizeof(char); +}; + +template ::Has, int> = 0> +T from_rapidjson(const RapidjsonValue &json); +template RapidjsonValue to_rapidjson(T &&t) +{ + RapidjsonAllocator allocator; + return to_rapidjson(std::forward(t), allocator); +} + +template ::Has, int> = 0> +T from_rapidjson(const RapidjsonValue &json) +{ + T t; + t.from_rapidjson(json); + return t; +} + +template ::Has, int> = 0> +RapidjsonValue to_rapidjson(const T &t, RapidjsonAllocator &allocator) +{ + return t.to_rapidjson(allocator); +} + +// serialization for each types +template <> int64_t from_rapidjson(const RapidjsonValue &json) +{ + return json.GetInt64(); +} +inline RapidjsonValue to_rapidjson(int64_t value, RapidjsonAllocator &allocator) +{ + return RapidjsonValue(value); +} + +template <> double from_rapidjson(const RapidjsonValue &json) +{ + return json.GetDouble(); +} +inline RapidjsonValue to_rapidjson(double value, RapidjsonAllocator &allocator) +{ + return RapidjsonValue(value); +} + +template <> Eigen::Vector3d from_rapidjson(const RapidjsonValue &json) +{ + return {json[0].GetDouble(), json[1].GetDouble(), + json.Size() > 2 ? json[2].GetDouble() : 0.0}; +} +inline RapidjsonValue to_rapidjson(const Eigen::Vector3d &value, + RapidjsonAllocator &allocator) +{ + RapidjsonValue arr(rapidjson::kArrayType); + arr.Reserve(3, allocator); + arr.PushBack(RapidjsonValue(value[0]), allocator); + arr.PushBack(RapidjsonValue(value[1]), allocator); + arr.PushBack(RapidjsonValue(value[2]), allocator); + return arr; +} + +template <> RowVectors from_rapidjson(const RapidjsonValue &json) +{ + const int N = json.Size(); + RowVectors xyzs(N, 3); + for (int i = 0; i < N; ++i) { + xyzs(i, 0) = json[i][0].GetDouble(); + xyzs(i, 1) = json[i][1].GetDouble(); + xyzs(i, 2) = json[i].Size() > 2 ? json[i][2].GetDouble() : 0.0; + } + return xyzs; +} +inline RapidjsonValue to_rapidjson(const RowVectors &value, + RapidjsonAllocator &allocator) +{ + RapidjsonValue xyzs(rapidjson::kArrayType); + const int N = value.rows(); + xyzs.Reserve(N, allocator); + for (int i = 0; i < N; ++i) { + RapidjsonValue xyz(rapidjson::kArrayType); + xyz.Reserve(3, allocator); + xyz.PushBack(RapidjsonValue(value(i, 0)), allocator); + xyz.PushBack(RapidjsonValue(value(i, 1)), allocator); + xyz.PushBack(RapidjsonValue(value(i, 2)), allocator); + xyzs.PushBack(xyz, allocator); + } + return xyzs; +} + +template <> std::vector from_rapidjson(const RapidjsonValue &json) +{ + const int N = json.Size(); + std::vector index; + index.reserve(N); + for (int i = 0; i < N; ++i) { + index.push_back(json[i].GetInt64()); + } + return index; +} +inline RapidjsonValue to_rapidjson(const std::vector &value, + RapidjsonAllocator &allocator) +{ + RapidjsonValue xyzs(rapidjson::kArrayType); + const int N = value.size(); + xyzs.Reserve(N, allocator); + for (int i = 0; i < N; ++i) { + xyzs.PushBack(RapidjsonValue(value[i]), allocator); + } + return xyzs; +} + +// helper macros +#define TO_RAPIDJSON(var, json, allocator, key) \ + json.AddMember(#key, nano_fmm::to_rapidjson(var.key(), allocator), \ + allocator); +#define FROM_RAPIDJSON(var, json, json_end, key) \ + auto key##_itr = json.FindMember(#key); \ + if (json_end != key##_itr) { \ + if (key##_itr->value.IsNull()) { \ + var.key(std::decay::type()); \ + } else { \ + var.key(nano_fmm::from_rapidjson< \ + std::decay::type>(key##_itr->value)); \ + } \ + } + +// ProjectedPoint +ProjectedPoint &ProjectedPoint::from_rapidjson(const RapidjsonValue &json) +{ + auto json_end = json.MemberEnd(); + FROM_RAPIDJSON((*this), json, json_end, position) + FROM_RAPIDJSON((*this), json, json_end, direction) + FROM_RAPIDJSON((*this), json, json_end, distance) + FROM_RAPIDJSON((*this), json, json_end, road_id) + FROM_RAPIDJSON((*this), json, json_end, offset) + return *this; +} +RapidjsonValue ProjectedPoint::to_rapidjson(RapidjsonAllocator &allocator) const +{ + RapidjsonValue json(rapidjson::kObjectType); + TO_RAPIDJSON((*this), json, allocator, position) + TO_RAPIDJSON((*this), json, allocator, direction) + TO_RAPIDJSON((*this), json, allocator, distance) + TO_RAPIDJSON((*this), json, allocator, road_id) + TO_RAPIDJSON((*this), json, allocator, offset) + return json; +} + +// UbodtRecord +UbodtRecord &UbodtRecord::from_rapidjson(const RapidjsonValue &json) +{ + auto json_end = json.MemberEnd(); + FROM_RAPIDJSON((*this), json, json_end, source_road) + FROM_RAPIDJSON((*this), json, json_end, target_road) + FROM_RAPIDJSON((*this), json, json_end, source_next) + FROM_RAPIDJSON((*this), json, json_end, target_prev) + FROM_RAPIDJSON((*this), json, json_end, cost) + return *this; +} +RapidjsonValue UbodtRecord::to_rapidjson(RapidjsonAllocator &allocator) const +{ + RapidjsonValue json(rapidjson::kObjectType); + TO_RAPIDJSON((*this), json, allocator, source_road) + TO_RAPIDJSON((*this), json, allocator, target_road) + TO_RAPIDJSON((*this), json, allocator, source_next) + TO_RAPIDJSON((*this), json, allocator, target_prev) + TO_RAPIDJSON((*this), json, allocator, cost) + return json; +} + +Config &Config::from_rapidjson(const RapidjsonValue &json) +{ + auto json_end = json.MemberEnd(); + return *this; +} +RapidjsonValue Config::to_rapidjson(RapidjsonAllocator &allocator) const +{ + RapidjsonValue json(rapidjson::kObjectType); + return json; +} + +Network &Network::from_geojson(const RapidjsonValue &json) +{ + auto json_end = json.MemberEnd(); + auto config_itr = json.FindMember("config"); + if (config_itr != json_end) { + config_ = nano_fmm::from_rapidjson(config_itr->value); + } + int index = -1; + int num_fail = 0; + int num_succ = 0; + for (auto &f : json["features"].GetArray()) { + ++index; + if (!f["properties"].HasMember("type") || + !f["properties"]["type"].IsString()) { + continue; + } + auto &type = f["properties"]["type"]; + if ("road" != std::string(type.GetString(), type.GetStringLength())) { + continue; + } + try { + auto id = f["properties"]["id"].GetInt64(); + auto coords = nano_fmm::from_rapidjson( + f["geometry"]["coordinates"]); + add_road(coords, id); + auto nexts = nano_fmm::from_rapidjson>( + f["properties"]["nexts"]); + auto prevs = nano_fmm::from_rapidjson>( + f["properties"]["prevs"]); + for (auto n : nexts) { + add_link(id, n, false); + } + for (auto p : prevs) { + add_link(p, id, false); + } + ++num_succ; + continue; + } catch (...) { + ++num_fail; + } + } + SPDLOG_INFO("loading roads, #succ:{}, #fail:{}", num_succ, num_fail); + if (num_fail) { + SPDLOG_ERROR("failed at loading roads from {} features", num_fail); + } + return *this; +} +RapidjsonValue Network::to_geojson(RapidjsonAllocator &allocator) const +{ + RapidjsonValue features(rapidjson::kArrayType); + features.Reserve(roads_.size(), allocator); + + std::vector road_ids; + for (auto &pair : roads_) { + road_ids.push_back(pair.first); + } + std::sort(road_ids.begin(), road_ids.end()); + for (auto id : road_ids) { + auto &ruler = roads_.at(id); + RapidjsonValue geometry(rapidjson::kObjectType); + geometry.AddMember("type", "LineString", allocator); + geometry.AddMember("coordinates", + nano_fmm::to_rapidjson(ruler.polyline(), allocator), + allocator); + RapidjsonValue feature(rapidjson::kObjectType); + feature.AddMember("type", "Feature", allocator); + feature.AddMember("geometry", geometry, allocator); + RapidjsonValue properties(rapidjson::kObjectType); + properties.AddMember("type", "road", allocator); + properties.AddMember("id", RapidjsonValue(id), allocator); + auto nexts_itr = nexts_.find(id); + if (nexts_itr == nexts_.end()) { + properties.AddMember( + "nexts", + nano_fmm::to_rapidjson(std::vector{}, allocator), + allocator); + } else { + auto ids = std::vector(nexts_itr->second.begin(), + nexts_itr->second.end()); + std::sort(ids.begin(), ids.end()); + properties.AddMember( + "nexts", nano_fmm::to_rapidjson(ids, allocator), allocator); + } + auto prevs_itr = prevs_.find(id); + if (prevs_itr == prevs_.end()) { + properties.AddMember( + "prevs", + nano_fmm::to_rapidjson(std::vector{}, allocator), + allocator); + } else { + auto ids = std::vector(prevs_itr->second.begin(), + prevs_itr->second.end()); + std::sort(ids.begin(), ids.end()); + properties.AddMember( + "prevs", nano_fmm::to_rapidjson(ids, allocator), allocator); + } + feature.AddMember("properties", properties, allocator); + features.PushBack(feature, allocator); + } + + RapidjsonValue geojson(rapidjson::kObjectType); + geojson.AddMember("type", "FeatureCollection", allocator); + geojson.AddMember("features", features, allocator); + geojson.AddMember("config", config_.to_rapidjson(allocator), allocator); + geojson.AddMember("is_wgs84", RapidjsonValue(is_wgs84_), allocator); + return geojson; +} + +Network &Network::from_rapidjson(const RapidjsonValue &json) +{ + auto json_end = json.MemberEnd(); + auto config_itr = json.FindMember("config"); + if (config_itr == json_end) { + config_ = nano_fmm::from_rapidjson(config_itr->value); + } + return *this; +} +RapidjsonValue Network::to_rapidjson(RapidjsonAllocator &allocator) const +{ + RapidjsonValue json(rapidjson::kObjectType); + return json; +} + +} // namespace nano_fmm diff --git a/src/nano_fmm/types.hpp b/src/nano_fmm/types.hpp index 5b5e36c..f93e22a 100644 --- a/src/nano_fmm/types.hpp +++ b/src/nano_fmm/types.hpp @@ -31,6 +31,17 @@ using RapidjsonDocument = using RapidjsonValue = rapidjson::GenericValue, RapidjsonAllocator>; +#ifndef SETUP_FLUENT_API +#define SETUP_FLUENT_API(Klass, VarType, VarName) \ + Klass &VarName(const VarType &v) \ + { \ + VarName##_ = v; \ + return *this; \ + } \ + VarType &VarName() { return VarName##_; } \ + const VarType &VarName() const { return VarName##_; } +#endif + // https://github.com/cubao/pybind11-rdp/blob/master/src/main.cpp struct LineSegment { diff --git a/tests/test_basic.py b/tests/test_basic.py index 6e9ca87..b1c29d6 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,5 +1,6 @@ import contextlib import io +import json import os import sys import tempfile @@ -9,9 +10,10 @@ from typing import Dict, List import numpy as np +import pytest import nano_fmm as fmm -from nano_fmm import LineSegment, Network +from nano_fmm import LineSegment, Network, rapidjson from nano_fmm import flatbush as fb @@ -463,11 +465,112 @@ def test_logging(): output_string = buffer.getvalue() # assert output_string == "std::cout: hello five\nstd::cerr: hello five\n" - with capture_and_discard_output() as output: - print("hello world") - fmm.utils.logging("hello seven") - fmm.utils.flush() - output.seek(0) - output.read() - fmm.utils.logging("hello eight") - print(f"Captured: {output}") + # with capture_and_discard_output() as output: + # print("hello world") + # fmm.utils.logging("hello seven") + # fmm.utils.flush() + # output.seek(0) + # output.read() + # fmm.utils.logging("hello eight") + # print(f"Captured: {output}") + + +def test_json(): + j = rapidjson() + assert j.dumps() == "null" + assert json.dumps(None) == "null" + j = rapidjson({}) + assert j.dumps() == "{}" + j = rapidjson([]) + assert j.dumps() == "[]" + assert rapidjson(5).dumps() == "5" + assert rapidjson(3.14).dumps() == "3.14" + assert rapidjson("text").dumps() == '"text"' + for text in [ + "3.14", + "5", + '"text"', + '{"key": "value"}', + '["list", "items"]', + ]: + assert rapidjson().loads(text)() == json.loads(text) + + +def test_project_point_rapidjson(): + pt = fmm.ProjectedPoint() + with pytest.raises(Exception) as excinfo: + pt.position[0] = 5 + assert "read-only" in str(excinfo.value) + j = pt.to_rapidjson() + assert j() == { + "position": [0.0, 0.0, 0.0], + "direction": [0.0, 0.0, 1.0], + "distance": 0.0, + "road_id": 0, + "offset": 0.0, + } + + +def test_ubodt_rapidjson(): + rec = fmm.UbodtRecord() + j = rec.to_rapidjson() + assert j() == { + "source_road": 0, + "target_road": 0, + "source_next": 0, + "target_prev": 0, + "cost": 0.0, + } + j["source_road"] = 666 + rec.from_rapidjson(j) + assert rec.source_road == 666 + + +def test_indexer(): + indexer = fmm.Indexer() + assert "5" == indexer.id(5) + assert "10" == indexer.id(10) + assert "1000" == indexer.id(1000) + assert 1000 == indexer.id("1000") + assert indexer.to_rapidjson()() == { + "10": 10, + "1000": 1000, + "5": 5, + } + + indexer = fmm.Indexer() + assert 1000000 == indexer.id("road1") + assert 1000001 == indexer.id("road2") + assert 1000002 == indexer.id("road3") + assert 1000001 == indexer.id("road2") + assert 13579 == indexer.id("13579") + assert "road3" == indexer.id(1000002) + assert 1000003 == indexer.id("1000002") + assert 1000005 == indexer.id("1000005") + assert indexer.to_rapidjson()() == { + "1000002": 1000003, + "1000005": 1000005, + "13579": 13579, + "road1": 1000000, + "road2": 1000001, + "road3": 1000002, + } + + indexer2 = fmm.Indexer().from_rapidjson(indexer.to_rapidjson()) + assert 1000000 == indexer2.id("road1") + assert 1000001 == indexer2.id("road2") + assert 1000002 == indexer2.id("road3") + assert 1000001 == indexer2.id("road2") + assert 13579 == indexer2.id("13579") + assert "road3" == indexer2.id(1000002) + assert 1000003 == indexer2.id("1000002") + assert 1000005 == indexer2.id("1000005") + assert indexer2.to_rapidjson() == indexer.to_rapidjson() + indexer2.id("add another road") + assert indexer2.to_rapidjson() != indexer.to_rapidjson() + + +# fmm.utils.set_logging_level(0) # trace +# # fmm.utils.set_logging_level(6) # off +# network = Network.load("build/remapped.geojson") +# assert network.to_geojson()