From 08175a747f747d29267b9fb7ba0f512ccbafce91 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 25 Sep 2020 21:58:29 +0200 Subject: [PATCH] First attempt to get the editor-layer-index working --- InitUiElements.ts | 17 +++-- Logic/AvailableBaseLayers.ts | 116 ++++++++++++++++++++++++++++++ Logic/GeoOperations.ts | 3 +- Logic/Leaflet/Basemap.ts | 136 +++++++++++++++++++++-------------- Logic/UIEventSource.ts | 2 +- State.ts | 2 +- UI/BackgroundSelector.ts | 50 +++++++++++++ UI/Input/TextField.ts | 2 - UI/LayerSelection.ts | 1 - deploy.sh | 4 ++ test.ts | 9 +-- 11 files changed, 268 insertions(+), 74 deletions(-) create mode 100644 Logic/AvailableBaseLayers.ts create mode 100644 UI/BackgroundSelector.ts diff --git a/InitUiElements.ts b/InitUiElements.ts index 6afe6b052..c2a2808c2 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -8,7 +8,7 @@ import {UIElement} from "./UI/UIElement"; import {MoreScreen} from "./UI/MoreScreen"; import {FilteredLayer} from "./Logic/FilteredLayer"; import {FeatureInfoBox} from "./UI/FeatureInfoBox"; -import {BaseLayers, Basemap} from "./Logic/Leaflet/Basemap"; +import {Basemap} from "./Logic/Leaflet/Basemap"; import {State} from "./State"; import {WelcomeMessage} from "./UI/WelcomeMessage"; import {Img} from "./UI/Img"; @@ -35,6 +35,7 @@ import {Layout} from "./Customizations/Layout"; import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; import {FromJSON} from "./Customizations/JSON/FromJSON"; import {Utils} from "./Utils"; +import BackgroundSelector from "./UI/BackgroundSelector"; export class InitUiElements { @@ -352,14 +353,12 @@ export class InitUiElements { } private static GenerateLayerControlPanel() { - let baseLayerOptions = BaseLayers.baseLayers.map((layer) => { - return {value: layer, shown: layer.name} - }); - let layerControlPanel = new Combine( - [new DropDown(Translations.t.general.backgroundMap, baseLayerOptions, State.state.bm.CurrentLayer)]); + let layerControlPanel: UIElement = new BackgroundSelector(State.state); layerControlPanel.SetStyle("margin:1em"); + layerControlPanel.onClick(() => {}); if (State.state.filteredLayers.data.length > 1) { const layerSelection = new LayerSelection(); + layerSelection.onClick(() => {}); layerControlPanel = new Combine([layerSelection, "
",layerControlPanel]); } return layerControlPanel; @@ -444,8 +443,8 @@ export class InitUiElements { ); State.state.bm = bm; State.state.layerUpdater = new LayerUpdater(State.state); - const queryParam = QueryParameters.GetQueryParameter("background", State.state.layoutToUse.data.defaultBackground); - const queryParamMapped: UIEventSource<{ id: string, name: string, layer: any }> = + /* const queryParam = QueryParameters.GetQueryParameter("background", State.state.layoutToUse.data.defaultBackground); + const queryParamMapped: UIEventSource<{ id: string, name: string, layer: any }> = queryParam.map<{ id: string, name: string, layer: any }>((id) => { for (const layer of BaseLayers.baseLayers) { if (layer.id === id) { @@ -457,7 +456,7 @@ export class InitUiElements { return layerInfo.id }); - queryParamMapped.syncWith(bm.CurrentLayer); + queryParamMapped.syncWith(bm.CurrentLayer);*/ } diff --git a/Logic/AvailableBaseLayers.ts b/Logic/AvailableBaseLayers.ts new file mode 100644 index 000000000..ce0af072f --- /dev/null +++ b/Logic/AvailableBaseLayers.ts @@ -0,0 +1,116 @@ +import * as editorlayerindex from "../assets/editor-layer-index.json" +import {UIEventSource} from "./UIEventSource"; +import {GeoOperations} from "./GeoOperations"; +import {State} from "../State"; +import {Basemap} from "./Leaflet/Basemap"; + +/** + * Calculates which layers are available at the current location + */ +export default class AvailableBaseLayers { + + public static layerOverview = AvailableBaseLayers.LoadRasterIndex(); + public availableEditorLayers: UIEventSource<{ id: string, url: string, max_zoom: number, license_url: number, name: string, geometry: any, leafletLayer: any }[]>; + + constructor(state: State) { + const self = this; + this.availableEditorLayers = + state.locationControl.map( + (currentLocation) => { + const currentLayers = self.availableEditorLayers?.data; + const newLayers = AvailableBaseLayers.AvailableLayersAt(currentLocation?.lon, currentLocation?.lat); + + if (currentLayers === undefined) { + return newLayers; + } + if (newLayers.length !== currentLayers.length) { + return newLayers; + } + for (let i = 0; i < newLayers.length; i++) { + if (newLayers[i].name !== currentLayers[i].name) { + return newLayers; + } + } + + return currentLocation; + }); + + } + + public static AvailableLayersAt(lon: number, lat: number): { url: string, max_zoom: number, license_url: number, name: string, geometry: any }[] { + const availableLayers = [] + const globalLayers = []; + for (const i in AvailableBaseLayers.layerOverview) { + const layer = AvailableBaseLayers.layerOverview[i]; + if (layer.feature.geometry === null) { + globalLayers.push(layer); + continue; + } + + if (lon === undefined || lat === undefined) { + continue; + } + + if (GeoOperations.inside([lon, lat], layer.feature)) { + availableLayers.push(layer); + } + } + + return availableLayers.concat(globalLayers); + } + + private static LoadRasterIndex(): { id: string, url: string, max_zoom: number, license_url: number, name: string, feature: any }[] { + const layers: { id: string, url: string, max_zoom: number, license_url: number, name: string, feature: any, leafletLayer: any }[] = [] + // @ts-ignore + const features = editorlayerindex.features; + for (const i in features) { + const layer = features[i]; + const props = layer.properties; + + if(props.id === "Bing"){ + // Doesnt work + continue; + } + + if (props.overlay) { + continue; + } + + if(props.max_zoom < 19){ + continue; + } + + if (props.url.toLowerCase().indexOf("apikey") > 0) { + continue; + } + + if (props.url.toLowerCase().indexOf("{bbox}") > 0) { + continue; + } + + const leafletLayer = Basemap.CreateBackgroundLayer( + props.id, + props.name, + props.url, + props.name, + props.max_zoom, + props.type.toLowerCase() === "wms", + props.type.toLowerCase() === "wmts" + ) + + // Note: if layer.geometry is null, there is global coverage for this layer + layers.push({ + id: props.id, + feature: layer, + url: props.url, + max_zoom: props.max_zoom, + license_url: props.license_url, + name: props.name, + leafletLayer: leafletLayer + }); + } + return layers; + + } + +} \ No newline at end of file diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index 096d9277a..b97ad5e6d 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -36,7 +36,6 @@ export class GeoOperations { return false; } - if (feature.geometry.type === "Polygon" || feature.geometry.type === "MultiPolygon") { const poly = feature; @@ -77,7 +76,7 @@ export class GeoOperations { return false; } - private static inside(pointCoordinate, feature): boolean { + public static inside(pointCoordinate, feature): boolean { // ray-casting algorithm based on // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html diff --git a/Logic/Leaflet/Basemap.ts b/Logic/Leaflet/Basemap.ts index aa852af86..f49dab638 100644 --- a/Logic/Leaflet/Basemap.ts +++ b/Logic/Leaflet/Basemap.ts @@ -5,54 +5,46 @@ import {UIElement} from "../../UI/UIElement"; export class BaseLayers { - public static readonly defaultLayer: { name: string, layer: any, id: string } = { - id: "osm", - name: "Kaart van OpenStreetMap", layer: L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", - { - attribution: '', - maxZoom: 19, - minZoom: 1 - }) - }; - public static readonly baseLayers: { name: string, layer: any, id: string } [] = [ - { - id: "aiv-latest", - name: "Luchtfoto Vlaanderen (recentste door AIV)", - layer: L.tileLayer("https://tile.informatievlaanderen.be/ws/raadpleegdiensten/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&" + - "LAYER=omwrgbmrvl&STYLE=&FORMAT=image/png&tileMatrixSet=GoogleMapsVL&tileMatrix={z}&tileRow={y}&tileCol={x}", - { - // omwrgbmrvl - attribution: 'Luchtfoto\'s van © AIV Vlaanderen (Laatste) © AGIV', - maxZoom: 22, - minZoom: 1, - wmts: true - }) - }, - BaseLayers.defaultLayer, - { - id: "aiv-13-15", - name: "Luchtfoto Vlaanderen (2013-2015, door AIV)", - layer: L.tileLayer.wms('https://geoservices.informatievlaanderen.be/raadpleegdiensten/OGW/wms?s', - { - maxZoom: 22, - layers: "OGWRGB13_15VL", - attribution: "Luchtfoto's van © AIV Vlaanderen (2013-2015) | " - }) - }, - { - id:"grb", - name: "Kaart Grootschalig ReferentieBestand Vlaanderen (GRB) door AIV", - layer: L.tileLayer("https://tile.informatievlaanderen.be/ws/raadpleegdiensten/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=grb_bsk&STYLE=&FORMAT=image/png&tileMatrixSet=GoogleMapsVL&tileMatrix={z}&tileCol={x}&tileRow={y}", - { - attribution: 'Achtergrond Grootschalig ReferentieBestand(GRB) © AGIV', - maxZoom: 22, - minZoom: 1, - wmts: true - }) - } - ] - ; + /*public static readonly baseLayers: { name: string, layer: any, id: string } [] = [ + + { + id: "aiv-latest", + name: "Luchtfoto Vlaanderen (recentste door AIV)", + layer: L.tileLayer("https://tile.informatievlaanderen.be/ws/raadpleegdiensten/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&" + + "LAYER=omwrgbmrvl&STYLE=&FORMAT=image/png&tileMatrixSet=GoogleMapsVL&tileMatrix={z}&tileRow={y}&tileCol={x}", + { + // omwrgbmrvl + attribution: 'Luchtfoto\'s van © AIV Vlaanderen (Laatste) © AGIV', + maxZoom: 22, + minZoom: 1, + wmts: true + }) + }, + BaseLayers.defaultLayer, + { + id: "aiv-13-15", + name: "Luchtfoto Vlaanderen (2013-2015, door AIV)", + layer: L.tileLayer.wms('https://geoservices.informatievlaanderen.be/raadpleegdiensten/OGW/wms?s', + { + maxZoom: 22, + layers: "OGWRGB13_15VL", + attribution: "Luchtfoto's van © AIV Vlaanderen (2013-2015) | " + }) + }, + { + id:"grb", + name: "Kaart Grootschalig ReferentieBestand Vlaanderen (GRB) door AIV", + layer: L.tileLayer("https://tile.informatievlaanderen.be/ws/raadpleegdiensten/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=grb_bsk&STYLE=&FORMAT=image/png&tileMatrixSet=GoogleMapsVL&tileMatrix={z}&tileCol={x}&tileRow={y}", + { + attribution: 'Achtergrond Grootschalig ReferentieBestand(GRB) © AGIV', + maxZoom: 22, + minZoom: 1, + wmts: true + }) + } + ] + ;*/ } @@ -60,26 +52,32 @@ export class BaseLayers { export class Basemap { + public static readonly defaultLayer: { name: string, layer: any, id: string } = + Basemap.CreateBackgroundLayer("osm", "OpenStreetMap", "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + "OpenStreetMap (ODBL)", + 22, false); + // @ts-ignore public readonly map: Map; public readonly Location: UIEventSource<{ zoom: number, lat: number, lon: number }>; public readonly LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{ lat: number, lon: number }>(undefined) - private _previousLayer: L.tileLayer = undefined; + private _previousLayer: L.tileLayer = undefined; public readonly CurrentLayer: UIEventSource<{ id: string, name: string, layer: L.tileLayer - }> = new UIEventSource(BaseLayers.defaultLayer); + }> = new UIEventSource(Basemap.defaultLayer); constructor(leafletElementId: string, location: UIEventSource<{ zoom: number, lat: number, lon: number }>, extraAttribution: UIElement) { + this._previousLayer = Basemap.defaultLayer.layer; this.map = L.map(leafletElementId, { center: [location.data.lat ?? 0, location.data.lon ?? 0], zoom: location.data.zoom ?? 2, - layers: [BaseLayers.defaultLayer.layer], + layers: [this._previousLayer], }); @@ -118,9 +116,43 @@ export class Basemap { this.map.on("contextmenu", function (e) { self.LastClickLocation.setData({lat: e.latlng.lat, lon: e.latlng.lng}); - console.log("Right click") e.preventDefault(); }); } - + + public static CreateBackgroundLayer(id: string, name: string, url: string, attribution: string, + maxZoom: number, isWms: boolean, isWMTS?: boolean) { + + url = url.replace("{zoom}", "{z}") + .replace("{switch:", "{") + .replace("{proj}", "EPSG:3857") + .replace("{width}", "256") + .replace("{height}", "256") + + //geoservices.informatievlaanderen.be/raadpleegdiensten/dhmv/wms?FORMAT=image/jpeg&VERSION=1.1.1&SERVICE=WMS&REQUEST=GetMap&LAYERS=DHMV_II_SVF_25cm&STYLES=&SRS=EPSG:3857&WIDTH=256&HEIGHT=256 + if (isWms) { + return { + id: id, + name: name, + layer: L.tileLayer.wms(url, + { + maxZoom: maxZoom ?? 19, + attribution: attribution + " | " + }) + } + } + + return { + id: id, + name: name, + layer: L.tileLayer(url, + { + attribution: attribution, + maxZoom: maxZoom, + minZoom: 1, + wmts: isWMTS ?? false + }) + } + } + } diff --git a/Logic/UIEventSource.ts b/Logic/UIEventSource.ts index 58eb8c1db..18b0cd217 100644 --- a/Logic/UIEventSource.ts +++ b/Logic/UIEventSource.ts @@ -62,7 +62,7 @@ export class UIEventSource{ newSource.setData(f(self.data)); } - this.addCallback(update); + this.addCallbackAndRun(update); for (const extraSource of extraSources) { extraSource?.addCallback(update); } diff --git a/State.ts b/State.ts index 49ace34b2..95f564d5c 100644 --- a/State.ts +++ b/State.ts @@ -65,7 +65,7 @@ export class State { public filteredLayers: UIEventSource = new UIEventSource([]) public presets: UIEventSource = new UIEventSource([]) - + /** * The message that should be shown at the center of the screen */ diff --git a/UI/BackgroundSelector.ts b/UI/BackgroundSelector.ts new file mode 100644 index 000000000..b7ecc7f76 --- /dev/null +++ b/UI/BackgroundSelector.ts @@ -0,0 +1,50 @@ +import {UIElement} from "./UIElement"; +import AvailableBaseLayers from "../Logic/AvailableBaseLayers"; +import {DropDown} from "./Input/DropDown"; +import Translations from "./i18n/Translations"; +import {State} from "../State"; +import {UIEventSource} from "../Logic/UIEventSource"; + +export default class BackgroundSelector extends UIElement { + + private _dropdown: UIElement; + private readonly state: State; + private readonly _availableLayers: UIEventSource; + + constructor(state: State) { + super(); + this.state = state; + + this._availableLayers = new AvailableBaseLayers(state).availableEditorLayers; + const self = this; + this._availableLayers.addCallbackAndRun(available => self.CreateDropDown(available)); + } + + private CreateDropDown(available) { + if(available.length === 0){ + console.warn("NO AVAILABLE LAYERS") + } + + console.log("ALL LAYERS", available) + + const baseLayers: { value: any, shown: string }[] = []; + for (const i in available) { + const layer: { url: string, max_zoom: number, license_url: number, name: string, geometry: any, leafletLayer: any } = available[i]; + + if (layer.name === undefined) { + continue; + } + + baseLayers.push({value: layer.leafletLayer, shown: layer.name}); + + } + + const dropdown = new DropDown(Translations.t.general.backgroundMap, baseLayers, State.state.bm.CurrentLayer) + this._dropdown = dropdown; + } + + InnerRender(): string { + return this._dropdown.Render(); + } + +} \ No newline at end of file diff --git a/UI/Input/TextField.ts b/UI/Input/TextField.ts index d6f1a3ea6..a1de27a5e 100644 --- a/UI/Input/TextField.ts +++ b/UI/Input/TextField.ts @@ -73,12 +73,10 @@ export class TextField extends InputElement { } Update() { - console.log("Updating TF") super.Update(); } InnerUpdate() { - console.log("Inner Updating TF") const field = document.getElementById("txt-" + this.id); const self = this; field.oninput = () => { diff --git a/UI/LayerSelection.ts b/UI/LayerSelection.ts index d023ed77b..56eb034e5 100644 --- a/UI/LayerSelection.ts +++ b/UI/LayerSelection.ts @@ -1,7 +1,6 @@ import {UIElement} from "./UIElement"; import {CheckBox} from "./Input/CheckBox"; import Combine from "./Base/Combine"; -import {Img} from "./Img"; import {State} from "../State"; import Translations from "./i18n/Translations"; import {FixedUiElement} from "./Base/FixedUiElement"; diff --git a/deploy.sh b/deploy.sh index d53796388..2b5e795a3 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,5 +1,9 @@ #! /bin/bash +cd assets/ +wget https://osmlab.github.io/editor-layer-index/imagery.geojson --output-document=editor-layer-index.json +cd .. + mkdir -p assets/generated ts-node createLayouts.ts || { echo 'Creating layouts failed' ; exit 1; } find -name '*.png' | parallel optipng '{}' diff --git a/test.ts b/test.ts index 84de39548..c3d60788e 100644 --- a/test.ts +++ b/test.ts @@ -1,8 +1,5 @@ -import ValidatedTextField from "./UI/Input/ValidatedTextField"; -import {VariableUiElement} from "./UI/Base/VariableUIElement"; +import AvailableBaseLayers from "./Logic/AvailableBaseLayers"; -const vtf= ValidatedTextField.KeyInput(true); -vtf.AttachTo('maindiv') -vtf.GetValue().addCallback(console.log) -new VariableUiElement(vtf.GetValue().map(n => ""+n)).AttachTo("extradiv") \ No newline at end of file +const layers = AvailableBaseLayers.AvailableLayersAt(51.2,3.2); +console.log(layers); \ No newline at end of file