From dc3d9d7bbd18182a65531dbd41f2efe4687af8d2 Mon Sep 17 00:00:00 2001 From: Serein Pfeiffer Date: Fri, 13 Feb 2026 16:43:25 +0100 Subject: [PATCH 1/5] pymapget: add read/iteration bindings for feature model --- libs/pymapget/binding/py-layer.h | 9 +- libs/pymapget/binding/py-model.h | 147 ++++++++++++++++++++++++------- 2 files changed, 124 insertions(+), 32 deletions(-) diff --git a/libs/pymapget/binding/py-layer.h b/libs/pymapget/binding/py-layer.h index 8b149852..3d5bcaa7 100644 --- a/libs/pymapget/binding/py-layer.h +++ b/libs/pymapget/binding/py-layer.h @@ -224,5 +224,12 @@ void bindTileLayer(py::module_& m) { return self.toJson().dump(); }, R"pbdoc( Convert this tile to a GeoJSON feature collection. - )pbdoc"); + )pbdoc") + .def("__len__", [](TileFeatureLayer const& self) { return self.size(); }) + .def("__getitem__", [](TileFeatureLayer const& self, int64_t i) { + auto sz = (int64_t)self.size(); + if (i < 0) i += sz; + if (i < 0 || i >= sz) throw py::index_error(); + return BoundFeature(self.at((size_t)i)); + }); } diff --git a/libs/pymapget/binding/py-model.h b/libs/pymapget/binding/py-model.h index 92e7c931..350fa8b6 100644 --- a/libs/pymapget/binding/py-model.h +++ b/libs/pymapget/binding/py-model.h @@ -51,7 +51,15 @@ struct BoundModelNodeBase : public BoundModelNode [](const model_ptr& node) { return node->value(); }, R"pbdoc( Get the node's scalar value if it has one. - )pbdoc"); + )pbdoc") + .def( + "to_json", + [](BoundModelNode& self) { + if (auto n = self.node()) + return n->toJson().dump(); + return std::string("null"); + }, + "Convert this node to a JSON string."); py::class_(m, "ModelNodeBase"); } @@ -164,6 +172,19 @@ struct BoundObject : public BoundModelNode { auto boundClass = py::class_(m, "Object"); bindObjectMethods(boundClass); + boundClass + .def("__len__", [](BoundObject& self) { return self.modelNodePtr_->size(); }) + .def( + "__getitem__", + [](BoundObject& self, std::string_view const& key) { + auto result = self.modelNodePtr_->get(key); + if (!result) throw py::key_error(std::string(key)); + BoundModelNodeBase node; + node.modelNodePtr_ = *result; + return node; + }, + py::arg("key"), + "Get a field by name."); } ModelNode::Ptr node() override { return modelNodePtr_; } @@ -185,7 +206,20 @@ struct BoundArray : public BoundModelNode std::visit([&self](auto&& value) { self.modelNodePtr_->append(value); }, vv); }, py::arg("value"), - "Append a value to the array."); + "Append a value to the array.") + .def("__len__", [](BoundArray& self) { return self.modelNodePtr_->size(); }) + .def( + "__getitem__", + [](BoundArray& self, int64_t i) { + auto sz = (int64_t)self.modelNodePtr_->size(); + if (i < 0) i += sz; + if (i < 0 || i >= sz) throw py::index_error(); + BoundModelNodeBase node; + node.modelNodePtr_ = self.modelNodePtr_->at(i); + return node; + }, + py::arg("index"), + "Get an element by index."); } ModelNode::Ptr node() override { return modelNodePtr_; } @@ -225,7 +259,24 @@ struct BoundGeometry : public BoundModelNode py::arg("point"), R"pbdoc( Append a point to the geometry. - )pbdoc"); + )pbdoc") + .def("geom_type", [](BoundGeometry& self) { return self.modelNodePtr_->geomType(); }, + "Get the type of the geometry.") + .def("num_points", [](BoundGeometry& self) { return self.modelNodePtr_->numPoints(); }, + "Get the number of points.") + .def("point_at", [](BoundGeometry& self, size_t i) { return self.modelNodePtr_->pointAt(i); }, + py::arg("index"), "Get a point at an index.") + .def("__len__", [](BoundGeometry& self) { return self.modelNodePtr_->numPoints(); }) + .def("__getitem__", [](BoundGeometry& self, int64_t i) { + auto n = (int64_t)self.modelNodePtr_->numPoints(); + if (i < 0) i += n; + if (i < 0 || i >= n) throw py::index_error(); + return self.modelNodePtr_->pointAt(i); + }) + .def("name", [](BoundGeometry& self) { return self.modelNodePtr_->name(); }, + "Get the geometry name, if set.") + .def("length", [](BoundGeometry& self) { return self.modelNodePtr_->length(); }, + "Get total length in metres (for polylines)."); } ModelNode::Ptr node() override { return modelNodePtr_; } @@ -245,7 +296,19 @@ struct BoundGeometryCollection : public BoundModelNode [](BoundGeometryCollection& self, GeomType const& geomType) { return BoundGeometry(self.modelNodePtr_->newGeometry(geomType)); }, py::arg("geom_type"), - "Create and insert a new geometry into the collection."); + "Create and insert a new geometry into the collection.") + .def("__len__", [](BoundGeometryCollection& self) { + return self.modelNodePtr_->numGeometries(); + }) + .def("__iter__", [](BoundGeometryCollection& self) { + py::list result; + self.modelNodePtr_->forEachGeometry( + [&result](model_ptr const& geom) { + result.append(BoundGeometry(geom)); + return true; + }); + return py::iter(result); + }); } ModelNode::Ptr node() override { return modelNodePtr_; } @@ -301,7 +364,16 @@ struct BoundAttributeLayer : public BoundModelNode [](BoundAttributeLayer& self, BoundAttribute const& a) { self.modelNodePtr_->addAttribute(a.modelNodePtr_); }, py::arg("a"), - "Add an existing attribute to the layer."); + "Add an existing attribute to the layer.") + .def("__iter__", [](BoundAttributeLayer& self) { + py::list result; + self.modelNodePtr_->forEachAttribute( + [&result](model_ptr const& attr) { + result.append(BoundAttribute(attr)); + return true; + }); + return py::iter(result); + }); } ModelNode::Ptr node() override { return modelNodePtr_; } @@ -330,7 +402,16 @@ struct BoundAttributeLayerList : public BoundModelNode { self.modelNodePtr_->addLayer(name, l.modelNodePtr_); }, py::arg("name"), py::arg("layer"), - "Add an existing layer to the collection."); + "Add an existing layer to the collection.") + .def("__iter__", [](BoundAttributeLayerList& self) { + py::list result; + self.modelNodePtr_->forEachLayer( + [&result](std::string_view name, model_ptr const& layer) { + result.append(py::make_tuple(std::string(name), BoundAttributeLayer(layer))); + return true; + }); + return py::iter(result); + }); } ModelNode::Ptr node() override { return modelNodePtr_; } @@ -342,6 +423,28 @@ struct BoundAttributeLayerList : public BoundModelNode model_ptr modelNodePtr_; }; +struct BoundFeatureId : public BoundModelNode +{ + static void bind(py::module_& m) + { + py::class_(m, "FeatureId") + .def( + "to_string", + [](BoundFeatureId& self) { return self.modelNodePtr_->toString(); }, + "Convert the FeatureId to a string.") + .def( + "type_id", + [](BoundFeatureId& self) { return self.modelNodePtr_->typeId(); }, + "Get the feature ID's type ID."); + } + + ModelNode::Ptr node() override { return modelNodePtr_; } + + explicit BoundFeatureId(model_ptr const& ptr) : modelNodePtr_(ptr) {} + + model_ptr modelNodePtr_; +}; + struct BoundFeature : public BoundModelNode { static void bind(py::module_& m) @@ -351,6 +454,10 @@ struct BoundFeature : public BoundModelNode "type_id", [](BoundFeature& self) { return self.modelNodePtr_->typeId(); }, "Get the type ID of the feature.") + .def( + "id", + [](BoundFeature& self) { return BoundFeatureId(self.modelNodePtr_->id()); }, + "Get the feature's unique ID.") .def( "evaluate", [](BoundFeature& self, std::string_view const& expression) @@ -364,8 +471,8 @@ struct BoundFeature : public BoundModelNode "Evaluate a filter expression on this feature.") .def( "to_json", - [](BoundFeature& self) { return self.modelNodePtr_->toJson(); }, - "Convert the Feature to JSON.") + [](BoundFeature& self) { return self.modelNodePtr_->toJson().dump(); }, + "Convert the Feature to a JSON string.") .def( "geom", [](BoundFeature& self) @@ -428,28 +535,6 @@ struct BoundFeature : public BoundModelNode model_ptr modelNodePtr_; }; -struct BoundFeatureId : public BoundModelNode -{ - static void bind(py::module_& m) - { - py::class_(m, "FeatureId") - .def( - "to_string", - [](BoundFeatureId& self) { return self.modelNodePtr_->toString(); }, - "Convert the FeatureId to a string.") - .def( - "type_id", - [](BoundFeatureId& self) { return self.modelNodePtr_->typeId(); }, - "Get the feature ID's type ID."); - } - - ModelNode::Ptr node() override { return modelNodePtr_; } - - explicit BoundFeatureId(model_ptr const& ptr) : modelNodePtr_(ptr) {} - - model_ptr modelNodePtr_; -}; - } // namespace mapget void bindModel(py::module& m) @@ -462,6 +547,6 @@ void bindModel(py::module& m) mapget::BoundAttribute::bind(m); mapget::BoundAttributeLayer::bind(m); mapget::BoundAttributeLayerList::bind(m); - mapget::BoundFeature::bind(m); mapget::BoundFeatureId::bind(m); + mapget::BoundFeature::bind(m); } From d165a8753cfa4cbd3763b94caff0ffed381f6ffe Mon Sep 17 00:00:00 2001 From: Serein Pfeiffer Date: Fri, 13 Feb 2026 16:43:41 +0100 Subject: [PATCH 2/5] geojsonsource: support all GeoJSON geometry types --- libs/geojsonsource/src/geojsonsource.cpp | 61 +++++++++++++++++++----- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/libs/geojsonsource/src/geojsonsource.cpp b/libs/geojsonsource/src/geojsonsource.cpp index 6a0bd37c..4d253f09 100644 --- a/libs/geojsonsource/src/geojsonsource.cpp +++ b/libs/geojsonsource/src/geojsonsource.cpp @@ -335,18 +335,57 @@ void GeoJsonSource::fill(const mapget::TileFeatureLayer::Ptr& tile) auto feature = tile->newFeature(featureTypeName, {{"featureIndex", featureId}}); featureId++; - // Get geometry data - auto geometry = feature_data["geometry"]; - if (geometry["type"] == "Point") { - auto coordinates = geometry["coordinates"]; - feature->addPoint({coordinates[0], coordinates[1]}); - } - else if (geometry["type"] == "LineString") { - auto line = feature->geom()->newGeometry(GeomType::Line, 2); - for (auto& coordinates : geometry["coordinates"]) { - line->append({coordinates[0], coordinates[1]}); + // Parse geometry data (recursive lambda to support GeometryCollection) + std::function addGeometry; + addGeometry = [&](nlohmann::json const& geom) { + if (!geom.is_object() || !geom.contains("type")) + return; + auto const type = geom["type"].get(); + if (type == "Point") { + auto const& c = geom["coordinates"]; + feature->addPoint({c[0], c[1]}); } - } + else if (type == "MultiPoint") { + auto points = feature->geom()->newGeometry(GeomType::Points, geom["coordinates"].size()); + for (auto const& c : geom["coordinates"]) + points->append({c[0], c[1]}); + } + else if (type == "LineString") { + auto line = feature->geom()->newGeometry(GeomType::Line, geom["coordinates"].size()); + for (auto const& c : geom["coordinates"]) + line->append({c[0], c[1]}); + } + else if (type == "MultiLineString") { + for (auto const& coords : geom["coordinates"]) { + auto line = feature->geom()->newGeometry(GeomType::Line, coords.size()); + for (auto const& c : coords) + line->append({c[0], c[1]}); + } + } + else if (type == "Polygon") { + if (!geom["coordinates"].empty()) { + auto const& ring = geom["coordinates"][0]; + auto poly = feature->geom()->newGeometry(GeomType::Polygon, ring.size()); + for (auto const& c : ring) + poly->append({c[0], c[1]}); + } + } + else if (type == "MultiPolygon") { + for (auto const& polygon : geom["coordinates"]) { + if (!polygon.empty()) { + auto const& ring = polygon[0]; + auto poly = feature->geom()->newGeometry(GeomType::Polygon, ring.size()); + for (auto const& c : ring) + poly->append({c[0], c[1]}); + } + } + } + else if (type == "GeometryCollection") { + for (auto const& child : geom["geometries"]) + addGeometry(child); + } + }; + addGeometry(feature_data["geometry"]); // Add top-level properties as attributes for (auto& property : feature_data["properties"].items()) { From e11244263188d8871cf5723278fe9668c78a064d Mon Sep 17 00:00:00 2001 From: Serein Pfeiffer Date: Fri, 13 Feb 2026 17:27:40 +0100 Subject: [PATCH 3/5] deps: use simfil v0.6.3 release tag --- cmake/deps.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/deps.cmake b/cmake/deps.cmake index 89300df1..20ba0220 100644 --- a/cmake/deps.cmake +++ b/cmake/deps.cmake @@ -15,7 +15,7 @@ CPMAddPackage( "EXPECTED_BUILD_TESTS OFF" "EXPECTED_BUILD_PACKAGE_DEB OFF") CPMAddPackage( - URI "gh:Klebert-Engineering/simfil#byte-array" + URI "gh:Klebert-Engineering/simfil#v0.6.3" OPTIONS "SIMFIL_WITH_MODEL_JSON ON" "SIMFIL_SHARED OFF") From 247cbfdb15d7fb444816e8474fe50f3e3eaa6811 Mon Sep 17 00:00:00 2001 From: Serein Pfeiffer Date: Sun, 15 Feb 2026 15:20:27 +0100 Subject: [PATCH 4/5] http-client: set default Accept header for tile requests --- libs/http-service/src/http-client.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/http-service/src/http-client.cpp b/libs/http-service/src/http-client.cpp index e618a649..f1380897 100644 --- a/libs/http-service/src/http-client.cpp +++ b/libs/http-service/src/http-client.cpp @@ -114,6 +114,7 @@ LayerTilesRequest::Ptr HttpClient::request(const LayerTilesRequest::Ptr& request httpReq->setMethod(drogon::Post); httpReq->setPath("/tiles"); httpReq->setContentTypeCode(drogon::CT_APPLICATION_JSON); + httpReq->addHeader("Accept", "application/binary"); httpReq->setBody(std::move(body)); applyHeaders(httpReq, impl_->headers_); From e28039f1107f74fc7fb023aa4df567b71b9c2f0a Mon Sep 17 00:00:00 2001 From: Serein Pfeiffer Date: Sun, 15 Feb 2026 15:20:51 +0100 Subject: [PATCH 5/5] pymapget: add to_dict(), iteration bindings, and ValueType enum --- libs/pymapget/binding/py-model.h | 246 ++++++++++++++++++++++++++++--- 1 file changed, 228 insertions(+), 18 deletions(-) diff --git a/libs/pymapget/binding/py-model.h b/libs/pymapget/binding/py-model.h index 350fa8b6..d1af9548 100644 --- a/libs/pymapget/binding/py-model.h +++ b/libs/pymapget/binding/py-model.h @@ -16,6 +16,8 @@ using namespace simfil; namespace mapget { +py::object nodeToPython(model_ptr const& n, TileFeatureLayer& fl, bool checkMultimap = false); + struct BoundModelNode { virtual ~BoundModelNode() = default; @@ -48,7 +50,11 @@ struct BoundModelNodeBase : public BoundModelNode py::class_(m, "ModelNode") .def( "value", - [](const model_ptr& node) { return node->value(); }, + [](BoundModelNode& self) { + if (auto n = self.node()) + return n->value(); + return ScalarValueType{}; + }, R"pbdoc( Get the node's scalar value if it has one. )pbdoc") @@ -59,8 +65,83 @@ struct BoundModelNodeBase : public BoundModelNode return n->toJson().dump(); return std::string("null"); }, - "Convert this node to a JSON string."); - py::class_(m, "ModelNodeBase"); + "Convert this node to a JSON string.") + .def( + "to_dict", + [](BoundModelNode& self) -> py::object { + if (auto n = self.node()) { + auto& fl = self.featureLayer(); + return nodeToPython(n, fl); + } + return py::none(); + }, + "Convert this node to a Python dict/list/scalar."); + py::enum_(m, "ValueType") + .value("UNDEF", ValueType::Undef) + .value("NULL_", ValueType::Null) + .value("BOOL", ValueType::Bool) + .value("INT", ValueType::Int) + .value("FLOAT", ValueType::Float) + .value("STRING", ValueType::String) + .value("BYTES", ValueType::Bytes) + .value("OBJECT", ValueType::Object) + .value("ARRAY", ValueType::Array); + + py::class_(m, "ModelNodeBase") + .def("__len__", [](BoundModelNodeBase& self) { + return self.modelNodePtr_->size(); + }) + .def("__getitem__", [](BoundModelNodeBase& self, std::string_view const& key) { + auto& fl = self.featureLayer(); + for (auto const& [fieldId, child] : self.modelNodePtr_->fields()) { + if (auto resolved = fl.lookupStringId(fieldId)) { + if (*resolved == key) { + BoundModelNodeBase node; + node.modelNodePtr_ = child; + return node; + } + } + } + throw py::key_error(std::string(key)); + }, py::arg("key")) + .def("__getitem__", [](BoundModelNodeBase& self, int64_t i) { + auto sz = (int64_t)self.modelNodePtr_->size(); + if (i < 0) i += sz; + if (i < 0 || i >= sz) throw py::index_error(); + BoundModelNodeBase node; + node.modelNodePtr_ = self.modelNodePtr_->at(i); + return node; + }, py::arg("index")) + .def("__iter__", [](BoundModelNodeBase& self) { + auto n = self.modelNodePtr_; + auto type = n->type(); + if (type == ValueType::Object) { + py::list result; + auto& fl = self.featureLayer(); + for (auto const& [fieldId, child] : n->fields()) { + if (auto key = fl.lookupStringId(fieldId)) { + BoundModelNodeBase node; + node.modelNodePtr_ = child; + result.append(py::make_tuple(std::string(*key), node)); + } + } + return py::iter(result); + } + else if (type == ValueType::Array) { + py::list result; + for (uint32_t i = 0; i < n->size(); ++i) { + BoundModelNodeBase node; + node.modelNodePtr_ = n->at(i); + result.append(node); + } + return py::iter(result); + } + py::list empty; + return py::iter(empty); + }) + .def("type", [](BoundModelNodeBase& self) { + return self.modelNodePtr_->type(); + }); } ModelNode::Ptr node() override { return modelNodePtr_; } @@ -184,7 +265,19 @@ struct BoundObject : public BoundModelNode return node; }, py::arg("key"), - "Get a field by name."); + "Get a field by name.") + .def("__iter__", [](BoundObject& self) { + py::list result; + auto& fl = self.featureLayer(); + for (auto const& [fieldId, childNode] : self.modelNodePtr_->fields()) { + if (auto resolved = fl.lookupStringId(fieldId)) { + BoundModelNodeBase node; + node.modelNodePtr_ = childNode; + result.append(py::make_tuple(std::string(*resolved), node)); + } + } + return py::iter(result); + }); } ModelNode::Ptr node() override { return modelNodePtr_; } @@ -219,7 +312,17 @@ struct BoundArray : public BoundModelNode return node; }, py::arg("index"), - "Get an element by index."); + "Get an element by index.") + .def("__iter__", [](BoundArray& self) { + py::list result; + auto sz = self.modelNodePtr_->size(); + for (uint32_t i = 0; i < sz; ++i) { + BoundModelNodeBase node; + node.modelNodePtr_ = self.modelNodePtr_->at(i); + result.append(node); + } + return py::iter(result); + }); } ModelNode::Ptr node() override { return modelNodePtr_; } @@ -343,6 +446,18 @@ struct BoundAttribute : public BoundObject "Get the name of the attribute."); bindObjectMethods(boundClass); + boundClass.def("__iter__", [](BoundAttribute& self) { + py::list result; + auto& fl = self.featureLayer(); + for (auto const& [fieldId, childNode] : self.modelNodePtr_->fields()) { + if (auto resolved = fl.lookupStringId(fieldId)) { + BoundModelNodeBase node; + node.modelNodePtr_ = childNode; + result.append(py::make_tuple(std::string(*resolved), node)); + } + } + return py::iter(result); + }); } explicit BoundAttribute(model_ptr const& ptr) : BoundObject(ptr) {} @@ -373,7 +488,14 @@ struct BoundAttributeLayer : public BoundModelNode return true; }); return py::iter(result); - }); + }) + .def("to_dict", [](BoundAttributeLayer& self) -> py::object { + if (auto n = self.node()) { + auto& fl = self.featureLayer(); + return nodeToPython(n, fl, true); + } + return py::none(); + }, "Convert this layer to a Python dict (handles duplicate attribute names)."); } ModelNode::Ptr node() override { return modelNodePtr_; } @@ -411,7 +533,17 @@ struct BoundAttributeLayerList : public BoundModelNode return true; }); return py::iter(result); - }); + }) + .def("to_dict", [](BoundAttributeLayerList& self) -> py::object { + py::dict d; + auto& fl = self.featureLayer(); + self.modelNodePtr_->forEachLayer( + [&](std::string_view name, model_ptr const& layer) { + d[py::str(std::string(name))] = nodeToPython(layer, fl, true); + return true; + }); + return d; + }, "Convert all layers to a Python dict (handles duplicate attribute names)."); } ModelNode::Ptr node() override { return modelNodePtr_; } @@ -458,17 +590,6 @@ struct BoundFeature : public BoundModelNode "id", [](BoundFeature& self) { return BoundFeatureId(self.modelNodePtr_->id()); }, "Get the feature's unique ID.") - .def( - "evaluate", - [](BoundFeature& self, std::string_view const& expression) - { - auto res = self.modelNodePtr_->evaluate(expression); - if (!res) - throw std::runtime_error(res.error().message); - return res->getScalar(); - }, - py::arg("expression"), - "Evaluate a filter expression on this feature.") .def( "to_json", [](BoundFeature& self) { return self.modelNodePtr_->toJson().dump(); }, @@ -535,6 +656,95 @@ struct BoundFeature : public BoundModelNode model_ptr modelNodePtr_; }; +/// Recursively convert a ModelNode tree to native Python objects. +/// Mirrors simfil's ModelNode::toJson() (nodes.cpp) but builds +/// py::dict/py::list/scalars directly, avoiding JSON serialization. +/// +/// Handles two special cases that toJson() also handles: +/// - Multimap objects: when checkMultimap is true and an Object has +/// duplicate keys, values are grouped into lists with a "_multimap" +/// marker. Only AttributeLayer-level objects can have duplicates, +/// so callers should only pass true at that level to avoid overhead. +/// - ByteArray scalars: converted to {"_bytes": True, "hex": ..., "number": ...}. +py::object nodeToPython(model_ptr const& n, TileFeatureLayer& fl, bool checkMultimap) +{ + auto type = n->type(); + if (type == ValueType::Object) { + py::dict d; + if (checkMultimap) { + // Pre-scan for duplicate field IDs using stack-allocated + // array with cheap uint16_t comparison. Only called for + // AttributeLayer-level objects, not recursively. + bool isMultiMap = false; + { + uint16_t seen[32]; + size_t count = 0; + for (auto const& [fieldId, _] : n->fields()) { + for (size_t j = 0; j < count; ++j) { + if (seen[j] == fieldId) { + isMultiMap = true; + break; + } + } + if (isMultiMap) break; + if (count < 32) + seen[count++] = fieldId; + } + } + if (isMultiMap) { + for (auto const& [fieldId, child] : n->fields()) { + if (auto key = fl.lookupStringId(fieldId)) { + auto pyKey = py::str(std::string(*key)); + if (d.contains(pyKey)) + d[pyKey].cast().append(nodeToPython(child, fl)); + else + d[pyKey] = py::list(py::make_tuple(nodeToPython(child, fl))); + } + } + d[py::str("_multimap")] = py::bool_(true); + return d; + } + } + for (auto const& [fieldId, child] : n->fields()) { + if (auto key = fl.lookupStringId(fieldId)) + d[py::str(std::string(*key))] = nodeToPython(child, fl); + } + return d; + } + if (type == ValueType::Array) { + py::list l; + for (uint32_t i = 0; i < n->size(); ++i) + l.append(nodeToPython(n->at(i), fl)); + return l; + } + auto v = n->value(); + return std::visit([](auto&& val) -> py::object { + using T = std::decay_t; + if constexpr (std::is_same_v) + return py::bool_(val); + else if constexpr (std::is_same_v) + return py::int_(val); + else if constexpr (std::is_same_v) + return py::float_(val); + else if constexpr (std::is_same_v) + return py::str(val); + else if constexpr (std::is_same_v) + return py::str(std::string(val)); + else if constexpr (std::is_same_v) { + py::dict d; + d["_bytes"] = py::bool_(true); + d["hex"] = py::str(val.toHex(false)); + if (auto decoded = val.decodeBigEndianI64()) + d["number"] = py::int_(*decoded); + else + d["number"] = py::none(); + return d; + } + else + return py::none(); + }, v); +} + } // namespace mapget void bindModel(py::module& m)