From ec72bea8db0844220e605981242d3f51ebf7763a Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Mon, 11 Nov 2024 13:00:26 +0100 Subject: [PATCH] Adjust to new validity API. --- CMakeLists.txt | 3 +- libs/core/include/erdblick/geometry.h | 11 +- libs/core/include/erdblick/inspection.h | 1 + libs/core/include/erdblick/testdataprovider.h | 2 +- libs/core/include/erdblick/visualization.h | 7 + libs/core/src/bindings.cpp | 2 +- libs/core/src/cesium-interface/billboards.cpp | 3 + libs/core/src/geometry.cpp | 53 +++---- libs/core/src/inspection.cpp | 136 +++++++++++++----- libs/core/src/visualization.cpp | 106 +++++++++----- 10 files changed, 205 insertions(+), 119 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c6d3dbe..309bd58c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,8 @@ message("Building for ${CMAKE_SYSTEM_NAME}.") FetchContent_Declare(mapget GIT_REPOSITORY "https://github.com/Klebert-Engineering/mapget" - GIT_TAG "v2024.4.1" + # Version with new validity model + GIT_TAG "5dbf63e30" GIT_SHALLOW ON) FetchContent_MakeAvailable(mapget) diff --git a/libs/core/include/erdblick/geometry.h b/libs/core/include/erdblick/geometry.h index f8949552..5418e491 100644 --- a/libs/core/include/erdblick/geometry.h +++ b/libs/core/include/erdblick/geometry.h @@ -30,19 +30,14 @@ bool isPointInsideTriangle(m::Point const& p, m::Point const& p0, m::Point const * This is used as a location for labels, and as the origin * for relation vectors. */ -m::Point geometryCenter(m::model_ptr const& g); +m::Point geometryCenter(m::SelfContainedGeometry const& g); /** * Calculate a point furthest from the center for the given geometry. * Used to properly scale the camera in the viewer * relative to the feature's bounding sphere. */ -m::Point boundingRadiusEndPoint(m::model_ptr const& g); - -/** - * Get type of the geometry. - */ -m::GeomType getGeometryType(m::model_ptr const& g); +m::Point boundingRadiusEndPoint(m::SelfContainedGeometry const& g); /** * Calculate a local WGS84 coordinate system for the geometry. @@ -50,6 +45,6 @@ m::GeomType getGeometryType(m::model_ptr const& g); * in real-world length. The y-axis will point in the direction * (first-point -> last-point). The x-axis is perpendicular. */ -glm::dmat3x3 localWgs84UnitCoordinateSystem(const m::model_ptr& g); +glm::dmat3x3 localWgs84UnitCoordinateSystem(mapget::SelfContainedGeometry const& g); } // namespace erdblick diff --git a/libs/core/include/erdblick/inspection.h b/libs/core/include/erdblick/inspection.h index 7d9b8b08..b224d7ba 100644 --- a/libs/core/include/erdblick/inspection.h +++ b/libs/core/include/erdblick/inspection.h @@ -76,6 +76,7 @@ class InspectionConverter void convertAttributeLayer(std::string_view const& name, mapget::model_ptr const& l); void convertRelation(mapget::model_ptr const& r); void convertGeometry(JsValue const& key, mapget::model_ptr const& r); + void convertValidity(JsValue const& key, mapget::model_ptr const& r); OptionalValueAndType convertField(simfil::StringId const& fieldId, simfil::ModelNode::Ptr const& value); OptionalValueAndType convertField(std::string_view const& fieldName, simfil::ModelNode::Ptr const& value); diff --git a/libs/core/include/erdblick/testdataprovider.h b/libs/core/include/erdblick/testdataprovider.h index 1c36913d..bbe3f45b 100644 --- a/libs/core/include/erdblick/testdataprovider.h +++ b/libs/core/include/erdblick/testdataprovider.h @@ -168,7 +168,7 @@ class TestDataProvider // Add an attribute layer auto attrLayer = feature->attributeLayers()->newLayer("lane"); auto attr = attrLayer->newAttribute("numLanes"); - attr->setDirection(mapget::Attribute::Direction::Positive); + attr->validity()->newDirection(mapget::Validity::Positive); attr->addField("count", (int64_t)rand()); } diff --git a/libs/core/include/erdblick/visualization.h b/libs/core/include/erdblick/visualization.h index 5789262b..88f00e42 100644 --- a/libs/core/include/erdblick/visualization.h +++ b/libs/core/include/erdblick/visualization.h @@ -168,6 +168,13 @@ class FeatureLayerVisualization * Add some geometry. The Cesium conversion will be dispatched, * based on the geometry type and the style rule instructions. */ + void addGeometry( + mapget::SelfContainedGeometry const& geom, + std::string_view id, + FeatureStyleRule const& rule, + std::string const& mapLayerStyleRuleId, + BoundEvalFun& evalFun, + glm::dvec3 const& offset = {.0, .0, .0}); void addGeometry( mapget::model_ptr const& geom, std::string_view id, diff --git a/libs/core/src/bindings.cpp b/libs/core/src/bindings.cpp index 504dd115..a6c59e36 100644 --- a/libs/core/src/bindings.cpp +++ b/libs/core/src/bindings.cpp @@ -397,7 +397,7 @@ EMSCRIPTEN_BINDINGS(erdblick) "getGeometryType", std::function( [](FeaturePtr& self){ - return getGeometryType(self->firstGeometry()); + return self->firstGeometry().geomType_; })); ////////// GeomType diff --git a/libs/core/src/cesium-interface/billboards.cpp b/libs/core/src/cesium-interface/billboards.cpp index 13bae0b7..be77ee2d 100644 --- a/libs/core/src/cesium-interface/billboards.cpp +++ b/libs/core/src/cesium-interface/billboards.cpp @@ -18,6 +18,9 @@ JsValue CesiumBillboardCollection::billboardParams( const BoundEvalFun& evalFun) { auto result = CesiumPointPrimitiveCollection::pointParams(position, style, id, evalFun); + // TODO: Support non-square icons. + result.set("width", JsValue(style.width())); + result.set("height", JsValue(style.width())); if (style.hasIconUrl()) { result.set("image", JsValue(style.iconUrl(evalFun))); } diff --git a/libs/core/src/geometry.cpp b/libs/core/src/geometry.cpp index fb2ff599..052b0cc6 100644 --- a/libs/core/src/geometry.cpp +++ b/libs/core/src/geometry.cpp @@ -5,17 +5,11 @@ using namespace mapget; -Point erdblick::geometryCenter(const model_ptr& g) +Point erdblick::geometryCenter(const SelfContainedGeometry& g) { - if (!g) { - std::cerr << "Cannot obtain center of null geometry." << std::endl; - return {}; - } - // Initialize variables for averaging. - uint32_t totalPoints = g->numPoints(); - std::vector points; - points.reserve(g->numPoints()); + auto totalPoints = g.points_.size(); + auto const& points = g.points_; // Lambda to update totalX, totalY, totalZ, and count. auto averageVectorPosition = [](const std::vector& points) @@ -32,21 +26,13 @@ Point erdblick::geometryCenter(const model_ptr& g) return result; }; - // Process all points to find the average position. - g->forEachPoint( - [&points](const auto& p) - { - points.push_back(p); - return true; // Continue iterating. - }); - if (totalPoints == 0) { std::cerr << "Geometry has no points." << std::endl; return {}; } Point averagePoint = averageVectorPosition(points); - if (g->geomType() != GeomType::Mesh && g->geomType() != GeomType::Line) { + if (g.geomType_ != GeomType::Mesh && g.geomType_ != GeomType::Line) { return averagePoint; } @@ -65,7 +51,7 @@ Point erdblick::geometryCenter(const model_ptr& g) }); // For lines, return the shape-point closest to the average. - if (g->geomType() == GeomType::Line) { + if (g.geomType_ == GeomType::Line) { if (totalPoints % 2 == 1) { // Odd number of points: Return closest point. return pointsSorted.front(); @@ -120,36 +106,29 @@ Point erdblick::geometryCenter(const model_ptr& g) return averageVectorPosition(intersectedTrianglePoints); } -Point erdblick::boundingRadiusEndPoint(const model_ptr& g) +Point erdblick::boundingRadiusEndPoint(const SelfContainedGeometry& g) { - const Point center = erdblick::geometryCenter(g); - if (!g) { + const Point center = geometryCenter(g); + if (g.points_.empty()) { std::cerr << "Cannot obtain bounding radius vector end point of null geometry." << std::endl; return center; } float maxDistanceSquared = 0.0f; Point farPoint = center; - g->forEachPoint([¢er, &maxDistanceSquared, &farPoint](const auto& p) + for (auto const& p : g.points_) { - float dx = p.x - center.x; - float dy = p.y - center.y; - float dz = p.z - center.z; - float distanceSquared = dx * dx + dy * dy + dz * dz; + auto d = p - center; + float distanceSquared = d.x * d.x + d.y * d.y + d.z * d.z; if (distanceSquared > maxDistanceSquared) { farPoint = p; maxDistanceSquared = distanceSquared; } - return true; - }); + } return farPoint; } -GeomType erdblick::getGeometryType(const model_ptr& g) { - return g->geomType(); -} - double erdblick::pointSideOfLine(const Point& lineVector, const Point& lineStart, const Point& p) { return lineVector.x * (p.y - lineStart.y) - lineVector.y * (p.x - lineStart.x); @@ -198,7 +177,7 @@ bool erdblick::isPointInsideTriangle( return (side0 <= 0 && side1 <= 0 && side2 <= 0) || (side0 >= 0 && side1 >= 0 && side2 >= 0); } -glm::dmat3x3 erdblick::localWgs84UnitCoordinateSystem(const model_ptr& g) +glm::dmat3x3 erdblick::localWgs84UnitCoordinateSystem(const SelfContainedGeometry& g) { constexpr auto latMetersPerDegree = 110574.; // Meters per degree of latitude constexpr auto lonMetersPerDegree = 111320.; // Meters per degree of longitude at equator @@ -207,13 +186,13 @@ glm::dmat3x3 erdblick::localWgs84UnitCoordinateSystem(const model_ptr& {.0, 1./latMetersPerDegree, .0}, {.0, .0, 1.}}; - if (!g || g->geomType() != GeomType::Line || g->numPoints() < 2) { + if (g.geomType_ != GeomType::Line || g.points_.size() < 2) { return defaultResult; } - auto const aWgs = g->pointAt(0); + auto const aWgs = g.points_[0]; auto const a = wgsToCartesian(aWgs); - auto const b = wgsToCartesian(g->pointAt(g->numPoints() - 1)); + auto const b = wgsToCartesian(g.points_.back()); auto const c = wgsToCartesian(aWgs, {.0, .0, 1.}); auto const forward = glm::normalize(b - a); auto const up = glm::normalize(c - a); diff --git a/libs/core/src/inspection.cpp b/libs/core/src/inspection.cpp index 6d7878c2..19c54dd2 100644 --- a/libs/core/src/inspection.cpp +++ b/libs/core/src/inspection.cpp @@ -27,7 +27,7 @@ InspectionConverter::InspectionNode& convertSourceDataReferences(const model_ptr const auto& strings = model.strings(); const auto tileId = model.tileId().value_; - modelNode->forEachReference([tileId, &node](const mapget::SourceDataReferenceItem& item) { + modelNode->forEachReference([tileId, &node](const SourceDataReferenceItem& item) { node.sourceDataRefs_.push_back(Ref{ .tileId_ = tileId, .address_ = item.address().u64(), @@ -81,7 +81,7 @@ JsValue InspectionConverter::convert(model_ptr const& featurePtr) } // Basic attributes section. - if (auto attrs = featurePtr->attributes()) + if (auto attrs = featurePtr->attributesOrNull()) { auto scope = push(convertStringView("Basic Attributes"), "properties", ValueType::Section); for (auto const& [k, v] : attrs->fields()) { @@ -90,7 +90,7 @@ JsValue InspectionConverter::convert(model_ptr const& featurePtr) } // Flexible attributes section. - if (auto layers = featurePtr->attributeLayers()) + if (auto layers = featurePtr->attributeLayersOrNull()) { auto scope = push(convertStringView("Attribute Layers"), "properties.layer", ValueType::Section); layers->forEachLayer([this](auto&& layerName, auto&& layer) -> bool { @@ -112,7 +112,7 @@ JsValue InspectionConverter::convert(model_ptr const& featurePtr) } // Geometry section. - if (auto geomCollection = featurePtr->geom()) + if (auto geomCollection = featurePtr->geomOrNull()) { auto scope = push(convertStringView("Geometry"), "geometry", ValueType::Section); uint32_t geomIndex = 0; @@ -202,27 +202,8 @@ void InspectionConverter::convertAttributeLayer( current_->type_ = ValueType::Boolean; } - if (attr->hasValidity()) { - convertGeometry(convertStringView("validity"), attr->validity()); - } - - if (auto direction = attr->direction()) { - auto dirScope = push("direction", "direction", ValueType::String); - switch (direction) { - case Attribute::Positive: - dirScope->value_ = convertStringView("POSITIVE"); - break; - case Attribute::Negative: - dirScope->value_ = convertStringView("NEGATIVE"); - break; - case Attribute::Both: - dirScope->value_ = convertStringView("BOTH"); - break; - case Attribute::None: - dirScope->value_ = convertStringView("NONE"); - break; - default: break; - } + if (auto validity = attr->validityOrNull()) { + convertValidity(convertStringView("validity"), validity); } attrScope->hoverId_ = featureId_+":attribute#"+std::to_string(nextAttributeIndex_); @@ -245,19 +226,18 @@ void InspectionConverter::convertRelation(const model_ptr& r) relScope->mapId_ = JsValue(r->model().mapId()); relScope->hoverId_ = featureId_+":relation#"+std::to_string(nextRelationIndex_); convertSourceDataReferences(r->sourceDataReferences(), *relScope); - if (r->hasSourceValidity()) { - convertGeometry(convertStringView("sourceValidity"), r->sourceValidity()); + if (auto const sourceValidity = r->sourceValidityOrNull()) { + convertValidity(convertStringView("sourceValidity"), sourceValidity); } - if (r->hasTargetValidity()) { - convertGeometry(convertStringView("targetValidity"), r->targetValidity()); + if (auto const targetValidity = r->targetValidityOrNull()) { + convertValidity(convertStringView("targetValidity"), targetValidity); } ++nextRelationIndex_; } -void InspectionConverter::convertGeometry( - JsValue const& key, - const model_ptr& g) +void InspectionConverter::convertGeometry(JsValue const& key, const model_ptr& g) { + // TODO: Show geometry name auto geomScope = push( key, key.type() == JsValue::Type::Number ? @@ -274,12 +254,92 @@ void InspectionConverter::convertGeometry( convertSourceDataReferences(g->sourceDataReferences(), *geomScope); uint32_t index = 0; - g->forEachPoint([this, &geomScope, &index](auto&& pt){ - auto ptScope = push( - JsValue(geomScope->children_.size()), - index++, - ValueType::Number | ValueType::ArrayBit); - ptScope->value_ = JsValue::List({JsValue(pt.x), JsValue(pt.y), JsValue(pt.z)}); + g->forEachPoint( + [this, &geomScope, &index](auto&& pt) + { + auto ptScope = push( + JsValue(geomScope->children_.size()), + index++, + ValueType::Number | ValueType::ArrayBit); + ptScope->value_ = JsValue::List({JsValue(pt.x), JsValue(pt.y), JsValue(pt.z)}); + return true; + }); +} + +void InspectionConverter::convertValidity( + JsValue const& key, + model_ptr const& multiValidity) +{ + auto scope = push(key, key.as(), ValueType::ArrayBit); + uint32_t valIndex = 0; + multiValidity->forEach([this, &valIndex](Validity const& v) -> bool { + auto validityScope = push( + JsValue(valIndex), + valIndex, + ValueType::ArrayBit); + + if (auto direction = v.direction()) { + auto dirScope = push("direction", "direction", ValueType::String); + switch (direction) { + case Validity::Positive: + dirScope->value_ = convertStringView("POSITIVE"); + break; + case Validity::Negative: + dirScope->value_ = convertStringView("NEGATIVE"); + break; + case Validity::Both: + dirScope->value_ = convertStringView("BOTH"); + break; + case Validity::None: + dirScope->value_ = convertStringView("NONE"); + break; + default: break; + } + } + + if (auto geom = v.simpleGeometry()) { + convertGeometry(JsValue("simpleGeometry"), geom); + return true; + } + + if (auto geomName = v.geometryName()) { + push("geometryName", "geometryName", ValueType::String)->value_ = convertStringView(*geomName); + } + + auto renderOffset = [this, &v](Point const& data, std::string_view const& name) + { + switch (v.geometryOffsetType()) { + case Validity::InvalidOffsetType: + break; + case Validity::GeoPosOffset: { + auto ptScope = push(name, name, ValueType::Number | ValueType::ArrayBit); + ptScope->value_ = JsValue::List({JsValue(data.x), JsValue(data.y), JsValue(data.z)}); + break; + } + case Validity::BufferOffset: { + push(name, name, ValueType::Number)->value_ = + JsValue(fmt::format("Point Index {}", static_cast(data.x))); + break; + } + case Validity::RelativeLengthOffset: + push(name, name, ValueType::Number)->value_ = + JsValue(fmt::format("{:.2f}%", data.x * 100.)); + break; + case Validity::MetricLengthOffset: + push(name, name, ValueType::Number)->value_ = + JsValue(fmt::format("{:.2f}m", data.x)); + break; + } + }; + + if (auto rangeOffset = v.offsetRange()) { + renderOffset(rangeOffset->first, "start"); + renderOffset(rangeOffset->second, "end"); + } + else if (auto pointOffset = v.offsetPoint()) { + renderOffset(*pointOffset, "point"); + } + return true; }); } diff --git a/libs/core/src/visualization.cpp b/libs/core/src/visualization.cpp index 9cad14c8..cf1eb26f 100644 --- a/libs/core/src/visualization.cpp +++ b/libs/core/src/visualization.cpp @@ -268,7 +268,7 @@ void FeatureLayerVisualization::addFeature( case FeatureStyleRule::Attribute: { // Use const-version of the attribute layers, so the feature does not // lazily initialize its attribute layer list. - auto attrLayers = const_cast(*feature).attributeLayers(); + auto attrLayers = feature->attributeLayersOrNull(); if (!attrLayers) break; @@ -306,6 +306,20 @@ void FeatureLayerVisualization::addGeometry( std::string const& mapLayerStyleRuleId, BoundEvalFun& evalFun, glm::dvec3 const& offset) +{ + if (!geom) { + return; + } + addGeometry(geom->toSelfContained(), id, rule, mapLayerStyleRuleId, evalFun, offset); +} + +void FeatureLayerVisualization::addGeometry( + SelfContainedGeometry const& geom, + std::string_view id, + FeatureStyleRule const& rule, + std::string const& mapLayerStyleRuleId, + BoundEvalFun& evalFun, + glm::dvec3 const& offset) { // Combine the ID with the mapTileKey to create an // easy link from the geometry back to the feature. @@ -325,15 +339,11 @@ void FeatureLayerVisualization::addGeometry( } std::vector vertsCartesian; - vertsCartesian.reserve(geom->numPoints()); - geom->forEachPoint( - [&vertsCartesian, &offset](auto&& vertex) - { - vertsCartesian.emplace_back(wgsToCartesian(vertex, offset)); - return true; - }); + for (auto const& vertCarto : geom.points_) { + vertsCartesian.emplace_back(wgsToCartesian(vertCarto, offset)); + } - switch (geom->geomType()) { + switch (geom.geomType_) { case GeomType::Polygon: if (vertsCartesian.size() >= 3) { auto jsVerts = encodeVerticesAsList(vertsCartesian); @@ -361,7 +371,7 @@ void FeatureLayerVisualization::addGeometry( id, mapLayerStyleRuleId, gridCellSize, - geom->pointAt(pointIndex), + geom.points_[pointIndex], "pointParameters", evalFun, [&](auto& augmentedEvalFun) @@ -684,7 +694,7 @@ void FeatureLayerVisualization::addAttribute( // Check if the attribute validity is accepted for the rule. if (auto const& validityGeomRequired = rule.attributeValidityGeometry()) { - if (*validityGeomRequired != attr->hasValidity()) { + if (*validityGeomRequired != attr->validityOrNull()) { return; } } @@ -722,14 +732,28 @@ void FeatureLayerVisualization::addAttribute( ++offsetFactor; // Draw validity geometry. - auto geom = attr->hasValidity() ? attr->validity() : feature->firstGeometry(); - addGeometry( - geom, - id, - rule, - mapLayerStyleRuleId, - boundEvalFun, - offset * static_cast(offsetFactor)); + if (auto multiValidity = attr->validityOrNull()) { + multiValidity->forEach([&, this](auto&& validity) + { + addGeometry( + validity.computeGeometry(feature->geomOrNull()), + id, + rule, + mapLayerStyleRuleId, + boundEvalFun, + offset * static_cast(offsetFactor)); + return true; + }); + } + else { + addGeometry( + feature->firstGeometry(), + id, + rule, + mapLayerStyleRuleId, + boundEvalFun, + offset * static_cast(offsetFactor)); + } } void FeatureLayerVisualization::addOptionsToSimfilContext(simfil::OverlayNode& context) @@ -883,15 +907,25 @@ void RecursiveRelationVisualizationState::render( }}; // Obtain source/target geometries. - auto sourceGeom = r.relation_->hasSourceValidity() ? - r.relation_->sourceValidity() : - r.sourceFeature_->firstGeometry(); - auto targetGeom = r.relation_->hasTargetValidity() ? - r.relation_->targetValidity() : - r.targetFeature_->firstGeometry(); + auto convertMultiValidityWithFallback = [](model_ptr const& vv, model_ptr const& feature) { + std::vector result; + if (vv) { + vv->forEach([&result, &feature](auto&& v) + { + result.emplace_back(v.computeGeometry(feature->geomOrNull())); + return true; + }); + } + if (result.empty()) { + result = {feature->firstGeometry()}; + } + return result; + }; + auto sourceGeoms = convertMultiValidityWithFallback(r.relation_->sourceValidityOrNull(), r.sourceFeature_); + auto targetGeoms = convertMultiValidityWithFallback(r.relation_->sourceValidityOrNull(), r.sourceFeature_);; // Get offset base vector. - auto offsetBase = localWgs84UnitCoordinateSystem(sourceGeom); + auto offsetBase = localWgs84UnitCoordinateSystem(sourceGeoms[0]); auto offset = offsetBase * rule_.offset(); // Ensure that sourceStyle, targetStyle and endMarkerStyle @@ -900,10 +934,10 @@ void RecursiveRelationVisualizationState::render( auto targetId = r.targetFeature_->id()->toString(); // Create line geometry which connects source and target feature. - if (sourceGeom && targetGeom) + if (!sourceGeoms[0].points_.empty() && !targetGeoms[0].points_.empty()) { - auto p1lo = geometryCenter(sourceGeom); - auto p2lo = geometryCenter(targetGeom); + auto p1lo = geometryCenter(sourceGeoms[0]); + auto p2lo = geometryCenter(targetGeoms[0]); auto p1hi = Point{p1lo.x, p1lo.y, p1lo.z + rule_.relationLineHeightOffset()}; auto p2hi = Point{p2lo.x, p2lo.y, p2lo.z + rule_.relationLineHeightOffset()}; @@ -931,16 +965,22 @@ void RecursiveRelationVisualizationState::render( } // Run source geometry visualization. - if (sourceGeom && visualizedFeatures_.emplace(sourceId).second) { + if (visualizedFeatures_.emplace(sourceId).second) { if (auto sourceRule = rule_.relationSourceStyle()) { - visu_.addGeometry(sourceGeom, UnselectableId, *sourceRule, "", boundEvalFun, offsetBase * sourceRule->offset()); + for (auto const& sourceGeom : sourceGeoms) { + if (sourceGeom.points_.empty()) continue; + visu_.addGeometry(sourceGeom, UnselectableId, *sourceRule, "", boundEvalFun, offsetBase * sourceRule->offset()); + } } } // Run target geometry visualization. - if (targetGeom && visualizedFeatures_.emplace(targetId).second) { + if (visualizedFeatures_.emplace(targetId).second) { if (auto targetRule = rule_.relationTargetStyle()) { - visu_.addGeometry(targetGeom, UnselectableId, *targetRule, "", boundEvalFun, offsetBase * targetRule->offset()); + for (auto const& targetGeom : targetGeoms) { + if (targetGeom.points_.empty()) continue; + visu_.addGeometry(targetGeom, UnselectableId, *targetRule, "", boundEvalFun, offsetBase * targetRule->offset()); + } } }