From c74989e88d1eb55259e79c335e62e4f850b7590b Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 28 Oct 2021 03:15:36 +0200 Subject: [PATCH] Rendering bug fixes, add 'get' to the ExtraFunctions, filtering in the GRB theme --- Logic/ExtraFunction.ts | 39 +++- Logic/FeatureSource/FeaturePipeline.ts | 5 +- .../Sources/FilteringFeatureSource.ts | 45 ++++- Models/ThemeConfig/LineRenderingConfig.ts | 10 +- Models/ThemeConfig/PointRenderingConfig.ts | 13 ++ UI/ShowDataLayer/ShowDataLayer.ts | 8 +- assets/themes/grb_import/grb.json | 102 +++++++++- assets/themes/natuurpunt/natuurpunt.json | 188 +++++++++++------- assets/themes/uk_addresses/uk_addresses.json | 22 +- 9 files changed, 318 insertions(+), 114 deletions(-) diff --git a/Logic/ExtraFunction.ts b/Logic/ExtraFunction.ts index 4fc1840c11..d9fb6ede8d 100644 --- a/Logic/ExtraFunction.ts +++ b/Logic/ExtraFunction.ts @@ -163,12 +163,41 @@ export class ExtraFunction { } ) + private static readonly GetParsed = new ExtraFunction( + { + name: "get", + doc: "Gets the property of the feature, parses it (as JSON) and returns it. Might return 'undefined' if not defined, null, ...", + args: ["key"] + }, + (params, feat) => { + return key => { + const value = feat.properties[key] + if (value === undefined) { + return undefined; + } + try { + const parsed = JSON.parse(value) + if(parsed === null){ + return undefined; + } + return parsed; + } catch (e) { + console.warn("Could not parse property " + key + " due to: " + e + ", the value is " + value) + return undefined; + } + + } + + } + ) + private static readonly allFuncs: ExtraFunction[] = [ ExtraFunction.DistanceToFunc, ExtraFunction.OverlapFunc, ExtraFunction.ClosestObjectFunc, ExtraFunction.ClosestNObjectFunc, - ExtraFunction.Memberships + ExtraFunction.Memberships, + ExtraFunction.GetParsed ]; private readonly _name: string; private readonly _args: string[]; @@ -248,11 +277,11 @@ export class ExtraFunction { console.error("Could not calculate the distance between", feature, "and", otherFeature) throw "Undefined distance!" } - - if(distance === 0){ - console.trace("Got a suspiciously zero distance between", otherFeature, "and self-feature",feature) + + if (distance === 0) { + console.trace("Got a suspiciously zero distance between", otherFeature, "and self-feature", feature) } - + if (distance > maxDistance) { continue } diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index a017f6818d..0f2fa19ef1 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -12,7 +12,6 @@ import OverpassFeatureSource from "../Actors/OverpassFeatureSource"; import {Changes} from "../Osm/Changes"; import GeoJsonSource from "./Sources/GeoJsonSource"; import Loc from "../../Models/Loc"; -import WayHandlingApplyingFeatureSource from "./Sources/RenderingMultiPlexerFeatureSource"; import RegisteringAllFromFeatureSourceActor from "./Actors/RegisteringAllFromFeatureSourceActor"; import TiledFromLocalStorageSource from "./TiledFeatureSource/TiledFromLocalStorageSource"; import SaveTileToLocalStorageActor from "./Actors/SaveTileToLocalStorageActor"; @@ -26,6 +25,7 @@ import OsmFeatureSource from "./TiledFeatureSource/OsmFeatureSource"; import {OsmConnection} from "../Osm/OsmConnection"; import {Tiles} from "../../Models/TileRange"; import TileFreshnessCalculator from "./TileFreshnessCalculator"; +import {ElementStorage} from "../ElementStorage"; /** @@ -85,7 +85,8 @@ export default class FeaturePipeline { readonly overpassMaxZoom: UIEventSource; readonly osmConnection: OsmConnection readonly currentBounds: UIEventSource, - readonly osmApiTileSize: UIEventSource + readonly osmApiTileSize: UIEventSource, + readonly allElements: ElementStorage }) { this.state = state; diff --git a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts index ec097accac..cf0475d260 100644 --- a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts @@ -3,6 +3,8 @@ import FilteredLayer from "../../../Models/FilteredLayer"; import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; import Hash from "../../Web/Hash"; import {BBox} from "../../BBox"; +import {ElementStorage} from "../../ElementStorage"; +import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; export default class FilteringFeatureSource implements FeatureSourceForLayer, Tiled { public features: UIEventSource<{ feature: any; freshness: Date }[]> = @@ -12,12 +14,16 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti public readonly tileIndex: number public readonly bbox: BBox private readonly upstream: FeatureSourceForLayer; - private readonly state: { locationControl: UIEventSource<{ zoom: number }>; selectedElement: UIEventSource }; + private readonly state: { + locationControl: UIEventSource<{ zoom: number }>; selectedElement: UIEventSource, + allElements: ElementStorage + }; constructor( state: { locationControl: UIEventSource<{ zoom: number }>, selectedElement: UIEventSource, + allElements: ElementStorage }, tileIndex, upstream: FeatureSourceForLayer @@ -30,23 +36,51 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti this.layer = upstream.layer; const layer = upstream.layer; - + const self = this; upstream.features.addCallback(() => { - this. update(); + self.update(); }); layer.appliedFilters.addCallback(_ => { - this.update() + self.update() + }) + + this._is_dirty.stabilized(250).addCallbackAndRunD(dirty => { + if (dirty) { + self.update() + } }) this.update(); } - public update() { + private readonly _alreadyRegistered = new Set>(); + private readonly _is_dirty = new UIEventSource(false) + + private registerCallback(feature: any, layer: LayerConfig) { + const src = this.state.allElements.addOrGetElement(feature) + if (this._alreadyRegistered.has(src)) { + return + } + this._alreadyRegistered.add(src) + if (layer.isShown !== undefined) { + + const self = this; + src.map(tags => layer.isShown?.GetRenderValue(tags, "yes").txt).addCallbackAndRunD(isShown => { + self._is_dirty.setData(true) + }) + } + } + + public update() { + const self = this; const layer = this.upstream.layer; const features: { feature: any; freshness: Date }[] = this.upstream.features.data; const newFeatures = features.filter((f) => { + + self.registerCallback(f.feature, layer.layerDef) + if ( this.state.selectedElement.data?.id === f.feature.id || f.feature.id === Hash.hash.data) { @@ -79,6 +113,7 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti }); this.features.setData(newFeatures); + this._is_dirty.setData(false) } } diff --git a/Models/ThemeConfig/LineRenderingConfig.ts b/Models/ThemeConfig/LineRenderingConfig.ts index 96f7f0a1ae..1f03978880 100644 --- a/Models/ThemeConfig/LineRenderingConfig.ts +++ b/Models/ThemeConfig/LineRenderingConfig.ts @@ -13,19 +13,19 @@ export default class LineRenderingConfig extends WithContextLoader { public readonly dashArray: TagRenderingConfig; public readonly offset: TagRenderingConfig; public readonly leftRightSensitive: boolean - + constructor(json: LineRenderingConfigJson, context: string) { super(json, context) this.color = this.tr("color", "#0000ff"); this.width = this.tr("width", "7"); this.dashArray = this.tr("dashArray", ""); - - this.leftRightSensitive = json.offset !== undefined && json.offset !== 0 && json.offset !== "0" - + + this.leftRightSensitive = json.offset !== undefined && json.offset !== 0 && json.offset !== "0" + this.offset = this.tr("offset", "0"); } -public GenerateLeafletStyle( tags: {} ): + public GenerateLeafletStyle(tags: {}): { color: string, weight: number, diff --git a/Models/ThemeConfig/PointRenderingConfig.ts b/Models/ThemeConfig/PointRenderingConfig.ts index e34d6e34c0..4eb6d2f46d 100644 --- a/Models/ThemeConfig/PointRenderingConfig.ts +++ b/Models/ThemeConfig/PointRenderingConfig.ts @@ -15,6 +15,7 @@ import {VariableUiElement} from "../../UI/Base/VariableUIElement"; export default class PointRenderingConfig extends WithContextLoader { + private static readonly allowed_location_codes = new Set(["point", "centroid","start","end"]) public readonly location: Set<"point" | "centroid" | "start" | "end"> public readonly icon: TagRenderingConfig; @@ -25,7 +26,19 @@ export default class PointRenderingConfig extends WithContextLoader { constructor(json: PointRenderingConfigJson, context: string) { super(json, context) + + if(typeof json.location === "string"){ + json.location = [json.location] + } + this.location = new Set(json.location) + + this.location.forEach(l => { + const allowed = PointRenderingConfig.allowed_location_codes + if(!allowed.has(l)){ + throw `A point rendering has an invalid location: '${l}' is not one of ${Array.from(allowed).join(", ")} (at ${context}.location)` + } + }) if(this.location.size == 0){ throw "A pointRendering should have at least one 'location' to defined where it should be rendered. (At "+context+".location)" diff --git a/UI/ShowDataLayer/ShowDataLayer.ts b/UI/ShowDataLayer/ShowDataLayer.ts index e2b5f64403..309903348c 100644 --- a/UI/ShowDataLayer/ShowDataLayer.ts +++ b/UI/ShowDataLayer/ShowDataLayer.ts @@ -212,12 +212,13 @@ export default class ShowDataLayer { const lineRenderingIndex = feature.lineRenderingIndex if (pointRenderingIndex !== undefined) { + const style = layer.mapRendering[pointRenderingIndex].GenerateLeafletStyle(tagsSource, this._enablePopups) return { - icon: layer.mapRendering[pointRenderingIndex].GenerateLeafletStyle(tagsSource, this._enablePopups) + icon: style } } if (lineRenderingIndex !== undefined) { - return layer.lineRendering[lineRenderingIndex].GenerateLeafletStyle(tagsSource) + return layer.lineRendering[lineRenderingIndex].GenerateLeafletStyle(tagsSource.data) } throw "Neither lineRendering nor mapRendering defined for " + feature @@ -232,9 +233,8 @@ export default class ShowDataLayer { if (layer === undefined) { return; } - let tagSource = this.allElements?.getEventSourceById(feature.properties.id) ?? new UIEventSource(feature.properties) - const clickable = !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0) + const clickable = !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0) && this._enablePopups let style: any = layer.mapRendering[feature.pointRenderingIndex].GenerateLeafletStyle(tagSource, clickable); const baseElement = style.html; if (!this._enablePopups) { diff --git a/assets/themes/grb_import/grb.json b/assets/themes/grb_import/grb.json index ab379c070c..144e85ffda 100644 --- a/assets/themes/grb_import/grb.json +++ b/assets/themes/grb_import/grb.json @@ -20,6 +20,9 @@ "startZoom": 14, "widenFactor": 2, "socialImage": "", + "clustering": { + "maxZoom": 15 + }, "layers": [ { "id": "OSM-buildings", @@ -28,7 +31,7 @@ "osmTags": "building~*", "maxCacheAge": 0 }, - "minzoom": 18, + "minzoom": 16, "mapRendering": [ { "width": { @@ -277,7 +280,7 @@ { "location": [ "point", - "center" + "centroid" ], "label": { "mappings": [ @@ -311,17 +314,46 @@ "geoJsonZoomLevel": 18, "maxCacheAge": 0 }, - "minzoom": 19, + "minzoom": 16, "name": "CRAB-addressen", "title": "CRAB-adres", "mapRendering": [ { - "location": "point", + "location": ["point"], "icon": "circle:#bb3322", "iconSize": "15,15,center" } ], + "calculatedTags": [ + "_embedded_in=feat.overlapWith('OSM-buildings').filter(f => f.feat.properties['addr:housenumber'] !== undefined)[0]?.feat?.properties ", + "_embedding_nr=feat.get('_embedded_in')['addr:housenumber']", + "_embedding_street=feat.get('_embedded_in')['addr:street']" + ], + "isShown": { + "render": "yes", + "mappings": [ + { + "if": { + "and":["_embedding_nr:={HUISNR}","_embedding_street:={STRAATNM}"] + }, + "then": "no" + } + ] + }, "tagRenderings": [ + + { + "id": "render_crab", + "render": "Volgens het CRAB ligt hier {STRAATNM} {HUISNR} (label: {HNRLABEL})" + }, + { + "id": "render_embedded", + "render": "Het omliggende object met addres heeft {_embedding_street} {_embedding_nr}", + "condition": { + "and": ["_embedding_street~*","_embedding_nr~*"] + } + }, + "all_tags", { "id": "import-button", @@ -334,7 +366,7 @@ "name": { "nl": "Fixmes op gebouwen" }, - "minzoom": 12, + "minzoom": 16, "source": { "maxCacheAge": 0, "osmTags": { @@ -490,7 +522,7 @@ { "location": [ "point", - "center" + "centroid" ], "iconSize": { "render": "40,40,center" @@ -512,12 +544,66 @@ "render": "#00f" } } + ] + }, + { + "id": "GRB", + "source": { + "geoJson": "https://betadata.grbosm.site/grb?bbox={x_min},{y_min},{x_max},{y_max}", + "geoJsonZoomLevel": 18, + "mercatorCrs": true, + "maxCacheAge": 0 + }, + "name": "GRB geometries", + "title": "GRB outline", + "minzoom": 16, + "calculatedTags": [ + "_overlaps_with=feat.overlapWith('OSM-buildings').filter(f => f.overlap > 1 && feat.properties._surface - f.overlap < 5)[0] ?? null", + "_osm_obj:source:ref=JSON.parse(feat.properties._overlaps_with)?.feat?.properties['source:geometry:ref']", + "_osm_obj:source:date=JSON.parse(feat.properties._overlaps_with)?.feat?.properties['source:geometry:date'].replace(/\\//g, '-')", + "_imported_osm_object_found= feat.properties['_osm_obj:source:ref'] == feat.properties['source:geometry:entity'] + '/' + feat.properties['source:geometry:oidn']", + "_grb_date=feat.properties['source:geometry:date'].replace(/\\//g,'-')", + "_imported_osm_still_fresh= feat.properties['_osm_obj:source:date'] == feat.properties._grb_date" ], - "presets": [] + "tagRenderings": [ + "all_tags" + ], + "isShown": { + "render": "yes", + "mappings": [ + { + "if": { + "and": [ + "_imported_osm_object_found=true", + "_imported_osm_still_fresh=true" + ] + }, + "then": "no" + } + ] + }, + "mapRendering": [ + { + "color": { + "render": "#00a", + "mappings": [ + { + "if": { + "and": [ + "_imported_osm_object_found=true", + "_imported_osm_still_fresh=true" + ] + }, + "then": "#0f0" + } + ] + } + } + ] } ], "hideFromOverview": true, "defaultBackgroundId": "AGIVFlandersGRB", - "overpassMaxZoom": 18, + "overpassMaxZoom": 15, "osmApiTileSize": 17 } \ No newline at end of file diff --git a/assets/themes/natuurpunt/natuurpunt.json b/assets/themes/natuurpunt/natuurpunt.json index 4fc3421eb3..6441dcf3a1 100644 --- a/assets/themes/natuurpunt/natuurpunt.json +++ b/assets/themes/natuurpunt/natuurpunt.json @@ -57,9 +57,13 @@ }, "minzoom": 13, "minzoomVisible": 0, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg" - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg" + } + } + ] } }, { @@ -77,9 +81,13 @@ "isOsmCache": "duplicate" }, "minzoom": 1, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg" - }, + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg" + } + } + ], "presets": [] } }, @@ -96,9 +104,13 @@ "isOsmCache": true }, "minzoom": 1, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/information.svg" - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/information.svg" + } + } + ] } }, { @@ -115,19 +127,23 @@ "isOsmCache": true }, "minzoom": 10, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/trail.svg", - "mappings": [ - { - "if": "wheelchair=yes", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/walk_wheelchair.svg" - }, - { - "if": "pushchair=yes", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/pushchair.svg" + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/trail.svg", + "mappings": [ + { + "if": "wheelchair=yes", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/walk_wheelchair.svg" + }, + { + "if": "pushchair=yes", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/pushchair.svg" + } + ] } - ] - } + } + ] } }, { @@ -139,19 +155,23 @@ "geoJsonZoomLevel": 12, "isOsmCache": true }, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/toilets.svg", - "mappings": [ - { - "if": "wheelchair=yes", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/wheelchair.svg" - }, - { - "if": "toilets:position=urinals", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/urinal.svg" + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/toilets.svg", + "mappings": [ + { + "if": "wheelchair=yes", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/wheelchair.svg" + }, + { + "if": "toilets:position=urinals", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/urinal.svg" + } + ] } - ] - } + } + ] } }, { @@ -163,10 +183,14 @@ "geoJsonZoomLevel": 12, "isOsmCache": true }, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/birdhide.svg", - "mappings": null - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/birdhide.svg", + "mappings": null + } + } + ] } }, { @@ -178,9 +202,13 @@ "geoJsonZoomLevel": 12, "isOsmCache": true }, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/picnic_table.svg" - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/picnic_table.svg" + } + } + ] } }, { @@ -192,34 +220,42 @@ "geoJsonZoomLevel": 12, "isOsmCache": true }, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/drips.svg" - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/drips.svg" + } + } + ] } }, { "builtin": "parking", "override": { "minzoom": "16", - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/parking.svg", - "mappings": [ - { - "if": "amenity=bicycle_parking", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/parkingbike.svg" - } - ] - }, - "iconOverlays": [ + "mapRendering": [ { - "if": "amenity=motorcycle_parking", - "then": "circle:#335D9F;./assets/themes/natuurpunt/parkingmotor.svg", - "badge": true - }, - { - "if": "capacity:disabled=yes", - "then": "circle:#335D9F;./assets/themes/natuurpunt/parkingwheels.svg", - "badge": true + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/parking.svg", + "mappings": [ + { + "if": "amenity=bicycle_parking", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/parkingbike.svg" + } + ] + }, + "iconOverlays": [ + { + "if": "amenity=motorcycle_parking", + "then": "circle:#335D9F;./assets/themes/natuurpunt/parkingmotor.svg", + "badge": true + }, + { + "if": "capacity:disabled=yes", + "then": "circle:#335D9F;./assets/themes/natuurpunt/parkingwheels.svg", + "badge": true + } + ] } ] } @@ -233,9 +269,13 @@ "geoJsonZoomLevel": 12, "isOsmCache": true }, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/information_board.svg" - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/information_board.svg" + } + } + ] } }, { @@ -247,9 +287,13 @@ "geoJsonZoomLevel": 12, "isOsmCache": true }, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/bench.svg" - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/bench.svg" + } + } + ] } }, { @@ -261,9 +305,13 @@ "geoJsonZoomLevel": 12, "isOsmCache": true }, - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/watermill.svg" - } + "mapRendering": [ + { + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/watermill.svg" + } + } + ] } } ], diff --git a/assets/themes/uk_addresses/uk_addresses.json b/assets/themes/uk_addresses/uk_addresses.json index 71e5c173ef..883ec7c531 100644 --- a/assets/themes/uk_addresses/uk_addresses.json +++ b/assets/themes/uk_addresses/uk_addresses.json @@ -251,12 +251,11 @@ } ] }, - "width": { - "render": "8" - }, "iconSize": { "render": "40,40,center" - }, + } + + }, { "color": { "render": "#00f", "mappings": [ @@ -275,6 +274,9 @@ "then": "#ff0" } ] + }, + "width": { + "render": "8" } } ] @@ -290,17 +292,7 @@ ] } }, - "mapRendering": [ - { - "location": "point", - "color": { - "render": "#ccc" - }, - "width": { - "render": "0" - } - } - ] + "mapRendering": [] } ], "enableShareScreen": false,