diff --git a/Customizations/LayerDefinition.ts b/Customizations/LayerDefinition.ts index 20cec0a..ce3fbe6 100644 --- a/Customizations/LayerDefinition.ts +++ b/Customizations/LayerDefinition.ts @@ -116,12 +116,8 @@ export class LayerDefinition { showOnPopup: (tags: UIEventSource<(any)>, feature: any) => UIElement): FilteredLayer { return new FilteredLayer( - this.name, + this, basemap, allElements, changes, - this.overpassFilter, - this.maxAllowedOverlapPercentage, - this.wayHandling, - this.style, selectedElement, showOnPopup); diff --git a/Logic/Basemap.ts b/Logic/Basemap.ts index ebdfe98..d4fb2a1 100644 --- a/Logic/Basemap.ts +++ b/Logic/Basemap.ts @@ -2,60 +2,62 @@ import L from "leaflet" import {UIEventSource} from "../UI/UIEventSource"; import {UIElement} from "../UI/UIElement"; + +export class BaseLayers { + + public static readonly defaultLayerName = "Kaart van OpenStreetMap"; + public static readonly baseLayers = { + ["Luchtfoto Vlaanderen (recentste door AIV)"]: + 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: 20, + minZoom: 1, + wmts: true + }), + ["Luchtfoto Vlaanderen (2013-2015, door AIV)"]: L.tileLayer.wms('https://geoservices.informatievlaanderen.be/raadpleegdiensten/OGW/wms?s', + { + layers: "OGWRGB13_15VL", + attribution: "Luchtfoto's van © AIV Vlaanderen (2013-2015) | " + }), + [BaseLayers.defaultLayerName]: L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", + { + attribution: '', + maxZoom: 19, + minZoom: 1 + }), + ["Kaart Grootschalig ReferentieBestand Vlaanderen (GRB) door AIV"]: 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: 20, + minZoom: 1, + wmts: true + }), + }; + + public static readonly defaultLayer = BaseLayers.baseLayers[BaseLayers.defaultLayerName]; + +} + // Contains all setup and baselayers for Leaflet stuff export class Basemap { + // @ts-ignore public map: Map; public Location: UIEventSource<{ zoom: number, lat: number, lon: number }>; - public LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{lat: number, lon: number}>(undefined) + public LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{ lat: number, lon: number }>(undefined) + private _previousLayer : L.tileLayer= undefined; + public CurrentLayer: UIEventSource<{ + name: string, + layer: L.tileLayer + }> = new UIEventSource( + {name: BaseLayers.defaultLayerName, layer: BaseLayers.defaultLayer} + ); - private aivLucht2013Layer = L.tileLayer.wms('https://geoservices.informatievlaanderen.be/raadpleegdiensten/OGW/wms?s', - { - layers: "OGWRGB13_15VL", - attribution: "Luchtfoto's van © AIV Vlaanderen (2013-2015) | " - }); - - private aivLuchtLatestLayer = 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: 20, - minZoom: 1, - wmts: true - }); - - - private osmLayer = L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", - { - attribution: '', - maxZoom: 19, - minZoom: 1 - }); - private osmBeLayer = L.tileLayer("https://tile.osm.be/osmbe/{z}/{x}/{y}.png", - { - attribution: 'Tile hosting courtesy of Geo6', - maxZoom: 18, - minZoom: 1 - }); - - private grbLayer = 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: 20, - minZoom: 1, - wmts: true - }); - - - private baseLayers = { - "Luchtfoto Vlaanderen (recentste door AIV)": this.aivLuchtLatestLayer, - "Luchtfoto Vlaanderen (2013-2015, door AIV)": this.aivLucht2013Layer, - "Kaart van OpenStreetMap": this.osmLayer, - "Kaart Grootschalig ReferentieBestand Vlaanderen (GRB) door AIV": this.grbLayer - }; constructor(leafletElementId: string, location: UIEventSource<{ zoom: number, lat: number, lon: number }>, @@ -63,19 +65,14 @@ export class Basemap { this.map = L.map(leafletElementId, { center: [location.data.lat, location.data.lon], zoom: location.data.zoom, - layers: [this.osmLayer], + layers: [BaseLayers.defaultLayer], }); + + this.map.attributionControl.setPrefix( extraAttribution.Render() + " | OpenStreetMap"); this.Location = location; - const layerControl = L.control.layers(this.baseLayers, null, - { - position: 'bottomright', - hideSingleBase: true - }) - layerControl.addTo(this.map); - this.map.zoomControl.setPosition("bottomright"); const self = this; @@ -86,6 +83,14 @@ export class Basemap { location.data.lon = self.map.getCenter().lng; location.ping(); }); + + this.CurrentLayer.addCallback((layer:{layer: L.tileLayer}) => { + if(self._previousLayer !== undefined){ + self.map.removeLayer(self._previousLayer); + } + self._previousLayer = layer.layer; + self.map.addLayer(layer.layer); + }); this.map.on("click", function (e) { self.LastClickLocation.setData({lat: e.latlng.lat, lon: e.latlng.lng}) diff --git a/Logic/FilteredLayer.ts b/Logic/FilteredLayer.ts index f1b530c..cbfdc0c 100644 --- a/Logic/FilteredLayer.ts +++ b/Logic/FilteredLayer.ts @@ -6,7 +6,7 @@ import { Changes } from "./Changes"; import L from "leaflet" import { GeoOperations } from "./GeoOperations"; import { UIElement } from "../UI/UIElement"; -import {LayerDefinition} from "../Customizations/LayerDefinition"; +import { LayerDefinition } from "../Customizations/LayerDefinition"; /*** * A filtered layer is a layer which offers a 'set-data' function @@ -22,7 +22,7 @@ export class FilteredLayer { public readonly name: string | UIElement; public readonly filters: TagsFilter; public readonly isDisplayed: UIEventSource = new UIEventSource(true); - + public readonly layerDef: LayerDefinition; private readonly _map: Basemap; private readonly _maxAllowedOverlap: number; @@ -41,36 +41,32 @@ export class FilteredLayer { * The leaflet layer object which should be removed on rerendering */ private _geolayer; - private _selectedElement: UIEventSource<{feature: any}>; + private _selectedElement: UIEventSource<{ feature: any }>; private _showOnPopup: (tags: UIEventSource, feature: any) => UIElement; constructor( - name: string | UIElement, + layerDef: LayerDefinition, map: Basemap, storage: ElementStorage, changes: Changes, - filters: TagsFilter, - maxAllowedOverlap: number, - wayHandling: number, - style: ((properties) => any), - selectedElement: UIEventSource<{feature: any}>, - showOnPopup: ((tags: UIEventSource, feature: any) => UIElement) + selectedElement: UIEventSource, + showOnPopup: ((tags: UIEventSource) => UIElement) ) { - this._wayHandling = wayHandling; + this.layerDef = layerDef; + + this._wayHandling = layerDef.wayHandling; this._selectedElement = selectedElement; this._showOnPopup = showOnPopup; - - if (style === undefined) { - style = function () { - return {}; + this._style = layerDef.style; + if (this._style === undefined) { + this._style = function () { + return {icon: "", color: "#000000"}; } } this.name = name; this._map = map; - this.filters = filters; - this._style = style; + this.filters = layerDef.overpassFilter; this._storage = storage; - this._maxAllowedOverlap = maxAllowedOverlap; - + this._maxAllowedOverlap = layerDef.maxAllowedOverlapPercentage; const self = this; this.isDisplayed.addCallback(function (isDisplayed) { if (self._geolayer !== undefined && self._geolayer !== null) { @@ -96,10 +92,10 @@ export class FilteredLayer { var tags = TagUtils.proprtiesToKV(feature.properties); if (this.filters.matches(tags)) { feature.properties["_surface"] = GeoOperations.surfaceAreaInSqMeters(feature); - if(feature.geometry.type !== "Point"){ - if(this._wayHandling === LayerDefinition.WAYHANDLING_CENTER_AND_WAY){ + if (feature.geometry.type !== "Point") { + if (this._wayHandling === LayerDefinition.WAYHANDLING_CENTER_AND_WAY) { selfFeatures.push(GeoOperations.centerpoint(feature)); - }else if(this._wayHandling === LayerDefinition.WAYHANDLING_CENTER_ONLY){ + } else if (this._wayHandling === LayerDefinition.WAYHANDLING_CENTER_ONLY) { feature = GeoOperations.centerpoint(feature); } } diff --git a/UI/Base/CheckBox.ts b/UI/Base/CheckBox.ts index 9cdb21e..c912425 100644 --- a/UI/Base/CheckBox.ts +++ b/UI/Base/CheckBox.ts @@ -1,19 +1,35 @@ import {UIElement} from "../UIElement"; import {UIEventSource} from "../UIEventSource"; +import { FilteredLayer } from "../../Logic/FilteredLayer"; +import Translations from "../../UI/i18n/Translations"; export class CheckBox extends UIElement{ private data: UIEventSource; - constructor(data: UIEventSource) { - super(data); - this.data = data; + private readonly _data: UIEventSource; + private readonly _showEnabled: string|UIElement; + private readonly _showDisabled: string|UIElement; + + constructor(showEnabled: string|UIElement, showDisabled: string|UIElement, data: UIEventSource = undefined) { + super(undefined); + this._data = data ?? new UIEventSource(false); + this.ListenTo(this._data); + this._showEnabled = showEnabled; + this._showDisabled = showDisabled; + const self = this; + this.onClick(() => { + self._data.setData(!self._data.data); + }) } - - protected InnerRender(): string { - return "Current val: "+this.data.data; + InnerRender(): string { + if (this._data.data) { + return Translations.W(this._showEnabled).Render(); + } else { + return Translations.W(this._showDisabled).Render(); + } } } \ No newline at end of file diff --git a/UI/Base/Combine.ts b/UI/Base/Combine.ts index 12b4338..649c497 100644 --- a/UI/Base/Combine.ts +++ b/UI/Base/Combine.ts @@ -21,7 +21,7 @@ export default class Combine extends UIElement { return elements; } - protected InnerUpdate(htmlElement: HTMLElement) { + InnerUpdate(htmlElement: HTMLElement) { for (const element of this.uiElements) { if (element instanceof UIElement) { element.Update(); diff --git a/UI/Img.ts b/UI/Img.ts index c4636f7..72ca2ba 100644 --- a/UI/Img.ts +++ b/UI/Img.ts @@ -10,5 +10,15 @@ export class Img { ""; + static closedFilterButton: string = ` + + + + ` + + static openFilterButton: string = ` + + ` + } diff --git a/UI/Input/DropDown.ts b/UI/Input/DropDown.ts index 8776381..d20bdb4 100644 --- a/UI/Input/DropDown.ts +++ b/UI/Input/DropDown.ts @@ -28,7 +28,10 @@ export class DropDown extends InputElement { for (const v of this._values) { this.ListenTo(v.shown._source); } - this.ListenTo(this._value) + this.ListenTo(this._value); + + this.onClick(() => {}) // by registering a click, the click event is consumed and doesn't bubble furter to other elements, e.g. checkboxes + } diff --git a/UI/LayerSelection.ts b/UI/LayerSelection.ts new file mode 100644 index 0000000..37049f2 --- /dev/null +++ b/UI/LayerSelection.ts @@ -0,0 +1,45 @@ +import { UIElement } from "./UIElement"; +import { FilteredLayer } from "../Logic/FilteredLayer"; +import { CheckBox } from "./Base/CheckBox"; +import Combine from "./Base/Combine"; + +export class LayerSelection extends UIElement{ + + private readonly _checkboxes: UIElement[]; + + constructor(layers: FilteredLayer[]) { + super(undefined); + this._checkboxes = []; + for (const layer of layers) { + this._checkboxes.push(new CheckBox( + new Combine([ + ` + + `, + `layer.layerDef.icon`, + layer.layerDef.name]), + new Combine([ + ` + + `, + `layer.layerDef.icon`, + layer.layerDef.name]), + layer.isDisplayed)); + } + } + + InnerRender(): string { + let html = ``; + + for (const checkBox of this._checkboxes) { + const checkBoxHTML = checkBox.Render(); + const checkBoxListItem = `
  • ${checkBoxHTML}
  • `; + + html = html + checkBoxListItem; + } + + + return `
      ${html}
    `; + } + +} \ No newline at end of file diff --git a/UI/UIElement.ts b/UI/UIElement.ts index a060e27..f32b6e5 100644 --- a/UI/UIElement.ts +++ b/UI/UIElement.ts @@ -53,8 +53,12 @@ export abstract class UIElement { if (this._onClick !== undefined) { const self = this; - element.onclick = () => { + element.onclick = (e) => { + if(e.consumed){ + return; + } self._onClick(); + e.consumed = true; } element.style.pointerEvents = "all"; element.style.cursor = "pointer"; diff --git a/index.css b/index.css index 82a2823..712afd5 100644 --- a/index.css +++ b/index.css @@ -261,6 +261,122 @@ form { pointer-events: all; } +/* filter ui */ + +.filter__popup { + position: absolute; + bottom: 0; + z-index: 500; + padding-left: 10px; + padding-bottom: 10px; +} + +.filter__button { + outline: none; + border: none; + width: 60px; + height: 60px; + border-radius: 50%; + background-color: white; + position: relative; +} + +.filter__button--shadow { + box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.25); +} + +.filter__button svg { + vertical-align: middle; +} + +#filter__selection form { + display: flex; + flex-flow: column; + width: 100%; + background-color: #ffffff; + border-radius: 15px; + border: none; + font-size: 16px; + transform: translateY(60px); + padding: 15px 0 60px 0; +} + +#filter__selection label { + font-size: 16px; + background-color: #ffffff; + padding: 0 15px 12px 15px; + margin: 0; + color: #003B8B; + font-weight: 600; +} + +#filter__selection select { + outline: none; + background-color: #F0EFEF; + border: none; + border-radius: 5px; + font-size: 14px; + padding: 5px; + margin: 0 15px; + max-width: 250px; +} + +#filter__selection ul { + background-color: #ffffff; + padding: 10px 25px 18px 18px; + list-style: none; + margin: 0; + font-weight: 600; + transform: translateY(75px); +} + +#filter__selection ul li span > span { + display: flex; + align-items: center; + /* padding: 10px 0; */ +} + +#filter__selection ul svg { + padding: 10px 14px 10px 0; + border-right: 1px solid #003B8B; +} + +#filter__selection ul img { + width: 20px; + height: auto; + margin: 0 10px 0 18px; +} + +.filter__label { + font-size: 16px; + transform: translateY(75px); + background-color: #ffffff; + padding: 10px 15px; + margin: 0; + color: #003B8B; + font-weight: 600; + border-radius: 15px 15px 0 0; +} + +#filter__layers { + pointer-events: all; + list-style: none; + padding-left: 0; +} + +#filter__layers li span { + display: flex; + align-items: center; +} + +.checkbox__label--checked { + margin-left: .7rem; +} + +.checkbox__label--unchecked { + margin-left: 2.45rem; +} + @media only screen and (max-width: 600px) { #messagesbox-wrapper { diff --git a/index.html b/index.html index eda152a..5be3001 100644 --- a/index.html +++ b/index.html @@ -44,6 +44,9 @@ +
    +
    +
    diff --git a/index.ts b/index.ts index afaaf4e..b971e53 100644 --- a/index.ts +++ b/index.ts @@ -3,7 +3,7 @@ import {Changes} from "./Logic/Changes"; import {ElementStorage} from "./Logic/ElementStorage"; import {UIEventSource} from "./UI/UIEventSource"; import {UserBadge} from "./UI/UserBadge"; -import {Basemap} from "./Logic/Basemap"; +import {Basemap, BaseLayers} from "./Logic/Basemap"; import {PendingChanges} from "./UI/PendingChanges"; import {CenterMessageBox} from "./UI/CenterMessageBox"; import {Helpers} from "./Helpers"; @@ -12,7 +12,6 @@ import {FilteredLayer} from "./Logic/FilteredLayer"; import {LayerUpdater} from "./Logic/LayerUpdater"; import {UIElement} from "./UI/UIElement"; import {FullScreenMessageBoxHandler} from "./UI/FullScreenMessageBoxHandler"; -import {Overpass} from "./Logic/Overpass"; import {FeatureInfoBox} from "./UI/FeatureInfoBox"; import {GeoLocationHandler} from "./Logic/GeoLocationHandler"; import {StrayClickHandler} from "./Logic/StrayClickHandler"; @@ -21,15 +20,15 @@ import {VariableUiElement} from "./UI/Base/VariableUIElement"; import {SearchAndGo} from "./UI/SearchAndGo"; import {CollapseButton} from "./UI/Base/CollapseButton"; import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; -import {All} from "./Customizations/Layouts/All"; +import {CheckBox} from "./UI/Base/CheckBox"; import Translations from "./UI/i18n/Translations"; -import Translation from "./UI/i18n/Translation"; import Locale from "./UI/i18n/Locale"; import {Layout, WelcomeMessage} from "./Customizations/Layout"; import {DropDown} from "./UI/Input/DropDown"; -import {FixedInputElement} from "./UI/Input/FixedInputElement"; import {FixedUiElement} from "./UI/Base/FixedUiElement"; -import ParkingType from "./Customizations/Questions/bike/ParkingType"; +import {LayerSelection} from "./UI/LayerSelection"; +import Combine from "./UI/Base/Combine"; +import {Img} from "./UI/Img"; // --------------------- Read the URL parameters ----------------- @@ -56,7 +55,7 @@ if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { // ----------------- SELECT THE RIGHT QUESTSET ----------------- -let defaultLayout = "buurtnatuur" +let defaultLayout = "walkbybrussels" // Run over all questsets. If a part of the URL matches a searched-for part in the layout, it'll take that as the default @@ -204,11 +203,38 @@ for (const layer of layoutToUse.layers) { } addButtons.push(addButton); flayers.push(flayer); + + console.log(flayers); + } const layerUpdater = new LayerUpdater(bm, minZoom, flayers); +// --------------- Setting up filter ui -------- + +// buttons + +const closedFilterButton = ``; + +const openFilterButton = ` +`; + +// basemap dropdown + +let baseLayerOptions = []; + +for (const key in BaseLayers.baseLayers) { + baseLayerOptions.push({value: {name: key, layer: BaseLayers.baseLayers[key]}, shown: key}); +} + +if (flayers.length > 1) { + new CheckBox(new Combine([`

    Maplayers

    `, new LayerSelection(flayers), new DropDown(`Background map`, baseLayerOptions, bm.CurrentLayer), openFilterButton]), closedFilterButton).AttachTo("filter__selection"); +} else { + new CheckBox(new Combine([new DropDown(`Background map`, baseLayerOptions, bm.CurrentLayer), openFilterButton]), closedFilterButton).AttachTo("filter__selection"); +} + + // ------------------ Setup various UI elements ------------ let languagePicker = new DropDown(" ", layoutToUse.supportedLanguages.map(lang => { @@ -304,3 +330,6 @@ new GeoLocationHandler(bm).AttachTo("geolocate-button"); // --------------- Send a ping to start various action -------- locationControl.ping(); + + + diff --git a/test.ts b/test.ts index 51ad56b..15d721f 100644 --- a/test.ts +++ b/test.ts @@ -1,32 +1,14 @@ -import {DropDown} from "./UI/Input/DropDown"; -import Locale from "./UI/i18n/Locale"; -import Combine from "./UI/Base/Combine"; -import Translations from "./UI/i18n/Translations"; -import {TagRenderingOptions} from "./Customizations/TagRendering"; -import {UIEventSource} from "./UI/UIEventSource"; -import {Tag} from "./Logic/TagsFilter"; -import {Changes} from "./Logic/Changes"; -import {OsmConnection} from "./Logic/OsmConnection"; -import Translation from "./UI/i18n/Translation"; +import { DropDown } from "./UI/Input/DropDown"; +import { BaseLayers, Basemap } from "./Logic/Basemap"; -console.log("Hello world") -Locale.language.setData("en"); -let languagePicker = new DropDown("", ["en", "nl"].map(lang => { - return {value: lang, shown: lang} - } -), Locale.language).AttachTo("maindiv"); +let baseLayerOptions = []; + +Object.entries(BaseLayers.baseLayers).forEach(([key, value], i) => { +// console.log(key, value, i); + baseLayerOptions.push({value: i, shown: key}); +}); + +console.log(Basemap); -let tags = new UIEventSource({ - x:"y" -}) - -new TagRenderingOptions({ - mappings: [{k: new Tag("x","y"), txt: new Translation({en: "ENG", nl: "NED"})}] -}).construct({ - tags: tags, - changes: new Changes( - "cs", - new OsmConnection(true) - ) -}).AttachTo("extradiv") \ No newline at end of file +new DropDown(`label`, baseLayerOptions, Basemap.CurrentLayer).AttachTo("maindiv"); \ No newline at end of file