forked from MapComplete/MapComplete
		
	More refactoring of the featurepipeline, introduction of fetching data from the OSM-API directly per tile, personal theme refactoring
This commit is contained in:
		
							parent
							
								
									0a9e7c0b36
								
							
						
					
					
						commit
						41a2a79fe9
					
				
					 48 changed files with 746 additions and 590 deletions
				
			
		|  | @ -1,6 +1,7 @@ | ||||||
| import AllKnownLayers from "./AllKnownLayers"; | import AllKnownLayers from "./AllKnownLayers"; | ||||||
| import * as known_themes from "../assets/generated/known_layers_and_themes.json" | import * as known_themes from "../assets/generated/known_layers_and_themes.json" | ||||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | ||||||
|  | import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||||
| 
 | 
 | ||||||
| export class AllKnownLayouts { | export class AllKnownLayouts { | ||||||
| 
 | 
 | ||||||
|  | @ -8,6 +9,26 @@ export class AllKnownLayouts { | ||||||
|     public static allKnownLayouts: Map<string, LayoutConfig> = AllKnownLayouts.AllLayouts(); |     public static allKnownLayouts: Map<string, LayoutConfig> = AllKnownLayouts.AllLayouts(); | ||||||
|     public static layoutsList: LayoutConfig[] = AllKnownLayouts.GenerateOrderedList(AllKnownLayouts.allKnownLayouts); |     public static layoutsList: LayoutConfig[] = AllKnownLayouts.GenerateOrderedList(AllKnownLayouts.allKnownLayouts); | ||||||
| 
 | 
 | ||||||
|  |     public static AllPublicLayers(){ | ||||||
|  |         const allLayers : LayerConfig[] = [] | ||||||
|  |         const seendIds = new Set<string>() | ||||||
|  |         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<string, LayoutConfig>): LayoutConfig[] { |     private static GenerateOrderedList(allKnownLayouts: Map<string, LayoutConfig>): LayoutConfig[] { | ||||||
|         const keys = ["personal", "cyclofix", "hailhydrant", "bookcases", "toilets", "aed"] |         const keys = ["personal", "cyclofix", "hailhydrant", "bookcases", "toilets", "aed"] | ||||||
|         const list = [] |         const list = [] | ||||||
|  |  | ||||||
|  | @ -27,7 +27,6 @@ import MapControlButton from "./UI/MapControlButton"; | ||||||
| import LZString from "lz-string"; | import LZString from "lz-string"; | ||||||
| import AllKnownLayers from "./Customizations/AllKnownLayers"; | import AllKnownLayers from "./Customizations/AllKnownLayers"; | ||||||
| import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; | import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; | ||||||
| import {TagsFilter} from "./Logic/Tags/TagsFilter"; |  | ||||||
| import LeftControls from "./UI/BigComponents/LeftControls"; | import LeftControls from "./UI/BigComponents/LeftControls"; | ||||||
| import RightControls from "./UI/BigComponents/RightControls"; | import RightControls from "./UI/BigComponents/RightControls"; | ||||||
| import {LayoutConfigJson} from "./Models/ThemeConfig/Json/LayoutConfigJson"; | import {LayoutConfigJson} from "./Models/ThemeConfig/Json/LayoutConfigJson"; | ||||||
|  | @ -40,10 +39,10 @@ import {SubtleButton} from "./UI/Base/SubtleButton"; | ||||||
| import ShowTileInfo from "./UI/ShowDataLayer/ShowTileInfo"; | import ShowTileInfo from "./UI/ShowDataLayer/ShowTileInfo"; | ||||||
| import {Tiles} from "./Models/TileRange"; | import {Tiles} from "./Models/TileRange"; | ||||||
| import {TileHierarchyAggregator} from "./UI/ShowDataLayer/PerTileCountAggregator"; | 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 FilterConfig from "./Models/ThemeConfig/FilterConfig"; | ||||||
| import FilteredLayer from "./Models/FilteredLayer"; | import FilteredLayer from "./Models/FilteredLayer"; | ||||||
|  | import {BBox} from "./Logic/BBox"; | ||||||
|  | import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; | ||||||
| 
 | 
 | ||||||
| export class InitUiElements { | export class InitUiElements { | ||||||
|     static InitAll( |     static InitAll( | ||||||
|  | @ -71,8 +70,22 @@ export class InitUiElements { | ||||||
|             layoutFromBase64 |             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); |         State.state = new State(layoutToUse); | ||||||
| 
 | 
 | ||||||
|  |         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
 |             // This 'leaks' the global state via the window object, useful for debugging
 | ||||||
|         // @ts-ignore
 |         // @ts-ignore
 | ||||||
|         window.mapcomplete_state = State.state; |         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<LayerConfig>(); |  | ||||||
| 
 |  | ||||||
|             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) { |         if (layoutToUse.customCss !== undefined) { | ||||||
|             Utils.LoadCustomCss(layoutToUse.customCss); |             Utils.LoadCustomCss(layoutToUse.customCss); | ||||||
|         } |         } | ||||||
|  | @ -206,18 +180,9 @@ export class InitUiElements { | ||||||
|             .addCallbackAndRunD(_ => addHomeMarker()); |             .addCallbackAndRunD(_ => addHomeMarker()); | ||||||
|         State.state.leafletMap.addCallbackAndRunD(_ => addHomeMarker()) |         State.state.leafletMap.addCallbackAndRunD(_ => addHomeMarker()) | ||||||
| 
 | 
 | ||||||
|         if (layoutToUse.id === personal.id) { |  | ||||||
|             updateFavs(); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         InitUiElements.setupAllLayerElements(); |         InitUiElements.setupAllLayerElements(); | ||||||
| 
 |  | ||||||
|         if (layoutToUse.id === personal.id) { |  | ||||||
|             State.state.favouriteLayers.addCallback(updateFavs); |  | ||||||
|             State.state.installedThemes.addCallback(updateFavs); |  | ||||||
|         } else { |  | ||||||
|             State.state.locationControl.ping(); |             State.state.locationControl.ping(); | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         new SelectedFeatureHandler(Hash.hash, State.state) |         new SelectedFeatureHandler(Hash.hash, State.state) | ||||||
| 
 | 
 | ||||||
|  | @ -414,15 +379,29 @@ export class InitUiElements { | ||||||
|             const flayers: FilteredLayer[] = []; |             const flayers: FilteredLayer[] = []; | ||||||
| 
 | 
 | ||||||
|             for (const layer of layoutToUse.layers) { |             for (const layer of layoutToUse.layers) { | ||||||
|                 const isDisplayed = QueryParameters.GetQueryParameter( |                 let defaultShown = "true" | ||||||
|  |                 if(layoutToUse.id === personal.id){ | ||||||
|  |                     defaultShown = "false" | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 let isDisplayed: UIEventSource<boolean> | ||||||
|  |                 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, |                         "layer-" + layer.id, | ||||||
|                     "true", |                         defaultShown, | ||||||
|                         "Wether or not layer " + layer.id + " is shown" |                         "Wether or not layer " + layer.id + " is shown" | ||||||
|                     ).map<boolean>( |                     ).map<boolean>( | ||||||
|                         (str) => str !== "false", |                         (str) => str !== "false", | ||||||
|                         [], |                         [], | ||||||
|                         (b) => b.toString() |                         (b) => b.toString() | ||||||
|                     ); |                     ); | ||||||
|  |                 } | ||||||
|                 const flayer = { |                 const flayer = { | ||||||
|                     isDisplayed: isDisplayed, |                     isDisplayed: isDisplayed, | ||||||
|                     layerDef: layer, |                     layerDef: layer, | ||||||
|  | @ -453,8 +432,6 @@ export class InitUiElements { | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const layers = State.state.layoutToUse.data.layers |  | ||||||
| 
 |  | ||||||
|         const clusterCounter = TileHierarchyAggregator.createHierarchy() |         const clusterCounter = TileHierarchyAggregator.createHierarchy() | ||||||
|         new ShowDataLayer({ |         new ShowDataLayer({ | ||||||
|             features: clusterCounter.getCountsForZoom(State.state.locationControl, State.state.layoutToUse.data.clustering.minNeededElements), |             features: clusterCounter.getCountsForZoom(State.state.locationControl, State.state.layoutToUse.data.clustering.minNeededElements), | ||||||
|  | @ -472,6 +449,10 @@ export class InitUiElements { | ||||||
|                     f => { |                     f => { | ||||||
|                         const z = State.state.locationControl.data.zoom |                         const z = State.state.locationControl.data.zoom | ||||||
|                          |                          | ||||||
|  |                         if(!source.layer.isDisplayed.data){ | ||||||
|  |                             return false; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|                         if (z < source.layer.layerDef.minzoom) { |                         if (z < source.layer.layerDef.minzoom) { | ||||||
|                             // Layer is always hidden for this zoom level
 |                             // Layer is always hidden for this zoom level
 | ||||||
|                             return false; |                             return false; | ||||||
|  | @ -482,7 +463,7 @@ export class InitUiElements { | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                         if (f.length > clustering.minNeededElements) { |                         if (f.length > clustering.minNeededElements) { | ||||||
|                             // This tile alone has too much features
 |                             // This tile alone already has too much features
 | ||||||
|                             return false |                             return false | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|  | @ -504,11 +485,12 @@ export class InitUiElements { | ||||||
|                         const bounds = State.state.currentBounds.data |                         const bounds = State.state.currentBounds.data | ||||||
|                         const tilebbox = BBox.fromTileIndex(source.tileIndex) |                         const tilebbox = BBox.fromTileIndex(source.tileIndex) | ||||||
|                         if (!tilebbox.overlapsWith(bounds)) { |                         if (!tilebbox.overlapsWith(bounds)) { | ||||||
|  |                             // Not within range
 | ||||||
|                             return false |                             return false | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                         return true |                         return true | ||||||
|                     }, [State.state.locationControl, State.state.currentBounds] |                     }, [State.state.currentBounds] | ||||||
|                 ) |                 ) | ||||||
| 
 | 
 | ||||||
|                 new ShowDataLayer( |                 new ShowDataLayer( | ||||||
|  |  | ||||||
|  | @ -9,9 +9,10 @@ import {TagsFilter} from "../Tags/TagsFilter"; | ||||||
| import SimpleMetaTagger from "../SimpleMetaTagger"; | import SimpleMetaTagger from "../SimpleMetaTagger"; | ||||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | ||||||
| import RelationsTracker from "../Osm/RelationsTracker"; | 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" |     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<any[]>(undefined); |     public readonly features: UIEventSource<{ feature: any, freshness: Date }[]> = new UIEventSource<any[]>(undefined); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     public readonly sufficientlyZoomed: UIEventSource<boolean>; |  | ||||||
|     public readonly runningQuery: UIEventSource<boolean> = new UIEventSource<boolean>(false); |     public readonly runningQuery: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||||
|     public readonly timeout: UIEventSource<number> = new UIEventSource<number>(0); |     public readonly timeout: UIEventSource<number> = new UIEventSource<number>(0); | ||||||
| 
 | 
 | ||||||
|  | @ -40,10 +40,12 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour | ||||||
|     private readonly state: { |     private readonly state: { | ||||||
|         readonly locationControl: UIEventSource<Loc>, |         readonly locationControl: UIEventSource<Loc>, | ||||||
|         readonly layoutToUse: UIEventSource<LayoutConfig>, |         readonly layoutToUse: UIEventSource<LayoutConfig>, | ||||||
|         readonly leafletMap: any, |  | ||||||
|         readonly overpassUrl: UIEventSource<string>; |         readonly overpassUrl: UIEventSource<string>; | ||||||
|         readonly overpassTimeout: UIEventSource<number>; |         readonly overpassTimeout: UIEventSource<number>; | ||||||
|  |         readonly currentBounds :UIEventSource<BBox> | ||||||
|     } |     } | ||||||
|  |     private readonly _isActive: UIEventSource<boolean>; | ||||||
|  |     private _onUpdated?: (bbox: BBox, dataFreshness: Date) => void; | ||||||
|     /** |     /** | ||||||
|      * The most important layer should go first, as that one gets first pick for the questions |      * 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: { |         state: { | ||||||
|             readonly locationControl: UIEventSource<Loc>, |             readonly locationControl: UIEventSource<Loc>, | ||||||
|             readonly layoutToUse: UIEventSource<LayoutConfig>, |             readonly layoutToUse: UIEventSource<LayoutConfig>, | ||||||
|             readonly leafletMap: any, |  | ||||||
|             readonly overpassUrl: UIEventSource<string>; |             readonly overpassUrl: UIEventSource<string>; | ||||||
|             readonly overpassTimeout: UIEventSource<number>; |             readonly overpassTimeout: UIEventSource<number>; | ||||||
|             readonly overpassMaxZoom: UIEventSource<number> |             readonly overpassMaxZoom: UIEventSource<number>, | ||||||
|         }) { |             readonly currentBounds :UIEventSource<BBox> | ||||||
|  |         },    | ||||||
|  |          | ||||||
|  |        options?: { | ||||||
|  |             isActive?: UIEventSource<boolean>, | ||||||
|  |            onUpdated?:  (bbox: BBox, freshness: Date) => void, | ||||||
|  |        relationTracker: RelationsTracker}) { | ||||||
| 
 | 
 | ||||||
|         this.state = state |         this.state = state | ||||||
|         this.relationsTracker = new RelationsTracker() |         this._isActive = options.isActive; | ||||||
|  |         this._onUpdated =options. onUpdated; | ||||||
|  |         this.relationsTracker = options.relationTracker | ||||||
|         const location = state.locationControl |         const location = state.locationControl | ||||||
|         const self = this; |         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++) { |         for (let i = 0; i < 25; i++) { | ||||||
|             // This update removes all data on all layers -> erase the map on lower levels too
 |             // This update removes all data on all layers -> erase the map on lower levels too
 | ||||||
|             this._previousBounds.set(i, []); |             this._previousBounds.set(i, []); | ||||||
|  | @ -89,16 +82,11 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour | ||||||
|         location.addCallback(() => { |         location.addCallback(() => { | ||||||
|             self.update() |             self.update() | ||||||
|         }); |         }); | ||||||
|         state.leafletMap.addCallbackAndRunD(_ => { |  | ||||||
|             self.update(); |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|          |          | ||||||
|     public ForceRefresh() { |         state.currentBounds.addCallback(_ => { | ||||||
|         for (let i = 0; i < 25; i++) { |             self.update() | ||||||
|             this._previousBounds.set(i, []); |         }) | ||||||
|         } |         | ||||||
|         this.update(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private GetFilter(): Overpass { |     private GetFilter(): Overpass { | ||||||
|  | @ -152,24 +140,34 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private update() { |     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<void> { |     private async updateAsync(): Promise<[BBox, Date]> { | ||||||
|         if (this.runningQuery.data) { |         if (this.runningQuery.data) { | ||||||
|             console.log("Still running a query, not updating"); |             console.log("Still running a query, not updating"); | ||||||
|             return; |             return undefined; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (this.timeout.data > 0) { |         if (this.timeout.data > 0) { | ||||||
|             console.log("Still in timeout - not updating") |             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) { |         if (bounds === undefined) { | ||||||
|             return; |             return undefined; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const n = Math.min(90, bounds.getNorth()); |         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 w = Math.max(-180, bounds.getWest()); | ||||||
|         const queryBounds = {north: n, east: e, south: s, west: w}; |         const queryBounds = {north: n, east: e, south: s, west: w}; | ||||||
| 
 | 
 | ||||||
|         const z = Math.floor(this.state.locationControl.data.zoom ?? 0); |  | ||||||
| 
 | 
 | ||||||
|         const self = this; |         const self = this; | ||||||
|         const overpass = this.GetFilter(); |         const overpass = this.GetFilter(); | ||||||
| 
 | 
 | ||||||
|         if (overpass === undefined) { |         if (overpass === undefined) { | ||||||
|             return; |             return undefined; | ||||||
|         } |         } | ||||||
|         this.runningQuery.setData(true); |         this.runningQuery.setData(true); | ||||||
| 
 | 
 | ||||||
|  | @ -195,14 +192,13 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour | ||||||
| 
 | 
 | ||||||
|             try { |             try { | ||||||
|                 [data, date] = await overpass.queryGeoJson(queryBounds) |                 [data, date] = await overpass.queryGeoJson(queryBounds) | ||||||
|  |                 console.log("Querying overpass is done", data) | ||||||
|             } catch (e) { |             } catch (e) { | ||||||
|                 console.error(`QUERY FAILED (retrying in ${5 * self.retries.data} sec) due to`, e); |  | ||||||
| 
 |  | ||||||
|                 self.retries.data++; |                 self.retries.data++; | ||||||
|                 self.retries.ping(); |                 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.timeout.setData(self.retries.data * 5); | ||||||
|                 self.runningQuery.setData(false); |  | ||||||
|                  |                  | ||||||
|                 while (self.timeout.data > 0) { |                 while (self.timeout.data > 0) { | ||||||
|                     await Utils.waitFor(1000) |                     await Utils.waitFor(1000) | ||||||
|  | @ -212,16 +208,20 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour | ||||||
|             } |             } | ||||||
|         } while (data === undefined); |         } while (data === undefined); | ||||||
| 
 | 
 | ||||||
|  |         const z = Math.floor(this.state.locationControl.data.zoom ?? 0); | ||||||
|         self._previousBounds.get(z).push(queryBounds); |         self._previousBounds.get(z).push(queryBounds); | ||||||
|         self.retries.setData(0); |         self.retries.setData(0); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date)); |             data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date)); | ||||||
|             self.features.setData(data.features.map(f => ({feature: f, freshness: date}))); |             self.features.setData(data.features.map(f => ({feature: f, freshness: date}))); | ||||||
|  |             return [bounds, date]; | ||||||
|         } catch (e) { |         } catch (e) { | ||||||
|             console.error("Got the overpass response, but could not process it: ", e, e.stack) |             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; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const b = this.state.leafletMap.data.getBounds(); |         const b = this.state.currentBounds.data; | ||||||
|         return b.getSouth() >= bounds.south && |         return b.getSouth() >= bounds.south && | ||||||
|             b.getNorth() <= bounds.north && |             b.getNorth() <= bounds.north && | ||||||
|             b.getEast() <= bounds.east && |             b.getEast() <= bounds.east && | ||||||
|  |  | ||||||
							
								
								
									
										158
									
								
								Logic/BBox.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								Logic/BBox.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| import {UIEventSource} from "./UIEventSource"; | import {UIEventSource} from "./UIEventSource"; | ||||||
| import FeaturePipeline from "./FeatureSource/FeaturePipeline"; | import FeaturePipeline from "./FeatureSource/FeaturePipeline"; | ||||||
| import Loc from "../Models/Loc"; | import Loc from "../Models/Loc"; | ||||||
| import {BBox} from "./GeoOperations"; | import {BBox} from "./BBox"; | ||||||
| 
 | 
 | ||||||
| export default class ContributorCount { | export default class ContributorCount { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {BBox, GeoOperations} from "./GeoOperations"; | import {GeoOperations} from "./GeoOperations"; | ||||||
| import Combine from "../UI/Base/Combine"; | import Combine from "../UI/Base/Combine"; | ||||||
| import RelationsTracker from "./Osm/RelationsTracker"; | import RelationsTracker from "./Osm/RelationsTracker"; | ||||||
| import State from "../State"; | import State from "../State"; | ||||||
|  | @ -7,6 +7,7 @@ import List from "../UI/Base/List"; | ||||||
| import Title from "../UI/Base/Title"; | import Title from "../UI/Base/Title"; | ||||||
| import {UIEventSourceTools} from "./UIEventSource"; | import {UIEventSourceTools} from "./UIEventSource"; | ||||||
| import AspectedRouting from "./Osm/aspectedRouting"; | import AspectedRouting from "./Osm/aspectedRouting"; | ||||||
|  | import {BBox} from "./BBox"; | ||||||
| 
 | 
 | ||||||
| export interface ExtraFuncParams { | export interface ExtraFuncParams { | ||||||
|     /** |     /** | ||||||
|  |  | ||||||
|  | @ -17,18 +17,23 @@ import RegisteringAllFromFeatureSourceActor from "./Actors/RegisteringAllFromFea | ||||||
| import TiledFromLocalStorageSource from "./TiledFeatureSource/TiledFromLocalStorageSource"; | import TiledFromLocalStorageSource from "./TiledFeatureSource/TiledFromLocalStorageSource"; | ||||||
| import SaveTileToLocalStorageActor from "./Actors/SaveTileToLocalStorageActor"; | import SaveTileToLocalStorageActor from "./Actors/SaveTileToLocalStorageActor"; | ||||||
| import DynamicGeoJsonTileSource from "./TiledFeatureSource/DynamicGeoJsonTileSource"; | import DynamicGeoJsonTileSource from "./TiledFeatureSource/DynamicGeoJsonTileSource"; | ||||||
| import {BBox} from "../GeoOperations"; |  | ||||||
| import {TileHierarchyMerger} from "./TiledFeatureSource/TileHierarchyMerger"; | import {TileHierarchyMerger} from "./TiledFeatureSource/TileHierarchyMerger"; | ||||||
| import RelationsTracker from "../Osm/RelationsTracker"; | import RelationsTracker from "../Osm/RelationsTracker"; | ||||||
| import {NewGeometryFromChangesFeatureSource} from "./Sources/NewGeometryFromChangesFeatureSource"; | import {NewGeometryFromChangesFeatureSource} from "./Sources/NewGeometryFromChangesFeatureSource"; | ||||||
| import ChangeGeometryApplicator from "./Sources/ChangeGeometryApplicator"; | 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<boolean>; |     public readonly sufficientlyZoomed: UIEventSource<boolean>; | ||||||
|  |      | ||||||
|     public readonly runningQuery: UIEventSource<boolean>; |     public readonly runningQuery: UIEventSource<boolean>; | ||||||
|     public readonly timeout: UIEventSource<number>; |     public readonly timeout: UIEventSource<number>; | ||||||
|  |      | ||||||
|     public readonly somethingLoaded: UIEventSource<boolean> = new UIEventSource<boolean>(false) |     public readonly somethingLoaded: UIEventSource<boolean> = new UIEventSource<boolean>(false) | ||||||
|     public readonly newDataLoadedSignal: UIEventSource<FeatureSource> = new UIEventSource<FeatureSource>(undefined) |     public readonly newDataLoadedSignal: UIEventSource<FeatureSource> = new UIEventSource<FeatureSource>(undefined) | ||||||
| 
 | 
 | ||||||
|  | @ -39,24 +44,56 @@ export default class FeaturePipeline implements FeatureSourceState { | ||||||
|     constructor( |     constructor( | ||||||
|         handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void, |         handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void, | ||||||
|         state: { |         state: { | ||||||
|             filteredLayers: UIEventSource<FilteredLayer[]>, |             readonly filteredLayers: UIEventSource<FilteredLayer[]>, | ||||||
|             locationControl: UIEventSource<Loc>, |             readonly locationControl: UIEventSource<Loc>, | ||||||
|             selectedElement: UIEventSource<any>, |             readonly selectedElement: UIEventSource<any>, | ||||||
|             changes: Changes, |             readonly changes: Changes, | ||||||
|             layoutToUse: UIEventSource<LayoutConfig>, |             readonly  layoutToUse: UIEventSource<LayoutConfig>, | ||||||
|             leafletMap: any, |             readonly leafletMap: any, | ||||||
|             readonly overpassUrl: UIEventSource<string>; |             readonly overpassUrl: UIEventSource<string>; | ||||||
|             readonly overpassTimeout: UIEventSource<number>; |             readonly overpassTimeout: UIEventSource<number>; | ||||||
|             readonly overpassMaxZoom: UIEventSource<number>; |             readonly overpassMaxZoom: UIEventSource<number>; | ||||||
|  |             readonly osmConnection: OsmConnection | ||||||
|  |             readonly currentBounds: UIEventSource<BBox> | ||||||
|         }) { |         }) { | ||||||
| 
 | 
 | ||||||
|         const self = this |         const self = this | ||||||
|         const updater = new OverpassFeatureSource(state); | 
 | ||||||
|  |         /** | ||||||
|  |          * Maps tileid onto last download moment | ||||||
|  |          */ | ||||||
|  |         const tileFreshnesses = new Map<number, Date>() | ||||||
|  |         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.overpassUpdater = updater; | ||||||
|         this.sufficientlyZoomed = updater.sufficientlyZoomed |         this.sufficientlyZoomed = state.locationControl.map(location => { | ||||||
|         this.runningQuery = updater.runningQuery |                 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.timeout = updater.timeout | ||||||
|         this.relationTracker = updater.relationsTracker |          | ||||||
|  |          | ||||||
|         // Register everything in the state' 'AllElements'
 |         // Register everything in the state' 'AllElements'
 | ||||||
|         new RegisteringAllFromFeatureSourceActor(updater) |         new RegisteringAllFromFeatureSourceActor(updater) | ||||||
| 
 | 
 | ||||||
|  | @ -81,6 +118,7 @@ export default class FeaturePipeline implements FeatureSourceState { | ||||||
|             perLayerHierarchy.get(layerId).registerTile(src) |             perLayerHierarchy.get(layerId).registerTile(src) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|         for (const filteredLayer of state.filteredLayers.data) { |         for (const filteredLayer of state.filteredLayers.data) { | ||||||
|             const hierarchy = new TileHierarchyMerger(filteredLayer, (tile, _) => patchedHandleFeatureSource(tile)) |             const hierarchy = new TileHierarchyMerger(filteredLayer, (tile, _) => patchedHandleFeatureSource(tile)) | ||||||
|             const id = filteredLayer.layerDef.id |             const id = filteredLayer.layerDef.id | ||||||
|  | @ -91,12 +129,25 @@ export default class FeaturePipeline implements FeatureSourceState { | ||||||
|                 // This is an OSM layer
 |                 // This is an OSM layer
 | ||||||
|                 // We load the cached values and register them
 |                 // We load the cached values and register them
 | ||||||
|                 // Getting data from upstream happens a bit lower
 |                 // Getting data from upstream happens a bit lower
 | ||||||
|                 new TiledFromLocalStorageSource(filteredLayer, |                 const localStorage = new TiledFromLocalStorageSource(filteredLayer, | ||||||
|                     (src) => { |                     (src) => { | ||||||
|                         new RegisteringAllFromFeatureSourceActor(src) |                         new RegisteringAllFromFeatureSourceActor(src) | ||||||
|                         hierarchy.registerTile(src); |                         hierarchy.registerTile(src); | ||||||
|                         src.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(src)) |                         src.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(src)) | ||||||
|                     }, state) |                     }, 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 |                 continue | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | @ -125,9 +176,47 @@ export default class FeaturePipeline implements FeatureSourceState { | ||||||
|                     state |                     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
 |         // Actually load data from the overpass source
 | ||||||
|         new PerLayerFeatureSourceSplitter(state.filteredLayers, |         new PerLayerFeatureSourceSplitter(state.filteredLayers, | ||||||
|             (source) => TiledFeatureSource.createHierarchy(source, { |             (source) => TiledFeatureSource.createHierarchy(source, { | ||||||
|  | @ -169,6 +258,12 @@ export default class FeaturePipeline implements FeatureSourceState { | ||||||
|             self.updateAllMetaTagging() |             self.updateAllMetaTagging() | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |         this.runningQuery = updater.runningQuery.map( | ||||||
|  |             overpass => overpass || osmFeatureSource.isRunning.data, [osmFeatureSource.isRunning] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private applyMetaTags(src: FeatureSourceForLayer) { |     private applyMetaTags(src: FeatureSourceForLayer) { | ||||||
|  | @ -231,7 +326,4 @@ export default class FeaturePipeline implements FeatureSourceState { | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public ForceRefresh() { |  | ||||||
|         this.overpassUpdater.ForceRefresh() |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {UIEventSource} from "../UIEventSource"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import FilteredLayer from "../../Models/FilteredLayer"; | import FilteredLayer from "../../Models/FilteredLayer"; | ||||||
| import {BBox} from "../GeoOperations"; | import {BBox} from "../BBox"; | ||||||
| 
 | 
 | ||||||
| export default interface FeatureSource { | export default interface FeatureSource { | ||||||
|     features: UIEventSource<{ feature: any, freshness: Date }[]>; |     features: UIEventSource<{ feature: any, freshness: Date }[]>; | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ export default class PerLayerFeatureSourceSplitter { | ||||||
|                 handleLayerData: (source: FeatureSourceForLayer & Tiled) => void, |                 handleLayerData: (source: FeatureSourceForLayer & Tiled) => void, | ||||||
|                 upstream: FeatureSource, |                 upstream: FeatureSource, | ||||||
|                 options?:{ |                 options?:{ | ||||||
|  |         tileIndex?: number, | ||||||
|         handleLeftovers?: (featuresWithoutLayer: any[]) => void |         handleLeftovers?: (featuresWithoutLayer: any[]) => void | ||||||
|                 }) { |                 }) { | ||||||
| 
 | 
 | ||||||
|  | @ -71,7 +72,7 @@ export default class PerLayerFeatureSourceSplitter { | ||||||
|                 let featureSource = knownLayers.get(id) |                 let featureSource = knownLayers.get(id) | ||||||
|                 if (featureSource === undefined) { |                 if (featureSource === undefined) { | ||||||
|                     // Not yet initialized - now is a good time
 |                     // Not yet initialized - now is a good time
 | ||||||
|                     featureSource = new SimpleFeatureSource(layer) |                     featureSource = new SimpleFeatureSource(layer, options?.tileIndex) | ||||||
|                     featureSource.features.setData(features) |                     featureSource.features.setData(features) | ||||||
|                     knownLayers.set(id, featureSource) |                     knownLayers.set(id, featureSource) | ||||||
|                     handleLayerData(featureSource) |                     handleLayerData(featureSource) | ||||||
|  |  | ||||||
|  | @ -5,9 +5,9 @@ | ||||||
| import {UIEventSource} from "../../UIEventSource"; | import {UIEventSource} from "../../UIEventSource"; | ||||||
| import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource"; | import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource"; | ||||||
| import FilteredLayer from "../../../Models/FilteredLayer"; | import FilteredLayer from "../../../Models/FilteredLayer"; | ||||||
| import {BBox} from "../../GeoOperations"; |  | ||||||
| import {Utils} from "../../../Utils"; | import {Utils} from "../../../Utils"; | ||||||
| import {Tiles} from "../../../Models/TileRange"; | import {Tiles} from "../../../Models/TileRange"; | ||||||
|  | import {BBox} from "../../BBox"; | ||||||
| 
 | 
 | ||||||
| export default class FeatureSourceMerger implements FeatureSourceForLayer, Tiled, IndexedFeatureSource { | export default class FeatureSourceMerger implements FeatureSourceForLayer, Tiled, IndexedFeatureSource { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; | ||||||
| import FilteredLayer from "../../../Models/FilteredLayer"; | import FilteredLayer from "../../../Models/FilteredLayer"; | ||||||
| import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; | import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; | ||||||
| import Hash from "../../Web/Hash"; | import Hash from "../../Web/Hash"; | ||||||
| import {BBox} from "../../GeoOperations"; | import {BBox} from "../../BBox"; | ||||||
| 
 | 
 | ||||||
| export default class FilteringFeatureSource implements FeatureSourceForLayer, Tiled { | export default class FilteringFeatureSource implements FeatureSourceForLayer, Tiled { | ||||||
|     public features: UIEventSource<{ feature: any; freshness: Date }[]> = |     public features: UIEventSource<{ feature: any; freshness: Date }[]> = | ||||||
|  |  | ||||||
|  | @ -5,8 +5,8 @@ import {UIEventSource} from "../../UIEventSource"; | ||||||
| import FilteredLayer from "../../../Models/FilteredLayer"; | import FilteredLayer from "../../../Models/FilteredLayer"; | ||||||
| import {Utils} from "../../../Utils"; | import {Utils} from "../../../Utils"; | ||||||
| import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; | import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; | ||||||
| import {BBox} from "../../GeoOperations"; |  | ||||||
| import {Tiles} from "../../../Models/TileRange"; | import {Tiles} from "../../../Models/TileRange"; | ||||||
|  | import {BBox} from "../../BBox"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export default class GeoJsonSource implements FeatureSourceForLayer, Tiled { | export default class GeoJsonSource implements FeatureSourceForLayer, Tiled { | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
|  */ |  */ | ||||||
| import FeatureSource, {Tiled} from "../FeatureSource"; | import FeatureSource, {Tiled} from "../FeatureSource"; | ||||||
| import {UIEventSource} from "../../UIEventSource"; | import {UIEventSource} from "../../UIEventSource"; | ||||||
| import {BBox} from "../../GeoOperations"; | import {BBox} from "../../BBox"; | ||||||
| 
 | 
 | ||||||
| export default class RememberingSource implements FeatureSource , Tiled{ | export default class RememberingSource implements FeatureSource , Tiled{ | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,20 +1,22 @@ | ||||||
| import {UIEventSource} from "../../UIEventSource"; | import {UIEventSource} from "../../UIEventSource"; | ||||||
| import FilteredLayer from "../../../Models/FilteredLayer"; | import FilteredLayer from "../../../Models/FilteredLayer"; | ||||||
| import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; | import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; | ||||||
| import {BBox} from "../../GeoOperations"; |  | ||||||
| import {Utils} from "../../../Utils"; | import {Utils} from "../../../Utils"; | ||||||
| import {Tiles} from "../../../Models/TileRange"; | import {Tiles} from "../../../Models/TileRange"; | ||||||
|  | import {BBox} from "../../BBox"; | ||||||
| 
 | 
 | ||||||
| export default class SimpleFeatureSource implements FeatureSourceForLayer, Tiled { | export default class SimpleFeatureSource implements FeatureSourceForLayer, Tiled { | ||||||
|     public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]); |     public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]); | ||||||
|     public readonly name: string = "SimpleFeatureSource"; |     public readonly name: string = "SimpleFeatureSource"; | ||||||
|     public readonly layer: FilteredLayer; |     public readonly layer: FilteredLayer; | ||||||
|     public readonly bbox: BBox = BBox.global; |     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.name = "SimpleFeatureSource(" + layer.layerDef.id + ")" | ||||||
|         this.layer = layer |         this.layer = layer | ||||||
|  |         this.tileIndex = tileIndex ?? 0; | ||||||
|  |         this.bbox = BBox.fromTileIndex(this.tileIndex) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
							
								
								
									
										112
									
								
								Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<boolean> = new UIEventSource<boolean>(false) | ||||||
|  |     private readonly filteredLayers: UIEventSource<FilteredLayer[]>; | ||||||
|  |     private readonly handleTile: (fs: (FeatureSourceForLayer & Tiled)) => void; | ||||||
|  |     private isActive: UIEventSource<boolean>; | ||||||
|  |     private options: { | ||||||
|  |         handleTile: (tile: FeatureSourceForLayer & Tiled) => void; | ||||||
|  |         isActive: UIEventSource<boolean>, | ||||||
|  |         neededTiles: UIEventSource<number[]>, | ||||||
|  |         state: { | ||||||
|  |             readonly osmConnection: OsmConnection; | ||||||
|  |         }; | ||||||
|  |     }; | ||||||
|  |     private readonly downloadedTiles = new Set<number>() | ||||||
|  | 
 | ||||||
|  |     constructor(options: { | ||||||
|  |         handleTile: (tile: FeatureSourceForLayer & Tiled) => void; | ||||||
|  |         isActive: UIEventSource<boolean>, | ||||||
|  |         neededTiles: UIEventSource<number[]>, | ||||||
|  |         state: { | ||||||
|  |             readonly filteredLayers: UIEventSource<FilteredLayer[]>; | ||||||
|  |             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<void> { | ||||||
|  |         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; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import FeatureSource, {Tiled} from "../FeatureSource"; | import FeatureSource, {Tiled} from "../FeatureSource"; | ||||||
| import {BBox} from "../../GeoOperations"; | import {BBox} from "../../BBox"; | ||||||
| 
 | 
 | ||||||
| export default interface TileHierarchy<T extends FeatureSource & Tiled> { | export default interface TileHierarchy<T extends FeatureSource & Tiled> { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,9 +3,9 @@ import {UIEventSource} from "../../UIEventSource"; | ||||||
| import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource"; | import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource"; | ||||||
| import FilteredLayer from "../../../Models/FilteredLayer"; | import FilteredLayer from "../../../Models/FilteredLayer"; | ||||||
| import {Utils} from "../../../Utils"; | import {Utils} from "../../../Utils"; | ||||||
| import {BBox} from "../../GeoOperations"; |  | ||||||
| import FeatureSourceMerger from "../Sources/FeatureSourceMerger"; | import FeatureSourceMerger from "../Sources/FeatureSourceMerger"; | ||||||
| import {Tiles} from "../../../Models/TileRange"; | import {Tiles} from "../../../Models/TileRange"; | ||||||
|  | import {BBox} from "../../BBox"; | ||||||
| 
 | 
 | ||||||
| export class TileHierarchyMerger implements TileHierarchy<FeatureSourceForLayer & Tiled> { | export class TileHierarchyMerger implements TileHierarchy<FeatureSourceForLayer & Tiled> { | ||||||
|     public readonly loadedTiles: Map<number, FeatureSourceForLayer & Tiled> = new Map<number, FeatureSourceForLayer & Tiled>(); |     public readonly loadedTiles: Map<number, FeatureSourceForLayer & Tiled> = new Map<number, FeatureSourceForLayer & Tiled>(); | ||||||
|  |  | ||||||
|  | @ -1,10 +1,10 @@ | ||||||
| import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource"; | import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource"; | ||||||
| import {UIEventSource} from "../../UIEventSource"; | import {UIEventSource} from "../../UIEventSource"; | ||||||
| import {Utils} from "../../../Utils"; | import {Utils} from "../../../Utils"; | ||||||
| import {BBox} from "../../GeoOperations"; |  | ||||||
| import FilteredLayer from "../../../Models/FilteredLayer"; | import FilteredLayer from "../../../Models/FilteredLayer"; | ||||||
| import TileHierarchy from "./TileHierarchy"; | import TileHierarchy from "./TileHierarchy"; | ||||||
| import {Tiles} from "../../../Models/TileRange"; | import {Tiles} from "../../../Models/TileRange"; | ||||||
|  | import {BBox} from "../../BBox"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Contains all features in a tiled fashion. |  * Contains all features in a tiled fashion. | ||||||
|  | @ -109,7 +109,6 @@ export default class TiledFeatureSource implements Tiled, IndexedFeatureSource, | ||||||
|         // To much features - we split
 |         // To much features - we split
 | ||||||
|         return featureCount > this.maxFeatureCount |         return featureCount > this.maxFeatureCount | ||||||
|          |          | ||||||
|          |  | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     /*** |     /*** | ||||||
|  | @ -143,7 +142,20 @@ export default class TiledFeatureSource implements Tiled, IndexedFeatureSource, | ||||||
| 
 | 
 | ||||||
|         for (const feature of features) { |         for (const feature of features) { | ||||||
|             const bbox = BBox.get(feature.feature) |             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)) { |                 if (bbox.isContainedIn(this.upper_left.bbox)) { | ||||||
|                     ulf.push(feature) |                     ulf.push(feature) | ||||||
|                 } else if (bbox.isContainedIn(this.upper_right.bbox)) { |                 } else if (bbox.isContainedIn(this.upper_right.bbox)) { | ||||||
|  |  | ||||||
|  | @ -5,11 +5,12 @@ import Loc from "../../../Models/Loc"; | ||||||
| import TileHierarchy from "./TileHierarchy"; | import TileHierarchy from "./TileHierarchy"; | ||||||
| import {Utils} from "../../../Utils"; | import {Utils} from "../../../Utils"; | ||||||
| import SaveTileToLocalStorageActor from "../Actors/SaveTileToLocalStorageActor"; | import SaveTileToLocalStorageActor from "../Actors/SaveTileToLocalStorageActor"; | ||||||
| import {BBox} from "../../GeoOperations"; |  | ||||||
| import {Tiles} from "../../../Models/TileRange"; | import {Tiles} from "../../../Models/TileRange"; | ||||||
|  | import {BBox} from "../../BBox"; | ||||||
| 
 | 
 | ||||||
| export default class TiledFromLocalStorageSource implements TileHierarchy<FeatureSourceForLayer & Tiled> { | export default class TiledFromLocalStorageSource implements TileHierarchy<FeatureSourceForLayer & Tiled> { | ||||||
|     public loadedTiles: Map<number, FeatureSourceForLayer & Tiled> = new Map<number, FeatureSourceForLayer & Tiled>(); |     public loadedTiles: Map<number, FeatureSourceForLayer & Tiled> = new Map<number, FeatureSourceForLayer & Tiled>(); | ||||||
|  | public tileFreshness : Map<number, Date> = new Map<number, Date>() | ||||||
|      |      | ||||||
|     constructor(layer: FilteredLayer, |     constructor(layer: FilteredLayer, | ||||||
|                 handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void, |                 handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void, | ||||||
|  | @ -29,7 +30,14 @@ export default class TiledFromLocalStorageSource implements TileHierarchy<Featur | ||||||
|                 return Number(key.substring(prefix.length)); |                 return Number(key.substring(prefix.length)); | ||||||
|             }) |             }) | ||||||
| 
 | 
 | ||||||
|         console.log("Layer", layer.layerDef.id, "has following tiles in available in localstorage", indexes.map(i => 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 zLevels = indexes.map(i => i % 100) | ||||||
|         const indexesSet = new Set(indexes) |         const indexesSet = new Set(indexes) | ||||||
|  | @ -72,7 +80,7 @@ export default class TiledFromLocalStorageSource implements TileHierarchy<Featur | ||||||
|             } |             } | ||||||
|             , [layer.isDisplayed, state.leafletMap]).stabilized(50); |             , [layer.isDisplayed, state.leafletMap]).stabilized(50); | ||||||
| 
 | 
 | ||||||
|         neededTiles.addCallbackAndRun(t => console.log("Tiles to load from localstorage:", t)) |         neededTiles.addCallbackAndRun(t => console.debug("Tiles to load from localstorage:", t)) | ||||||
| 
 | 
 | ||||||
|         neededTiles.addCallbackAndRunD(neededIndexes => { |         neededTiles.addCallbackAndRunD(neededIndexes => { | ||||||
|             for (const neededIndex of neededIndexes) { |             for (const neededIndex of neededIndexes) { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| import * as turf from '@turf/turf' | import * as turf from '@turf/turf' | ||||||
| import {Utils} from "../Utils"; | import {BBox} from "./BBox"; | ||||||
| import {Tiles} from "../Models/TileRange"; |  | ||||||
| 
 | 
 | ||||||
| export class GeoOperations { | 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], |  | ||||||
| 
 |  | ||||||
|                 ]] |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -62,26 +62,26 @@ export class OsmConnection { | ||||||
|     }; |     }; | ||||||
|     private isChecking = false; |     private isChecking = false; | ||||||
| 
 | 
 | ||||||
|     constructor(dryRun: boolean, |     constructor(options:{dryRun?: false | boolean, | ||||||
|                 fakeUser: boolean, |                 fakeUser?: false | boolean, | ||||||
|                 allElements: ElementStorage, |                 allElements: ElementStorage, | ||||||
|                 changes: Changes, |                 changes: Changes, | ||||||
|                 oauth_token: UIEventSource<string>, |                 oauth_token?: UIEventSource<string>, | ||||||
|                 // Used to keep multiple changesets open and to write to the correct changeset
 |                 // Used to keep multiple changesets open and to write to the correct changeset
 | ||||||
|                 layoutName: string, |                 layoutName: string, | ||||||
|                 singlePage: boolean = true, |                 singlePage?: boolean, | ||||||
|                 osmConfiguration: "osm" | "osm-test" = 'osm' |                 osmConfiguration?: "osm" | "osm-test" } | ||||||
|     ) { |     ) { | ||||||
|         this.fakeUser = fakeUser; |         this.fakeUser = options.fakeUser ?? false; | ||||||
|         this._singlePage = singlePage; |         this._singlePage = options.singlePage ?? true; | ||||||
|         this._oauth_config = OsmConnection.oauth_configs[osmConfiguration] ?? OsmConnection.oauth_configs.osm; |         this._oauth_config = OsmConnection.oauth_configs[options.osmConfiguration ?? 'osm'] ?? OsmConnection.oauth_configs.osm; | ||||||
|         console.debug("Using backend", this._oauth_config.url) |         console.debug("Using backend", this._oauth_config.url) | ||||||
|         OsmObject.SetBackendUrl(this._oauth_config.url + "/") |         OsmObject.SetBackendUrl(this._oauth_config.url + "/") | ||||||
|         this._iframeMode = Utils.runningFromConsole ? false : window !== window.top; |         this._iframeMode = Utils.runningFromConsole ? false : window !== window.top; | ||||||
| 
 | 
 | ||||||
|         this.userDetails = new UIEventSource<UserDetails>(new UserDetails(this._oauth_config.url), "userDetails"); |         this.userDetails = new UIEventSource<UserDetails>(new UserDetails(this._oauth_config.url), "userDetails"); | ||||||
|         this.userDetails.data.dryRun = dryRun || fakeUser; |         this.userDetails.data.dryRun = (options.dryRun ?? false) || (options.fakeUser ?? false) ; | ||||||
|         if (fakeUser) { |         if (options.fakeUser) { | ||||||
|             const ud = this.userDetails.data; |             const ud = this.userDetails.data; | ||||||
|             ud.csCount = 5678 |             ud.csCount = 5678 | ||||||
|             ud.loggedIn = true; |             ud.loggedIn = true; | ||||||
|  | @ -98,23 +98,23 @@ export class OsmConnection { | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         this.isLoggedIn.addCallbackAndRunD(li => console.log("User is logged in!", li)) |         this.isLoggedIn.addCallbackAndRunD(li => console.log("User is logged in!", li)) | ||||||
|         this._dryRun = dryRun; |         this._dryRun = options.dryRun; | ||||||
| 
 | 
 | ||||||
|         this.updateAuthObject(); |         this.updateAuthObject(); | ||||||
| 
 | 
 | ||||||
|         this.preferencesHandler = new OsmPreferences(this.auth, this); |         this.preferencesHandler = new OsmPreferences(this.auth, this); | ||||||
| 
 | 
 | ||||||
|         this.changesetHandler = new ChangesetHandler(layoutName, dryRun, this, allElements, changes, this.auth); |         this.changesetHandler = new ChangesetHandler(options.layoutName, options.dryRun, this, options.allElements, options.changes, this.auth); | ||||||
|         if (oauth_token.data !== undefined) { |         if (options.oauth_token?.data !== undefined) { | ||||||
|             console.log(oauth_token.data) |             console.log(options.oauth_token.data) | ||||||
|             const self = this; |             const self = this; | ||||||
|             this.auth.bootstrapToken(oauth_token.data, |             this.auth.bootstrapToken(options.oauth_token.data, | ||||||
|                 (x) => { |                 (x) => { | ||||||
|                     console.log("Called back: ", x) |                     console.log("Called back: ", x) | ||||||
|                     self.AttemptLogin(); |                     self.AttemptLogin(); | ||||||
|                 }, this.auth); |                 }, this.auth); | ||||||
| 
 | 
 | ||||||
|             oauth_token.setData(undefined); |             options.   oauth_token.setData(undefined); | ||||||
| 
 | 
 | ||||||
|         } |         } | ||||||
|         if (this.auth.authenticated()) { |         if (this.auth.authenticated()) { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import * as polygon_features from "../../assets/polygon-features.json"; | import * as polygon_features from "../../assets/polygon-features.json"; | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {UIEventSource} from "../UIEventSource"; | ||||||
| import {BBox} from "../GeoOperations"; | import {BBox} from "../BBox"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export abstract class OsmObject { | export abstract class OsmObject { | ||||||
|  |  | ||||||
|  | @ -41,10 +41,13 @@ export class Overpass { | ||||||
|         } |         } | ||||||
|         const self = this; |         const self = this; | ||||||
|         const json = await Utils.downloadJson(query) |         const json = await Utils.downloadJson(query) | ||||||
|          |         console.log("Got json!", json) | ||||||
|         if (json.elements === [] && ((json.remarks ?? json.remark).indexOf("runtime error") >= 0)) { |         if (json.elements.length === 0 && json.remark !== undefined) { | ||||||
|             console.log("Timeout or other runtime error"); |             console.warn("Timeout or other runtime error while querying overpass", json.remark); | ||||||
|             throw("Runtime error (timeout)") |             throw `Runtime error (timeout or similar)${json.remark}` | ||||||
|  |         } | ||||||
|  |         if(json.elements.length === 0){ | ||||||
|  |          console.warn("No features for" ,json)    | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         self._relationTracker.RegisterRelations(json) |         self._relationTracker.RegisterRelations(json) | ||||||
|  |  | ||||||
							
								
								
									
										24
									
								
								State.ts
									
										
									
									
									
								
							
							
						
						
									
										24
									
								
								State.ts
									
										
									
									
									
								
							|  | @ -17,7 +17,7 @@ import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline"; | ||||||
| import FilteredLayer from "./Models/FilteredLayer"; | import FilteredLayer from "./Models/FilteredLayer"; | ||||||
| import ChangeToElementsActor from "./Logic/Actors/ChangeToElementsActor"; | import ChangeToElementsActor from "./Logic/Actors/ChangeToElementsActor"; | ||||||
| import LayoutConfig from "./Models/ThemeConfig/LayoutConfig"; | 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 |  * Contains the global state: a bunch of UI-event sources | ||||||
|  | @ -83,7 +83,7 @@ export default class State { | ||||||
|     public readonly featureSwitchExportAsPdf: UIEventSource<boolean>; |     public readonly featureSwitchExportAsPdf: UIEventSource<boolean>; | ||||||
|     public readonly overpassUrl: UIEventSource<string>; |     public readonly overpassUrl: UIEventSource<string>; | ||||||
|     public readonly overpassTimeout: UIEventSource<number>; |     public readonly overpassTimeout: UIEventSource<number>; | ||||||
|     public readonly overpassMaxZoom: UIEventSource<number> = new UIEventSource<number>(undefined); |     public readonly overpassMaxZoom: UIEventSource<number> = new UIEventSource<number>(20); | ||||||
| 
 | 
 | ||||||
|     public featurePipeline: FeaturePipeline; |     public featurePipeline: FeaturePipeline; | ||||||
| 
 | 
 | ||||||
|  | @ -372,21 +372,19 @@ export default class State { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.osmConnection = new OsmConnection( |         this.osmConnection = new OsmConnection({ | ||||||
|             this.featureSwitchIsTesting.data, |             changes: this.changes, | ||||||
|             this.featureSwitchFakeUser.data, |             dryRun: this.featureSwitchIsTesting.data, | ||||||
|             this.allElements,  |             fakeUser: this.featureSwitchFakeUser.data, | ||||||
|             this.changes, |             allElements: this.allElements, | ||||||
|             QueryParameters.GetQueryParameter( |             oauth_token: QueryParameters.GetQueryParameter( | ||||||
|                 "oauth_token", |                 "oauth_token", | ||||||
|                 undefined, |                 undefined, | ||||||
|                 "Used to complete the login" |                 "Used to complete the login" | ||||||
|             ), |             ), | ||||||
|             layoutToUse?.id, |             layoutName: layoutToUse?.id, | ||||||
|             true, |             osmConfiguration: <'osm' | 'osm-test'>this.featureSwitchApiURL.data | ||||||
|             // @ts-ignore
 |         }) | ||||||
|             this.featureSwitchApiURL.data |  | ||||||
|         ); |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         new ChangeToElementsActor(this.changes, this.allElements) |         new ChangeToElementsActor(this.changes, this.allElements) | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import Loc from "../../Models/Loc"; | import Loc from "../../Models/Loc"; | ||||||
| import BaseLayer from "../../Models/BaseLayer"; | import BaseLayer from "../../Models/BaseLayer"; | ||||||
| import {BBox} from "../../Logic/GeoOperations"; |  | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
|  | import {BBox} from "../../Logic/BBox"; | ||||||
| 
 | 
 | ||||||
| export interface MinimapOptions { | export interface MinimapOptions { | ||||||
|     background?: UIEventSource<BaseLayer>, |     background?: UIEventSource<BaseLayer>, | ||||||
|  |  | ||||||
|  | @ -4,10 +4,10 @@ import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Loc from "../../Models/Loc"; | import Loc from "../../Models/Loc"; | ||||||
| import BaseLayer from "../../Models/BaseLayer"; | import BaseLayer from "../../Models/BaseLayer"; | ||||||
| import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | ||||||
| import {BBox} from "../../Logic/GeoOperations"; |  | ||||||
| import * as L from "leaflet"; | import * as L from "leaflet"; | ||||||
| import {Map} from "leaflet"; | import {Map} from "leaflet"; | ||||||
| import Minimap, {MinimapObj, MinimapOptions} from "./Minimap"; | import Minimap, {MinimapObj, MinimapOptions} from "./Minimap"; | ||||||
|  | import {BBox} from "../../Logic/BBox"; | ||||||
| 
 | 
 | ||||||
| export default class MinimapImplementation extends BaseUIElement implements MinimapObj { | export default class MinimapImplementation extends BaseUIElement implements MinimapObj { | ||||||
|     private static _nextId = 0; |     private static _nextId = 0; | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ import Constants from "../../Models/Constants"; | ||||||
| import Loc from "../../Models/Loc"; | import Loc from "../../Models/Loc"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | 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 |  * The bottom right attribution panel in the leaflet map | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import State from "../../State"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import CheckBoxes from "../Input/Checkboxes"; | import CheckBoxes from "../Input/Checkboxes"; | ||||||
| import {BBox, GeoOperations} from "../../Logic/GeoOperations"; | import {GeoOperations} from "../../Logic/GeoOperations"; | ||||||
| import Toggle from "../Input/Toggle"; | import Toggle from "../Input/Toggle"; | ||||||
| import Title from "../Base/Title"; | import Title from "../Base/Title"; | ||||||
| import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; | import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; | ||||||
|  | @ -13,6 +13,7 @@ import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import SimpleMetaTagger from "../../Logic/SimpleMetaTagger"; | import SimpleMetaTagger from "../../Logic/SimpleMetaTagger"; | ||||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | ||||||
| import {meta} from "@turf/turf"; | import {meta} from "@turf/turf"; | ||||||
|  | import {BBox} from "../../Logic/BBox"; | ||||||
| 
 | 
 | ||||||
| export class DownloadPanel extends Toggle { | export class DownloadPanel extends Toggle { | ||||||
|      |      | ||||||
|  |  | ||||||
|  | @ -1,7 +1,5 @@ | ||||||
| import State from "../../State"; | import State from "../../State"; | ||||||
| import ThemeIntroductionPanel from "./ThemeIntroductionPanel"; | import ThemeIntroductionPanel from "./ThemeIntroductionPanel"; | ||||||
| import * as personal from "../../assets/themes/personal/personal.json"; |  | ||||||
| import PersonalLayersPanel from "./PersonalLayersPanel"; |  | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import ShareScreen from "./ShareScreen"; | import ShareScreen from "./ShareScreen"; | ||||||
|  | @ -32,9 +30,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { | ||||||
|     private static ConstructBaseTabs(layoutToUse: LayoutConfig, isShown: UIEventSource<boolean>): { header: string | BaseUIElement; content: BaseUIElement }[] { |     private static ConstructBaseTabs(layoutToUse: LayoutConfig, isShown: UIEventSource<boolean>): { header: string | BaseUIElement; content: BaseUIElement }[] { | ||||||
| 
 | 
 | ||||||
|         let welcome: BaseUIElement = new ThemeIntroductionPanel(isShown); |         let welcome: BaseUIElement = new ThemeIntroductionPanel(isShown); | ||||||
|         if (layoutToUse.id === personal.id) { |         | ||||||
|             welcome = new PersonalLayersPanel(); |  | ||||||
|         } |  | ||||||
|         const tabs: { header: string | BaseUIElement, content: BaseUIElement }[] = [ |         const tabs: { header: string | BaseUIElement, content: BaseUIElement }[] = [ | ||||||
|             {header: `<img src='${layoutToUse.icon}'>`, content: welcome}, |             {header: `<img src='${layoutToUse.icon}'>`, content: welcome}, | ||||||
|             { |             { | ||||||
|  |  | ||||||
|  | @ -11,8 +11,8 @@ import AllDownloads from "./AllDownloads"; | ||||||
| import FilterView from "./FilterView"; | import FilterView from "./FilterView"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; | import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; | ||||||
| import {BBox} from "../../Logic/GeoOperations"; |  | ||||||
| import Loc from "../../Models/Loc"; | import Loc from "../../Models/Loc"; | ||||||
|  | import {BBox} from "../../Logic/BBox"; | ||||||
| 
 | 
 | ||||||
| export default class LeftControls extends Combine { | export default class LeftControls extends Combine { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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<string>() |  | ||||||
|                     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<any>({id: "node/-1"}), |  | ||||||
|             false |  | ||||||
|         ).icon.html]).SetClass("relative") |  | ||||||
|         let iconUnset = new Combine([layer.GenerateLeafletStyle( |  | ||||||
|             new UIEventSource<any>({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(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -20,7 +20,7 @@ import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject"; | ||||||
| import PresetConfig from "../../Models/ThemeConfig/PresetConfig"; | import PresetConfig from "../../Models/ThemeConfig/PresetConfig"; | ||||||
| import FilteredLayer from "../../Models/FilteredLayer"; | import FilteredLayer from "../../Models/FilteredLayer"; | ||||||
| import {And} from "../../Logic/Tags/And"; | 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: | * The SimpleAddUI is a single panel, which can have multiple states: | ||||||
|  |  | ||||||
|  | @ -5,7 +5,6 @@ import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter"; | ||||||
| import {UIEventSource} from "../Logic/UIEventSource"; | import {UIEventSource} from "../Logic/UIEventSource"; | ||||||
| import Minimap from "./Base/Minimap"; | import Minimap from "./Base/Minimap"; | ||||||
| import Loc from "../Models/Loc"; | import Loc from "../Models/Loc"; | ||||||
| import {BBox} from "../Logic/GeoOperations"; |  | ||||||
| import BaseLayer from "../Models/BaseLayer"; | import BaseLayer from "../Models/BaseLayer"; | ||||||
| import {FixedUiElement} from "./Base/FixedUiElement"; | import {FixedUiElement} from "./Base/FixedUiElement"; | ||||||
| import Translations from "./i18n/Translations"; | import Translations from "./i18n/Translations"; | ||||||
|  | @ -14,6 +13,7 @@ import Constants from "../Models/Constants"; | ||||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | ||||||
| import FeaturePipeline from "../Logic/FeatureSource/FeaturePipeline"; | import FeaturePipeline from "../Logic/FeatureSource/FeaturePipeline"; | ||||||
| import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"; | import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"; | ||||||
|  | import {BBox} from "../Logic/BBox"; | ||||||
| /** | /** | ||||||
|  * Creates screenshoter to take png screenshot |  * Creates screenshoter to take png screenshot | ||||||
|  * Creates jspdf and downloads it |  * Creates jspdf and downloads it | ||||||
|  |  | ||||||
|  | @ -7,11 +7,12 @@ import Combine from "../Base/Combine"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import State from "../../State"; | import State from "../../State"; | ||||||
| import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | ||||||
| import {BBox, GeoOperations} from "../../Logic/GeoOperations"; | import {GeoOperations} from "../../Logic/GeoOperations"; | ||||||
| import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; | import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; | ||||||
| import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; | import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; | ||||||
| import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
|  | import {BBox} from "../../Logic/BBox"; | ||||||
| 
 | 
 | ||||||
| export default class LocationInput extends InputElement<Loc> { | export default class LocationInput extends InputElement<Loc> { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import {SubtleButton} from "../Base/SubtleButton"; | ||||||
| import Minimap from "../Base/Minimap"; | import Minimap from "../Base/Minimap"; | ||||||
| import State from "../../State"; | import State from "../../State"; | ||||||
| import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; | import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; | ||||||
| import {BBox, GeoOperations} from "../../Logic/GeoOperations"; | import {GeoOperations} from "../../Logic/GeoOperations"; | ||||||
| import {LeafletMouseEvent} from "leaflet"; | import {LeafletMouseEvent} from "leaflet"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {Button} from "../Base/Button"; | import {Button} from "../Base/Button"; | ||||||
|  | @ -15,6 +15,7 @@ import Title from "../Base/Title"; | ||||||
| import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||||
| import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; | import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
|  | import {BBox} from "../../Logic/BBox"; | ||||||
| 
 | 
 | ||||||
| export default class SplitRoadWizard extends Toggle { | export default class SplitRoadWizard extends Toggle { | ||||||
|     private static splitLayerStyling = new LayerConfig({ |     private static splitLayerStyling = new LayerConfig({ | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| import FeatureSource, {FeatureSourceForLayer, Tiled} from "../../Logic/FeatureSource/FeatureSource"; | import FeatureSource, {FeatureSourceForLayer, Tiled} from "../../Logic/FeatureSource/FeatureSource"; | ||||||
| import {BBox} from "../../Logic/GeoOperations"; |  | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {Tiles} from "../../Models/TileRange"; | import {Tiles} from "../../Models/TileRange"; | ||||||
| import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||||
|  | import {BBox} from "../../Logic/BBox"; | ||||||
| 
 | 
 | ||||||
| export class TileHierarchyAggregator implements FeatureSource { | export class TileHierarchyAggregator implements FeatureSource { | ||||||
|     private _parent: TileHierarchyAggregator; |     private _parent: TileHierarchyAggregator; | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										21
									
								
								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 |      * If the key starts with a '+', the values of the list will be appended to the target instead of overwritten | ||||||
|      * @param source |      * @param source | ||||||
|      * @param target |      * @param target | ||||||
|      * @constructor |      * @constructor | ||||||
|  |      * @return the second parameter as is | ||||||
|      */ |      */ | ||||||
|     static Merge(source: any, target: any) { |     static Merge(source: any, target: any) { | ||||||
|         for (const key in source) { |         for (const key in source) { | ||||||
|  | @ -288,15 +289,13 @@ export class Utils { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     private static injectedDownloads = {} |     private static injectedDownloads = {} | ||||||
| 
 | 
 | ||||||
|     public static injectJsonDownloadForTests(url: string, data) { |     public static injectJsonDownloadForTests(url: string, data) { | ||||||
|         Utils.injectedDownloads[url] = data |         Utils.injectedDownloads[url] = data | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static downloadJson(url: string, headers?: any): Promise<any> { |     public static download(url: string, headers?: any): Promise<string> { | ||||||
| 
 |  | ||||||
|         const injected = Utils.injectedDownloads[url] |         const injected = Utils.injectedDownloads[url] | ||||||
|         if (injected !== undefined) { |         if (injected !== undefined) { | ||||||
|             console.log("Using injected resource for test for URL", url) |             console.log("Using injected resource for test for URL", url) | ||||||
|  | @ -311,17 +310,14 @@ export class Utils { | ||||||
|                 const xhr = new XMLHttpRequest(); |                 const xhr = new XMLHttpRequest(); | ||||||
|                 xhr.onload = () => { |                 xhr.onload = () => { | ||||||
|                     if (xhr.status == 200) { |                     if (xhr.status == 200) { | ||||||
|                         try { |                         resolve(xhr.response) | ||||||
|                             resolve(JSON.parse(xhr.response)) |                     } else if (xhr.status === 509 || xhr.status === 429){ | ||||||
|                         } catch (e) { |                       reject("rate limited")   | ||||||
|                             reject("Not a valid json: " + xhr.response) |  | ||||||
|                         } |  | ||||||
|                     } else { |                     } else { | ||||||
|                         reject(xhr.statusText) |                         reject(xhr.statusText) | ||||||
|                     } |                     } | ||||||
|                 }; |                 }; | ||||||
|                 xhr.open('GET', url); |                 xhr.open('GET', url); | ||||||
|                 xhr.setRequestHeader("accept", "application/json") |  | ||||||
|                 if (headers !== undefined) { |                 if (headers !== undefined) { | ||||||
| 
 | 
 | ||||||
|                     for (const key in headers) { |                     for (const key in headers) { | ||||||
|  | @ -334,6 +330,11 @@ export class Utils { | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public static async downloadJson(url: string, headers?: any): Promise<any> { | ||||||
|  |         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 |      * Triggers a 'download file' popup which will download the contents | ||||||
|      */ |      */ | ||||||
|  |  | ||||||
|  | @ -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", |                     "##": "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": [ |                     "and": [ | ||||||
|                         "shop=sports", |                         "shop=sports", | ||||||
|  | @ -38,13 +38,6 @@ | ||||||
|                             ] |                             ] | ||||||
|                         } |                         } | ||||||
|                     ] |                     ] | ||||||
|                 }, |  | ||||||
|                 { |  | ||||||
|                     "#": "Any shop with any bicycle service", |  | ||||||
|                     "and": [ |  | ||||||
|                         "shop~*", |  | ||||||
|                         "service:bicycle:.*~~.*" |  | ||||||
|                     ] |  | ||||||
|                 } |                 } | ||||||
|             ] |             ] | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ | ||||||
|         "mappings": [ |         "mappings": [ | ||||||
|             { |             { | ||||||
|                 "if": "wheelchair=yes", |                 "if": "wheelchair=yes", | ||||||
|                 "then": "./assets/layers/toilet/wheelchair.svg" |                 "then": "circle:white;./assets/layers/toilet/wheelchair.svg" | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|                 "if": { |                 "if": { | ||||||
|  |  | ||||||
|  | @ -5,9 +5,10 @@ | ||||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" |    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||||
|    xmlns:cc="http://creativecommons.org/ns#" |    xmlns:cc="http://creativecommons.org/ns#" | ||||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" |    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||||
|  |    xmlns:svg="http://www.w3.org/2000/svg" | ||||||
|  |    xmlns="http://www.w3.org/2000/svg" | ||||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" |    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" |    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||||
|         xmlns="http://www.w3.org/2000/svg" |  | ||||||
|    version="1.1" |    version="1.1" | ||||||
|    id="Layer_1" |    id="Layer_1" | ||||||
|    width="483.2226563" |    width="483.2226563" | ||||||
|  | @ -17,7 +18,7 @@ | ||||||
|    enable-background="new 0 0 483.2226563 551.4306641" |    enable-background="new 0 0 483.2226563 551.4306641" | ||||||
|    xml:space="preserve" |    xml:space="preserve" | ||||||
|    sodipodi:docname="wheelchair.svg" |    sodipodi:docname="wheelchair.svg" | ||||||
|         inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata |    inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"><metadata | ||||||
|    id="metadata11"><rdf:RDF><cc:Work |    id="metadata11"><rdf:RDF><cc:Work | ||||||
|        rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type |        rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type | ||||||
|          rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata> |          rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata> | ||||||
|  | @ -33,16 +34,16 @@ | ||||||
|    inkscape:pageopacity="0" |    inkscape:pageopacity="0" | ||||||
|    inkscape:pageshadow="2" |    inkscape:pageshadow="2" | ||||||
|    inkscape:window-width="1920" |    inkscape:window-width="1920" | ||||||
|             inkscape:window-height="1001" |    inkscape:window-height="999" | ||||||
|    id="namedview7" |    id="namedview7" | ||||||
|    showgrid="false" |    showgrid="false" | ||||||
|    inkscape:zoom="0.8559553" |    inkscape:zoom="0.8559553" | ||||||
|             inkscape:cx="-66.220714" |    inkscape:cx="-16.568588" | ||||||
|    inkscape:cy="292.29436" |    inkscape:cy="292.29436" | ||||||
|    inkscape:window-x="0" |    inkscape:window-x="0" | ||||||
|    inkscape:window-y="0" |    inkscape:window-y="0" | ||||||
|    inkscape:window-maximized="1" |    inkscape:window-maximized="1" | ||||||
|             inkscape:current-layer="Layer_1"/> |    inkscape:current-layer="layer2" /> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     <g |     <g | ||||||
|  | @ -55,13 +56,7 @@ | ||||||
|    cy="274.54706" |    cy="274.54706" | ||||||
|    rx="241.83505" |    rx="241.83505" | ||||||
|    ry="275.71533" /> |    ry="275.71533" /> | ||||||
|         <ellipse |         </g> | ||||||
|                 style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.484;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" |  | ||||||
|                 id="path819" |  | ||||||
|                 cx="240.66678" |  | ||||||
|                 cy="275.71533" |  | ||||||
|                 rx="241.83505" |  | ||||||
|                 ry="274.54706"/></g> |  | ||||||
|     <g |     <g | ||||||
|    inkscape:groupmode="layer" |    inkscape:groupmode="layer" | ||||||
|    id="layer1" |    id="layer1" | ||||||
|  |  | ||||||
| Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.1 KiB | 
|  | @ -43,6 +43,12 @@ | ||||||
|   "widenFactor": 1.5, |   "widenFactor": 1.5, | ||||||
|   "roamingRenderings": [], |   "roamingRenderings": [], | ||||||
|   "layers": [ |   "layers": [ | ||||||
|     "bicycle_library" |     { | ||||||
|  |       "builtin": "bicycle_library", | ||||||
|  |       "override": { | ||||||
|  |         "minZoom": 0 | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |      | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|  | @ -12,7 +12,7 @@ | ||||||
|     "zh_Hant": "個人化主題" |     "zh_Hant": "個人化主題" | ||||||
|   }, |   }, | ||||||
|   "description": { |   "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", |     "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", |     "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", |     "ca": "Crea una interfície basada en totes les capes disponibles de totes les interfícies", | ||||||
|  | @ -37,11 +37,14 @@ | ||||||
|   ], |   ], | ||||||
|   "maintainer": "MapComplete", |   "maintainer": "MapComplete", | ||||||
|   "icon": "./assets/svg/addSmall.svg", |   "icon": "./assets/svg/addSmall.svg", | ||||||
|  |   "clustering": { | ||||||
|  |     "maxZoom": 19 | ||||||
|  |   }, | ||||||
|   "version": "0", |   "version": "0", | ||||||
|   "startLat": 0, |   "startLat": 0, | ||||||
|   "startLon": 0, |   "startLon": 0, | ||||||
|   "startZoom": 16, |   "startZoom": 16, | ||||||
|   "widenFactor": 3, |   "widenFactor": 1.2, | ||||||
|   "layers": [], |   "layers": [], | ||||||
|   "roamingRenderings": [] |   "roamingRenderings": [] | ||||||
| } | } | ||||||
|  | @ -20,7 +20,7 @@ | ||||||
|   "startZoom": 8, |   "startZoom": 8, | ||||||
|   "startLat": 50.8536, |   "startLat": 50.8536, | ||||||
|   "startLon": 4.433, |   "startLon": 4.433, | ||||||
|   "widenFactor": 2, |   "widenFactor": 1.5, | ||||||
|   "layers": [ |   "layers": [ | ||||||
|     { |     { | ||||||
|       "builtin": [ |       "builtin": [ | ||||||
|  |  | ||||||
|  | @ -10,9 +10,17 @@ import LZString from "lz-string"; | ||||||
| import BaseUIElement from "./UI/BaseUIElement"; | import BaseUIElement from "./UI/BaseUIElement"; | ||||||
| import Table from "./UI/Base/Table"; | import Table from "./UI/Base/Table"; | ||||||
| import {LayoutConfigJson} from "./Models/ThemeConfig/Json/LayoutConfigJson"; | 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<string>(undefined), ""); | const connection = new OsmConnection({ | ||||||
|  |     osmConfiguration: 'osm', | ||||||
|  |     changes: new Changes(), | ||||||
|  |     layoutName: '', | ||||||
|  |     allElements: new ElementStorage() | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| let rendered = false; | let rendered = false; | ||||||
| 
 | 
 | ||||||
|  | @ -20,6 +28,7 @@ function salvageThemes(preferences: any) { | ||||||
|     const knownThemeNames = new Set<string>(); |     const knownThemeNames = new Set<string>(); | ||||||
|     const correctThemeNames = [] |     const correctThemeNames = [] | ||||||
|     for (const key in preferences) { |     for (const key in preferences) { | ||||||
|  |             try{ | ||||||
|         if (!(typeof key === "string")) { |         if (!(typeof key === "string")) { | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
|  | @ -36,7 +45,7 @@ function salvageThemes(preferences: any) { | ||||||
|         } else { |         } else { | ||||||
|             knownThemeNames.add(theme); |             knownThemeNames.add(theme); | ||||||
|         } |         } | ||||||
|     } |     }catch(e){console.error(e)}} | ||||||
| 
 | 
 | ||||||
|     for (const correctThemeName of correctThemeNames) { |     for (const correctThemeName of correctThemeNames) { | ||||||
|         knownThemeNames.delete(correctThemeName); |         knownThemeNames.delete(correctThemeName); | ||||||
|  | @ -65,8 +74,13 @@ function salvageThemes(preferences: any) { | ||||||
|         try { |         try { | ||||||
|             jsonObject = JSON.parse(atob(combined)); |             jsonObject = JSON.parse(atob(combined)); | ||||||
|         } catch (e) { |         } catch (e) { | ||||||
|  |             try{ | ||||||
|  |                  | ||||||
|             // We try to decode with lz-string
 |             // We try to decode with lz-string
 | ||||||
|             jsonObject = JSON.parse(Utils.UnMinify(LZString.decompressFromBase64(combined))) as LayoutConfigJson; |             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) | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										56
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										56
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -1,33 +1,35 @@ | ||||||
| import SplitRoadWizard from "./UI/Popup/SplitRoadWizard"; | import {Tiles} from "./Models/TileRange"; | ||||||
| import State from "./State"; | import OsmFeatureSource from "./Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource"; | ||||||
|  | import {Utils} from "./Utils"; | ||||||
| import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; | import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; | ||||||
| import MinimapImplementation from "./UI/Base/MinimapImplementation"; | import LayerConfig from "./Models/ThemeConfig/LayerConfig"; | ||||||
| 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"; |  | ||||||
| 
 | 
 | ||||||
| State.state = new State(undefined) | const allLayers: LayerConfig[] = [] | ||||||
|  | const seenIds = new Set<string>() | ||||||
|  | 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) | console.log("All layer ids", allLayers.map(l => l.id)) | ||||||
| MinimapImplementation.initialize() |  | ||||||
| Minimap.createMiniMap({ |  | ||||||
|     leafletMap: leafletMap, |  | ||||||
| }).SetStyle("height: 600px; width: 600px") |  | ||||||
|     .AttachTo("maindiv") |  | ||||||
| 
 | 
 | ||||||
| const bbox = BBox.fromTile(16,32754,21785).asGeoJson({ | const src = new OsmFeatureSource({ | ||||||
|     count: 42, |     backend: "https://www.openstreetmap.org", | ||||||
|     tileId: 42 |     handleTile: tile => console.log("Got tile", tile), | ||||||
|  |     allLayers: allLayers | ||||||
| }) | }) | ||||||
| 
 | src.LoadTile(16, 33354, 21875).then(geojson => { | ||||||
| console.log(bbox) |     console.log("Got geojson", geojson); | ||||||
| new ShowDataLayer({ |     Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson), "test.geojson", { | ||||||
|     layerToShow: ShowTileInfo.styling, |         mimetype: "application/vnd.geo+json" | ||||||
|     leafletMap: leafletMap, |  | ||||||
|     features: new StaticFeatureSource([ bbox], false) |  | ||||||
|     }) |     }) | ||||||
|  | }) | ||||||
|  | //*/
 | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
| import {Utils} from "../Utils"; | import {Utils} from "../Utils"; | ||||||
| import * as Assert from "assert"; | import * as Assert from "assert"; | ||||||
| import T from "./TestHelper"; | import T from "./TestHelper"; | ||||||
| import {BBox, GeoOperations} from "../Logic/GeoOperations"; | import {GeoOperations} from "../Logic/GeoOperations"; | ||||||
| import {equal} from "assert"; | import {equal} from "assert"; | ||||||
|  | import {BBox} from "../Logic/BBox"; | ||||||
| 
 | 
 | ||||||
| Utils.runningFromConsole = true; | Utils.runningFromConsole = true; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,9 @@ import T from "./TestHelper"; | ||||||
| import UserDetails, {OsmConnection} from "../Logic/Osm/OsmConnection"; | import UserDetails, {OsmConnection} from "../Logic/Osm/OsmConnection"; | ||||||
| import {UIEventSource} from "../Logic/UIEventSource"; | import {UIEventSource} from "../Logic/UIEventSource"; | ||||||
| import ScriptUtils from "../scripts/ScriptUtils"; | 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 { | export default class OsmConnectionSpec extends T { | ||||||
|  | @ -15,12 +18,14 @@ export default class OsmConnectionSpec extends T { | ||||||
|         super("osmconnection", [ |         super("osmconnection", [ | ||||||
|             ["login on dev", |             ["login on dev", | ||||||
|                 () => { |                 () => { | ||||||
|                     const osmConn = new OsmConnection(false, false, |                     const osmConn = new OsmConnection({ | ||||||
|                         new UIEventSource<string>(undefined), |                             osmConfiguration: "osm-test", | ||||||
|                         "Unit test", |                             layoutName: "Unit test", | ||||||
|                         true, |                             allElements: new ElementStorage(), | ||||||
|                         "osm-test" |                             changes: new Changes(), | ||||||
|                     ) |                             oauth_token: new UIEventSource<string>(OsmConnectionSpec._osm_token) | ||||||
|  |                         } | ||||||
|  |                     ); | ||||||
| 
 | 
 | ||||||
|                     osmConn.userDetails.map((userdetails: UserDetails) => { |                     osmConn.userDetails.map((userdetails: UserDetails) => { | ||||||
|                         if (userdetails.loggedIn) { |                         if (userdetails.loggedIn) { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue