diff --git a/Customizations/AllKnownLayouts.ts b/Customizations/AllKnownLayouts.ts index 35cfb010c..57dbbf0ba 100644 --- a/Customizations/AllKnownLayouts.ts +++ b/Customizations/AllKnownLayouts.ts @@ -1,6 +1,7 @@ import AllKnownLayers from "./AllKnownLayers"; import * as known_themes from "../assets/generated/known_layers_and_themes.json" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; +import LayerConfig from "../Models/ThemeConfig/LayerConfig"; export class AllKnownLayouts { @@ -8,6 +9,26 @@ export class AllKnownLayouts { public static allKnownLayouts: Map = AllKnownLayouts.AllLayouts(); public static layoutsList: LayoutConfig[] = AllKnownLayouts.GenerateOrderedList(AllKnownLayouts.allKnownLayouts); + public static AllPublicLayers(){ + const allLayers : LayerConfig[] = [] + const seendIds = new Set() + const publicLayouts = AllKnownLayouts.layoutsList.filter(l => !l.hideFromOverview) + for (const layout of publicLayouts) { + if(layout.hideFromOverview){ + continue + } + for (const layer of layout.layers) { + if(seendIds.has(layer.id)){ + continue + } + seendIds.add(layer.id) + allLayers.push(layer) + } + + } + return allLayers + } + private static GenerateOrderedList(allKnownLayouts: Map): LayoutConfig[] { const keys = ["personal", "cyclofix", "hailhydrant", "bookcases", "toilets", "aed"] const list = [] diff --git a/InitUiElements.ts b/InitUiElements.ts index f3593556f..202e64149 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -27,7 +27,6 @@ import MapControlButton from "./UI/MapControlButton"; import LZString from "lz-string"; import AllKnownLayers from "./Customizations/AllKnownLayers"; import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; -import {TagsFilter} from "./Logic/Tags/TagsFilter"; import LeftControls from "./UI/BigComponents/LeftControls"; import RightControls from "./UI/BigComponents/RightControls"; import {LayoutConfigJson} from "./Models/ThemeConfig/Json/LayoutConfigJson"; @@ -40,10 +39,10 @@ import {SubtleButton} from "./UI/Base/SubtleButton"; import ShowTileInfo from "./UI/ShowDataLayer/ShowTileInfo"; import {Tiles} from "./Models/TileRange"; import {TileHierarchyAggregator} from "./UI/ShowDataLayer/PerTileCountAggregator"; -import {BBox} from "./Logic/GeoOperations"; -import StaticFeatureSource from "./Logic/FeatureSource/Sources/StaticFeatureSource"; import FilterConfig from "./Models/ThemeConfig/FilterConfig"; import FilteredLayer from "./Models/FilteredLayer"; +import {BBox} from "./Logic/BBox"; +import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; export class InitUiElements { static InitAll( @@ -70,10 +69,24 @@ export class InitUiElements { "LayoutFromBase64 is ", layoutFromBase64 ); + + if(layoutToUse.id === personal.id){ + layoutToUse.layers = AllKnownLayouts.AllPublicLayers() + for (const layer of layoutToUse.layers) { + layer.minzoomVisible = Math.max(layer.minzoomVisible, layer.minzoom) + layer.minzoom = Math.max(16, layer.minzoom) + } + } State.state = new State(layoutToUse); - // This 'leaks' the global state via the window object, useful for debugging + if(layoutToUse.id === personal.id) { + // Disable overpass all together + State.state.overpassMaxZoom.setData(0) + + } + + // This 'leaks' the global state via the window object, useful for debugging // @ts-ignore window.mapcomplete_state = State.state; @@ -102,45 +115,6 @@ export class InitUiElements { } } - function updateFavs() { - // This is purely for the personal theme to load the layers there - const favs = State.state.favouriteLayers.data ?? []; - - const neededLayers = new Set(); - - console.log("Favourites are: ", favs); - layoutToUse.layers.splice(0, layoutToUse.layers.length); - let somethingChanged = false; - for (const fav of favs) { - if (AllKnownLayers.sharedLayers.has(fav)) { - const layer = AllKnownLayers.sharedLayers.get(fav); - if (!neededLayers.has(layer)) { - neededLayers.add(layer); - somethingChanged = true; - } - } - - for (const layouts of State.state.installedThemes.data) { - for (const layer of layouts.layout.layers) { - if (typeof layer === "string") { - continue; - } - if (layer.id === fav) { - if (!neededLayers.has(layer)) { - neededLayers.add(layer); - somethingChanged = true; - } - } - } - } - } - if (somethingChanged) { - State.state.layoutToUse.data.layers = Array.from(neededLayers); - State.state.layoutToUse.ping(); - State.state.featurePipeline?.ForceRefresh(); - } - } - if (layoutToUse.customCss !== undefined) { Utils.LoadCustomCss(layoutToUse.customCss); } @@ -206,18 +180,9 @@ export class InitUiElements { .addCallbackAndRunD(_ => addHomeMarker()); State.state.leafletMap.addCallbackAndRunD(_ => addHomeMarker()) - if (layoutToUse.id === personal.id) { - updateFavs(); - } InitUiElements.setupAllLayerElements(); - - if (layoutToUse.id === personal.id) { - State.state.favouriteLayers.addCallback(updateFavs); - State.state.installedThemes.addCallback(updateFavs); - } else { State.state.locationControl.ping(); - } new SelectedFeatureHandler(Hash.hash, State.state) @@ -414,15 +379,29 @@ export class InitUiElements { const flayers: FilteredLayer[] = []; for (const layer of layoutToUse.layers) { - const isDisplayed = QueryParameters.GetQueryParameter( - "layer-" + layer.id, - "true", - "Wether or not layer " + layer.id + " is shown" - ).map( - (str) => str !== "false", - [], - (b) => b.toString() - ); + let defaultShown = "true" + if(layoutToUse.id === personal.id){ + defaultShown = "false" + } + + let isDisplayed: UIEventSource + if(layoutToUse.id === personal.id){ + isDisplayed = State.state.osmConnection.GetPreference("personal-theme-layer-" + layer.id + "-enabled") + .map(value => value === "yes", [], enabled => { + return enabled ? "yes" : ""; + }) + isDisplayed.addCallbackAndRun(d =>console.log("IsDisplayed for layer", layer.id, "is currently", d) ) + }else{ + isDisplayed = QueryParameters.GetQueryParameter( + "layer-" + layer.id, + defaultShown, + "Wether or not layer " + layer.id + " is shown" + ).map( + (str) => str !== "false", + [], + (b) => b.toString() + ); + } const flayer = { isDisplayed: isDisplayed, layerDef: layer, @@ -453,8 +432,6 @@ export class InitUiElements { }); - const layers = State.state.layoutToUse.data.layers - const clusterCounter = TileHierarchyAggregator.createHierarchy() new ShowDataLayer({ features: clusterCounter.getCountsForZoom(State.state.locationControl, State.state.layoutToUse.data.clustering.minNeededElements), @@ -471,6 +448,10 @@ export class InitUiElements { const doShowFeatures = source.features.map( f => { const z = State.state.locationControl.data.zoom + + if(!source.layer.isDisplayed.data){ + return false; + } if (z < source.layer.layerDef.minzoom) { // Layer is always hidden for this zoom level @@ -482,7 +463,7 @@ export class InitUiElements { } if (f.length > clustering.minNeededElements) { - // This tile alone has too much features + // This tile alone already has too much features return false } @@ -504,11 +485,12 @@ export class InitUiElements { const bounds = State.state.currentBounds.data const tilebbox = BBox.fromTileIndex(source.tileIndex) if (!tilebbox.overlapsWith(bounds)) { + // Not within range return false } return true - }, [State.state.locationControl, State.state.currentBounds] + }, [State.state.currentBounds] ) new ShowDataLayer( diff --git a/Logic/Actors/OverpassFeatureSource.ts b/Logic/Actors/OverpassFeatureSource.ts index 20181fc14..d77f326e5 100644 --- a/Logic/Actors/OverpassFeatureSource.ts +++ b/Logic/Actors/OverpassFeatureSource.ts @@ -9,9 +9,10 @@ import {TagsFilter} from "../Tags/TagsFilter"; import SimpleMetaTagger from "../SimpleMetaTagger"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import RelationsTracker from "../Osm/RelationsTracker"; +import {BBox} from "../BBox"; -export default class OverpassFeatureSource implements FeatureSource, FeatureSourceState { +export default class OverpassFeatureSource implements FeatureSource { public readonly name = "OverpassFeatureSource" @@ -21,7 +22,6 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour public readonly features: UIEventSource<{ feature: any, freshness: Date }[]> = new UIEventSource(undefined); - public readonly sufficientlyZoomed: UIEventSource; public readonly runningQuery: UIEventSource = new UIEventSource(false); public readonly timeout: UIEventSource = new UIEventSource(0); @@ -40,10 +40,12 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour private readonly state: { readonly locationControl: UIEventSource, readonly layoutToUse: UIEventSource, - readonly leafletMap: any, readonly overpassUrl: UIEventSource; readonly overpassTimeout: UIEventSource; + readonly currentBounds :UIEventSource } + private readonly _isActive: UIEventSource; + private _onUpdated?: (bbox: BBox, dataFreshness: Date) => void; /** * The most important layer should go first, as that one gets first pick for the questions */ @@ -51,33 +53,24 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour state: { readonly locationControl: UIEventSource, readonly layoutToUse: UIEventSource, - readonly leafletMap: any, readonly overpassUrl: UIEventSource; readonly overpassTimeout: UIEventSource; - readonly overpassMaxZoom: UIEventSource - }) { + readonly overpassMaxZoom: UIEventSource, + readonly currentBounds :UIEventSource + }, + + options?: { + isActive?: UIEventSource, + onUpdated?: (bbox: BBox, freshness: Date) => void, + relationTracker: RelationsTracker}) { this.state = state - this.relationsTracker = new RelationsTracker() + this._isActive = options.isActive; + this._onUpdated =options. onUpdated; + this.relationsTracker = options.relationTracker const location = state.locationControl const self = this; - this.sufficientlyZoomed = location.map(location => { - if (location?.zoom === undefined) { - return false; - } - let minzoom = Math.min(...state.layoutToUse.data.layers.map(layer => layer.minzoom ?? 18)); - if (location.zoom < minzoom) { - return false; - } - const maxZoom = state.overpassMaxZoom.data - if (maxZoom !== undefined && location.zoom > maxZoom) { - return false; - } - - return true; - }, [state.layoutToUse] - ); for (let i = 0; i < 25; i++) { // This update removes all data on all layers -> erase the map on lower levels too this._previousBounds.set(i, []); @@ -89,16 +82,11 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour location.addCallback(() => { self.update() }); - state.leafletMap.addCallbackAndRunD(_ => { - self.update(); + + state.currentBounds.addCallback(_ => { + self.update() }) - } - - public ForceRefresh() { - for (let i = 0; i < 25; i++) { - this._previousBounds.set(i, []); - } - this.update(); + } private GetFilter(): Overpass { @@ -152,24 +140,34 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour } private update() { - this.updateAsync().then(_ => { + if(!this._isActive.data){ + return; + } + const self = this + this.updateAsync().then(bboxAndDate => { + if(bboxAndDate === undefined || self._onUpdated === undefined){ + return; + } + const [bbox, date] = bboxAndDate + self._onUpdated(bbox, date); }) } - private async updateAsync(): Promise { + private async updateAsync(): Promise<[BBox, Date]> { if (this.runningQuery.data) { console.log("Still running a query, not updating"); - return; + return undefined; } if (this.timeout.data > 0) { console.log("Still in timeout - not updating") - return; + return undefined; } - const bounds = this.state.leafletMap.data?.getBounds()?.pad(this.state.layoutToUse.data.widenFactor); + const bounds = this.state.currentBounds.data?.pad(this.state.layoutToUse.data.widenFactor)?.expandToTileBounds(14); + if (bounds === undefined) { - return; + return undefined; } const n = Math.min(90, bounds.getNorth()); @@ -178,13 +176,12 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour const w = Math.max(-180, bounds.getWest()); const queryBounds = {north: n, east: e, south: s, west: w}; - const z = Math.floor(this.state.locationControl.data.zoom ?? 0); const self = this; const overpass = this.GetFilter(); if (overpass === undefined) { - return; + return undefined; } this.runningQuery.setData(true); @@ -195,15 +192,14 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour try { [data, date] = await overpass.queryGeoJson(queryBounds) + console.log("Querying overpass is done", data) } catch (e) { - console.error(`QUERY FAILED (retrying in ${5 * self.retries.data} sec) due to`, e); - self.retries.data++; self.retries.ping(); + console.error(`QUERY FAILED (retrying in ${5 * self.retries.data} sec) due to`, e); self.timeout.setData(self.retries.data * 5); - self.runningQuery.setData(false); - + while (self.timeout.data > 0) { await Utils.waitFor(1000) self.timeout.data-- @@ -212,16 +208,20 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour } } while (data === undefined); + const z = Math.floor(this.state.locationControl.data.zoom ?? 0); self._previousBounds.get(z).push(queryBounds); self.retries.setData(0); try { data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date)); self.features.setData(data.features.map(f => ({feature: f, freshness: date}))); + return [bounds, date]; } catch (e) { console.error("Got the overpass response, but could not process it: ", e, e.stack) + }finally { + self.runningQuery.setData(false); } - self.runningQuery.setData(false); + } @@ -231,7 +231,7 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour return false; } - const b = this.state.leafletMap.data.getBounds(); + const b = this.state.currentBounds.data; return b.getSouth() >= bounds.south && b.getNorth() <= bounds.north && b.getEast() <= bounds.east && diff --git a/Logic/BBox.ts b/Logic/BBox.ts new file mode 100644 index 000000000..e642c6ce3 --- /dev/null +++ b/Logic/BBox.ts @@ -0,0 +1,158 @@ +import * as turf from "@turf/turf"; +import {TileRange, Tiles} from "../Models/TileRange"; + +export class BBox { + + readonly maxLat: number; + readonly maxLon: number; + readonly minLat: number; + readonly minLon: number; + static global: BBox = new BBox([[-180, -90], [180, 90]]); + + constructor(coordinates) { + this.maxLat = -90; + this.maxLon = -180; + this.minLat = 90; + this.minLon = 180; + + + for (const coordinate of coordinates) { + this.maxLon = Math.max(this.maxLon, coordinate[0]); + this.maxLat = Math.max(this.maxLat, coordinate[1]); + this.minLon = Math.min(this.minLon, coordinate[0]); + this.minLat = Math.min(this.minLat, coordinate[1]); + } + this.check(); + } + + static fromLeafletBounds(bounds) { + return new BBox([[bounds.getWest(), bounds.getNorth()], [bounds.getEast(), bounds.getSouth()]]) + } + + static get(feature): BBox { + if (feature.bbox?.overlapsWith === undefined) { + const turfBbox: number[] = turf.bbox(feature) + feature.bbox = new BBox([[turfBbox[0], turfBbox[1]], [turfBbox[2], turfBbox[3]]]); + } + return feature.bbox; + } + + /** + * Constructs a tilerange which fully contains this bbox (thus might be a bit larger) + * @param zoomlevel + */ + public containingTileRange(zoomlevel): TileRange{ + return Tiles.TileRangeBetween(zoomlevel, this.minLat, this.minLon, this.maxLat, this.maxLon) + } + + public overlapsWith(other: BBox) { + if (this.maxLon < other.minLon) { + return false; + } + if (this.maxLat < other.minLat) { + return false; + } + if (this.minLon > other.maxLon) { + return false; + } + return this.minLat <= other.maxLat; + + } + + public isContainedIn(other: BBox) { + if (this.maxLon > other.maxLon) { + return false; + } + if (this.maxLat > other.maxLat) { + return false; + } + if (this.minLon < other.minLon) { + return false; + } + if (this.minLat < other.minLat) { + return false + } + return true; + } + + private check() { + if (isNaN(this.maxLon) || isNaN(this.maxLat) || isNaN(this.minLon) || isNaN(this.minLat)) { + console.log(this); + throw "BBOX has NAN"; + } + } + + static fromTile(z: number, x: number, y: number): BBox { + return new BBox(Tiles.tile_bounds_lon_lat(z, x, y)) + } + + static fromTileIndex(i: number): BBox { + if (i === 0) { + return BBox.global + } + return BBox.fromTile(...Tiles.tile_from_index(i)) + } + + getEast() { + return this.maxLon + } + + getNorth() { + return this.maxLat + } + + getWest() { + return this.minLon + } + + getSouth() { + return this.minLat + } + + pad(factor: number): BBox { + const latDiff = this.maxLat - this.minLat + const lat = (this.maxLat + this.minLat) / 2 + const lonDiff = this.maxLon - this.minLon + const lon = (this.maxLon + this.minLon) / 2 + return new BBox([[ + lon - lonDiff * factor, + lat - latDiff * factor + ], [lon + lonDiff * factor, + lat + latDiff * factor]]) + } + + toLeaflet() { + return [[this.minLat, this.minLon], [this.maxLat, this.maxLon]] + } + + asGeoJson(properties: any): any { + return { + type: "Feature", + properties: properties, + geometry: { + type: "Polygon", + coordinates: [[ + + [this.minLon, this.minLat], + [this.maxLon, this.minLat], + [this.maxLon, this.maxLat], + [this.minLon, this.maxLat], + [this.minLon, this.minLat], + + ]] + } + } + } + + /** + * Expands the BBOx so that it contains complete tiles for the given zoomlevel + * @param zoomlevel + */ + expandToTileBounds(zoomlevel: number) : BBox{ + const ul = Tiles.embedded_tile(this.minLat, this.minLon, zoomlevel) + const lr = Tiles.embedded_tile(this.maxLat, this.maxLon, zoomlevel) + const boundsul = Tiles.tile_bounds_lon_lat(ul.z, ul.x, ul.y) + const boundslr = Tiles.tile_bounds_lon_lat(lr.z, lr.x, lr.y) + return new BBox([].concat(boundsul, boundslr)) + } +} \ No newline at end of file diff --git a/Logic/ContributorCount.ts b/Logic/ContributorCount.ts index 2dcd8450f..f39d1106f 100644 --- a/Logic/ContributorCount.ts +++ b/Logic/ContributorCount.ts @@ -2,7 +2,7 @@ import {UIEventSource} from "./UIEventSource"; import FeaturePipeline from "./FeatureSource/FeaturePipeline"; import Loc from "../Models/Loc"; -import {BBox} from "./GeoOperations"; +import {BBox} from "./BBox"; export default class ContributorCount { diff --git a/Logic/ExtraFunction.ts b/Logic/ExtraFunction.ts index 5839fca08..76c28c5a9 100644 --- a/Logic/ExtraFunction.ts +++ b/Logic/ExtraFunction.ts @@ -1,4 +1,4 @@ -import {BBox, GeoOperations} from "./GeoOperations"; +import {GeoOperations} from "./GeoOperations"; import Combine from "../UI/Base/Combine"; import RelationsTracker from "./Osm/RelationsTracker"; import State from "../State"; @@ -7,6 +7,7 @@ import List from "../UI/Base/List"; import Title from "../UI/Base/Title"; import {UIEventSourceTools} from "./UIEventSource"; import AspectedRouting from "./Osm/aspectedRouting"; +import {BBox} from "./BBox"; export interface ExtraFuncParams { /** diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index a24d6f516..fab051aa5 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -17,18 +17,23 @@ import RegisteringAllFromFeatureSourceActor from "./Actors/RegisteringAllFromFea import TiledFromLocalStorageSource from "./TiledFeatureSource/TiledFromLocalStorageSource"; import SaveTileToLocalStorageActor from "./Actors/SaveTileToLocalStorageActor"; import DynamicGeoJsonTileSource from "./TiledFeatureSource/DynamicGeoJsonTileSource"; -import {BBox} from "../GeoOperations"; import {TileHierarchyMerger} from "./TiledFeatureSource/TileHierarchyMerger"; import RelationsTracker from "../Osm/RelationsTracker"; import {NewGeometryFromChangesFeatureSource} from "./Sources/NewGeometryFromChangesFeatureSource"; import ChangeGeometryApplicator from "./Sources/ChangeGeometryApplicator"; +import {BBox} from "../BBox"; +import OsmFeatureSource from "./TiledFeatureSource/OsmFeatureSource"; +import {OsmConnection} from "../Osm/OsmConnection"; +import {Tiles} from "../../Models/TileRange"; -export default class FeaturePipeline implements FeatureSourceState { +export default class FeaturePipeline { public readonly sufficientlyZoomed: UIEventSource; + public readonly runningQuery: UIEventSource; public readonly timeout: UIEventSource; + public readonly somethingLoaded: UIEventSource = new UIEventSource(false) public readonly newDataLoadedSignal: UIEventSource = new UIEventSource(undefined) @@ -39,27 +44,59 @@ export default class FeaturePipeline implements FeatureSourceState { constructor( handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void, state: { - filteredLayers: UIEventSource, - locationControl: UIEventSource, - selectedElement: UIEventSource, - changes: Changes, - layoutToUse: UIEventSource, - leafletMap: any, + readonly filteredLayers: UIEventSource, + readonly locationControl: UIEventSource, + readonly selectedElement: UIEventSource, + readonly changes: Changes, + readonly layoutToUse: UIEventSource, + readonly leafletMap: any, readonly overpassUrl: UIEventSource; readonly overpassTimeout: UIEventSource; readonly overpassMaxZoom: UIEventSource; + readonly osmConnection: OsmConnection + readonly currentBounds: UIEventSource }) { const self = this - const updater = new OverpassFeatureSource(state); + + /** + * Maps tileid onto last download moment + */ + const tileFreshnesses = new Map() + const osmSourceZoomLevel = 14 + const useOsmApi = state.locationControl.map(l => l.zoom > (state.overpassMaxZoom.data ?? 12)) + this.relationTracker = new RelationsTracker() + + const updater = new OverpassFeatureSource(state, + { + relationTracker: this.relationTracker, + isActive: useOsmApi.map(b => !b), + onUpdated: (bbox, freshness) => { + // This callback contains metadata of the overpass call + const range = bbox.containingTileRange(osmSourceZoomLevel) + Tiles.MapRange(range, (x, y) => { + tileFreshnesses.set(Tiles.tile_index(osmSourceZoomLevel, x, y), freshness) + }) + + } + }); + this.overpassUpdater = updater; - this.sufficientlyZoomed = updater.sufficientlyZoomed - this.runningQuery = updater.runningQuery + this.sufficientlyZoomed = state.locationControl.map(location => { + if (location?.zoom === undefined) { + return false; + } + let minzoom = Math.min(...state.layoutToUse.data.layers.map(layer => layer.minzoom ?? 18)); + return location.zoom >= minzoom; + } + ); + this.timeout = updater.timeout - this.relationTracker = updater.relationsTracker + + // Register everything in the state' 'AllElements' new RegisteringAllFromFeatureSourceActor(updater) - + const perLayerHierarchy = new Map() this.perLayerHierarchy = perLayerHierarchy @@ -72,7 +109,7 @@ export default class FeaturePipeline implements FeatureSourceState { new ChangeGeometryApplicator(src, state.changes) ) ) - + handleFeatureSource(srcFiltered) self.somethingLoaded.setData(true) }; @@ -81,6 +118,7 @@ export default class FeaturePipeline implements FeatureSourceState { perLayerHierarchy.get(layerId).registerTile(src) } + for (const filteredLayer of state.filteredLayers.data) { const hierarchy = new TileHierarchyMerger(filteredLayer, (tile, _) => patchedHandleFeatureSource(tile)) const id = filteredLayer.layerDef.id @@ -91,12 +129,25 @@ export default class FeaturePipeline implements FeatureSourceState { // This is an OSM layer // We load the cached values and register them // Getting data from upstream happens a bit lower - new TiledFromLocalStorageSource(filteredLayer, + const localStorage = new TiledFromLocalStorageSource(filteredLayer, (src) => { new RegisteringAllFromFeatureSourceActor(src) hierarchy.registerTile(src); src.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(src)) }, state) + + localStorage.tileFreshness.forEach((value, key) => { + if (tileFreshnesses.has(key)) { + const previous = tileFreshnesses.get(key) + if (value < previous) { + tileFreshnesses.set(key, value) + } + } else { + tileFreshnesses.set(key, value) + } + }) + + continue } @@ -106,7 +157,7 @@ export default class FeaturePipeline implements FeatureSourceState { const src = new GeoJsonSource(filteredLayer) TiledFeatureSource.createHierarchy(src, { layer: src.layer, - minZoomLevel:14, + minZoomLevel: 14, dontEnforceMinZoom: true, registerTile: (tile) => { new RegisteringAllFromFeatureSourceActor(tile) @@ -118,16 +169,54 @@ export default class FeaturePipeline implements FeatureSourceState { new DynamicGeoJsonTileSource( filteredLayer, tile => { - new RegisteringAllFromFeatureSourceActor(tile) - addToHierarchy(tile, id) - tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile)) - }, + new RegisteringAllFromFeatureSourceActor(tile) + addToHierarchy(tile, id) + tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile)) + }, state ) } - } + console.log("Tilefreshnesses are", tileFreshnesses) + const oldestAllowedDate = new Date(new Date().getTime() - (60 * 60 * 24 * 30 * 1000)); + + const neededTilesFromOsm = state.currentBounds.map(bbox => { + if (bbox === undefined) { + return + } + const range = bbox.containingTileRange(osmSourceZoomLevel) + const tileIndexes = [] + if (range.total > 100) { + // Too much tiles! + return [] + } + Tiles.MapRange(range, (x, y) => { + const i = Tiles.tile_index(osmSourceZoomLevel, x, y); + if (tileFreshnesses.get(i) > oldestAllowedDate) { + console.debug("Skipping tile", osmSourceZoomLevel, x, y, "as a decently fresh one is available") + // The cached tiles contain decently fresh data + return; + } + tileIndexes.push(i) + }) + return tileIndexes + }) + + const osmFeatureSource = new OsmFeatureSource({ + isActive: useOsmApi, + neededTiles: neededTilesFromOsm, + handleTile: tile => { + new RegisteringAllFromFeatureSourceActor(tile) + new SaveTileToLocalStorageActor(tile, tile.tileIndex) + addToHierarchy(tile, tile.layer.layerDef.id), + tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile)) + + }, + state: state + }) + + // Actually load data from the overpass source new PerLayerFeatureSourceSplitter(state.filteredLayers, (source) => TiledFeatureSource.createHierarchy(source, { @@ -169,9 +258,15 @@ export default class FeaturePipeline implements FeatureSourceState { self.updateAllMetaTagging() }) + + this.runningQuery = updater.runningQuery.map( + overpass => overpass || osmFeatureSource.isRunning.data, [osmFeatureSource.isRunning] + ) + + } - - private applyMetaTags(src: FeatureSourceForLayer){ + + private applyMetaTags(src: FeatureSourceForLayer) { const self = this console.debug("Applying metatagging onto ", src.name) window.setTimeout( @@ -192,7 +287,7 @@ export default class FeaturePipeline implements FeatureSourceState { }, 15 ) - + } private updateAllMetaTagging() { @@ -231,7 +326,4 @@ export default class FeaturePipeline implements FeatureSourceState { }) } - public ForceRefresh() { - this.overpassUpdater.ForceRefresh() - } } \ No newline at end of file diff --git a/Logic/FeatureSource/FeatureSource.ts b/Logic/FeatureSource/FeatureSource.ts index 791882e11..7d603d1f6 100644 --- a/Logic/FeatureSource/FeatureSource.ts +++ b/Logic/FeatureSource/FeatureSource.ts @@ -1,7 +1,7 @@ import {UIEventSource} from "../UIEventSource"; import {Utils} from "../../Utils"; import FilteredLayer from "../../Models/FilteredLayer"; -import {BBox} from "../GeoOperations"; +import {BBox} from "../BBox"; export default interface FeatureSource { features: UIEventSource<{ feature: any, freshness: Date }[]>; diff --git a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts index d16e2c558..f9a0cbe1a 100644 --- a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts +++ b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts @@ -15,6 +15,7 @@ export default class PerLayerFeatureSourceSplitter { handleLayerData: (source: FeatureSourceForLayer & Tiled) => void, upstream: FeatureSource, options?:{ + tileIndex?: number, handleLeftovers?: (featuresWithoutLayer: any[]) => void }) { @@ -71,7 +72,7 @@ export default class PerLayerFeatureSourceSplitter { let featureSource = knownLayers.get(id) if (featureSource === undefined) { // Not yet initialized - now is a good time - featureSource = new SimpleFeatureSource(layer) + featureSource = new SimpleFeatureSource(layer, options?.tileIndex) featureSource.features.setData(features) knownLayers.set(id, featureSource) handleLayerData(featureSource) diff --git a/Logic/FeatureSource/Sources/FeatureSourceMerger.ts b/Logic/FeatureSource/Sources/FeatureSourceMerger.ts index 44ae28543..b1797d0ae 100644 --- a/Logic/FeatureSource/Sources/FeatureSourceMerger.ts +++ b/Logic/FeatureSource/Sources/FeatureSourceMerger.ts @@ -5,9 +5,9 @@ import {UIEventSource} from "../../UIEventSource"; import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource"; import FilteredLayer from "../../../Models/FilteredLayer"; -import {BBox} from "../../GeoOperations"; import {Utils} from "../../../Utils"; import {Tiles} from "../../../Models/TileRange"; +import {BBox} from "../../BBox"; export default class FeatureSourceMerger implements FeatureSourceForLayer, Tiled, IndexedFeatureSource { diff --git a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts index 65c6df0ec..6b433f88d 100644 --- a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts @@ -3,7 +3,7 @@ import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; import FilteredLayer from "../../../Models/FilteredLayer"; import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; import Hash from "../../Web/Hash"; -import {BBox} from "../../GeoOperations"; +import {BBox} from "../../BBox"; export default class FilteringFeatureSource implements FeatureSourceForLayer, Tiled { public features: UIEventSource<{ feature: any; freshness: Date }[]> = diff --git a/Logic/FeatureSource/Sources/GeoJsonSource.ts b/Logic/FeatureSource/Sources/GeoJsonSource.ts index 6222fc729..301f1bc55 100644 --- a/Logic/FeatureSource/Sources/GeoJsonSource.ts +++ b/Logic/FeatureSource/Sources/GeoJsonSource.ts @@ -5,8 +5,8 @@ import {UIEventSource} from "../../UIEventSource"; import FilteredLayer from "../../../Models/FilteredLayer"; import {Utils} from "../../../Utils"; import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; -import {BBox} from "../../GeoOperations"; import {Tiles} from "../../../Models/TileRange"; +import {BBox} from "../../BBox"; export default class GeoJsonSource implements FeatureSourceForLayer, Tiled { diff --git a/Logic/FeatureSource/Sources/RememberingSource.ts b/Logic/FeatureSource/Sources/RememberingSource.ts index b31097061..683576736 100644 --- a/Logic/FeatureSource/Sources/RememberingSource.ts +++ b/Logic/FeatureSource/Sources/RememberingSource.ts @@ -4,7 +4,7 @@ */ import FeatureSource, {Tiled} from "../FeatureSource"; import {UIEventSource} from "../../UIEventSource"; -import {BBox} from "../../GeoOperations"; +import {BBox} from "../../BBox"; export default class RememberingSource implements FeatureSource , Tiled{ diff --git a/Logic/FeatureSource/Sources/SimpleFeatureSource.ts b/Logic/FeatureSource/Sources/SimpleFeatureSource.ts index b3a476f5f..b06aae6d2 100644 --- a/Logic/FeatureSource/Sources/SimpleFeatureSource.ts +++ b/Logic/FeatureSource/Sources/SimpleFeatureSource.ts @@ -1,20 +1,22 @@ import {UIEventSource} from "../../UIEventSource"; import FilteredLayer from "../../../Models/FilteredLayer"; import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; -import {BBox} from "../../GeoOperations"; import {Utils} from "../../../Utils"; import {Tiles} from "../../../Models/TileRange"; +import {BBox} from "../../BBox"; export default class SimpleFeatureSource implements FeatureSourceForLayer, Tiled { public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]); public readonly name: string = "SimpleFeatureSource"; public readonly layer: FilteredLayer; public readonly bbox: BBox = BBox.global; - public readonly tileIndex: number = Tiles.tile_index(0, 0, 0); + public readonly tileIndex: number; - constructor(layer: FilteredLayer) { + constructor(layer: FilteredLayer, tileIndex: number) { this.name = "SimpleFeatureSource(" + layer.layerDef.id + ")" this.layer = layer + this.tileIndex = tileIndex ?? 0; + this.bbox = BBox.fromTileIndex(this.tileIndex) } } \ No newline at end of file diff --git a/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts b/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts new file mode 100644 index 000000000..2bed33298 --- /dev/null +++ b/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts @@ -0,0 +1,112 @@ +import {Utils} from "../../../Utils"; +import * as OsmToGeoJson from "osmtogeojson"; +import StaticFeatureSource from "../Sources/StaticFeatureSource"; +import PerLayerFeatureSourceSplitter from "../PerLayerFeatureSourceSplitter"; +import {UIEventSource} from "../../UIEventSource"; +import FilteredLayer from "../../../Models/FilteredLayer"; +import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; +import {Tiles} from "../../../Models/TileRange"; +import {BBox} from "../../BBox"; +import {OsmConnection} from "../../Osm/OsmConnection"; + +export default class OsmFeatureSource { + private readonly _backend: string; + + public readonly isRunning: UIEventSource = new UIEventSource(false) + private readonly filteredLayers: UIEventSource; + private readonly handleTile: (fs: (FeatureSourceForLayer & Tiled)) => void; + private isActive: UIEventSource; + private options: { + handleTile: (tile: FeatureSourceForLayer & Tiled) => void; + isActive: UIEventSource, + neededTiles: UIEventSource, + state: { + readonly osmConnection: OsmConnection; + }; + }; + private readonly downloadedTiles = new Set() + + constructor(options: { + handleTile: (tile: FeatureSourceForLayer & Tiled) => void; + isActive: UIEventSource, + neededTiles: UIEventSource, + state: { + readonly filteredLayers: UIEventSource; + readonly osmConnection: OsmConnection; + }; + }) { + this.options = options; + this._backend = options.state.osmConnection._oauth_config.url; + this.filteredLayers = options.state.filteredLayers.map(layers => layers.filter(layer => layer.layerDef.source.geojsonSource === undefined)) + this.handleTile = options.handleTile + this.isActive = options.isActive + const self = this + options.neededTiles.addCallbackAndRunD(neededTiles => { + if (options.isActive?.data === false) { + return; + } + + self.isRunning.setData(true) + try { + + for (const neededTile of neededTiles) { + if (self.downloadedTiles.has(neededTile)) { + return; + } + self.downloadedTiles.add(neededTile) + Promise.resolve(self.LoadTile(...Tiles.tile_from_index(neededTile)).then(_ => { + })) + } + } catch (e) { + console.error(e) + } + self.isRunning.setData(false) + }) + } + + private async LoadTile(z, x, y): Promise { + if (z > 18) { + throw "This is an absurd high zoom level" + } + + const bbox = BBox.fromTile(z, x, y) + const url = `${this._backend}/api/0.6/map?bbox=${bbox.minLon},${bbox.minLat},${bbox.maxLon},${bbox.maxLat}` + try { + + console.log("Attempting to get tile", z, x, y, "from the osm api") + const osmXml = await Utils.download(url, {"accept": "application/xml"}) + try { + const parsed = new DOMParser().parseFromString(osmXml, "text/xml"); + console.log("Got tile", z, x, y, "from the osm api") + const geojson = OsmToGeoJson.default(parsed, + // @ts-ignore + { + flatProperties: true + }); + console.log("Tile geojson:", z, x, y, "is", geojson) + new PerLayerFeatureSourceSplitter(this.filteredLayers, + this.handleTile, + new StaticFeatureSource(geojson.features, false), + { + tileIndex: Tiles.tile_index(z, x, y) + } + ); + } catch (e) { + console.error("Weird error: ", e) + } + } catch (e) { + console.error("Could not download tile", z, x, y, "due to", e, "; retrying with smaller bounds") + if (e === "rate limited") { + return; + } + await this.LoadTile(z + 1, x * 2, y * 2) + await this.LoadTile(z + 1, 1 + x * 2, y * 2) + await this.LoadTile(z + 1, x * 2, 1 + y * 2) + await this.LoadTile(z + 1, 1 + x * 2, 1 + y * 2) + return; + } + + + } + +} \ No newline at end of file diff --git a/Logic/FeatureSource/TiledFeatureSource/TileHierarchy.ts b/Logic/FeatureSource/TiledFeatureSource/TileHierarchy.ts index d905d2f65..f2e43069d 100644 --- a/Logic/FeatureSource/TiledFeatureSource/TileHierarchy.ts +++ b/Logic/FeatureSource/TiledFeatureSource/TileHierarchy.ts @@ -1,5 +1,5 @@ import FeatureSource, {Tiled} from "../FeatureSource"; -import {BBox} from "../../GeoOperations"; +import {BBox} from "../../BBox"; export default interface TileHierarchy { diff --git a/Logic/FeatureSource/TiledFeatureSource/TileHierarchyMerger.ts b/Logic/FeatureSource/TiledFeatureSource/TileHierarchyMerger.ts index d8ab294ce..844754854 100644 --- a/Logic/FeatureSource/TiledFeatureSource/TileHierarchyMerger.ts +++ b/Logic/FeatureSource/TiledFeatureSource/TileHierarchyMerger.ts @@ -3,9 +3,9 @@ import {UIEventSource} from "../../UIEventSource"; import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource"; import FilteredLayer from "../../../Models/FilteredLayer"; import {Utils} from "../../../Utils"; -import {BBox} from "../../GeoOperations"; import FeatureSourceMerger from "../Sources/FeatureSourceMerger"; import {Tiles} from "../../../Models/TileRange"; +import {BBox} from "../../BBox"; export class TileHierarchyMerger implements TileHierarchy { public readonly loadedTiles: Map = new Map(); diff --git a/Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource.ts b/Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource.ts index 2dcf3e6d2..4950ea328 100644 --- a/Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource.ts @@ -1,10 +1,10 @@ import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource"; import {UIEventSource} from "../../UIEventSource"; import {Utils} from "../../../Utils"; -import {BBox} from "../../GeoOperations"; import FilteredLayer from "../../../Models/FilteredLayer"; import TileHierarchy from "./TileHierarchy"; import {Tiles} from "../../../Models/TileRange"; +import {BBox} from "../../BBox"; /** * Contains all features in a tiled fashion. @@ -109,7 +109,6 @@ export default class TiledFeatureSource implements Tiled, IndexedFeatureSource, // To much features - we split return featureCount > this.maxFeatureCount - } /*** @@ -143,7 +142,20 @@ export default class TiledFeatureSource implements Tiled, IndexedFeatureSource, for (const feature of features) { const bbox = BBox.get(feature.feature) - if (this.options.dontEnforceMinZoom || this.options.minZoomLevel === undefined) { + + if (this.options.dontEnforceMinZoom) { + if (bbox.overlapsWith(this.upper_left.bbox)) { + ulf.push(feature) + } else if (bbox.overlapsWith(this.upper_right.bbox)) { + urf.push(feature) + } else if (bbox.overlapsWith(this.lower_left.bbox)) { + llf.push(feature) + } else if (bbox.overlapsWith(this.lower_right.bbox)) { + lrf.push(feature) + } else { + overlapsboundary.push(feature) + } + }else if (this.options.minZoomLevel === undefined) { if (bbox.isContainedIn(this.upper_left.bbox)) { ulf.push(feature) } else if (bbox.isContainedIn(this.upper_right.bbox)) { diff --git a/Logic/FeatureSource/TiledFeatureSource/TiledFromLocalStorageSource.ts b/Logic/FeatureSource/TiledFeatureSource/TiledFromLocalStorageSource.ts index 3e879bd4a..4b578b7da 100644 --- a/Logic/FeatureSource/TiledFeatureSource/TiledFromLocalStorageSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/TiledFromLocalStorageSource.ts @@ -5,12 +5,13 @@ import Loc from "../../../Models/Loc"; import TileHierarchy from "./TileHierarchy"; import {Utils} from "../../../Utils"; import SaveTileToLocalStorageActor from "../Actors/SaveTileToLocalStorageActor"; -import {BBox} from "../../GeoOperations"; import {Tiles} from "../../../Models/TileRange"; +import {BBox} from "../../BBox"; export default class TiledFromLocalStorageSource implements TileHierarchy { public loadedTiles: Map = new Map(); - +public tileFreshness : Map = new Map() + constructor(layer: FilteredLayer, handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void, state: { @@ -29,7 +30,14 @@ export default class TiledFromLocalStorageSource implements TileHierarchy Tiles.tile_from_index(i).join("/")).join(", ")) + console.debug("Layer", layer.layerDef.id, "has following tiles in available in localstorage", indexes.map(i => Tiles.tile_from_index(i).join("/")).join(", ")) + for (const index of indexes) { + const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layer.layerDef.id + "-" +index+"-time"; + const data = Number(localStorage.getItem(prefix)) + const freshness = new Date() + freshness.setTime(data) + this.tileFreshness.set(index, freshness) + } const zLevels = indexes.map(i => i % 100) const indexesSet = new Set(indexes) @@ -72,7 +80,7 @@ export default class TiledFromLocalStorageSource implements TileHierarchy console.log("Tiles to load from localstorage:", t)) + neededTiles.addCallbackAndRun(t => console.debug("Tiles to load from localstorage:", t)) neededTiles.addCallbackAndRunD(neededIndexes => { for (const neededIndex of neededIndexes) { diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index cfef09e5d..3fa10f7a2 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -1,6 +1,5 @@ import * as turf from '@turf/turf' -import {Utils} from "../Utils"; -import {Tiles} from "../Models/TileRange"; +import {BBox} from "./BBox"; export class GeoOperations { @@ -379,135 +378,3 @@ export class GeoOperations { } -export class BBox { - - readonly maxLat: number; - readonly maxLon: number; - readonly minLat: number; - readonly minLon: number; - static global: BBox = new BBox([[-180, -90], [180, 90]]); - - constructor(coordinates) { - this.maxLat = -90; - this.maxLon = -180; - this.minLat = 90; - this.minLon = 180; - - - for (const coordinate of coordinates) { - this.maxLon = Math.max(this.maxLon, coordinate[0]); - this.maxLat = Math.max(this.maxLat, coordinate[1]); - this.minLon = Math.min(this.minLon, coordinate[0]); - this.minLat = Math.min(this.minLat, coordinate[1]); - } - this.check(); - } - - static fromLeafletBounds(bounds) { - return new BBox([[bounds.getWest(), bounds.getNorth()], [bounds.getEast(), bounds.getSouth()]]) - } - - static get(feature): BBox { - if (feature.bbox?.overlapsWith === undefined) { - const turfBbox: number[] = turf.bbox(feature) - feature.bbox = new BBox([[turfBbox[0], turfBbox[1]], [turfBbox[2], turfBbox[3]]]); - } - return feature.bbox; - } - - public overlapsWith(other: BBox) { - if (this.maxLon < other.minLon) { - return false; - } - if (this.maxLat < other.minLat) { - return false; - } - if (this.minLon > other.maxLon) { - return false; - } - return this.minLat <= other.maxLat; - - } - - public isContainedIn(other: BBox) { - if (this.maxLon > other.maxLon) { - return false; - } - if (this.maxLat > other.maxLat) { - return false; - } - if (this.minLon < other.minLon) { - return false; - } - if (this.minLat < other.minLat) { - return false - } - return true; - } - - private check() { - if (isNaN(this.maxLon) || isNaN(this.maxLat) || isNaN(this.minLon) || isNaN(this.minLat)) { - console.log(this); - throw "BBOX has NAN"; - } - } - - static fromTile(z: number, x: number, y: number): BBox { - return new BBox(Tiles.tile_bounds_lon_lat(z, x, y)) - } - - static fromTileIndex(i: number): BBox { - return BBox.fromTile(...Tiles.tile_from_index(i)) - } - - getEast() { - return this.maxLon - } - - getNorth() { - return this.maxLat - } - - getWest() { - return this.minLon - } - - getSouth() { - return this.minLat - } - - pad(factor: number) : BBox { - const latDiff = this.maxLat - this.minLat - const lat = (this.maxLat + this.minLat) / 2 - const lonDiff = this.maxLon - this.minLon - const lon = (this.maxLon + this.minLon) / 2 - return new BBox([[ - lon - lonDiff * factor, - lat - latDiff * factor - ], [lon + lonDiff * factor, - lat + latDiff * factor]]) - } - - toLeaflet() { - return [[this.minLat, this.minLon], [this.maxLat, this.maxLon]] - } - - asGeoJson(properties: any) : any{ - return { - type:"Feature", - properties: properties, - geometry:{ - type:"Polygon", - coordinates:[[ - - [this.minLon, this.minLat], - [this.maxLon, this.minLat], - [this.maxLon, this.maxLat], - [this.minLon, this.maxLat], - [this.minLon, this.minLat], - - ]] - } - } - } -} \ No newline at end of file diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 5d955eb04..4e1a9297a 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -62,26 +62,26 @@ export class OsmConnection { }; private isChecking = false; - constructor(dryRun: boolean, - fakeUser: boolean, + constructor(options:{dryRun?: false | boolean, + fakeUser?: false | boolean, allElements: ElementStorage, changes: Changes, - oauth_token: UIEventSource, + oauth_token?: UIEventSource, // Used to keep multiple changesets open and to write to the correct changeset layoutName: string, - singlePage: boolean = true, - osmConfiguration: "osm" | "osm-test" = 'osm' + singlePage?: boolean, + osmConfiguration?: "osm" | "osm-test" } ) { - this.fakeUser = fakeUser; - this._singlePage = singlePage; - this._oauth_config = OsmConnection.oauth_configs[osmConfiguration] ?? OsmConnection.oauth_configs.osm; + this.fakeUser = options.fakeUser ?? false; + this._singlePage = options.singlePage ?? true; + this._oauth_config = OsmConnection.oauth_configs[options.osmConfiguration ?? 'osm'] ?? OsmConnection.oauth_configs.osm; console.debug("Using backend", this._oauth_config.url) OsmObject.SetBackendUrl(this._oauth_config.url + "/") this._iframeMode = Utils.runningFromConsole ? false : window !== window.top; this.userDetails = new UIEventSource(new UserDetails(this._oauth_config.url), "userDetails"); - this.userDetails.data.dryRun = dryRun || fakeUser; - if (fakeUser) { + this.userDetails.data.dryRun = (options.dryRun ?? false) || (options.fakeUser ?? false) ; + if (options.fakeUser) { const ud = this.userDetails.data; ud.csCount = 5678 ud.loggedIn = true; @@ -98,23 +98,23 @@ export class OsmConnection { } }); this.isLoggedIn.addCallbackAndRunD(li => console.log("User is logged in!", li)) - this._dryRun = dryRun; + this._dryRun = options.dryRun; this.updateAuthObject(); this.preferencesHandler = new OsmPreferences(this.auth, this); - this.changesetHandler = new ChangesetHandler(layoutName, dryRun, this, allElements, changes, this.auth); - if (oauth_token.data !== undefined) { - console.log(oauth_token.data) + this.changesetHandler = new ChangesetHandler(options.layoutName, options.dryRun, this, options.allElements, options.changes, this.auth); + if (options.oauth_token?.data !== undefined) { + console.log(options.oauth_token.data) const self = this; - this.auth.bootstrapToken(oauth_token.data, + this.auth.bootstrapToken(options.oauth_token.data, (x) => { console.log("Called back: ", x) self.AttemptLogin(); }, this.auth); - oauth_token.setData(undefined); + options. oauth_token.setData(undefined); } if (this.auth.authenticated()) { diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index d95e0fa49..4538622d1 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -1,7 +1,7 @@ import {Utils} from "../../Utils"; import * as polygon_features from "../../assets/polygon-features.json"; import {UIEventSource} from "../UIEventSource"; -import {BBox} from "../GeoOperations"; +import {BBox} from "../BBox"; export abstract class OsmObject { diff --git a/Logic/Osm/Overpass.ts b/Logic/Osm/Overpass.ts index de6b65287..2bf1b4e70 100644 --- a/Logic/Osm/Overpass.ts +++ b/Logic/Osm/Overpass.ts @@ -16,8 +16,8 @@ export class Overpass { private readonly _extraScripts: string[]; private _includeMeta: boolean; private _relationTracker: RelationsTracker; - - + + constructor(filter: TagsFilter, extraScripts: string[], interpreterUrl: UIEventSource, timeout: UIEventSource, @@ -41,10 +41,13 @@ export class Overpass { } const self = this; const json = await Utils.downloadJson(query) - - if (json.elements === [] && ((json.remarks ?? json.remark).indexOf("runtime error") >= 0)) { - console.log("Timeout or other runtime error"); - throw("Runtime error (timeout)") + console.log("Got json!", json) + if (json.elements.length === 0 && json.remark !== undefined) { + console.warn("Timeout or other runtime error while querying overpass", json.remark); + throw `Runtime error (timeout or similar)${json.remark}` + } + if(json.elements.length === 0){ + console.warn("No features for" ,json) } self._relationTracker.RegisterRelations(json) diff --git a/Models/TileRange.ts b/Models/TileRange.ts index 215d5a76f..b61f192a0 100644 --- a/Models/TileRange.ts +++ b/Models/TileRange.ts @@ -86,7 +86,7 @@ export class Tiles { static embedded_tile(lat: number, lon: number, z: number): { x: number, y: number, z: number } { return {x: Tiles.lon2tile(lon, z), y: Tiles.lat2tile(lat, z), z: z} } - + static TileRangeBetween(zoomlevel: number, lat0: number, lon0: number, lat1: number, lon1: number): TileRange { const t0 = Tiles.embedded_tile(lat0, lon0, zoomlevel) const t1 = Tiles.embedded_tile(lat1, lon1, zoomlevel) diff --git a/State.ts b/State.ts index 04b9728b3..85dfc9cfd 100644 --- a/State.ts +++ b/State.ts @@ -17,7 +17,7 @@ import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline"; import FilteredLayer from "./Models/FilteredLayer"; import ChangeToElementsActor from "./Logic/Actors/ChangeToElementsActor"; import LayoutConfig from "./Models/ThemeConfig/LayoutConfig"; -import {BBox} from "./Logic/GeoOperations"; +import {BBox} from "./Logic/BBox"; /** * Contains the global state: a bunch of UI-event sources @@ -83,7 +83,7 @@ export default class State { public readonly featureSwitchExportAsPdf: UIEventSource; public readonly overpassUrl: UIEventSource; public readonly overpassTimeout: UIEventSource; - public readonly overpassMaxZoom: UIEventSource = new UIEventSource(undefined); + public readonly overpassMaxZoom: UIEventSource = new UIEventSource(20); public featurePipeline: FeaturePipeline; @@ -97,7 +97,7 @@ export default class State { * The current visible extent of the screen */ public readonly currentBounds = new UIEventSource(undefined) - + public backgroundLayer; public readonly backgroundLayerId: UIEventSource; @@ -372,23 +372,21 @@ export default class State { return; } - this.osmConnection = new OsmConnection( - this.featureSwitchIsTesting.data, - this.featureSwitchFakeUser.data, - this.allElements, - this.changes, - QueryParameters.GetQueryParameter( + this.osmConnection = new OsmConnection({ + changes: this.changes, + dryRun: this.featureSwitchIsTesting.data, + fakeUser: this.featureSwitchFakeUser.data, + allElements: this.allElements, + oauth_token: QueryParameters.GetQueryParameter( "oauth_token", undefined, "Used to complete the login" ), - layoutToUse?.id, - true, - // @ts-ignore - this.featureSwitchApiURL.data - ); + layoutName: layoutToUse?.id, + osmConfiguration: <'osm' | 'osm-test'>this.featureSwitchApiURL.data + }) + - new ChangeToElementsActor(this.changes, this.allElements) new PendingChangesUploader(this.changes, this.selectedElement); diff --git a/UI/Base/Minimap.ts b/UI/Base/Minimap.ts index e276c8281..32cdadbd6 100644 --- a/UI/Base/Minimap.ts +++ b/UI/Base/Minimap.ts @@ -1,8 +1,8 @@ import BaseUIElement from "../BaseUIElement"; import Loc from "../../Models/Loc"; import BaseLayer from "../../Models/BaseLayer"; -import {BBox} from "../../Logic/GeoOperations"; import {UIEventSource} from "../../Logic/UIEventSource"; +import {BBox} from "../../Logic/BBox"; export interface MinimapOptions { background?: UIEventSource, diff --git a/UI/Base/MinimapImplementation.ts b/UI/Base/MinimapImplementation.ts index 1761d1ada..110235437 100644 --- a/UI/Base/MinimapImplementation.ts +++ b/UI/Base/MinimapImplementation.ts @@ -4,10 +4,10 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import Loc from "../../Models/Loc"; import BaseLayer from "../../Models/BaseLayer"; import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; -import {BBox} from "../../Logic/GeoOperations"; import * as L from "leaflet"; import {Map} from "leaflet"; import Minimap, {MinimapObj, MinimapOptions} from "./Minimap"; +import {BBox} from "../../Logic/BBox"; export default class MinimapImplementation extends BaseUIElement implements MinimapObj { private static _nextId = 0; diff --git a/UI/BigComponents/Attribution.ts b/UI/BigComponents/Attribution.ts index 7e70fa367..9b1ddb013 100644 --- a/UI/BigComponents/Attribution.ts +++ b/UI/BigComponents/Attribution.ts @@ -7,7 +7,7 @@ import Constants from "../../Models/Constants"; import Loc from "../../Models/Loc"; import {VariableUiElement} from "../Base/VariableUIElement"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; -import {BBox} from "../../Logic/GeoOperations"; +import {BBox} from "../../Logic/BBox"; /** * The bottom right attribution panel in the leaflet map diff --git a/UI/BigComponents/DownloadPanel.ts b/UI/BigComponents/DownloadPanel.ts index 746715826..500d28e19 100644 --- a/UI/BigComponents/DownloadPanel.ts +++ b/UI/BigComponents/DownloadPanel.ts @@ -5,7 +5,7 @@ import State from "../../State"; import {Utils} from "../../Utils"; import Combine from "../Base/Combine"; import CheckBoxes from "../Input/Checkboxes"; -import {BBox, GeoOperations} from "../../Logic/GeoOperations"; +import {GeoOperations} from "../../Logic/GeoOperations"; import Toggle from "../Input/Toggle"; import Title from "../Base/Title"; import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; @@ -13,6 +13,7 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import SimpleMetaTagger from "../../Logic/SimpleMetaTagger"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import {meta} from "@turf/turf"; +import {BBox} from "../../Logic/BBox"; export class DownloadPanel extends Toggle { diff --git a/UI/BigComponents/FullWelcomePaneWithTabs.ts b/UI/BigComponents/FullWelcomePaneWithTabs.ts index a4a3e826a..f6574f04b 100644 --- a/UI/BigComponents/FullWelcomePaneWithTabs.ts +++ b/UI/BigComponents/FullWelcomePaneWithTabs.ts @@ -1,7 +1,5 @@ import State from "../../State"; import ThemeIntroductionPanel from "./ThemeIntroductionPanel"; -import * as personal from "../../assets/themes/personal/personal.json"; -import PersonalLayersPanel from "./PersonalLayersPanel"; import Svg from "../../Svg"; import Translations from "../i18n/Translations"; import ShareScreen from "./ShareScreen"; @@ -32,9 +30,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { private static ConstructBaseTabs(layoutToUse: LayoutConfig, isShown: UIEventSource): { header: string | BaseUIElement; content: BaseUIElement }[] { let welcome: BaseUIElement = new ThemeIntroductionPanel(isShown); - if (layoutToUse.id === personal.id) { - welcome = new PersonalLayersPanel(); - } + const tabs: { header: string | BaseUIElement, content: BaseUIElement }[] = [ {header: ``, content: welcome}, { diff --git a/UI/BigComponents/LeftControls.ts b/UI/BigComponents/LeftControls.ts index b3e848d4d..7ee2a74e6 100644 --- a/UI/BigComponents/LeftControls.ts +++ b/UI/BigComponents/LeftControls.ts @@ -11,8 +11,8 @@ import AllDownloads from "./AllDownloads"; import FilterView from "./FilterView"; import {UIEventSource} from "../../Logic/UIEventSource"; import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; -import {BBox} from "../../Logic/GeoOperations"; import Loc from "../../Models/Loc"; +import {BBox} from "../../Logic/BBox"; export default class LeftControls extends Combine { diff --git a/UI/BigComponents/PersonalLayersPanel.ts b/UI/BigComponents/PersonalLayersPanel.ts deleted file mode 100644 index e5a41b733..000000000 --- a/UI/BigComponents/PersonalLayersPanel.ts +++ /dev/null @@ -1,120 +0,0 @@ -import {AllKnownLayouts} from "../../Customizations/AllKnownLayouts"; -import Svg from "../../Svg"; -import State from "../../State"; -import Combine from "../Base/Combine"; -import Toggle from "../Input/Toggle"; -import {SubtleButton} from "../Base/SubtleButton"; -import Translations from "../i18n/Translations"; -import BaseUIElement from "../BaseUIElement"; -import {VariableUiElement} from "../Base/VariableUIElement"; -import {UIEventSource} from "../../Logic/UIEventSource"; -import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; - -export default class PersonalLayersPanel extends VariableUiElement { - - constructor() { - super( - State.state.installedThemes.map(installedThemes => { - const t = Translations.t.favourite; - - // Lets get all the layers - const allThemes = AllKnownLayouts.layoutsList.concat(installedThemes.map(layout => layout.layout)) - .filter(theme => !theme.hideFromOverview) - - const allLayers = [] - { - const seenLayers = new Set() - for (const layers of allThemes.map(theme => theme.layers)) { - for (const layer of layers) { - if (seenLayers.has(layer.id)) { - continue - } - seenLayers.add(layer.id) - allLayers.push(layer) - } - } - } - - // Time to create a panel based on them! - const panel: BaseUIElement = new Combine(allLayers.map(PersonalLayersPanel.CreateLayerToggle)); - - - return new Toggle( - new Combine([ - t.panelIntro.Clone(), - panel - ]).SetClass("flex flex-col"), - new SubtleButton( - Svg.osm_logo_ui(), - t.loginNeeded.Clone().SetClass("text-center") - ).onClick(() => State.state.osmConnection.AttemptLogin()), - State.state.osmConnection.isLoggedIn - ) - }) - ) - } - - /*** - * Creates a toggle for the given layer, which'll update State.state.favouriteLayers right away - * @param layer - * @constructor - * @private - */ - private static CreateLayerToggle(layer: LayerConfig): Toggle { - let icon: BaseUIElement = new Combine([layer.GenerateLeafletStyle( - new UIEventSource({id: "node/-1"}), - false - ).icon.html]).SetClass("relative") - let iconUnset = new Combine([layer.GenerateLeafletStyle( - new UIEventSource({id: "node/-1"}), - false - ).icon.html]).SetClass("relative") - - iconUnset.SetStyle("opacity:0.1") - - let name = layer.name; - if (name === undefined) { - return undefined; - } - const content = new Combine([ - Translations.WT(name).Clone().SetClass("font-bold"), - Translations.WT(layer.description)?.Clone() - ]).SetClass("flex flex-col") - - const contentUnselected = new Combine([ - Translations.WT(name).Clone().SetClass("font-bold"), - Translations.WT(layer.description)?.Clone() - ]).SetClass("flex flex-col line-through") - - return new Toggle( - new SubtleButton( - icon, - content), - new SubtleButton( - iconUnset, - contentUnselected - ), - State.state.favouriteLayers.map(favLayers => { - return favLayers.indexOf(layer.id) >= 0 - }, [], (selected, current) => { - if (!selected && current.indexOf(layer.id) <= 0) { - // Not selected and not contained: nothing to change: we return current as is - return current; - } - if (selected && current.indexOf(layer.id) >= 0) { - // Selected and contained: this is fine! - return current; - } - const clone = [...current] - if (selected) { - clone.push(layer.id) - } else { - clone.splice(clone.indexOf(layer.id), 1) - } - return clone - }) - ).ToggleOnClick(); - } - - -} \ No newline at end of file diff --git a/UI/BigComponents/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts index 5dca2cfc2..ca758ab80 100644 --- a/UI/BigComponents/SimpleAddUI.ts +++ b/UI/BigComponents/SimpleAddUI.ts @@ -20,7 +20,7 @@ import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject"; import PresetConfig from "../../Models/ThemeConfig/PresetConfig"; import FilteredLayer from "../../Models/FilteredLayer"; import {And} from "../../Logic/Tags/And"; -import {BBox} from "../../Logic/GeoOperations"; +import {BBox} from "../../Logic/BBox"; /* * The SimpleAddUI is a single panel, which can have multiple states: diff --git a/UI/ExportPDF.ts b/UI/ExportPDF.ts index f1a113daa..54879b9b2 100644 --- a/UI/ExportPDF.ts +++ b/UI/ExportPDF.ts @@ -5,7 +5,6 @@ import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter"; import {UIEventSource} from "../Logic/UIEventSource"; import Minimap from "./Base/Minimap"; import Loc from "../Models/Loc"; -import {BBox} from "../Logic/GeoOperations"; import BaseLayer from "../Models/BaseLayer"; import {FixedUiElement} from "./Base/FixedUiElement"; import Translations from "./i18n/Translations"; @@ -14,6 +13,7 @@ import Constants from "../Models/Constants"; import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; import FeaturePipeline from "../Logic/FeatureSource/FeaturePipeline"; import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"; +import {BBox} from "../Logic/BBox"; /** * Creates screenshoter to take png screenshot * Creates jspdf and downloads it diff --git a/UI/Input/LocationInput.ts b/UI/Input/LocationInput.ts index 0621eccc8..262d9a7db 100644 --- a/UI/Input/LocationInput.ts +++ b/UI/Input/LocationInput.ts @@ -7,11 +7,12 @@ import Combine from "../Base/Combine"; import Svg from "../../Svg"; import State from "../../State"; import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; -import {BBox, GeoOperations} from "../../Logic/GeoOperations"; +import {GeoOperations} from "../../Logic/GeoOperations"; import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; +import {BBox} from "../../Logic/BBox"; export default class LocationInput extends InputElement { diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index b1df0a5fa..17e1bd013 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -5,7 +5,7 @@ import {SubtleButton} from "../Base/SubtleButton"; import Minimap from "../Base/Minimap"; import State from "../../State"; import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; -import {BBox, GeoOperations} from "../../Logic/GeoOperations"; +import {GeoOperations} from "../../Logic/GeoOperations"; import {LeafletMouseEvent} from "leaflet"; import Combine from "../Base/Combine"; import {Button} from "../Base/Button"; @@ -15,6 +15,7 @@ import Title from "../Base/Title"; import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; +import {BBox} from "../../Logic/BBox"; export default class SplitRoadWizard extends Toggle { private static splitLayerStyling = new LayerConfig({ diff --git a/UI/ShowDataLayer/PerTileCountAggregator.ts b/UI/ShowDataLayer/PerTileCountAggregator.ts index e4085f2cd..f313e741f 100644 --- a/UI/ShowDataLayer/PerTileCountAggregator.ts +++ b/UI/ShowDataLayer/PerTileCountAggregator.ts @@ -1,9 +1,9 @@ import FeatureSource, {FeatureSourceForLayer, Tiled} from "../../Logic/FeatureSource/FeatureSource"; -import {BBox} from "../../Logic/GeoOperations"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import {UIEventSource} from "../../Logic/UIEventSource"; import {Tiles} from "../../Models/TileRange"; import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; +import {BBox} from "../../Logic/BBox"; export class TileHierarchyAggregator implements FeatureSource { private _parent: TileHierarchyAggregator; diff --git a/Utils.ts b/Utils.ts index 477598f79..aced008ad 100644 --- a/Utils.ts +++ b/Utils.ts @@ -192,11 +192,12 @@ export class Utils { } /** - * Copies all key-value pairs of the source into the target. + * Copies all key-value pairs of the source into the target. This will change the target * If the key starts with a '+', the values of the list will be appended to the target instead of overwritten * @param source * @param target * @constructor + * @return the second parameter as is */ static Merge(source: any, target: any) { for (const key in source) { @@ -288,15 +289,13 @@ export class Utils { } - private static injectedDownloads = {} public static injectJsonDownloadForTests(url: string, data) { Utils.injectedDownloads[url] = data } - public static downloadJson(url: string, headers?: any): Promise { - + public static download(url: string, headers?: any): Promise { const injected = Utils.injectedDownloads[url] if (injected !== undefined) { console.log("Using injected resource for test for URL", url) @@ -311,17 +310,14 @@ export class Utils { const xhr = new XMLHttpRequest(); xhr.onload = () => { if (xhr.status == 200) { - try { - resolve(JSON.parse(xhr.response)) - } catch (e) { - reject("Not a valid json: " + xhr.response) - } + resolve(xhr.response) + } else if (xhr.status === 509 || xhr.status === 429){ + reject("rate limited") } else { reject(xhr.statusText) } }; xhr.open('GET', url); - xhr.setRequestHeader("accept", "application/json") if (headers !== undefined) { for (const key in headers) { @@ -334,6 +330,11 @@ export class Utils { ) } + public static async downloadJson(url: string, headers?: any): Promise { + const data = await Utils.download(url, Utils.Merge({"accept": "application/json"}, headers ?? {})) + return JSON.parse(data) + } + /** * Triggers a 'download file' popup which will download the contents */ diff --git a/assets/layers/bike_shop/bike_shop.json b/assets/layers/bike_shop/bike_shop.json index 571ff46e2..d7930f854 100644 --- a/assets/layers/bike_shop/bike_shop.json +++ b/assets/layers/bike_shop/bike_shop.json @@ -24,7 +24,7 @@ ] }, { - "#": "if sport is defined and is not bicycle, it is retrackted; if bicycle retail/repair is marked as 'no', it is retracted too.", + "#": "if sport is defined and is not bicycle, it is not matched; if bicycle retail/repair is marked as 'no', it is not shown to too.", "##": "There will be a few false-positives with this. They will get filtered out by people marking both 'not selling bikes' and 'not repairing bikes'. Furthermore, the OSMers will add a sports-subcategory on it", "and": [ "shop=sports", @@ -38,13 +38,6 @@ ] } ] - }, - { - "#": "Any shop with any bicycle service", - "and": [ - "shop~*", - "service:bicycle:.*~~.*" - ] } ] } diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index c8ee81894..da9b3ae90 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -27,7 +27,7 @@ "mappings": [ { "if": "wheelchair=yes", - "then": "./assets/layers/toilet/wheelchair.svg" + "then": "circle:white;./assets/layers/toilet/wheelchair.svg" }, { "if": { diff --git a/assets/layers/toilet/wheelchair.svg b/assets/layers/toilet/wheelchair.svg index 929ea6c40..d113492ad 100644 --- a/assets/layers/toilet/wheelchair.svg +++ b/assets/layers/toilet/wheelchair.svg @@ -2,76 +2,71 @@ image/svg+xml + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + id="defs9" /> + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="999" + id="namedview7" + showgrid="false" + inkscape:zoom="0.8559553" + inkscape:cx="-16.568588" + inkscape:cy="292.29436" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer2" /> - + inkscape:groupmode="layer" + id="layer2" + inkscape:label="background"> + + inkscape:groupmode="layer" + id="layer1" + inkscape:label="wheelchair"> \ No newline at end of file + inkscape:connector-curvature="0" + style="clip-rule:evenodd;fill:#000000;fill-rule:evenodd;stroke-width:0.66635805" + d="m 310.84431,395.24795 c -20.28873,40.10642 -62.75413,66.52908 -108.0502,66.52908 -66.52908,0 -120.790412,-54.26133 -120.790412,-120.79041 0,-46.71209 28.310452,-90.1207 70.555212,-109.36341 l 2.73376,35.67521 c -24.98647,15.74498 -40.3895,44.15435 -40.3895,73.93288 0,48.26215 39.36263,87.62413 87.62413,87.62413 44.15407,0 81.80523,-33.88535 86.93958,-77.35545 z" + id="path4" /> \ No newline at end of file diff --git a/assets/themes/bicyclelib/bicyclelib.json b/assets/themes/bicyclelib/bicyclelib.json index 3a8fb3e8e..c0c85b13b 100644 --- a/assets/themes/bicyclelib/bicyclelib.json +++ b/assets/themes/bicyclelib/bicyclelib.json @@ -43,6 +43,12 @@ "widenFactor": 1.5, "roamingRenderings": [], "layers": [ - "bicycle_library" + { + "builtin": "bicycle_library", + "override": { + "minZoom": 0 + } + }, + ] } \ No newline at end of file diff --git a/assets/themes/personal/personal.json b/assets/themes/personal/personal.json index d9df14a7b..88296088d 100644 --- a/assets/themes/personal/personal.json +++ b/assets/themes/personal/personal.json @@ -12,7 +12,7 @@ "zh_Hant": "個人化主題" }, "description": { - "en": "Create a personal theme based on all the available layers of all themes", + "en": "Create a personal theme based on all the available layers of all themes. Open the layer selection to select one or more layers.", "nl": "Stel je eigen thema samen door lagen te combineren van alle andere themas", "es": "Crea una interficie basada en todas las capas disponibles de todas las interficies", "ca": "Crea una interfície basada en totes les capes disponibles de totes les interfícies", @@ -37,11 +37,14 @@ ], "maintainer": "MapComplete", "icon": "./assets/svg/addSmall.svg", + "clustering": { + "maxZoom": 19 + }, "version": "0", "startLat": 0, "startLon": 0, "startZoom": 16, - "widenFactor": 3, + "widenFactor": 1.2, "layers": [], "roamingRenderings": [] } \ No newline at end of file diff --git a/assets/themes/toerisme_vlaanderen/toerisme_vlaanderen.json b/assets/themes/toerisme_vlaanderen/toerisme_vlaanderen.json index c09b548e8..7bef2f129 100644 --- a/assets/themes/toerisme_vlaanderen/toerisme_vlaanderen.json +++ b/assets/themes/toerisme_vlaanderen/toerisme_vlaanderen.json @@ -20,7 +20,7 @@ "startZoom": 8, "startLat": 50.8536, "startLon": 4.433, - "widenFactor": 2, + "widenFactor": 1.5, "layers": [ { "builtin": [ diff --git a/preferences.ts b/preferences.ts index 310892003..c96912b5d 100644 --- a/preferences.ts +++ b/preferences.ts @@ -10,9 +10,17 @@ import LZString from "lz-string"; import BaseUIElement from "./UI/BaseUIElement"; import Table from "./UI/Base/Table"; import {LayoutConfigJson} from "./Models/ThemeConfig/Json/LayoutConfigJson"; +import {Changes} from "./Logic/Osm/Changes"; +import {ElementStorage} from "./Logic/ElementStorage"; -const connection = new OsmConnection(false, false, new UIEventSource(undefined), ""); +const connection = new OsmConnection({ + osmConfiguration: 'osm', + changes: new Changes(), + layoutName: '', + allElements: new ElementStorage() +}); + let rendered = false; @@ -20,6 +28,7 @@ function salvageThemes(preferences: any) { const knownThemeNames = new Set(); const correctThemeNames = [] for (const key in preferences) { + try{ if (!(typeof key === "string")) { continue; } @@ -36,7 +45,7 @@ function salvageThemes(preferences: any) { } else { knownThemeNames.add(theme); } - } + }catch(e){console.error(e)}} for (const correctThemeName of correctThemeNames) { knownThemeNames.delete(correctThemeName); @@ -65,8 +74,13 @@ function salvageThemes(preferences: any) { try { jsonObject = JSON.parse(atob(combined)); } catch (e) { + try{ + // We try to decode with lz-string jsonObject = JSON.parse(Utils.UnMinify(LZString.decompressFromBase64(combined))) as LayoutConfigJson; + }catch(e0){ + console.log("Could not salvage theme. Initial parsing failed due to:", e,"\nWith LZ failed due ", e0) + } } diff --git a/test.ts b/test.ts index c70e45da5..e758de8cd 100644 --- a/test.ts +++ b/test.ts @@ -1,33 +1,35 @@ -import SplitRoadWizard from "./UI/Popup/SplitRoadWizard"; -import State from "./State"; +import {Tiles} from "./Models/TileRange"; +import OsmFeatureSource from "./Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource"; +import {Utils} from "./Utils"; import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; -import MinimapImplementation from "./UI/Base/MinimapImplementation"; -import {UIEventSource} from "./Logic/UIEventSource"; -import FilteredLayer from "./Models/FilteredLayer"; -import {And} from "./Logic/Tags/And"; -import ShowDataLayer from "./UI/ShowDataLayer/ShowDataLayer"; -import ShowTileInfo from "./UI/ShowDataLayer/ShowTileInfo"; -import StaticFeatureSource from "./Logic/FeatureSource/Sources/StaticFeatureSource"; -import {BBox} from "./Logic/GeoOperations"; -import Minimap from "./UI/Base/Minimap"; +import LayerConfig from "./Models/ThemeConfig/LayerConfig"; -State.state = new State(undefined) +const allLayers: LayerConfig[] = [] +const seenIds = new Set() +for (const layoutConfig of AllKnownLayouts.layoutsList) { + if (layoutConfig.hideFromOverview) { + continue + } + for (const layer of layoutConfig.layers) { + if (seenIds.has(layer.id)) { + continue + } + seenIds.add(layer.id) + allLayers.push(layer) + } +} -const leafletMap = new UIEventSource(undefined) -MinimapImplementation.initialize() -Minimap.createMiniMap({ - leafletMap: leafletMap, -}).SetStyle("height: 600px; width: 600px") - .AttachTo("maindiv") +console.log("All layer ids", allLayers.map(l => l.id)) -const bbox = BBox.fromTile(16,32754,21785).asGeoJson({ - count: 42, - tileId: 42 +const src = new OsmFeatureSource({ + backend: "https://www.openstreetmap.org", + handleTile: tile => console.log("Got tile", tile), + allLayers: allLayers }) - -console.log(bbox) -new ShowDataLayer({ - layerToShow: ShowTileInfo.styling, - leafletMap: leafletMap, - features: new StaticFeatureSource([ bbox], false) -}) \ No newline at end of file +src.LoadTile(16, 33354, 21875).then(geojson => { + console.log("Got geojson", geojson); + Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson), "test.geojson", { + mimetype: "application/vnd.geo+json" + }) +}) +//*/ \ No newline at end of file diff --git a/test/GeoOperations.spec.ts b/test/GeoOperations.spec.ts index 372402648..56e07acaf 100644 --- a/test/GeoOperations.spec.ts +++ b/test/GeoOperations.spec.ts @@ -1,8 +1,9 @@ import {Utils} from "../Utils"; import * as Assert from "assert"; import T from "./TestHelper"; -import {BBox, GeoOperations} from "../Logic/GeoOperations"; +import {GeoOperations} from "../Logic/GeoOperations"; import {equal} from "assert"; +import {BBox} from "../Logic/BBox"; Utils.runningFromConsole = true; diff --git a/test/OsmConnection.spec.ts b/test/OsmConnection.spec.ts index 0b6cf3b47..9d7b114f5 100644 --- a/test/OsmConnection.spec.ts +++ b/test/OsmConnection.spec.ts @@ -2,6 +2,9 @@ import T from "./TestHelper"; import UserDetails, {OsmConnection} from "../Logic/Osm/OsmConnection"; import {UIEventSource} from "../Logic/UIEventSource"; import ScriptUtils from "../scripts/ScriptUtils"; +import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; +import {ElementStorage} from "../Logic/ElementStorage"; +import {Changes} from "../Logic/Osm/Changes"; export default class OsmConnectionSpec extends T { @@ -15,12 +18,14 @@ export default class OsmConnectionSpec extends T { super("osmconnection", [ ["login on dev", () => { - const osmConn = new OsmConnection(false, false, - new UIEventSource(undefined), - "Unit test", - true, - "osm-test" - ) + const osmConn = new OsmConnection({ + osmConfiguration: "osm-test", + layoutName: "Unit test", + allElements: new ElementStorage(), + changes: new Changes(), + oauth_token: new UIEventSource(OsmConnectionSpec._osm_token) + } + ); osmConn.userDetails.map((userdetails: UserDetails) => { if (userdetails.loggedIn) {