diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ee23ee6..73120bfc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ include(FetchContent) set(CMAKE_CXX_STANDARD 20) # Treat warnings as errors, with some exceptions for Cesium. -set (ERDBLICK_CXX_FLAGS +set(ERDBLICK_CXX_FLAGS "-Wall -Wno-error=sign-conversion -Wno-sign-compare -Wno-sign-conversion -Wno-unused-local-typedefs -Wno-comment -Wno-effc++") if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${ERDBLICK_CXX_FLAGS} -Wno-bool-compare") @@ -14,8 +14,12 @@ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${ERDBLICK_CXX_FLAGS} -Wno-error=shorten-64-to-32") endif() -# External dependencies. +# Adjust CXX_FLAGS for Debug builds +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") +endif() +# External dependencies message("Building for ${CMAKE_SYSTEM_NAME}.") FetchContent_Declare(mapget @@ -44,4 +48,3 @@ else() WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" DEPENDS erdblick-core) endif() - diff --git a/CMakePresets.json b/CMakePresets.json index 0a6c4045..3b97b56a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -16,7 +16,11 @@ "inherits": "common", "displayName": "Debug", "cacheVariables": { - "CMAKE_BUILD_TYPE": "Debug" + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_CXX_FLAGS_DEBUG": "-g" + }, + "environment": { + "EMCC_DEBUG": "1" } }, { diff --git a/README.md b/README.md index 0878ec6f..608b258d 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ Each rule within the YAML `rules` array can have the following fields: | `relation-recursive` | Specifies whether relations should be resolved recursively. Only done if `mode=="Highlight"`, and only works for relations within the same layer. | Boolean | `true`, `false` | | `relation-merge-twoway` | Specifies whether bidirectional relations should be followed and merged. | Boolean | `true`, `false` | | `attribute-type` | A regular expression to match against an attribute type. | String | `SPEED_LIMIT_.*` | +| `attribute-filter` | A [simfil](https://github.com/klebert-engineering/simfil) expression to evaluate against the attribute's fields. | String | `attributeValue.speedLimitKmh > 100` | | `attribute-layer-type` | A regular expression to match against the attribute layer type name. | String | `Road.*Layer` | | `attribute-validity-geom` | Set to `required`, `none` or `any` to control whether matching attributes must have a validity geometry. | String | `Road.*Layer` | | `label-color` | Text color of the label. | String | `#00ccdd` | diff --git a/angular.json b/angular.json index 6d27c1bf..5ed801fa 100644 --- a/angular.json +++ b/angular.json @@ -27,7 +27,7 @@ "assets": [ { "glob": "**/*", - "input": "node_modules/cesium/Build/Cesium", + "input": "node_modules/cesium/Build/CesiumUnminified", "output": "/bundle/cesium" }, { @@ -58,9 +58,7 @@ "node_modules/material-icons/iconfont/material-icons.css", "erdblick_app/styles.scss" ], - "scripts": [ - - ], + "scripts": [], "customWebpackConfig": { "path": "./webpack.config.js" }, @@ -80,7 +78,34 @@ "maximumError": "4kb" } ], - "outputHashing": "all" + "outputHashing": "all", + "assets": [ + { + "glob": "**/*", + "input": "node_modules/cesium/Build/Cesium", + "output": "/bundle/cesium" + }, + { + "glob": "**/*", + "input": "config/styles", + "output": "/bundle/styles" + }, + { + "glob": "**/*", + "input": "images", + "output": "/bundle/images" + }, + { + "glob": "**/*.json", + "input": "config", + "output": "/" + }, + { + "glob": "VERSION", + "input": ".", + "output": "/bundle/" + } + ] }, "development": { "buildOptimizer": false, @@ -123,8 +148,7 @@ "zone.js/testing" ], "tsConfig": "tsconfig.spec.json", - "assets": [ - ], + "assets": [], "styles": [ "erdblick_app/styles.scss" ], @@ -136,6 +160,6 @@ } }, "cli": { - "analytics": false + "analytics": false } } diff --git a/erdblick_app/app/debugapi.component.ts b/erdblick_app/app/debugapi.component.ts index b9c2c7dc..d42c724f 100644 --- a/erdblick_app/app/debugapi.component.ts +++ b/erdblick_app/app/debugapi.component.ts @@ -89,4 +89,15 @@ export class ErdblickDebugApi { coreLib(): ErdblickCore_ { return coreLib; } + + /** Run some simfil query to reproduce problems with search. */ + runSimfilQuery(query: string = "**.transition") { + for (const [_, tile] of this.mapService.loadedTileLayers) { + tile.peek(parsedTile => { + let search = new coreLib.FeatureLayerSearch(parsedTile); + const matchingFeatures = search.filter(query); + search.delete(); + }) + } + } } diff --git a/erdblick_app/app/editor.component.ts b/erdblick_app/app/editor.component.ts index f4b40d06..fd6e3357 100644 --- a/erdblick_app/app/editor.component.ts +++ b/erdblick_app/app/editor.component.ts @@ -60,6 +60,7 @@ const completionsList = [ {label: "offset-scale-by-distance", type: "property"}, {label: "first-of", type: "property"}, {label: "attribute-type", type: "property"}, + {label: "attribute-filter", type: "property"}, {label: "attribute-layer-type", type: "property"}, {label: "point-merge-grid-cell", type: "property"}, {label: "FILL", type: "keyword"}, diff --git a/erdblick_app/app/feature.panel.component.ts b/erdblick_app/app/feature.panel.component.ts index f983b1a3..3f1f47d6 100644 --- a/erdblick_app/app/feature.panel.component.ts +++ b/erdblick_app/app/feature.panel.component.ts @@ -79,7 +79,10 @@ interface Column { {{ rowData['key'] }} + (mouseover)="onKeyHover($event, rowData)" + (mouseout)="onKeyHoverExit($event, rowData)" + style="cursor: pointer"> + {{ rowData['key'] }} @@ -402,6 +405,11 @@ export class FeaturePanelComponent implements OnInit, AfterViewInit, OnDestroy rowData["mapId"], rowData["value"], coreLib.HighlightMode.HOVER_HIGHLIGHT).then(); + } else if (rowData["hoverId"]) { + // this.mapService.highlightFeatures([{ + // mapTileKey: this.inspectionService.selectedFeatures[rowData["featureIndex"]].featureTile.mapTileKey, + // featureId: rowData["hoverId"] + // }], false, coreLib.HighlightMode.HOVER_HIGHLIGHT).then(); } } @@ -412,6 +420,23 @@ export class FeaturePanelComponent implements OnInit, AfterViewInit, OnDestroy } } + onKeyHover(event: any, rowData: any) { + event.stopPropagation(); + if (rowData["hoverId"]) { + // this.mapService.highlightFeatures([{ + // mapTileKey: this.inspectionService.selectedFeatures[rowData["featureIndex"]].featureTile.mapTileKey, + // featureId: rowData["hoverId"] + // }], false, coreLib.HighlightMode.HOVER_HIGHLIGHT).then(); + } + } + + onKeyHoverExit(event: any, rowData: any) { + event.stopPropagation(); + // if (rowData["type"] == this.InspectionValueType.FEATUREID.value) { + // this.mapService.highlightFeatures([], false, coreLib.HighlightMode.HOVER_HIGHLIGHT).then(); + // } + } + getStyleClassByType(valueType: number): string { switch (valueType) { case this.InspectionValueType.SECTION.value: diff --git a/erdblick_app/app/inspection.service.ts b/erdblick_app/app/inspection.service.ts index c70b6264..40776151 100644 --- a/erdblick_app/app/inspection.service.ts +++ b/erdblick_app/app/inspection.service.ts @@ -131,7 +131,7 @@ export class InspectionService { } getFeatureTreeDataFromModel() { - let convertToTreeTableNodes = (dataNodes: Array): TreeTableNode[] => { + let convertToTreeTableNodes = (dataNodes: Array, featureIndex: number): TreeTableNode[] => { let treeNodes: Array = []; for (const data of dataNodes) { const node: TreeTableNode = {}; @@ -164,6 +164,8 @@ export class InspectionService { } if (data.hasOwnProperty("hoverId")) { node.data["hoverId"] = data.hoverId; + // Necessary to query one of the selectedFeatures for its mapTileKey + node.data["featureIndex"] = featureIndex; } if (data.hasOwnProperty("mapId")) { node.data["mapId"] = data.mapId; @@ -174,7 +176,7 @@ export class InspectionService { if (data.hasOwnProperty("sourceDataReferences")) { node.data["sourceDataReferences"] = data.sourceDataReferences; } - node.children = data.hasOwnProperty("children") ? convertToTreeTableNodes(data.children) : []; + node.children = data.hasOwnProperty("children") ? convertToTreeTableNodes(data.children, featureIndex) : []; treeNodes.push(node); } return treeNodes; @@ -182,7 +184,8 @@ export class InspectionService { let treeNodes: Array = []; if (this.selectedFeatureInspectionModel) { - for (const section of this.selectedFeatureInspectionModel) { + for (let i = 0; i < this.selectedFeatureInspectionModel.length; i++) { + const section = this.selectedFeatureInspectionModel[i]; const node: TreeTableNode = {}; node.data = {key: section.key, value: section.value, type: section.type}; if (section.hasOwnProperty("info")) { @@ -191,7 +194,7 @@ export class InspectionService { if (section.hasOwnProperty("sourceDataReferences")) { node.data["sourceDataReferences"] = section.sourceDataReferences; } - node.children = convertToTreeTableNodes(section.children); + node.children = convertToTreeTableNodes(section.children, i); treeNodes.push(node); } } diff --git a/erdblick_app/app/sourcedataselection.dialog.component.ts b/erdblick_app/app/sourcedataselection.dialog.component.ts index 9368e6cb..b2dfbe3f 100644 --- a/erdblick_app/app/sourcedataselection.dialog.component.ts +++ b/erdblick_app/app/sourcedataselection.dialog.component.ts @@ -124,8 +124,19 @@ export class SourceDataLayerSelectionDialogComponent { this.mapIdsPerTileId.set(id, maps); } + if (this.menuService.lastInspectedTileSourceDataOption.getValue()) { + const savedTileId = this.menuService.lastInspectedTileSourceDataOption.getValue()?.tileId; + let tileIdSelection = this.tileIds.find(element => + Number(element.id) == savedTileId && !element.disabled && [...this.mapService.tileLayersForTileId(element.id as bigint)].length + ); + if (tileIdSelection) { + this.setCurrentTileId(tileIdSelection); + } + return; + } + // Pre-select the tile ID. - let tileIdSelection = this.tileIds.find(element => + const tileIdSelection = this.tileIds.find(element => !element.disabled && [...this.mapService.tileLayersForTileId(element.id as bigint)].length ); if (tileIdSelection) { @@ -165,13 +176,18 @@ export class SourceDataLayerSelectionDialogComponent { this.onTileIdChange(tileId); if (this.customMapId) { - let mapSelection = this.mapIds.find(entry => entry.id == this.customMapId); + const mapSelection = this.mapIds.find(entry => entry.id == this.customMapId); if (mapSelection) { this.selectedMapId = mapSelection; - } - else { + } else { this.mapIds.unshift({ id: this.customMapId, name: this.customMapId }); } + } else if (this.menuService.lastInspectedTileSourceDataOption.getValue()) { + const savedMapId = this.menuService.lastInspectedTileSourceDataOption.getValue()?.mapId; + const mapSelection = this.mapIds.find(entry => entry.id == savedMapId); + if (mapSelection) { + this.selectedMapId = mapSelection; + } } if (this.mapIds.length) { @@ -180,7 +196,17 @@ export class SourceDataLayerSelectionDialogComponent { } this.onMapIdChange(this.selectedMapId); if (this.sourceDataLayers.length) { - this.selectedSourceDataLayer = this.sourceDataLayers[0]; + if (this.menuService.lastInspectedTileSourceDataOption.getValue()) { + const savedLayerId = this.menuService.lastInspectedTileSourceDataOption.getValue()?.layerId; + const layerSelection = this.sourceDataLayers.find(entry => entry.id == savedLayerId); + if (layerSelection) { + this.selectedSourceDataLayer = layerSelection; + } else { + this.selectedSourceDataLayer = this.sourceDataLayers[0]; + } + } else { + this.selectedSourceDataLayer = this.sourceDataLayers[0]; + } this.onLayerIdChange(this.selectedSourceDataLayer); } } diff --git a/libs/core/CMakeLists.txt b/libs/core/CMakeLists.txt index fd822fe2..5d5b451d 100644 --- a/libs/core/CMakeLists.txt +++ b/libs/core/CMakeLists.txt @@ -44,6 +44,12 @@ set(ERDBLICK_SOURCE_FILES ) if(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten") + # Defaulting to debug symbols if the build type is Debug + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g") + endif() + list(APPEND ERDBLICK_SOURCE_FILES src/bindings.cpp) add_executable(erdblick-core ${ERDBLICK_SOURCE_FILES}) target_compile_definitions(erdblick-core PUBLIC EMSCRIPTEN) diff --git a/libs/core/include/erdblick/inspection.h b/libs/core/include/erdblick/inspection.h index b224d7ba..23ad2478 100644 --- a/libs/core/include/erdblick/inspection.h +++ b/libs/core/include/erdblick/inspection.h @@ -1,13 +1,13 @@ #pragma once +#include +#include +#include #include "cesium-interface/object.h" #include "mapget/model/feature.h" -#include "mapget/model/sourceinfo.h" -#include "simfil/model/string-pool.h" #include "sfl/small_vector.hpp" -#include -#include -#include +#include "simfil/model/string-pool.h" +#include "mapget/model/featurelayer.h" namespace erdblick { @@ -82,8 +82,10 @@ class InspectionConverter OptionalValueAndType convertField(std::string_view const& fieldName, simfil::ModelNode::Ptr const& value); OptionalValueAndType convertField(JsValue const& fieldName, simfil::ModelNode::Ptr const& value); - JsValue convertStringView(const simfil::StringId& f); - JsValue convertStringView(const std::string_view& f); + JsValue convertString(const simfil::StringId& f); + JsValue convertString(const std::string_view& f); + JsValue convertString(const std::string& f); + JsValue convertString(const char* s); std::string featureId_; uint32_t nextRelationIndex_ = 0; @@ -94,6 +96,7 @@ class InspectionConverter std::shared_ptr stringPool_; std::unordered_map translatedFieldNames_; std::unordered_map relationsByType_; + mapget::TileFeatureLayer* tile_ = nullptr; }; } // namespace erdblick diff --git a/libs/core/include/erdblick/rule.h b/libs/core/include/erdblick/rule.h index 5b924c92..ec409202 100644 --- a/libs/core/include/erdblick/rule.h +++ b/libs/core/include/erdblick/rule.h @@ -78,6 +78,7 @@ class FeatureStyleRule [[nodiscard]] bool relationMergeTwoWay() const; [[nodiscard]] std::optional const& attributeType() const; + [[nodiscard]] std::optional const& attributeFilter() const; [[nodiscard]] std::optional const& attributeLayerType() const; [[nodiscard]] std::optional const& attributeValidityGeometry() const; @@ -166,6 +167,7 @@ class FeatureStyleRule bool relationMergeTwoWay_ = false; std::optional attributeType_; + std::optional attributeFilter_; std::optional attributeLayerType_; std::optional attributeValidityGeometry_; diff --git a/libs/core/src/bindings.cpp b/libs/core/src/bindings.cpp index a6c59e36..51efc67b 100644 --- a/libs/core/src/bindings.cpp +++ b/libs/core/src/bindings.cpp @@ -8,12 +8,16 @@ #include #include +#include #if defined(__has_feature) #if __has_feature(address_sanitizer) const char *__lsan_default_options() { return "verbosity=1:malloc_context_size=64"; } +const char *__asan_default_options() { + return "verbosity=1:malloc_context_size=64:detect_container_overflow=0"; +} #endif #endif diff --git a/libs/core/src/inspection.cpp b/libs/core/src/inspection.cpp index 879f558f..a115a1dc 100644 --- a/libs/core/src/inspection.cpp +++ b/libs/core/src/inspection.cpp @@ -45,6 +45,7 @@ JsValue InspectionConverter::convert(model_ptr const& featurePtr) { stringPool_ = featurePtr->model().strings(); featureId_ = featurePtr->id()->toString(); + tile_ = &featurePtr->model(); // Top-Level Feature Item auto featureScope = push("Feature", "", ValueType::Section); @@ -53,12 +54,12 @@ JsValue InspectionConverter::convert(model_ptr const& featurePtr) // Identifiers section. { - auto scope = push(convertStringView("Identifiers"), "", ValueType::Section); - push("type", "typeId", ValueType::String)->value_ = convertStringView(featurePtr->typeId()); + auto scope = push(convertString("Identifiers"), "", ValueType::Section); + push("type", "typeId", ValueType::String)->value_ = convertString(featurePtr->typeId()); // Add map and layer names to the Identifiers section. - push("mapId", "mapId", ValueType::String)->value_ = convertStringView(featurePtr->model().mapId()); - push("layerId", "layerId", ValueType::String)->value_ = convertStringView(featurePtr->model().layerInfo()->layerId_); + push("mapId", "mapId", ValueType::String)->value_ = convertString(featurePtr->model().mapId()); + push("layerId", "layerId", ValueType::String)->value_ = convertString(featurePtr->model().layerInfo()->layerId_); // TODO: Investigate and fix the issue for "index out of bounds" error. // Affects boundaries and lane connectors @@ -73,17 +74,17 @@ JsValue InspectionConverter::convert(model_ptr const& featurePtr) for (auto const& [key, value]: featurePtr->id()->keyValuePairs()) { auto &field = current_->children_.emplace_back(); - field.key_ = convertStringView(key); + field.key_ = convertString(key); field.value_ = JsValue::fromVariant(value); field.type_ = ValueType::String; - field.geoJsonPath_ = convertStringView(key).toString(); + field.geoJsonPath_ = convertString(key).toString(); } } // Basic attributes section. if (auto attrs = featurePtr->attributesOrNull()) { - auto scope = push(convertStringView("Basic Attributes"), "properties", ValueType::Section); + auto scope = push(convertString("Basic Attributes"), "properties", ValueType::Section); for (auto const& [k, v] : attrs->fields()) { convertField(k, v); } @@ -92,7 +93,7 @@ JsValue InspectionConverter::convert(model_ptr const& featurePtr) // Flexible attributes section. if (auto layers = featurePtr->attributeLayersOrNull()) { - auto scope = push(convertStringView("Attribute Layers"), "properties.layer", ValueType::Section); + auto scope = push(convertString("Attribute Layers"), "properties.layer", ValueType::Section); layers->forEachLayer([this](auto&& layerName, auto&& layer) -> bool { convertAttributeLayer(layerName, layer); return true; @@ -103,7 +104,7 @@ JsValue InspectionConverter::convert(model_ptr const& featurePtr) using namespace mapget; if (featurePtr->numRelations()) { - auto scope = push(convertStringView("Relations"), "relations", ValueType::Section); + auto scope = push(convertString("Relations"), "relations", ValueType::Section); std::unordered_map>> relsByName; featurePtr->forEachRelation([this](model_ptr const& relation) -> bool { convertRelation(relation); @@ -114,7 +115,7 @@ JsValue InspectionConverter::convert(model_ptr const& featurePtr) // Geometry section. if (auto geomCollection = featurePtr->geomOrNull()) { - auto scope = push(convertStringView("Geometry"), "geometry", ValueType::Section); + auto scope = push(convertString("Geometry"), "geometry", ValueType::Section); uint32_t geomIndex = 0; geomCollection->forEachGeometry([this, &geomIndex](model_ptr const& geom) -> bool { convertGeometry(JsValue(geomIndex++), geom); @@ -152,7 +153,7 @@ InspectionConverter::InspectionNodeScope InspectionConverter::push( FieldOrIndex const& path, InspectionConverter::ValueType type) { - return push(convertStringView(key), path, type); + return push(convertString(key), path, type); } InspectionConverter::InspectionNodeScope @@ -177,10 +178,10 @@ void InspectionConverter::convertAttributeLayer( const std::string_view& name, const model_ptr& l) { - auto layerScope = push(convertStringView(name), name); + auto layerScope = push(convertString(name), name); l->forEachAttribute([this](model_ptr const& attr) { - auto attrScope = push(convertStringView(attr->name()), attr->name(), ValueType::Null); + auto attrScope = push(convertString(attr->name()), attr->name(), ValueType::Null); convertSourceDataReferences(attr->sourceDataReferences(), *attrScope); auto numValues = 0; @@ -203,9 +204,10 @@ void InspectionConverter::convertAttributeLayer( } if (auto validity = attr->validityOrNull()) { - convertValidity(convertStringView("validity"), validity); + convertValidity(convertString("validity"), validity); } + attrScope->mapId_ = JsValue(tile_->mapId()); attrScope->hoverId_ = featureId_+":attribute#"+std::to_string(nextAttributeIndex_); ++nextAttributeIndex_; @@ -227,29 +229,33 @@ void InspectionConverter::convertRelation(const model_ptr& r) relScope->hoverId_ = featureId_+":relation#"+std::to_string(nextRelationIndex_); convertSourceDataReferences(r->sourceDataReferences(), *relScope); if (auto const sourceValidity = r->sourceValidityOrNull()) { - convertValidity(convertStringView("sourceValidity"), sourceValidity); + convertValidity(convertString("sourceValidity"), sourceValidity); } if (auto const targetValidity = r->targetValidityOrNull()) { - convertValidity(convertStringView("targetValidity"), targetValidity); + convertValidity(convertString("targetValidity"), targetValidity); } ++nextRelationIndex_; } void InspectionConverter::convertGeometry(JsValue const& key, const model_ptr& g) { - // TODO: Show geometry name auto geomScope = push( key, key.type() == JsValue::Type::Number ? FieldOrIndex(key.as()) : FieldOrIndex(key.as()), ValueType::String); + std::string typeString; switch (g->geomType()) { - case GeomType::Points: geomScope->value_ = convertStringView("Points"); break; - case GeomType::Line: geomScope->value_ = convertStringView("Polyline"); break; - case GeomType::Polygon: geomScope->value_ = convertStringView("Polygon"); break; - case GeomType::Mesh: geomScope->value_ = convertStringView("Mesh"); break; + case GeomType::Points: typeString = "Points"; break; + case GeomType::Line: typeString = "Polyline"; break; + case GeomType::Polygon: typeString = "Polygon"; break; + case GeomType::Mesh: typeString = "Mesh"; break; } + if (g->name()) { + typeString += fmt::format(" ({})", *g->name()); + } + geomScope->value_ = convertString(typeString); convertSourceDataReferences(g->sourceDataReferences(), *geomScope); @@ -281,28 +287,32 @@ void InspectionConverter::convertValidity( auto dirScope = push("direction", "direction", ValueType::String); switch (direction) { case Validity::Positive: - dirScope->value_ = convertStringView("POSITIVE"); + dirScope->value_ = convertString("POSITIVE"); break; case Validity::Negative: - dirScope->value_ = convertStringView("NEGATIVE"); + dirScope->value_ = convertString("NEGATIVE"); break; case Validity::Both: - dirScope->value_ = convertStringView("BOTH"); + dirScope->value_ = convertString("BOTH"); break; case Validity::None: - dirScope->value_ = convertStringView("NONE"); + dirScope->value_ = convertString("NONE"); break; default: break; } } + if (auto featureId = v.featureId()) { + push("featureId", "featureId", ValueType::FeatureId)->value_ = convertString(featureId_); + } + if (auto geom = v.simpleGeometry()) { convertGeometry(JsValue("simpleGeometry"), geom); return true; } if (auto geomName = v.geometryName()) { - push("geometryName", "geometryName", ValueType::String)->value_ = convertStringView(*geomName); + push("geometryName", "geometryName", ValueType::String)->value_ = convertString(*geomName); } auto renderOffset = [this, &v](Point const& data, std::string_view const& name) @@ -347,14 +357,14 @@ InspectionConverter::OptionalValueAndType InspectionConverter::convertField( const simfil::StringId& fieldId, const simfil::ModelNode::Ptr& value) { - return convertField(convertStringView(fieldId), value); + return convertField(convertString(fieldId), value); } InspectionConverter::OptionalValueAndType InspectionConverter::convertField( const std::string_view& fieldName, const simfil::ModelNode::Ptr& value) { - return convertField(convertStringView(fieldName), value); + return convertField(convertString(fieldName), value); } InspectionConverter::OptionalValueAndType @@ -364,23 +374,29 @@ InspectionConverter::convertField(const JsValue& fieldName, const simfil::ModelN bool isArray = false; OptionalValueAndType singleValue; - switch (value->type()) { - case simfil::ValueType::Undef: return {}; - case simfil::ValueType::TransientObject: break; - case simfil::ValueType::Null: singleValue = {JsValue(), ValueType::Null}; break; - case simfil::ValueType::Bool: singleValue = {JsValue(std::get(value->value())), ValueType::Boolean}; break; - case simfil::ValueType::Int: singleValue = {JsValue(std::get(value->value())), ValueType::Number}; break; - case simfil::ValueType::Float: singleValue = {JsValue(std::get(value->value())), ValueType::Number}; break; - case simfil::ValueType::String: { - auto vv = value->value(); - if (std::holds_alternative(vv)) - singleValue = {convertStringView(std::get(vv)), ValueType::String}; - else - singleValue = {JsValue(std::get(vv)), ValueType::String}; - break; + if (value->addr().column() == TileFeatureLayer::ColumnId::FeatureIds) { + singleValue = {convertString(tile_->resolveFeatureId(*value)->toString()), ValueType::FeatureId}; + fieldScope->mapId_ = JsValue(tile_->mapId()); } - case simfil::ValueType::Object: break; - case simfil::ValueType::Array: isArray = true; break; + else { + switch (value->type()) { + case simfil::ValueType::Undef: return {}; + case simfil::ValueType::TransientObject: break; + case simfil::ValueType::Null: singleValue = {JsValue(), ValueType::Null}; break; + case simfil::ValueType::Bool: singleValue = {JsValue(std::get(value->value())), ValueType::Boolean}; break; + case simfil::ValueType::Int: singleValue = {JsValue(std::get(value->value())), ValueType::Number}; break; + case simfil::ValueType::Float: singleValue = {JsValue(std::get(value->value())), ValueType::Number}; break; + case simfil::ValueType::String: { + auto vv = value->value(); + if (std::holds_alternative(vv)) + singleValue = {convertString(std::get(vv)), ValueType::String}; + else + singleValue = {JsValue(std::get(vv)), ValueType::String}; + break; + } + case simfil::ValueType::Object: break; + case simfil::ValueType::Array: isArray = true; break; + } } if (singleValue) { @@ -395,7 +411,7 @@ InspectionConverter::convertField(const JsValue& fieldName, const simfil::ModelN if (isArray) kk = JsValue(index); else - kk = convertStringView(k); + kk = convertString(k); auto singleValueForField = convertField(kk, v); if (singleValueForField) { ++numValues; @@ -411,15 +427,15 @@ InspectionConverter::convertField(const JsValue& fieldName, const simfil::ModelN return {}; } -JsValue InspectionConverter::convertStringView(const simfil::StringId& f) +JsValue InspectionConverter::convertString(const simfil::StringId& f) { if (auto fieldStr = stringPool_->resolve(f)) { - return convertStringView(*fieldStr); + return convertString(*fieldStr); } return {}; } -JsValue InspectionConverter::convertStringView(const std::string_view& f) +JsValue InspectionConverter::convertString(const std::string_view& f) { auto translation = translatedFieldNames_.find(f); if (translation != translatedFieldNames_.end()) @@ -428,6 +444,16 @@ JsValue InspectionConverter::convertStringView(const std::string_view& f) return newTrans->second; } +JsValue InspectionConverter::convertString(const std::string& s) +{ + return convertString(std::string_view(s)); +} + +JsValue InspectionConverter::convertString(const char* s) +{ + return convertString(std::string_view(s)); +} + JsValue InspectionConverter::InspectionNode::toJsValue() const { auto newDict = JsValue::Dict({ diff --git a/libs/core/src/rule.cpp b/libs/core/src/rule.cpp index e64c3b73..ac2b1bd8 100644 --- a/libs/core/src/rule.cpp +++ b/libs/core/src/rule.cpp @@ -259,6 +259,10 @@ void FeatureStyleRule::parse(const YAML::Node& yaml) // Parse an attribute type regular expression, e.g. `SPEED_LIMIT_.*` attributeType_ = yaml["attribute-type"].as(); } + if (yaml["attribute-filter"].IsDefined()) { + // Parse an attribute based on it's field value, e.g. `speedLimitKmh > 100` + attributeFilter_ = yaml["attribute-filter"].as(); + } if (yaml["attribute-layer-type"].IsDefined()) { // Parse an attribute type regular expression, e.g. `Road.*Layer` attributeLayerType_ = yaml["attribute-layer-type"].as(); @@ -691,6 +695,11 @@ std::optional const& FeatureStyleRule::attributeType() const return attributeType_; } +std::optional const& FeatureStyleRule::attributeFilter() const +{ + return attributeFilter_; +} + std::optional const& FeatureStyleRule::attributeLayerType() const { return attributeLayerType_; diff --git a/libs/core/src/visualization.cpp b/libs/core/src/visualization.cpp index cf1eb26f..17f8fcfc 100644 --- a/libs/core/src/visualization.cpp +++ b/libs/core/src/visualization.cpp @@ -243,7 +243,16 @@ void FeatureLayerVisualization::addFeature( { auto featureId = feature->id()->toString(); if (!featureIdSubset_.empty()) { - if (featureIdSubset_.find(featureId) == featureIdSubset_.end()) { + bool isAllowed = false; + for (auto const& allowedFeatureId : featureIdSubset_) { + // The featureId may also refer to an attribute, + // in this case :attribute# is appended to the string. + if (allowedFeatureId == featureId || allowedFeatureId.starts_with(featureId + ':')) { + isAllowed = true; + break; + } + } + if (!isAllowed) { return; } } @@ -273,14 +282,24 @@ void FeatureLayerVisualization::addFeature( break; uint32_t offsetFactor = 0; + uint32_t attrIndex = 0; attrLayers->forEachLayer([&, this](auto&& layerName, auto&& layer){ // Check if the attribute layer name is accepted for the rule. if (auto const& attrLayerTypeRegex = rule.attributeLayerType()) { - if (!std::regex_match(layerName.begin(), layerName.end(), *attrLayerTypeRegex)) + if (!std::regex_match(layerName.begin(), layerName.end(), *attrLayerTypeRegex)) { + attrIndex += layer->size(); return true; + } } // Iterate over all the layer's attributes. layer->forEachAttribute([&, this](auto&& attr){ + // if (!featureIdSubset_.empty()) { + // if (!featureIdSubset_.contains(fmt::format("{}:attribute#{}", featureId, attrIndex))) { + // attrIndex++; + // return true; + // } + // } + attrIndex++; addAttribute( feature, layerName, @@ -321,6 +340,9 @@ void FeatureLayerVisualization::addGeometry( BoundEvalFun& evalFun, glm::dvec3 const& offset) { + if (!rule.supports(geom.geomType_)) + return; + // Combine the ID with the mapTileKey to create an // easy link from the geometry back to the feature. auto tileFeatureId = JsValue::Undefined(); @@ -339,6 +361,7 @@ void FeatureLayerVisualization::addGeometry( } std::vector vertsCartesian; + vertsCartesian.reserve(geom.points_.size()); for (auto const& vertCarto : geom.points_) { vertsCartesian.emplace_back(wgsToCartesian(vertCarto, offset)); } @@ -418,7 +441,7 @@ void FeatureLayerVisualization::addGeometry( evalFun, [&](auto& augmentedEvalFun) { - return labelCollection_.labelParams( + return CesiumLabelCollection::labelParams( xyzPos, text, rule, @@ -694,7 +717,7 @@ void FeatureLayerVisualization::addAttribute( // Check if the attribute validity is accepted for the rule. if (auto const& validityGeomRequired = rule.attributeValidityGeometry()) { - if (*validityGeomRequired != attr->validityOrNull()) { + if (*validityGeomRequired != (attr->validityOrNull() && attr->validityOrNull()->size())) { return; } } @@ -720,6 +743,7 @@ void FeatureLayerVisualization::addAttribute( internalStringPoolCopy_->emplace("$layer"), simfil::Value(layer)); + // Function which can evaluate a simfil expression in the attribute context. auto boundEvalFun = BoundEvalFun{ attrEvaluationContext, @@ -731,6 +755,17 @@ void FeatureLayerVisualization::addAttribute( // Bump visual offset factor for next visualized attribute. ++offsetFactor; + // Check if the attribute's values match the attribute filter for the rule. + if (auto const& attrFilter = rule.attributeFilter()) { + if (!attrFilter->empty()) { + auto result = boundEvalFun.eval_(*attrFilter); + if ((result.isa(simfil::ValueType::Bool) && !result.template as()) || + result.isa(simfil::ValueType::Undef) || result.isa(simfil::ValueType::Null)) { + return; + } + } + } + // Draw validity geometry. if (auto multiValidity = attr->validityOrNull()) { multiValidity->forEach([&, this](auto&& validity) diff --git a/package.json b/package.json index ec521841..a5363d02 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@ngx-formly/core": "^6.3.9", "assert": "^2.1.0", "browserify-zlib": "^0.2.0", - "cesium": "1.120.0", + "cesium": "1.124.0", "codemirror": "^6.0.1", "https-browserify": "^1.0.0", "js-yaml": "^4.1.0",