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",