From 314894085a56300f0e33c9f2bacc13ee78ba3cd0 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 16 Nov 2020 01:59:30 +0100 Subject: [PATCH] Cleaning filtered layer --- Customizations/JSON/LayerConfig.ts | 59 ++++ InitUiElements.ts | 18 +- Logic/FilteredLayer.ts | 344 ++++++++------------ UI/FullScreenMessageBoxHandler.ts | 31 +- UI/Input/DirectionInput.ts | 1 - UI/Popup/EditableTagRendering.ts | 28 +- UI/Popup/TagRenderingQuestion.ts | 3 - UI/SimpleAddUI.ts | 3 +- assets/themes/surveillance_cameras/dome.svg | 10 +- css/mobile.css | 9 +- index.css | 4 +- package.json | 2 +- 12 files changed, 232 insertions(+), 280 deletions(-) diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts index 7b610b1..268fa98 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Customizations/JSON/LayerConfig.ts @@ -116,4 +116,63 @@ export default class LayerConfig { } + + + public GenerateLeafletStyle(tags: any): + { + color: string; + icon: { popupAnchor: [number, number]; iconAnchor: [number, number]; iconSize: [number, number]; iconUrl: string }; weight: number; dashArray: number[] + } { + const iconUrl = this.icon?.GetRenderValue(tags)?.txt; + const iconSize = (this.iconSize?.GetRenderValue(tags)?.txt ?? "40,40,center").split(","); + + + const dashArray = this.dashArray.GetRenderValue(tags)?.txt.split(" ").map(Number); + + function num(str, deflt = 40) { + const n = Number(str); + if (isNaN(n)) { + return deflt; + } + return n; + } + + const iconW = num(iconSize[0]); + const iconH = num(iconSize[1]); + const mode = iconSize[2] ?? "center" + + let anchorW = iconW / 2; + let anchorH = iconH / 2; + if (mode === "left") { + anchorW = 0; + } + if (mode === "right") { + anchorW = iconW; + } + + if (mode === "top") { + anchorH = 0; + } + if (mode === "bottom") { + anchorH = iconH; + } + + + const color = this.color?.GetRenderValue(tags)?.txt ?? "#00f"; + let weight = num(this.width?.GetRenderValue(tags)?.txt, 5); + return { + icon: + { + iconUrl: iconUrl, + iconSize: [iconW, iconH], + iconAnchor: [anchorW, anchorH], + popupAnchor: [0, 3 - anchorH] + }, + color: color, + weight: weight, + dashArray: dashArray + }; + } + + } \ No newline at end of file diff --git a/InitUiElements.ts b/InitUiElements.ts index 7b660bf..20af0c8 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -475,18 +475,16 @@ export class InitUiElements { throw "Layer " + layer + " was not substituted"; } - const generateInfo = (tagsES) => { - - return new FeatureInfoBox( - tagsES, - layer, - ) - }; - - const flayer: FilteredLayer = FilteredLayer.fromDefinition(layer, generateInfo); + const flayer: FilteredLayer = new FilteredLayer(layer, + (tagsES) => { + return new FeatureInfoBox( + tagsES, + layer, + ) + }); flayers.push(flayer); - QueryParameters.GetQueryParameter("layer-" + layer.id, "true", "Wehter or not layer "+layer.id+" is shown") + QueryParameters.GetQueryParameter("layer-" + layer.id, "true", "Wehter or not layer " + layer.id + " is shown") .map((str) => str !== "false", [], (b) => b.toString()) .syncWith( flayer.isDisplayed diff --git a/Logic/FilteredLayer.ts b/Logic/FilteredLayer.ts index 5e0a106..12522ab 100644 --- a/Logic/FilteredLayer.ts +++ b/Logic/FilteredLayer.ts @@ -25,13 +25,9 @@ export class FilteredLayer { public readonly layerDef: LayerConfig; private readonly _maxAllowedOverlap: number; - private readonly _style: (properties) => { color: string, weight?: number, icon: { iconUrl: string, iconSize?: [number, number], popupAnchor?: [number, number], iconAnchor?: [number, number] } }; - - /** The featurecollection from overpass */ private _dataFromOverpass: any[]; - private readonly _wayHandling: number; /** List of new elements, geojson features */ private _newElements = []; @@ -49,60 +45,7 @@ export class FilteredLayer { ) { this.layerDef = layerDef; - this._wayHandling = layerDef.wayHandling; this._showOnPopup = showOnPopup; - this._style = (tags) => { - - const iconUrl = layerDef.icon?.GetRenderValue(tags)?.txt; - const iconSize = (layerDef.iconSize?.GetRenderValue(tags)?.txt ?? "40,40,center").split(","); - - - const dashArray = layerDef.dashArray.GetRenderValue(tags)?.txt.split(" ").map(Number); - - function num(str, deflt = 40) { - const n = Number(str); - if (isNaN(n)) { - return deflt; - } - return n; - } - - const iconW = num(iconSize[0]); - const iconH = num(iconSize[1]); - const mode = iconSize[2] ?? "center" - - let anchorW = iconW / 2; - let anchorH = iconH / 2; - if (mode === "left") { - anchorW = 0; - } - if (mode === "right") { - anchorW = iconW; - } - - if (mode === "top") { - anchorH = 0; - } - if (mode === "bottom") { - anchorH = iconH; - } - - - const color = layerDef.color?.GetRenderValue(tags)?.txt ?? "#00f"; - let weight = num(layerDef.width?.GetRenderValue(tags)?.txt, 5); - return { - icon: - { - iconUrl: iconUrl, - iconSize: [iconW, iconH], - iconAnchor: [anchorW, anchorH], - popupAnchor: [0, 3 - anchorH] - }, - color: color, - weight: weight, - dashArray: dashArray - }; - }; this.name = name; this.filters = layerDef.overpassTags; this._maxAllowedOverlap = layerDef.hideUnderlayingFeaturesMinPercentage; @@ -123,13 +66,6 @@ export class FilteredLayer { } }) } - - static fromDefinition(definition, showOnPopup: (tags: UIEventSource, feature: any) => UIElement): FilteredLayer { - return new FilteredLayer(definition, showOnPopup); - - } - - /** * The main function to load data into this layer. * The data that is NOT used by this layer, is returned as a geojson object; the other data is rendered @@ -138,32 +74,15 @@ export class FilteredLayer { const leftoverFeatures = []; const selfFeatures = []; for (let feature of geojson.features) { - // feature.properties contains all the properties - const tags = TagUtils.proprtiesToKV(feature.properties); - if (!this.filters.matches(tags)) { leftoverFeatures.push(feature); continue; } - - if (feature.geometry.type !== "Point") { - const centerPoint = GeoOperations.centerpoint(feature); - if (this._wayHandling === LayerConfig.WAYHANDLING_CENTER_AND_WAY) { - selfFeatures.push(centerPoint); - } else if (this._wayHandling === LayerConfig.WAYHANDLING_CENTER_ONLY) { - feature = centerPoint; - } - } selfFeatures.push(feature); - } - - this.RenderLayer({ - type: "FeatureCollection", - features: selfFeatures - }) + this.RenderLayer(selfFeatures) const notShadowed = []; for (const feature of leftoverFeatures) { @@ -186,18 +105,140 @@ export class FilteredLayer { public AddNewElement(element) { this._newElements.push(element); - this.RenderLayer({features: this._dataFromOverpass}, element); // Update the layer - + this.RenderLayer( this._dataFromOverpass); // Update the layer } - private RenderLayer(data, openPopupOf = undefined) { - let self = this; + private RenderLayer(features) { if (this._geolayer !== undefined && this._geolayer !== null) { // Remove the old geojson layer from the map - we'll reshow all the elements later on anyway State.state.bm.map.removeLayer(this._geolayer); } + // We fetch all the data we have to show: + let fusedFeatures = this.ApplyWayHandling(this.FuseData(features)); + console.log("Fused:",fusedFeatures) + + // And we copy some features as points - if needed + const data = { + type: "FeatureCollection", + features: fusedFeatures + } + + let self = this; + console.log(data); + this._geolayer = L.geoJSON(data, { + /* style: feature => { + self.layerDef.GenerateLeafletStyle(feature.properties); + return { + color: "#f00", + weight: 4 + } + },*/ + /* + pointToLayer: function (feature, latLng) { + // Point to layer converts the 'point' to a layer object - as the geojson layer natively cannot handle points + // Click handling is done in the next step + + const style = self.layerDef.GenerateLeafletStyle(feature.properties); + let marker; + if (style.icon === undefined) { + marker = L.circle(latLng, { + radius: 25, + color: style.color + }); + } else if (style.icon.iconUrl.startsWith("$circle")) { + marker = L.circle(latLng, { + radius: 25, + color: style.color + }); + } else { + if (style.icon.iconSize === undefined) { + style.icon.iconSize = [50, 50] + } + + marker = L.marker(latLng, { + icon: L.icon(style.icon) + }); + } + return marker; + },*/ +/* + onEachFeature: function (feature, layer:Layer) { + + layer.on("click", (e) => { + if (layer.getPopup() === undefined + && (window.screen.availHeight > 600 || window.screen.availWidth > 600) // We DON'T trigger this code on small screens! No need to create a popup + ) { + const popup = L.popup({ + autoPan: true, + closeOnEscapeKey: true, + }, layer); + + // @ts-ignore + popup.setLatLng(e.latlng) + + layer.bindPopup(popup); + const eventSource = State.state.allElements.addOrGetElement(feature); + const uiElement = self._showOnPopup(eventSource, feature); + // We first render the UIelement (which'll still need an update later on...) + // But at least it'll be visible already + popup.setContent(uiElement.Render()); + popup.openOn(State.state.bm.map); + // popup.openOn(State.state.bm.map); + // ANd we perform the pending update + uiElement.Update(); + } + // We set the element as selected... + State.state.selectedElement.setData(feature); + + // We mark the event as consumed + L.DomEvent.stop(e); + }); + } + */ + } + ) + ; + + if (this.combinedIsDisplayed.data) { + this._geolayer.addTo(State.state.bm.map); + } + + } + + private ApplyWayHandling(fusedFeatures: any[]) { + if (this.layerDef.wayHandling === LayerConfig.WAYHANDLING_DEFAULT) { + // We don't have to do anything special + return fusedFeatures; + } + + + // We have to convert all the ways into centerpoints + const existingPoints = []; + const newPoints = []; + const existingWays = []; + + for (const feature of fusedFeatures) { + if (feature.geometry.type === "Point") { + existingPoints.push(feature); + continue; + } + + existingWays.push(feature); + const centerPoint = GeoOperations.centerpoint(feature); + newPoints.push(centerPoint); + } + + fusedFeatures = existingPoints.concat(newPoints); + if (this.layerDef.wayHandling === LayerConfig.WAYHANDLING_CENTER_AND_WAY) { + fusedFeatures = fusedFeatures.concat(existingWays) + } + return fusedFeatures; + } + + //*Fuses the old and the new datasets*/ + private FuseData(data: any[]) { const oldData = this._dataFromOverpass ?? []; // We keep track of all the ids that are freshly loaded in order to avoid adding duplicates @@ -205,7 +246,7 @@ export class FilteredLayer { // A list of all the features to show const fusedFeatures = []; // First, we add all the fresh data: - for (const feature of data.features) { + for (const feature of data) { idsFromOverpass.add(feature.properties.id); fusedFeatures.push(feature); } @@ -226,133 +267,6 @@ export class FilteredLayer { fusedFeatures.push(feature); } } - - - // We use a new, fused dataset - data = { - type: "FeatureCollection", - features: fusedFeatures - } - - - // The data is split in two parts: the point and the rest - // The points get a special treatment in order to render them properly - // Note that some features might get a point representation as well - - const runWhenAdded: (() => void)[] = [] - - this._geolayer = L.geoJSON(data, { - style: function (feature) { - return self._style(feature.properties); - }, - pointToLayer: function (feature, latLng) { - const style = self._style(feature.properties); - let marker; - if (style.icon === undefined) { - marker = L.circle(latLng, { - radius: 25, - color: style.color - }); - - } else if (style.icon.iconUrl.startsWith("$circle")) { - marker = L.circle(latLng, { - radius: 25, - color: style.color - }); - } else { - if (style.icon.iconSize === undefined) { - style.icon.iconSize = [50, 50] - } - - // @ts-ignore - marker = L.marker(latLng, { - icon: L.icon(style.icon), - }); - } - let eventSource = State.state.allElements.addOrGetElement(feature); - const popup = L.popup({}, marker); - let uiElement: UIElement; - let content = undefined; - let p = marker.bindPopup(popup) - .on("popupopen", () => { - if (content === undefined) { - uiElement = self._showOnPopup(eventSource, feature); - // Lazily create the content - content = uiElement.Render(); - } - popup.setContent(content); - uiElement.Update(); - }); - - if (feature === openPopupOf) { - runWhenAdded.push(() => { - p.openPopup(); - }) - } - - return marker; - }, - - onEachFeature: function (feature, layer:Layer) { - - // We monky-patch the feature element with an update-style - function updateStyle () { - // @ts-ignore - if (layer.setIcon) { - const style = self._style(feature.properties); - const icon = style.icon; - if (icon.iconUrl) { - if (icon.iconUrl.startsWith("$circle")) { - // pass - } else { - // @ts-ignore - layer.setIcon(L.icon(icon)) - } - } - } else { - self._geolayer.setStyle(function (featureX) { - return self._style(featureX.properties); - }); - } - } - - let eventSource = State.state.allElements.addOrGetElement(feature); - - - eventSource.addCallback(updateStyle); - - function openPopup(e) { - if (feature.geometry.type === "Point") { - return; // Points bind their own popups - } - const uiElement = self._showOnPopup(eventSource, feature); - L.popup({ - autoPan: true, - }).setContent(uiElement.Render()) - .setLatLng(e.latlng) - .openOn(State.state.bm.map); - uiElement.Update(); - if (e) { - L.DomEvent.stop(e); // Marks the event as consumed - } - } - - layer.on("click", (e) => { - updateStyle(); - openPopup(e); - State.state.selectedElement.setData(feature); - - }); - } - }); - - if (this.combinedIsDisplayed.data) { - this._geolayer.addTo(State.state.bm.map); - for (const f of runWhenAdded) { - f(); - } - } + return fusedFeatures; } - - } \ No newline at end of file diff --git a/UI/FullScreenMessageBoxHandler.ts b/UI/FullScreenMessageBoxHandler.ts index b94595e..d1e9355 100644 --- a/UI/FullScreenMessageBoxHandler.ts +++ b/UI/FullScreenMessageBoxHandler.ts @@ -8,34 +8,13 @@ import Combine from "./Base/Combine"; */ export class FullScreenMessageBox extends UIElement { - private static readonly _toTheMap_height : string = "5em"; - private readonly returnToTheMap: UIElement; private _content: UIElement; constructor(onClear: (() => void)) { - super(); + super(State.state.fullScreenMessage); this.HideOnEmpty(true); const self = this; - State.state.fullScreenMessage.addCallbackAndRun(uiElement => { - self.Update(); - if (uiElement === undefined) { - location.hash = ""; - } else { - // The 'hash' makes sure a new piece of history is added. This makes the 'back-button' on android remove the popup - location.hash = "#element"; - } - }); - - if (window !== undefined) { - window.onhashchange = function () { - if (location.hash === "") { - // No more element: back to the map! - State.state.fullScreenMessage.setData(undefined); - onClear(); - } - } - } this.returnToTheMap = new Combine([Translations.t.general.returnToTheMap.Clone().SetStyle("font-size:xx-large")]) @@ -44,7 +23,7 @@ export class FullScreenMessageBox extends UIElement { "z-index: 10000;" + "bottom: 0;" + "left: 0;" + - `height: ${FullScreenMessageBox._toTheMap_height};` + + `height: var(--return-to-the-map-height);` + "width: 100vw;" + "color: var(--catch-detail-color-contrast);" + "font-weight: bold;" + @@ -55,10 +34,8 @@ export class FullScreenMessageBox extends UIElement { "padding-bottom: 1.2em;" + "box-sizing:border-box") .onClick(() => { - console.log("Returning...") State.state.fullScreenMessage.setData(undefined); onClear(); - self.Update(); }); } @@ -73,9 +50,9 @@ export class FullScreenMessageBox extends UIElement { "display:block;" + "padding: 1em;" + "padding-bottom:6em;" + - `margin-bottom:${FullScreenMessageBox._toTheMap_height};` + + `margin-bottom: var(--return-to-the-map-height);` + "box-sizing:border-box;" + - `height:calc(100vh - ${FullScreenMessageBox._toTheMap_height});` + + `height:calc(100vh - var(--return-to-the-map-height));` + "overflow-y: auto;" + "max-width:100vw;" + "overflow-x:hidden;" + diff --git a/UI/Input/DirectionInput.ts b/UI/Input/DirectionInput.ts index f5c5c43..7188ad2 100644 --- a/UI/Input/DirectionInput.ts +++ b/UI/Input/DirectionInput.ts @@ -68,7 +68,6 @@ export default class DirectionInput extends InputElement { htmlElement.ontouchstart = (ev: TouchEvent) => { onPosChange(ev.touches[0].clientX, ev.touches[0].clientY); - ev.preventDefault(); } let isDown = false; diff --git a/UI/Popup/EditableTagRendering.ts b/UI/Popup/EditableTagRendering.ts index fce78e6..b8e9d5b 100644 --- a/UI/Popup/EditableTagRendering.ts +++ b/UI/Popup/EditableTagRendering.ts @@ -27,24 +27,25 @@ export default class EditableTagRendering extends UIElement { this.ListenTo(this._editMode); this.ListenTo(State.state?.osmConnection?.userDetails) - const self = this; - this._answer = new TagRenderingAnswer(tags, configuration); - this._answer.SetStyle("width:100%;") if (this._configuration.question !== undefined) { - // 2.3em total width - if(State.state.featureSwitchUserbadge.data){ - - this._editButton = - Svg.pencil_svg().SetClass("edit-button") - .onClick(() => { - self._editMode.setData(true); - }); + if (State.state.featureSwitchUserbadge.data) { + // 2.3em total width + const self = this; + this._editButton = + Svg.pencil_svg().SetClass("edit-button") + .onClick(() => { + self._editMode.setData(true); + }); } + } + } - + private GenerateQuestion() { + const self = this; + if (this._configuration.question !== undefined) { // And at last, set up the skip button const cancelbutton = Translations.t.general.cancel.Clone() @@ -53,7 +54,7 @@ export default class EditableTagRendering extends UIElement { self._editMode.setData(false) }); - this._question = new TagRenderingQuestion(tags, configuration, + return new TagRenderingQuestion(this._tags, this._configuration, () => { self._editMode.setData(false) }, @@ -65,6 +66,7 @@ export default class EditableTagRendering extends UIElement { InnerRender(): string { if (this._editMode.data) { + this._question = this.GenerateQuestion(); return this._question.Render(); } diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index 10ba6bc..d803429 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -118,7 +118,6 @@ export default class TagRenderingQuestion extends UIElement { const inputEl = new InputElementMap( checkBoxes, (t0, t1) => { - console.log("IsEquiv?",t0, t1, t0?.isEquivalent(t1)) return t0?.isEquivalent(t1) ?? false }, (indices) => { @@ -162,8 +161,6 @@ export default class TagRenderingQuestion extends UIElement { } } - console.log(indices, freeformExtras); - if (freeformField) { if (freeformExtras.length > 0) { freeformField.GetValue().setData(new Tag(this._configuration.freeform.key, freeformExtras.join(";"))); diff --git a/UI/SimpleAddUI.ts b/UI/SimpleAddUI.ts index c4acc5f..db1c600 100644 --- a/UI/SimpleAddUI.ts +++ b/UI/SimpleAddUI.ts @@ -8,7 +8,6 @@ import Locale from "./i18n/Locale"; import State from "../State"; import {UIEventSource} from "../Logic/UIEventSource"; -import {Img} from "./Img"; import Svg from "../Svg"; /** @@ -115,8 +114,8 @@ export class SimpleAddUI extends UIElement { const loc = State.state.bm.LastClickLocation.data; let feature = State.state.changes.createElement(tags, loc.lat, loc.lon); - layerToAddTo.AddNewElement(feature); State.state.selectedElement.setData(feature); + layerToAddTo.AddNewElement(feature); } } diff --git a/assets/themes/surveillance_cameras/dome.svg b/assets/themes/surveillance_cameras/dome.svg index 389759d..ce0f5c0 100644 --- a/assets/themes/surveillance_cameras/dome.svg +++ b/assets/themes/surveillance_cameras/dome.svg @@ -40,27 +40,27 @@ id="namedview8" showgrid="false" inkscape:zoom="1.5733334" - inkscape:cx="84.526201" + inkscape:cx="-20.982269" inkscape:cy="188.2981" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="svg6" />