forked from MapComplete/MapComplete
		
	Full code cleanup
This commit is contained in:
		
							parent
							
								
									3a4a2a2016
								
							
						
					
					
						commit
						fa971ffbbf
					
				
					 300 changed files with 16352 additions and 19284 deletions
				
			
		|  | @ -64,7 +64,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL | |||
|                 console.warn("Editor layer index: name not defined on ", props) | ||||
|                 continue | ||||
|             } | ||||
|              | ||||
| 
 | ||||
| 
 | ||||
|             const leafletLayer: () => TileLayer = () => AvailableBaseLayersImplementation.CreateBackgroundLayer( | ||||
|                 props.id, | ||||
|  | @ -189,13 +189,13 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL | |||
|                 attribution: attribution, | ||||
|                 maxZoom: Math.max(21, maxZoom ?? 19), | ||||
|                 maxNativeZoom: maxZoom ?? 19, | ||||
|                 minZoom:  1, | ||||
|                 minZoom: 1, | ||||
|                 // @ts-ignore
 | ||||
|                 wmts: isWMTS ?? false, | ||||
|                 subdomains: domains | ||||
|             }); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     public AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> { | ||||
|         return UIEventSource.ListStabilized(location.map( | ||||
|             (currentLocation) => { | ||||
|  | @ -209,50 +209,50 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL | |||
|     public SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> { | ||||
|         return this.AvailableLayersAt(location) | ||||
|             .map(available => { | ||||
|             // First float all 'best layers' to the top
 | ||||
|             available.sort((a, b) => { | ||||
|                     if (a.isBest && b.isBest) { | ||||
|                         return 0; | ||||
|                     } | ||||
|                     if (!a.isBest) { | ||||
|                         return 1 | ||||
|                     } | ||||
| 
 | ||||
|                     return -1; | ||||
|                 } | ||||
|             ) | ||||
| 
 | ||||
|             if (preferedCategory.data === undefined) { | ||||
|                 return available[0] | ||||
|             } | ||||
| 
 | ||||
|             let prefered: string [] | ||||
|             if (typeof preferedCategory.data === "string") { | ||||
|                 prefered = [preferedCategory.data] | ||||
|             } else { | ||||
|                 prefered = preferedCategory.data; | ||||
|             } | ||||
| 
 | ||||
|             prefered.reverse(); | ||||
|             for (const category of prefered) { | ||||
|                 //Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top
 | ||||
|                 // First float all 'best layers' to the top
 | ||||
|                 available.sort((a, b) => { | ||||
|                         if (a.category === category && b.category === category) { | ||||
|                         if (a.isBest && b.isBest) { | ||||
|                             return 0; | ||||
|                         } | ||||
|                         if (a.category !== category) { | ||||
|                         if (!a.isBest) { | ||||
|                             return 1 | ||||
|                         } | ||||
| 
 | ||||
|                         return -1; | ||||
|                     } | ||||
|                 ) | ||||
|             } | ||||
|             return available[0] | ||||
|         }, [preferedCategory]) | ||||
| 
 | ||||
|                 if (preferedCategory.data === undefined) { | ||||
|                     return available[0] | ||||
|                 } | ||||
| 
 | ||||
|                 let prefered: string [] | ||||
|                 if (typeof preferedCategory.data === "string") { | ||||
|                     prefered = [preferedCategory.data] | ||||
|                 } else { | ||||
|                     prefered = preferedCategory.data; | ||||
|                 } | ||||
| 
 | ||||
|                 prefered.reverse(); | ||||
|                 for (const category of prefered) { | ||||
|                     //Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top
 | ||||
|                     available.sort((a, b) => { | ||||
|                             if (a.category === category && b.category === category) { | ||||
|                                 return 0; | ||||
|                             } | ||||
|                             if (a.category !== category) { | ||||
|                                 return 1 | ||||
|                             } | ||||
| 
 | ||||
|                             return -1; | ||||
|                         } | ||||
|                     ) | ||||
|                 } | ||||
|                 return available[0] | ||||
|             }, [preferedCategory]) | ||||
|     } | ||||
| 
 | ||||
|      | ||||
| 
 | ||||
|     private CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] { | ||||
|         const availableLayers = [this.osmCarto] | ||||
|         const globalLayers = []; | ||||
|  |  | |||
|  | @ -11,11 +11,11 @@ export interface GeoLocationPointProperties { | |||
|     "user:location": "yes", | ||||
|     "date": string, | ||||
|     "latitude": number | ||||
|     "longitude":number, | ||||
|     "longitude": number, | ||||
|     "speed": number, | ||||
|     "accuracy": number | ||||
|     "heading": number | ||||
|     "altitude":number | ||||
|     "altitude": number | ||||
| } | ||||
| 
 | ||||
| export default class GeoLocationHandler extends VariableUiElement { | ||||
|  |  | |||
|  | @ -64,22 +64,6 @@ export default class DetermineLayout { | |||
|         return layoutToUse | ||||
|     } | ||||
| 
 | ||||
|     private static prepCustomTheme(json: any): LayoutConfigJson{ | ||||
|         const knownLayersDict = new Map<string, LayerConfigJson>() | ||||
|         for (const key in known_layers["default"]) { | ||||
|             const layer = known_layers["default"][key] | ||||
|             knownLayersDict.set(layer.id, layer) | ||||
|         } | ||||
|         const converState = { | ||||
|             tagRenderings: SharedTagRenderings.SharedTagRenderingJson, | ||||
|             sharedLayers: knownLayersDict | ||||
|         } | ||||
|         json = new FixLegacyTheme().convertStrict(converState, json, "While loading a dynamic theme") | ||||
|         json = new PrepareTheme().convertStrict(converState, json, "While preparing a dynamic theme") | ||||
|         console.log("The layoutconfig is ", json) | ||||
|         return json | ||||
|     } | ||||
|      | ||||
|     public static LoadLayoutFromHash( | ||||
|         userLayoutParam: UIEventSource<string> | ||||
|     ): LayoutConfig | null { | ||||
|  | @ -148,6 +132,22 @@ export default class DetermineLayout { | |||
|             .AttachTo("centermessage"); | ||||
|     } | ||||
| 
 | ||||
|     private static prepCustomTheme(json: any): LayoutConfigJson { | ||||
|         const knownLayersDict = new Map<string, LayerConfigJson>() | ||||
|         for (const key in known_layers["default"]) { | ||||
|             const layer = known_layers["default"][key] | ||||
|             knownLayersDict.set(layer.id, layer) | ||||
|         } | ||||
|         const converState = { | ||||
|             tagRenderings: SharedTagRenderings.SharedTagRenderingJson, | ||||
|             sharedLayers: knownLayersDict | ||||
|         } | ||||
|         json = new FixLegacyTheme().convertStrict(converState, json, "While loading a dynamic theme") | ||||
|         json = new PrepareTheme().convertStrict(converState, json, "While preparing a dynamic theme") | ||||
|         console.log("The layoutconfig is ", json) | ||||
|         return json | ||||
|     } | ||||
| 
 | ||||
|     private static async LoadRemoteTheme(link: string): Promise<LayoutConfig | null> { | ||||
|         console.log("Downloading map theme from ", link); | ||||
| 
 | ||||
|  | @ -160,7 +160,7 @@ export default class DetermineLayout { | |||
|             try { | ||||
|                 parsed.id = link; | ||||
|                 const layoutToUse = DetermineLayout.prepCustomTheme(parsed) | ||||
|                 return new LayoutConfig(layoutToUse,false) | ||||
|                 return new LayoutConfig(layoutToUse, false) | ||||
|             } catch (e) { | ||||
|                 console.error(e) | ||||
|                 DetermineLayout.ShowErrorOnCustomTheme( | ||||
|  |  | |||
|  | @ -94,7 +94,7 @@ class IntersectionFunc implements ExtraFunction { | |||
|                     for (const otherFeature of tile) { | ||||
| 
 | ||||
|                         const intersections = GeoOperations.LineIntersections(feat, otherFeature) | ||||
|                         if(intersections.length === 0){ | ||||
|                         if (intersections.length === 0) { | ||||
|                             continue | ||||
|                         } | ||||
|                         result.push({feat: otherFeature, intersections}) | ||||
|  | @ -154,28 +154,12 @@ class ClosestObjectFunc implements ExtraFunction { | |||
| 
 | ||||
| 
 | ||||
| class ClosestNObjectFunc implements ExtraFunction { | ||||
|     _f(params, feature) { | ||||
| 
 | ||||
|         return (features, amount, uniqueTag, maxDistanceInMeters) => { | ||||
|             let distance: number = Number(maxDistanceInMeters) | ||||
|             if (isNaN(distance)) { | ||||
|                 distance = undefined | ||||
|             } | ||||
|             return ClosestNObjectFunc.GetClosestNFeatures(params, feature, features, { | ||||
|                 maxFeatures: Number(amount), | ||||
|                 uniqueTag: uniqueTag, | ||||
|                 maxDistance: distance | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     _name = "closestn" | ||||
|     _doc = "Given either a list of geojson features or a single layer name, gives the n closest objects which are nearest to the feature (excluding the feature itself). In the case of ways/polygons, only the centerpoint is considered. " + | ||||
|         "Returns a list of `{feat: geojson, distance:number}` the empty list if nothing is found (or not yet loaded)\n\n" + | ||||
|         "If a 'unique tag key' is given, the tag with this key will only appear once (e.g. if 'name' is given, all features will have a different name)" | ||||
|     _args = ["list of features or layer name or '*' to get all features", "amount of features", "unique tag key (optional)", "maxDistanceInMeters (optional)"] | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Gets the closes N features, sorted by ascending distance. | ||||
|      * | ||||
|  | @ -311,6 +295,21 @@ class ClosestNObjectFunc implements ExtraFunction { | |||
|         return closestFeatures; | ||||
|     } | ||||
| 
 | ||||
|     _f(params, feature) { | ||||
| 
 | ||||
|         return (features, amount, uniqueTag, maxDistanceInMeters) => { | ||||
|             let distance: number = Number(maxDistanceInMeters) | ||||
|             if (isNaN(distance)) { | ||||
|                 distance = undefined | ||||
|             } | ||||
|             return ClosestNObjectFunc.GetClosestNFeatures(params, feature, features, { | ||||
|                 maxFeatures: Number(amount), | ||||
|                 uniqueTag: uniqueTag, | ||||
|                 maxDistance: distance | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -401,7 +400,7 @@ export class ExtraFunctions { | |||
|     ]; | ||||
| 
 | ||||
|     public static FullPatchFeature(params: ExtraFuncParams, feature) { | ||||
|         if(feature._is_patched){ | ||||
|         if (feature._is_patched) { | ||||
|             return | ||||
|         } | ||||
|         feature._is_patched = true | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ class MetatagUpdater { | |||
|     private source: FeatureSourceForLayer & Tiled; | ||||
|     private readonly params: ExtraFuncParams | ||||
|     private state: { allElements?: ElementStorage }; | ||||
|      | ||||
| 
 | ||||
|     private readonly isDirty = new UIEventSource(false) | ||||
| 
 | ||||
|     constructor(source: FeatureSourceForLayer & Tiled, state: { allElements?: ElementStorage }, featurePipeline: FeaturePipeline) { | ||||
|  | @ -31,14 +31,14 @@ class MetatagUpdater { | |||
|                 if (oldBbox === undefined) { | ||||
|                     self.neededLayerBboxes.set(layerId, bbox); | ||||
|                 } else if (!bbox.isContainedIn(oldBbox)) { | ||||
|                     self.neededLayerBboxes.set(layerId,oldBbox.unionWith(bbox)) | ||||
|                     self.neededLayerBboxes.set(layerId, oldBbox.unionWith(bbox)) | ||||
|                 } | ||||
|                 return featurePipeline.GetFeaturesWithin(layerId, bbox) | ||||
|             }, | ||||
|             memberships: featurePipeline.relationTracker | ||||
|         } | ||||
|         this.isDirty.stabilized(100).addCallback(dirty => { | ||||
|             if(dirty){ | ||||
|             if (dirty) { | ||||
|                 self.updateMetaTags() | ||||
|             } | ||||
|         }) | ||||
|  | @ -46,10 +46,10 @@ class MetatagUpdater { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public requestUpdate(){ | ||||
|     public requestUpdate() { | ||||
|         this.isDirty.setData(true) | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     private updateMetaTags() { | ||||
|         const features = this.source.features.data | ||||
| 
 | ||||
|  | @ -74,7 +74,8 @@ export default class MetaTagRecalculator { | |||
|     }; | ||||
|     private _featurePipeline: FeaturePipeline; | ||||
|     private readonly _alreadyRegistered: Set<FeatureSourceForLayer & Tiled> = new Set<FeatureSourceForLayer & Tiled>() | ||||
| private readonly _notifiers : MetatagUpdater[] = [] | ||||
|     private readonly _notifiers: MetatagUpdater[] = [] | ||||
| 
 | ||||
|     /** | ||||
|      * The meta tag recalculator receives tiles of layers. | ||||
|      * It keeps track of which sources have had their share calculated, and which should be re-updated if some other data is loaded | ||||
|  | @ -92,16 +93,16 @@ private readonly _notifiers : MetatagUpdater[] = [] | |||
|             return; | ||||
|         } | ||||
|         this._alreadyRegistered.add(source) | ||||
|         this._notifiers.push(new MetatagUpdater(source,this._state,this._featurePipeline)) | ||||
|         this._notifiers.push(new MetatagUpdater(source, this._state, this._featurePipeline)) | ||||
|         const self = this; | ||||
|         source.features.addCallbackAndRunD(_ => { | ||||
|             const layerName = source.layer.layerDef.id | ||||
|             for (const updater of self._notifiers ) { | ||||
|             for (const updater of self._notifiers) { | ||||
|                 const neededBbox = updater.neededLayerBboxes.get(layerName) | ||||
|                 if(neededBbox == undefined){ | ||||
|                 if (neededBbox == undefined) { | ||||
|                     continue | ||||
|                 } | ||||
|                 if(source.bbox === undefined || neededBbox.overlapsWith(source.bbox)){ | ||||
|                 if (source.bbox === undefined || neededBbox.overlapsWith(source.bbox)) { | ||||
|                     updater.requestUpdate() | ||||
|                 } | ||||
|             } | ||||
|  |  | |||
|  | @ -41,7 +41,7 @@ export default class SaveTileToLocalStorageActor { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|      | ||||
| 
 | ||||
|     public LoadTilesFromDisk(currentBounds: UIEventSource<BBox>, location: UIEventSource<Loc>, | ||||
|                              registerFreshness: (tileId: number, freshness: Date) => void, | ||||
|                              registerTile: ((src: FeatureSource & Tiled) => void)) { | ||||
|  | @ -55,7 +55,7 @@ export default class SaveTileToLocalStorageActor { | |||
|             } | ||||
|             currentBounds.addCallbackAndRunD(bbox => { | ||||
| 
 | ||||
|                 if(self._layer.minzoomVisible > location.data.zoom){ | ||||
|                 if (self._layer.minzoomVisible > location.data.zoom) { | ||||
|                     // Not enough zoom
 | ||||
|                     return; | ||||
|                 } | ||||
|  | @ -119,9 +119,9 @@ export default class SaveTileToLocalStorageActor { | |||
|     } | ||||
| 
 | ||||
|     private SetIdb(tileIndex, data) { | ||||
|         try{ | ||||
|         try { | ||||
|             IdbLocalStorage.SetDirectly(this._layer.id + "_" + tileIndex, data) | ||||
|         }catch(e){ | ||||
|         } catch (e) { | ||||
|             console.error("Could not save tile to indexed-db: ", e, "tileIndex is:", tileIndex, "for layer", this._layer.id) | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -43,40 +43,33 @@ export default class FeaturePipeline { | |||
|     public readonly timeout: UIEventSource<number>; | ||||
|     public readonly somethingLoaded: UIEventSource<boolean> = new UIEventSource<boolean>(false) | ||||
|     public readonly newDataLoadedSignal: UIEventSource<FeatureSource> = new UIEventSource<FeatureSource>(undefined) | ||||
| 
 | ||||
|      | ||||
|      | ||||
|     public readonly relationTracker: RelationsTracker | ||||
|     /** | ||||
|      * Keeps track of all raw OSM-nodes. | ||||
|      * Only initialized if 'type_node' is defined as layer | ||||
|      */ | ||||
|     public readonly fullNodeDatabase?: FullNodeDatabaseSource | ||||
|     private readonly overpassUpdater: OverpassFeatureSource | ||||
|     private state: MapState; | ||||
|     public readonly relationTracker: RelationsTracker | ||||
|     private readonly perLayerHierarchy: Map<string, TileHierarchyMerger>; | ||||
| 
 | ||||
|     /** | ||||
|      * Keeps track of the age of the loaded data. | ||||
|      * Has one freshness-Calculator for every layer | ||||
|      * @private | ||||
|      */ | ||||
|     private readonly freshnesses = new Map<string, TileFreshnessCalculator>(); | ||||
| 
 | ||||
|     private readonly oldestAllowedDate: Date; | ||||
|     private readonly osmSourceZoomLevel | ||||
| 
 | ||||
|     private readonly localStorageSavers = new Map<string, SaveTileToLocalStorageActor>() | ||||
|    | ||||
|     /** | ||||
|      * Keeps track of all raw OSM-nodes. | ||||
|      * Only initialized if 'type_node' is defined as layer | ||||
|      */ | ||||
|     public readonly fullNodeDatabase? : FullNodeDatabaseSource | ||||
|      | ||||
| 
 | ||||
|     constructor( | ||||
|         handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void, | ||||
|         state: MapState, | ||||
|         options? : { | ||||
|         options?: { | ||||
|             /*Used for metatagging - will receive all the sources with changeGeometry applied but without filtering*/ | ||||
|             handleRawFeatureSource: (source: FeatureSourceForLayer) => void | ||||
|         } | ||||
|        ) { | ||||
|     ) { | ||||
|         this.state = state; | ||||
| 
 | ||||
|         const self = this | ||||
|  | @ -104,7 +97,7 @@ export default class FeaturePipeline { | |||
|                 return location.zoom >= minzoom; | ||||
|             } | ||||
|         ); | ||||
|          | ||||
| 
 | ||||
|         const neededTilesFromOsm = this.getNeededTilesFromOsm(this.sufficientlyZoomed) | ||||
| 
 | ||||
|         const perLayerHierarchy = new Map<string, TileHierarchyMerger>() | ||||
|  | @ -114,10 +107,10 @@ export default class FeaturePipeline { | |||
|         function patchedHandleFeatureSource(src: FeatureSourceForLayer & IndexedFeatureSource & Tiled) { | ||||
|             // This will already contain the merged features for this tile. In other words, this will only be triggered once for every tile
 | ||||
|             const withChanges = new ChangeGeometryApplicator(src, state.changes); | ||||
|             const srcFiltered =                new FilteringFeatureSource(state, src.tileIndex,withChanges) | ||||
|             const srcFiltered = new FilteringFeatureSource(state, src.tileIndex, withChanges) | ||||
| 
 | ||||
|             handleFeatureSource(srcFiltered) | ||||
|             if(options?.handleRawFeatureSource){ | ||||
|             if (options?.handleRawFeatureSource) { | ||||
|                 options.handleRawFeatureSource(withChanges) | ||||
|             } | ||||
|             self.somethingLoaded.setData(true) | ||||
|  | @ -267,7 +260,7 @@ export default class FeaturePipeline { | |||
|                 }) | ||||
|         }) | ||||
| 
 | ||||
|         if(this.fullNodeDatabase !== undefined){ | ||||
|         if (this.fullNodeDatabase !== undefined) { | ||||
|             osmFeatureSource.rawDataHandlers.push((osmJson, tileId) => this.fullNodeDatabase.handleOsmJson(osmJson, tileId)) | ||||
|         } | ||||
| 
 | ||||
|  | @ -289,7 +282,7 @@ export default class FeaturePipeline { | |||
|                     self.localStorageSavers.get(tile.layer.layerDef.id)?.addTile(tile) | ||||
|                     perLayerHierarchy.get(source.layer.layerDef.id).registerTile(new RememberingSource(tile)) | ||||
|                     tile.features.addCallbackAndRunD(f => { | ||||
|                         if(f.length === 0){ | ||||
|                         if (f.length === 0) { | ||||
|                             return | ||||
|                         } | ||||
|                         self.onNewDataLoaded(tile) | ||||
|  | @ -298,9 +291,11 @@ export default class FeaturePipeline { | |||
|                 } | ||||
|             }), | ||||
|             updater, | ||||
|             {handleLeftovers: (leftOvers) => { | ||||
|                 console.warn("Overpass returned a few non-matched features:", leftOvers) | ||||
|                 }}) | ||||
|             { | ||||
|                 handleLeftovers: (leftOvers) => { | ||||
|                     console.warn("Overpass returned a few non-matched features:", leftOvers) | ||||
|                 } | ||||
|             }) | ||||
| 
 | ||||
| 
 | ||||
|         // Also load points/lines that are newly added. 
 | ||||
|  | @ -308,7 +303,7 @@ export default class FeaturePipeline { | |||
|         newGeometry.features.addCallbackAndRun(geometries => { | ||||
|             console.debug("New geometries are:", geometries) | ||||
|         }) | ||||
|          | ||||
| 
 | ||||
|         new RegisteringAllFromFeatureSourceActor(newGeometry, state.allElements) | ||||
|         // A NewGeometryFromChangesFeatureSource does not split per layer, so we do this next
 | ||||
|         new PerLayerFeatureSourceSplitter(state.filteredLayers, | ||||
|  | @ -322,9 +317,11 @@ export default class FeaturePipeline { | |||
| 
 | ||||
|             }, | ||||
|             newGeometry, | ||||
|             {handleLeftovers: (leftOvers) => { | ||||
|                 console.warn("Got some leftovers from the filteredLayers: ", leftOvers) | ||||
|                 }} | ||||
|             { | ||||
|                 handleLeftovers: (leftOvers) => { | ||||
|                     console.warn("Got some leftovers from the filteredLayers: ", leftOvers) | ||||
|                 } | ||||
|             } | ||||
|         ) | ||||
| 
 | ||||
|         this.runningQuery = updater.runningQuery.map( | ||||
|  | @ -337,10 +334,6 @@ export default class FeaturePipeline { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private onNewDataLoaded(src: FeatureSource){ | ||||
|         this.newDataLoadedSignal.setData(src) | ||||
|     } | ||||
|      | ||||
|     public GetAllFeaturesWithin(bbox: BBox): any[][] { | ||||
|         const self = this | ||||
|         const tiles = [] | ||||
|  | @ -369,6 +362,10 @@ export default class FeaturePipeline { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     private onNewDataLoaded(src: FeatureSource) { | ||||
|         this.newDataLoadedSignal.setData(src) | ||||
|     } | ||||
| 
 | ||||
|     private freshnessForVisibleLayers(z: number, x: number, y: number): Date { | ||||
|         let oldestDate = undefined; | ||||
|         for (const flayer of this.state.filteredLayers.data) { | ||||
|  | @ -378,11 +375,11 @@ export default class FeaturePipeline { | |||
|             if (this.state.locationControl.data.zoom < flayer.layerDef.minzoom) { | ||||
|                 continue; | ||||
|             } | ||||
|             if(flayer.layerDef.maxAgeOfCache === 0){ | ||||
|             if (flayer.layerDef.maxAgeOfCache === 0) { | ||||
|                 return undefined; | ||||
|             } | ||||
|             const freshnessCalc = this.freshnesses.get(flayer.layerDef.id) | ||||
|             if(freshnessCalc === undefined){ | ||||
|             if (freshnessCalc === undefined) { | ||||
|                 console.warn("No freshness tracker found for ", flayer.layerDef.id) | ||||
|                 return undefined | ||||
|             } | ||||
|  |  | |||
|  | @ -19,8 +19,8 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti | |||
|     }; | ||||
|     private readonly _alreadyRegistered = new Set<UIEventSource<any>>(); | ||||
|     private readonly _is_dirty = new UIEventSource(false) | ||||
|     private previousFeatureSet : Set<any> = undefined; | ||||
|      | ||||
|     private previousFeatureSet: Set<any> = undefined; | ||||
| 
 | ||||
|     constructor( | ||||
|         state: { | ||||
|             locationControl: UIEventSource<{ zoom: number }>, | ||||
|  | @ -54,7 +54,7 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti | |||
|                 self.update() | ||||
|             } | ||||
|         }) | ||||
|          | ||||
| 
 | ||||
|         metataggingUpdated?.addCallback(_ => { | ||||
|             self._is_dirty.setData(true) | ||||
|         }) | ||||
|  | @ -66,7 +66,7 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti | |||
|         const self = this; | ||||
|         const layer = this.upstream.layer; | ||||
|         const features: { feature: any; freshness: Date }[] = (this.upstream.features.data ?? []); | ||||
|         const includedFeatureIds  = new Set<string>();  | ||||
|         const includedFeatureIds = new Set<string>(); | ||||
|         const newFeatures = (features ?? []).filter((f) => { | ||||
| 
 | ||||
|             self.registerCallback(f.feature) | ||||
|  | @ -97,29 +97,29 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti | |||
| 
 | ||||
|         const previousSet = this.previousFeatureSet; | ||||
|         this._is_dirty.setData(false) | ||||
|          | ||||
| 
 | ||||
|         // Is there any difference between the two sets?
 | ||||
|         if(previousSet !== undefined && previousSet.size === includedFeatureIds.size){ | ||||
|         if (previousSet !== undefined && previousSet.size === includedFeatureIds.size) { | ||||
|             // The size of the sets is the same - they _might_ be identical
 | ||||
|             const newItemFound = Array.from(includedFeatureIds).some(id => !previousSet.has(id)) | ||||
|             if(!newItemFound){ | ||||
|             if (!newItemFound) { | ||||
|                 // We know that: 
 | ||||
|                 // - The sets have the same size
 | ||||
|                 // - Every item from the new set has been found in the old set
 | ||||
|                 // which means they are identical!
 | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
| 
 | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         // Something new has been found!
 | ||||
|         this.features.setData(newFeatures); | ||||
|       | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private registerCallback(feature: any) { | ||||
|         const src = this.state?.allElements?.addOrGetElement(feature) | ||||
|         if(src == undefined){ | ||||
|         if (src == undefined) { | ||||
|             return | ||||
|         } | ||||
|         if (this._alreadyRegistered.has(src)) { | ||||
|  |  | |||
|  | @ -65,7 +65,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource { | |||
|                         for (const kv of change.tags) { | ||||
|                             feat.tags[kv.k] = kv.v | ||||
|                         } | ||||
|                         const geojson=  feat.asGeoJson(); | ||||
|                         const geojson = feat.asGeoJson(); | ||||
|                         allElementStorage.addOrGetElement(geojson) | ||||
|                         self.features.data.push({feature: geojson, freshness: new Date()}) | ||||
|                         self.features.ping() | ||||
|  | @ -81,7 +81,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource { | |||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 | ||||
| 
 | ||||
|                 try { | ||||
|                     const tags = {} | ||||
|                     for (const kv of change.tags) { | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ export default class SimpleFeatureSource implements FeatureSourceForLayer, Tiled | |||
|     public readonly bbox: BBox = BBox.global; | ||||
|     public readonly tileIndex: number; | ||||
| 
 | ||||
|     constructor(layer: FilteredLayer, tileIndex: number, featureSource?: UIEventSource<{ feature:any; freshness: Date }[]>) { | ||||
|     constructor(layer: FilteredLayer, tileIndex: number, featureSource?: UIEventSource<{ feature: any; freshness: Date }[]>) { | ||||
|         this.name = "SimpleFeatureSource(" + layer.layerDef.id + ")" | ||||
|         this.layer = layer | ||||
|         this.tileIndex = tileIndex ?? 0; | ||||
|  |  | |||
|  | @ -40,7 +40,7 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource { | |||
|                     json => { | ||||
|                         const data = new Map<number, Set<number>>(); | ||||
|                         for (const x in json) { | ||||
|                             if(x === "zoom"){ | ||||
|                             if (x === "zoom") { | ||||
|                                 continue | ||||
|                             } | ||||
|                             data.set(Number(x), new Set(json[x])) | ||||
|  | @ -91,7 +91,7 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource { | |||
|     public static RegisterWhitelist(url: string, json: any) { | ||||
|         const data = new Map<number, Set<number>>(); | ||||
|         for (const x in json) { | ||||
|             if(x === "zoom"){ | ||||
|             if (x === "zoom") { | ||||
|                 continue | ||||
|             } | ||||
|             data.set(Number(x), new Set(json[x])) | ||||
|  |  | |||
|  | @ -44,11 +44,11 @@ export default class DynamicTileSource implements TileHierarchy<FeatureSourceFor | |||
|                     return undefined | ||||
|                 } | ||||
|                 const tileRange = Tiles.TileRangeBetween(zoomlevel, bounds.getNorth(), bounds.getEast(), bounds.getSouth(), bounds.getWest()) | ||||
|                 if(tileRange.total > 10000){ | ||||
|                 if (tileRange.total > 10000) { | ||||
|                     console.error("Got a really big tilerange, bounds and location might be out of sync") | ||||
|                     return undefined | ||||
|                 } | ||||
|                  | ||||
| 
 | ||||
|                 const needed = Tiles.MapRange(tileRange, (x, y) => Tiles.tile_index(zoomlevel, x, y)).filter(i => !self._loadedTiles.has(i)) | ||||
|                 if (needed.length === 0) { | ||||
|                     return undefined | ||||
|  |  | |||
|  | @ -45,8 +45,8 @@ export default class FullNodeDatabaseSource implements TileHierarchy<FeatureSour | |||
|             for (const nodeId of osmWay.nodes) { | ||||
| 
 | ||||
|                 if (!this.parentWays.has(nodeId)) { | ||||
|                     const src =  new UIEventSource<OsmWay[]>([]) | ||||
|                     this.parentWays.set(nodeId,src) | ||||
|                     const src = new UIEventSource<OsmWay[]>([]) | ||||
|                     this.parentWays.set(nodeId, src) | ||||
|                     src.addCallback(parentWays => { | ||||
|                         const tgs = nodesById.get(nodeId).tags | ||||
|                         tgs    ["parent_ways"] = JSON.stringify(parentWays.map(w => w.tags)) | ||||
|  |  | |||
|  | @ -89,8 +89,8 @@ export default class OsmFeatureSource { | |||
|         if (z > 20) { | ||||
|             throw "This is an absurd high zoom level" | ||||
|         } | ||||
|          | ||||
|         if( z < 14){ | ||||
| 
 | ||||
|         if (z < 14) { | ||||
|             throw `Zoom ${z} is too much for OSM to handle! Use a higher zoom level!` | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -558,19 +558,19 @@ export class GeoOperations { | |||
|             const prevCoordinate = coordinates[i - 1] | ||||
| 
 | ||||
|             const distP = GeoOperations.distanceBetween(coordinate, prevCoordinate) | ||||
|             if(distP < 0.1){ | ||||
|             if (distP < 0.1) { | ||||
|                 coordinates.splice(i, 1) | ||||
|                 continue | ||||
|             } | ||||
|          | ||||
|             if(i == coordinates.length - 2){ | ||||
| 
 | ||||
|             if (i == coordinates.length - 2) { | ||||
|                 const distN = GeoOperations.distanceBetween(coordinate, nextCoordinate) | ||||
|                 if(distN < 0.1){ | ||||
|                 if (distN < 0.1) { | ||||
|                     coordinates.splice(i, 1) | ||||
|                     continue | ||||
|                 } | ||||
|             } | ||||
|              | ||||
| 
 | ||||
|             const bearingN = turf.bearing(coordinate, nextCoordinate) | ||||
|             const bearingP = turf.bearing(prevCoordinate, coordinate) | ||||
|             const diff = Math.abs(bearingN - bearingP) | ||||
|  | @ -683,8 +683,8 @@ export class GeoOperations { | |||
|         throw "CalculateIntersection fallthrough: can not calculate an intersection between features" | ||||
| 
 | ||||
|     } | ||||
|      | ||||
|      | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ export default class MetaTagging { | |||
| 
 | ||||
|     private static errorPrintCount = 0; | ||||
|     private static readonly stopErrorOutputAt = 10; | ||||
|     private static retaggingFuncCache = new Map<string, ((feature: any) => void)[]>() | ||||
| 
 | ||||
|     /** | ||||
|      * This method (re)calculates all metatags and calculated tags on every given object. | ||||
|  | @ -24,7 +25,7 @@ export default class MetaTagging { | |||
|     public static addMetatags(features: { feature: any; freshness: Date }[], | ||||
|                               params: ExtraFuncParams, | ||||
|                               layer: LayerConfig, | ||||
|                               state?: {allElements?: ElementStorage}, | ||||
|                               state?: { allElements?: ElementStorage }, | ||||
|                               options?: { | ||||
|                                   includeDates?: true | boolean, | ||||
|                                   includeNonDates?: true | boolean | ||||
|  | @ -56,7 +57,7 @@ export default class MetaTagging { | |||
|             const feature = ff.feature | ||||
|             const freshness = ff.freshness | ||||
|             let somethingChanged = false | ||||
|             let definedTags = new Set(Object.getOwnPropertyNames( feature.properties )) | ||||
|             let definedTags = new Set(Object.getOwnPropertyNames(feature.properties)) | ||||
|             for (const metatag of metatagsToApply) { | ||||
|                 try { | ||||
|                     if (!metatag.keys.some(key => feature.properties[key] === undefined)) { | ||||
|  | @ -65,7 +66,7 @@ export default class MetaTagging { | |||
|                     } | ||||
| 
 | ||||
|                     if (metatag.isLazy) { | ||||
|                         if(!metatag.keys.some(key => !definedTags.has(key))) { | ||||
|                         if (!metatag.keys.some(key => !definedTags.has(key))) { | ||||
|                             // All keys are defined - lets skip!
 | ||||
|                             continue | ||||
|                         } | ||||
|  | @ -104,7 +105,7 @@ export default class MetaTagging { | |||
|         } | ||||
|         return atLeastOneFeatureChanged | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     private static createFunctionsForFeature(layerId: string, calculatedTags: [string, string, boolean][]): ((feature: any) => void)[] { | ||||
|         const functions: ((feature: any) => any)[] = []; | ||||
|         for (const entry of calculatedTags) { | ||||
|  | @ -128,7 +129,7 @@ export default class MetaTagging { | |||
|                     delete feat.properties[key] | ||||
|                     feat.properties[key] = result; | ||||
|                     return result | ||||
|                 }catch(e){ | ||||
|                 } catch (e) { | ||||
|                     if (MetaTagging.errorPrintCount < MetaTagging.stopErrorOutputAt) { | ||||
|                         console.warn("Could not calculate a " + (isStrict ? "strict " : "") + " calculated tag for key " + key + " defined by " + code + " (in layer" + layerId + ") due to \n" + e + "\n. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features", e, e.stack) | ||||
|                         MetaTagging.errorPrintCount++; | ||||
|  | @ -138,10 +139,10 @@ export default class MetaTagging { | |||
|                     } | ||||
|                     return undefined; | ||||
|                 } | ||||
|             }  | ||||
|                  | ||||
|              | ||||
|             if(isStrict){ | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             if (isStrict) { | ||||
|                 functions.push(calculateAndAssign) | ||||
|                 continue | ||||
|             } | ||||
|  | @ -166,8 +167,6 @@ export default class MetaTagging { | |||
|         return functions; | ||||
|     } | ||||
| 
 | ||||
|     private static retaggingFuncCache = new Map<string, ((feature: any) => void)[]>() | ||||
| 
 | ||||
|     /** | ||||
|      * Creates the function which adds all the calculated tags to a feature. Called once per layer | ||||
|      * @param layer | ||||
|  | @ -182,7 +181,7 @@ export default class MetaTagging { | |||
|             return undefined; | ||||
|         } | ||||
| 
 | ||||
|         let functions :((feature: any) => void)[] = MetaTagging.retaggingFuncCache.get(layer.id); | ||||
|         let functions: ((feature: any) => void)[] = MetaTagging.retaggingFuncCache.get(layer.id); | ||||
|         if (functions === undefined) { | ||||
|             functions = MetaTagging.createFunctionsForFeature(layer.id, calculatedTags) | ||||
|             MetaTagging.retaggingFuncCache.set(layer.id, functions) | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ export default class ChangeLocationAction extends OsmChangeAction { | |||
|         theme: string, | ||||
|         reason: string | ||||
|     }) { | ||||
|         super(id,true); | ||||
|         super(id, true); | ||||
|         if (!id.startsWith("node/")) { | ||||
|             throw "Invalid ID: only 'node/number' is accepted" | ||||
|         } | ||||
|  | @ -19,7 +19,7 @@ export default class ChangeLocationAction extends OsmChangeAction { | |||
|         this._newLonLat = newLonLat; | ||||
|         this._meta = meta; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     protected async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> { | ||||
| 
 | ||||
|         const d: ChangeDescription = { | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ export default class ChangeTagAction extends OsmChangeAction { | |||
|         this._currentTags = currentTags; | ||||
|         this._meta = meta; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     /** | ||||
|      * Doublechecks that no stupid values are added | ||||
|      */ | ||||
|  |  | |||
|  | @ -14,35 +14,36 @@ import {TagUtils} from "../../Tags/TagUtils"; | |||
|  * More or less the same as 'CreateNewWay', except that it'll try to reuse already existing points | ||||
|  */ | ||||
| export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAction { | ||||
|     private readonly _tags: Tag[]; | ||||
|     public newElementId: string = undefined; | ||||
|     public newElementIdNumber: number  = undefined; | ||||
|     public newElementIdNumber: number = undefined; | ||||
|     private readonly _tags: Tag[]; | ||||
|     private readonly createOuterWay: CreateWayWithPointReuseAction | ||||
|     private readonly createInnerWays : CreateNewWayAction[] | ||||
| private readonly geojsonPreview: any; | ||||
|     private readonly createInnerWays: CreateNewWayAction[] | ||||
|     private readonly geojsonPreview: any; | ||||
|     private readonly theme: string; | ||||
|     private readonly changeType: "import" | "create" | string; | ||||
| 
 | ||||
|     constructor(tags: Tag[], | ||||
|                 outerRingCoordinates: [number, number][], | ||||
|                 innerRingsCoordinates:  [number, number][][], | ||||
|                 innerRingsCoordinates: [number, number][][], | ||||
|                 state: FeaturePipelineState, | ||||
|                 config: MergePointConfig[], | ||||
|                 changeType: "import" | "create" | string | ||||
|     ) { | ||||
|         super(null,true); | ||||
|         this._tags = [...tags, new Tag("type","multipolygon")]; | ||||
|         super(null, true); | ||||
|         this._tags = [...tags, new Tag("type", "multipolygon")]; | ||||
|         this.changeType = changeType; | ||||
|         this.theme = state.layoutToUse.id | ||||
|         this. createOuterWay = new CreateWayWithPointReuseAction([], outerRingCoordinates, state, config) | ||||
|         this. createInnerWays = innerRingsCoordinates.map(ringCoordinates =>  | ||||
|             new CreateNewWayAction([],  | ||||
|             ringCoordinates.map(([lon, lat] )=> ({lat, lon})),  | ||||
|             {theme: state.layoutToUse.id})) | ||||
|          | ||||
|         this.geojsonPreview =  { | ||||
|         this.createOuterWay = new CreateWayWithPointReuseAction([], outerRingCoordinates, state, config) | ||||
|         this.createInnerWays = innerRingsCoordinates.map(ringCoordinates => | ||||
|             new CreateNewWayAction([], | ||||
|                 ringCoordinates.map(([lon, lat]) => ({lat, lon})), | ||||
|                 {theme: state.layoutToUse.id})) | ||||
| 
 | ||||
|         this.geojsonPreview = { | ||||
|             type: "Feature", | ||||
|             properties: TagUtils.changeAsProperties(new And(this._tags).asChange({})), | ||||
|             geometry:{ | ||||
|             geometry: { | ||||
|                 type: "Polygon", | ||||
|                 coordinates: [ | ||||
|                     outerRingCoordinates, | ||||
|  | @ -59,7 +60,7 @@ private readonly geojsonPreview: any; | |||
|             freshness: new Date(), | ||||
|             feature: this.geojsonPreview | ||||
|         }) | ||||
|        return outerPreview | ||||
|         return outerPreview | ||||
|     } | ||||
| 
 | ||||
|     protected async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> { | ||||
|  | @ -72,14 +73,14 @@ private readonly geojsonPreview: any; | |||
| 
 | ||||
| 
 | ||||
|         this.newElementIdNumber = changes.getNewID(); | ||||
|         this.newElementId = "relation/"+this.newElementIdNumber | ||||
|         this.newElementId = "relation/" + this.newElementIdNumber | ||||
|         descriptions.push({ | ||||
|             type:"relation", | ||||
|             type: "relation", | ||||
|             id: this.newElementIdNumber, | ||||
|             tags: new And(this._tags).asChange({}), | ||||
|             meta: { | ||||
|                 theme: this.theme, | ||||
|                 changeType:this.changeType | ||||
|                 changeType: this.changeType | ||||
|             }, | ||||
|             changes: { | ||||
|                 members: [ | ||||
|  | @ -93,8 +94,8 @@ private readonly geojsonPreview: any; | |||
|                 ] | ||||
|             } | ||||
|         }) | ||||
|          | ||||
|          | ||||
| 
 | ||||
| 
 | ||||
|         return descriptions | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -33,7 +33,7 @@ export default class CreateNewNodeAction extends OsmCreateAction { | |||
|                     changeType: "create" | "import" | null, | ||||
|                     specialMotivation?: string | ||||
|                 }) { | ||||
|         super(null,basicTags !== undefined && basicTags.length > 0) | ||||
|         super(null, basicTags !== undefined && basicTags.length > 0) | ||||
|         this._basicTags = basicTags; | ||||
|         this._lat = lat; | ||||
|         this._lon = lon; | ||||
|  | @ -46,7 +46,7 @@ export default class CreateNewNodeAction extends OsmCreateAction { | |||
|         this.meta = { | ||||
|             theme: options.theme, | ||||
|             changeType: options.changeType, | ||||
|              | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ export default class CreateNewWayAction extends OsmCreateAction { | |||
|                 options: { | ||||
|                     theme: string | ||||
|                 }) { | ||||
|         super(null,true) | ||||
|         super(null, true) | ||||
|         this.coordinates = coordinates; | ||||
|         this.tags = tags; | ||||
|         this._options = options; | ||||
|  | @ -56,7 +56,7 @@ export default class CreateNewWayAction extends OsmCreateAction { | |||
| 
 | ||||
| 
 | ||||
|         const id = changes.getNewID() | ||||
|         this.newElementIdNumber  = id | ||||
|         this.newElementIdNumber = id | ||||
|         const newWay = <ChangeDescription>{ | ||||
|             id, | ||||
|             type: "way", | ||||
|  |  | |||
|  | @ -20,14 +20,14 @@ export interface MergePointConfig { | |||
| 
 | ||||
| /** | ||||
|  * CreateWayWithPointreuse will create a 'CoordinateInfo' for _every_ point in the way to be created. | ||||
|  *  | ||||
|  * | ||||
|  * The CoordinateInfo indicates the action to take, e.g.: | ||||
|  *  | ||||
|  * | ||||
|  * - Create a new point | ||||
|  * - Reuse an existing OSM point (and don't move it) | ||||
|  * - Reuse an existing OSM point (and leave it where it is) | ||||
|  * - Reuse another Coordinate info (and don't do anything else with it) | ||||
|  *  | ||||
|  * | ||||
|  */ | ||||
| interface CoordinateInfo { | ||||
|     /** | ||||
|  | @ -56,6 +56,8 @@ interface CoordinateInfo { | |||
|  * More or less the same as 'CreateNewWay', except that it'll try to reuse already existing points | ||||
|  */ | ||||
| export default class CreateWayWithPointReuseAction extends OsmCreateAction { | ||||
|     public newElementId: string = undefined; | ||||
|     public newElementIdNumber: number = undefined | ||||
|     private readonly _tags: Tag[]; | ||||
|     /** | ||||
|      * lngLat-coordinates | ||||
|  | @ -64,20 +66,17 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { | |||
|     private _coordinateInfo: CoordinateInfo[]; | ||||
|     private _state: FeaturePipelineState; | ||||
|     private _config: MergePointConfig[]; | ||||
|      | ||||
|     public newElementId: string = undefined; | ||||
|     public newElementIdNumber: number = undefined | ||||
| 
 | ||||
|     constructor(tags: Tag[], | ||||
|                 coordinates: [number, number][], | ||||
|                 state: FeaturePipelineState, | ||||
|                 config: MergePointConfig[] | ||||
|     ) { | ||||
|         super(null,true); | ||||
|         super(null, true); | ||||
|         this._tags = tags; | ||||
|         this._state = state; | ||||
|         this._config = config; | ||||
|          | ||||
| 
 | ||||
|         // The main logic of this class: the coordinateInfo contains all the changes
 | ||||
|         this._coordinateInfo = this.CalculateClosebyNodes(coordinates); | ||||
| 
 | ||||
|  | @ -117,7 +116,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { | |||
|                         "move": "yes", | ||||
|                         "osm-id": reusedPoint.node.properties.id, | ||||
|                         "id": "new-geometry-move-existing" + i, | ||||
|                         "distance":GeoOperations.distanceBetween(coordinateInfo.lngLat, reusedPoint.node.geometry.coordinates) | ||||
|                         "distance": GeoOperations.distanceBetween(coordinateInfo.lngLat, reusedPoint.node.geometry.coordinates) | ||||
|                     }, | ||||
|                     geometry: { | ||||
|                         type: "LineString", | ||||
|  | @ -136,7 +135,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { | |||
|                         "move": "no", | ||||
|                         "osm-id": reusedPoint.node.properties.id, | ||||
|                         "id": "new-geometry-reuse-existing" + i, | ||||
|                         "distance":GeoOperations.distanceBetween(coordinateInfo.lngLat, reusedPoint.node.geometry.coordinates) | ||||
|                         "distance": GeoOperations.distanceBetween(coordinateInfo.lngLat, reusedPoint.node.geometry.coordinates) | ||||
|                     }, | ||||
|                     geometry: { | ||||
|                         type: "LineString", | ||||
|  | @ -238,7 +237,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { | |||
|         const newWay = new CreateNewWayAction(this._tags, nodeIdsToUse, { | ||||
|             theme | ||||
|         }) | ||||
|          | ||||
| 
 | ||||
|         allChanges.push(...(await newWay.CreateChangeDescriptions(changes))) | ||||
|         this.newElementId = newWay.newElementId | ||||
|         this.newElementIdNumber = newWay.newElementIdNumber | ||||
|  | @ -266,7 +265,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { | |||
|             }[] | ||||
|         }[] = coordinates.map(_ => undefined) | ||||
| 
 | ||||
|          | ||||
| 
 | ||||
|         // First loop: gather all information...
 | ||||
|         for (let i = 0; i < coordinates.length; i++) { | ||||
| 
 | ||||
|  | @ -328,7 +327,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { | |||
| 
 | ||||
|         } | ||||
| 
 | ||||
|          | ||||
| 
 | ||||
|         // Second loop: figure out which point moves where without creating conflicts
 | ||||
|         let conflictFree = true; | ||||
|         do { | ||||
|  | @ -348,8 +347,8 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { | |||
|                     if (other.closebyNodes === undefined || other.closebyNodes[0] === undefined) { | ||||
|                         continue | ||||
|                     } | ||||
|                      | ||||
|                     if(coorInfo.closebyNodes[0] === undefined){ | ||||
| 
 | ||||
|                     if (coorInfo.closebyNodes[0] === undefined) { | ||||
|                         continue | ||||
|                     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ export default class DeleteAction extends OsmChangeAction { | |||
|                     specialMotivation: string | ||||
|                 }, | ||||
|                 hardDelete: boolean) { | ||||
|         super(id,true) | ||||
|         super(id, true) | ||||
|         this._id = id; | ||||
|         this._hardDelete = hardDelete; | ||||
|         this.meta = {...meta, changeType: "deletion"}; | ||||
|  | @ -51,7 +51,7 @@ export default class DeleteAction extends OsmChangeAction { | |||
|             return await new ChangeTagAction( | ||||
|                 this._id, this._softDeletionTags, osmObject.tags, | ||||
|                 { | ||||
|                     ... this.meta, | ||||
|                     ...this.meta, | ||||
|                     changeType: "soft-delete" | ||||
|                 } | ||||
|             ).CreateChangeDescriptions(changes) | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ import {ChangeDescription} from "./ChangeDescription"; | |||
| 
 | ||||
| export default abstract class OsmChangeAction { | ||||
| 
 | ||||
|     private isUsed = false | ||||
|     public readonly trackStatistics: boolean; | ||||
|     /** | ||||
|      * The ID of the object that is the center of this change. | ||||
|  | @ -15,7 +14,8 @@ export default abstract class OsmChangeAction { | |||
|      * Undefined if such an id does not make sense | ||||
|      */ | ||||
|     public readonly mainObjectId: string; | ||||
|      | ||||
|     private isUsed = false | ||||
| 
 | ||||
|     constructor(mainObjectId: string, trackStatistics: boolean = true) { | ||||
|         this.trackStatistics = trackStatistics; | ||||
|         this.mainObjectId = mainObjectId | ||||
|  | @ -32,9 +32,9 @@ export default abstract class OsmChangeAction { | |||
|     protected abstract CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> | ||||
| } | ||||
| 
 | ||||
| export abstract class OsmCreateAction extends OsmChangeAction{ | ||||
| export abstract class OsmCreateAction extends OsmChangeAction { | ||||
| 
 | ||||
|     public newElementId : string | ||||
|     public newElementId: string | ||||
|     public newElementIdNumber: number | ||||
|      | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -16,10 +16,11 @@ abstract class AbstractRelationSplitHandler extends OsmChangeAction { | |||
|     protected readonly _theme: string; | ||||
| 
 | ||||
|     constructor(input: RelationSplitInput, theme: string) { | ||||
|         super("relation/"+input.relation.id, false) | ||||
|         super("relation/" + input.relation.id, false) | ||||
|         this._input = input; | ||||
|         this._theme = theme; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns which node should border the member at the given index | ||||
|      */ | ||||
|  |  | |||
|  | @ -468,12 +468,12 @@ export default class ReplaceGeometryAction extends OsmChangeAction { | |||
|             proj.sort((a, b) => { | ||||
|                 // Sort descending
 | ||||
|                 const diff = b.projectAfterIndex - a.projectAfterIndex; | ||||
|                 if(diff !== 0){ | ||||
|                 if (diff !== 0) { | ||||
|                     return diff | ||||
|                 } | ||||
|                 return b.distance - a.distance; | ||||
|                  | ||||
|                  | ||||
| 
 | ||||
| 
 | ||||
|             }) | ||||
| 
 | ||||
|             for (const reprojectedNode of proj) { | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ export default class SplitAction extends OsmChangeAction { | |||
|      * @param toleranceInMeters: if a splitpoint closer then this amount of meters to an existing point, the existing point will be used to split the line instead of a new point | ||||
|      */ | ||||
|     constructor(wayId: string, splitPointCoordinates: [number, number][], meta: { theme: string }, toleranceInMeters = 5) { | ||||
|         super(wayId,true) | ||||
|         super(wayId, true) | ||||
|         this.wayId = wayId; | ||||
|         this._splitPointsCoordinates = splitPointCoordinates | ||||
|         this._toleranceInMeters = toleranceInMeters; | ||||
|  |  | |||
|  | @ -27,16 +27,13 @@ export class Changes { | |||
|     public features = new UIEventSource<{ feature: any, freshness: Date }[]>([]); | ||||
|     public readonly pendingChanges: UIEventSource<ChangeDescription[]> = LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", []) | ||||
|     public readonly allChanges = new UIEventSource<ChangeDescription[]>(undefined) | ||||
|     public readonly state: { allElements: ElementStorage; historicalUserLocations: FeatureSource; osmConnection: OsmConnection } | ||||
|     public readonly extraComment: UIEventSource<string> = new UIEventSource(undefined) | ||||
|     private _nextId: number = -1; // Newly assigned ID's are negative
 | ||||
|     private readonly isUploading = new UIEventSource(false); | ||||
| 
 | ||||
|     private readonly previouslyCreated: OsmObject[] = [] | ||||
|     private readonly _leftRightSensitive: boolean; | ||||
| 
 | ||||
|     public readonly state: { allElements: ElementStorage; historicalUserLocations: FeatureSource; osmConnection: OsmConnection } | ||||
|      | ||||
|     public readonly extraComment:UIEventSource<string> = new UIEventSource(undefined) | ||||
| 
 | ||||
|     constructor( | ||||
|         state?: { | ||||
|             allElements: ElementStorage, | ||||
|  | @ -107,7 +104,7 @@ export class Changes { | |||
|      * Uploads all the pending changes in one go. | ||||
|      * Triggered by the 'PendingChangeUploader'-actor in Actors | ||||
|      */ | ||||
|     public async flushChanges(flushreason: string = undefined, openChangeset?: UIEventSource<number>) : Promise<void>{ | ||||
|     public async flushChanges(flushreason: string = undefined, openChangeset?: UIEventSource<number>): Promise<void> { | ||||
|         if (this.pendingChanges.data.length === 0) { | ||||
|             return; | ||||
|         } | ||||
|  | @ -116,19 +113,37 @@ export class Changes { | |||
|             return; | ||||
|         } | ||||
| 
 | ||||
|          | ||||
| 
 | ||||
|         console.log("Uploading changes due to: ", flushreason) | ||||
|         this.isUploading.setData(true) | ||||
|         try { | ||||
|             const csNumber = await this.flushChangesAsync(openChangeset) | ||||
|             this.isUploading.setData(false) | ||||
|             console.log("Changes flushed. Your changeset is "+csNumber); | ||||
|             console.log("Changes flushed. Your changeset is " + csNumber); | ||||
|         } catch (e) { | ||||
|             this.isUploading.setData(false) | ||||
|             console.error("Flushing changes failed due to", e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public async applyAction(action: OsmChangeAction): Promise<void> { | ||||
|         const changeDescriptions = await action.Perform(this) | ||||
|         changeDescriptions[0].meta.distanceToObject = this.calculateDistanceToChanges(action, changeDescriptions) | ||||
|         this.applyChanges(changeDescriptions) | ||||
|     } | ||||
| 
 | ||||
|     public applyChanges(changes: ChangeDescription[]) { | ||||
|         console.log("Received changes:", changes) | ||||
|         this.pendingChanges.data.push(...changes); | ||||
|         this.pendingChanges.ping(); | ||||
|         this.allChanges.data.push(...changes) | ||||
|         this.allChanges.ping() | ||||
|     } | ||||
| 
 | ||||
|     public registerIdRewrites(mappings: Map<string, string>): void { | ||||
|         CreateNewNodeAction.registerIdRewrites(mappings) | ||||
|     } | ||||
| 
 | ||||
|     private calculateDistanceToChanges(change: OsmChangeAction, changeDescriptions: ChangeDescription[]) { | ||||
| 
 | ||||
|         const locations = this.state?.historicalUserLocations?.features?.data | ||||
|  | @ -140,7 +155,7 @@ export class Changes { | |||
|             // Probably irrelevant, such as a new helper node
 | ||||
|             return; | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         const now = new Date() | ||||
|         const recentLocationPoints = locations.map(ff => ff.feature) | ||||
|             .filter(feat => feat.geometry.type === "Point") | ||||
|  | @ -188,26 +203,6 @@ export class Changes { | |||
|         )) | ||||
|     } | ||||
| 
 | ||||
|     public async applyAction(action: OsmChangeAction): Promise<void> { | ||||
|         const changeDescriptions = await action.Perform(this) | ||||
|         changeDescriptions[0].meta.distanceToObject = this.calculateDistanceToChanges(action, changeDescriptions) | ||||
|         this.applyChanges(changeDescriptions) | ||||
|     } | ||||
| 
 | ||||
|     public applyChanges(changes: ChangeDescription[]) { | ||||
|         console.log("Received changes:", changes) | ||||
|         this.pendingChanges.data.push(...changes); | ||||
|         this.pendingChanges.ping(); | ||||
|         this.allChanges.data.push(...changes) | ||||
|         this.allChanges.ping() | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public registerIdRewrites(mappings: Map<string, string>): void { | ||||
|         CreateNewNodeAction.registerIdRewrites(mappings) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * UPload the selected changes to OSM. | ||||
|      * Returns 'true' if successfull and if they can be removed | ||||
|  | @ -287,10 +282,10 @@ export class Changes { | |||
|         // This method is only called with changedescriptions for this theme
 | ||||
|         const theme = pending[0].meta.theme | ||||
|         let comment = "Adding data with #MapComplete for theme #" + theme | ||||
|         if(this.extraComment.data !== undefined){ | ||||
|             comment+="\n\n"+this.extraComment.data | ||||
|         if (this.extraComment.data !== undefined) { | ||||
|             comment += "\n\n" + this.extraComment.data | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         const metatags: ChangesetTag[] = [{ | ||||
|             key: "comment", | ||||
|             value: comment | ||||
|  | @ -329,10 +324,10 @@ export class Changes { | |||
|                 pendingPerTheme.get(theme).push(changeDescription) | ||||
|             } | ||||
| 
 | ||||
|             const successes = await Promise.all(Array.from(pendingPerTheme,  | ||||
|                async ([theme, pendingChanges]) => { | ||||
|             const successes = await Promise.all(Array.from(pendingPerTheme, | ||||
|                 async ([theme, pendingChanges]) => { | ||||
|                     try { | ||||
|                         if(openChangeset === undefined){ | ||||
|                         if (openChangeset === undefined) { | ||||
|                             openChangeset = this.state.osmConnection.GetPreference("current-open-changeset-" + theme).map( | ||||
|                                 str => { | ||||
|                                     const n = Number(str); | ||||
|  | @ -342,9 +337,9 @@ export class Changes { | |||
|                                     return n | ||||
|                                 }, [], n => "" + n | ||||
|                             ); | ||||
|                             console.log("Using current-open-changeset-"+theme+" from the preferences, got "+openChangeset.data) | ||||
|                             console.log("Using current-open-changeset-" + theme + " from the preferences, got " + openChangeset.data) | ||||
|                         } | ||||
|                          | ||||
| 
 | ||||
|                         return await self.flushSelectChanges(pendingChanges, openChangeset); | ||||
|                     } catch (e) { | ||||
|                         console.error("Could not upload some changes:", e) | ||||
|  | @ -395,7 +390,7 @@ export class Changes { | |||
|                     // Might be a failed fetch for simply this object
 | ||||
|                     throw "Did not get an object that should be known: " + id | ||||
|                 } | ||||
|                 if(change.changes === undefined){ | ||||
|                 if (change.changes === undefined) { | ||||
|                     // This object is a change to a newly created object. However, we have not seen the creation changedescription yet!
 | ||||
|                     throw "Not a creation of the object" | ||||
|                 } | ||||
|  | @ -522,7 +517,7 @@ export class Changes { | |||
| 
 | ||||
|         }) | ||||
| 
 | ||||
|         console.debug("Calculated the pending changes: ", result.newObjects.length,"new; ", result.modifiedObjects.length,"modified;",result.deletedObjects,"deleted") | ||||
|         console.debug("Calculated the pending changes: ", result.newObjects.length, "new; ", result.modifiedObjects.length, "modified;", result.deletedObjects, "deleted") | ||||
|         return result | ||||
|     } | ||||
| } | ||||
|  | @ -61,7 +61,7 @@ export class ChangesetHandler { | |||
|         if (!extraMetaTags.some(tag => tag.key === "comment") || !extraMetaTags.some(tag => tag.key === "theme")) { | ||||
|             throw "The meta tags should at least contain a `comment` and a `theme`" | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         if (this.userDetails.data.csCount == 0) { | ||||
|             // The user became a contributor!
 | ||||
|             this.userDetails.data.csCount = 1; | ||||
|  |  | |||
|  | @ -122,6 +122,34 @@ export class OsmPreferences { | |||
|         return pref; | ||||
|     } | ||||
| 
 | ||||
|     public ClearPreferences() { | ||||
|         let isRunning = false; | ||||
|         const self = this; | ||||
|         this.preferences.addCallbackAndRun(prefs => { | ||||
|             if (Object.keys(prefs).length == 0) { | ||||
|                 return; | ||||
|             } | ||||
|             if (isRunning) { | ||||
|                 return | ||||
|             } | ||||
|             isRunning = true | ||||
|             const prefixes = ["mapcomplete-installed-theme", "mapcomplete-installed-themes-", "mapcomplete-current-open-changeset", "mapcomplete-personal-theme-layer"] | ||||
|             for (const key in prefs) { | ||||
|                 for (const prefix of prefixes) { | ||||
|                     // console.log(key)
 | ||||
|                     if (key.startsWith(prefix)) { | ||||
|                         console.log("Clearing ", key) | ||||
|                         self.GetPreference(key, "").setData("") | ||||
| 
 | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             isRunning = false; | ||||
|             return true; | ||||
| 
 | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     private UpdatePreferences() { | ||||
|         const self = this; | ||||
|         this.auth.xhr({ | ||||
|  | @ -184,34 +212,6 @@ export class OsmPreferences { | |||
|             console.debug(`Preference ${k} written!`); | ||||
|         }); | ||||
|     } | ||||
|      | ||||
|     public ClearPreferences(){ | ||||
|         let isRunning = false; | ||||
|         const self = this; | ||||
|         this.preferences.addCallbackAndRun(prefs => { | ||||
|             if(Object.keys(prefs).length == 0){ | ||||
|                 return; | ||||
|             } | ||||
|             if (isRunning) { | ||||
|                 return | ||||
|             } | ||||
|             isRunning = true | ||||
|             const prefixes = ["mapcomplete-installed-theme","mapcomplete-installed-themes-","mapcomplete-current-open-changeset","mapcomplete-personal-theme-layer"] | ||||
|             for (const key in prefs) { | ||||
|                 for (const prefix of prefixes) { | ||||
|                     // console.log(key)
 | ||||
|                     if (key.startsWith(prefix)) { | ||||
|                         console.log("Clearing ", key) | ||||
|                         self.GetPreference(key, "").setData("") | ||||
| 
 | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             isRunning = false; | ||||
|             return true; | ||||
| 
 | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -125,7 +125,8 @@ export default class SimpleMetaTaggers { | |||
|             return changed | ||||
|         } | ||||
|     ) | ||||
| 
 | ||||
|     public static readonly lazyTags: string[] = [].concat(...SimpleMetaTaggers.metatags.filter(tagger => tagger.isLazy) | ||||
|         .map(tagger => tagger.keys)); | ||||
|     private static readonly cardinalDirections = { | ||||
|         N: 0, NNE: 22.5, NE: 45, ENE: 67.5, | ||||
|         E: 90, ESE: 112.5, SE: 135, SSE: 157.5, | ||||
|  | @ -290,27 +291,27 @@ export default class SimpleMetaTaggers { | |||
|                 // isOpen is irrelevant
 | ||||
|                 return false | ||||
|             } | ||||
|             if(feature.properties.opening_hours === "24/7"){ | ||||
|             if (feature.properties.opening_hours === "24/7") { | ||||
|                 feature.properties._isOpen = "yes" | ||||
|                 return true; | ||||
|             } | ||||
|              | ||||
| 
 | ||||
|             Object.defineProperty(feature.properties, "_isOpen", { | ||||
|                 enumerable: false, | ||||
|                 configurable: true, | ||||
|                 get: () => { | ||||
|                     if(feature.properties.id === "node/7464543832"){ | ||||
|                     console.log("Getting _isOpen for ", feature.properties.i) | ||||
|                     if (feature.properties.id === "node/7464543832") { | ||||
|                         console.log("Getting _isOpen for ", feature.properties.i) | ||||
|                     } | ||||
|                     delete feature.properties._isOpen | ||||
|                     feature.properties._isOpen = undefined | ||||
|                     const tagsSource = state.allElements.getEventSourceById(feature.properties.id); | ||||
|                     tagsSource.addCallbackAndRunD(tags => { | ||||
|                         // Install a listener to the tags...
 | ||||
|                         if (tags.opening_hours === undefined){ | ||||
|                         if (tags.opening_hours === undefined) { | ||||
|                             return; | ||||
|                         } | ||||
|                         if(tags._country === undefined) { | ||||
|                         if (tags._country === undefined) { | ||||
|                             return; | ||||
|                         } | ||||
|                         try { | ||||
|  | @ -322,7 +323,7 @@ export default class SimpleMetaTaggers { | |||
|                                     country_code: tags._country.toLowerCase() | ||||
|                                 } | ||||
|                             }, {tag_key: "opening_hours"}); | ||||
|                              | ||||
| 
 | ||||
|                             // AUtomatically triggered on the next change (and a bit below)
 | ||||
|                             const updateTags = () => { | ||||
|                                 const oldValueIsOpen = tags["_isOpen"]; | ||||
|  | @ -442,8 +443,6 @@ export default class SimpleMetaTaggers { | |||
|         SimpleMetaTaggers.geometryType | ||||
| 
 | ||||
|     ]; | ||||
|     public static readonly lazyTags: string[] = [].concat(...SimpleMetaTaggers.metatags.filter(tagger => tagger.isLazy) | ||||
|         .map(tagger => tagger.keys)); | ||||
| 
 | ||||
|     /** | ||||
|      * Edits the given object to rewrite 'both'-tagging into a 'left-right' tagging scheme. | ||||
|  |  | |||
|  | @ -49,7 +49,7 @@ export default class ElementsState extends FeatureSwitchState { | |||
|         super(layoutToUse); | ||||
| 
 | ||||
|         // @ts-ignore
 | ||||
|         this.changes = new Changes(this,layoutToUse?.isLeftRightSensitive() ?? false) | ||||
|         this.changes = new Changes(this, layoutToUse?.isLeftRightSensitive() ?? false) | ||||
|         { | ||||
|             // -- Location control initialization
 | ||||
|             const zoom = UIEventSource.asFloat( | ||||
|  |  | |||
|  | @ -20,7 +20,8 @@ export default class FeaturePipelineState extends MapState { | |||
|      */ | ||||
|     public readonly featurePipeline: FeaturePipeline; | ||||
|     private readonly featureAggregator: TileHierarchyAggregator; | ||||
| private readonly metatagRecalculator : MetaTagRecalculator | ||||
|     private readonly metatagRecalculator: MetaTagRecalculator | ||||
| 
 | ||||
|     constructor(layoutToUse: LayoutConfig) { | ||||
|         super(layoutToUse); | ||||
| 
 | ||||
|  | @ -33,21 +34,21 @@ private readonly metatagRecalculator : MetaTagRecalculator | |||
|          * We are a bit in a bind: | ||||
|          * There is the featurePipeline, which creates some sources during construction | ||||
|          * THere is the metatagger, which needs to have these sources registered AND which takes a FeaturePipeline as argument | ||||
|          *  | ||||
|          * | ||||
|          * This is a bit of a catch-22 (except that it isn't) | ||||
|          * The sources that are registered in the constructor are saved into 'registeredSources' temporary  | ||||
|          *  | ||||
|          * The sources that are registered in the constructor are saved into 'registeredSources' temporary | ||||
|          * | ||||
|          */ | ||||
|         const sourcesToRegister = [] | ||||
|          | ||||
|         function registerRaw(source: FeatureSourceForLayer & Tiled){ | ||||
|             if(self.metatagRecalculator === undefined){ | ||||
| 
 | ||||
|         function registerRaw(source: FeatureSourceForLayer & Tiled) { | ||||
|             if (self.metatagRecalculator === undefined) { | ||||
|                 sourcesToRegister.push(source) | ||||
|             }else{ | ||||
|             } else { | ||||
|                 self.metatagRecalculator.registerSource(source) | ||||
|             } | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         function registerSource(source: FeatureSourceForLayer & Tiled) { | ||||
| 
 | ||||
|             clusterCounter.addTile(source) | ||||
|  | @ -127,7 +128,7 @@ private readonly metatagRecalculator : MetaTagRecalculator | |||
|         this.metatagRecalculator.registerSource(this.currentView, true) | ||||
| 
 | ||||
|         sourcesToRegister.forEach(source => self.metatagRecalculator.registerSource(source)) | ||||
|          | ||||
| 
 | ||||
|         new SelectedFeatureHandler(Hash.hash, this) | ||||
| 
 | ||||
|         this.AddClusteringToMap(this.leafletMap) | ||||
|  |  | |||
|  | @ -223,7 +223,7 @@ export default class MapState extends UserRelatedState { | |||
|     private initGpsLocation() { | ||||
|         // Initialize the gps layer data. This is emtpy for now, the actual writing happens in the Geolocationhandler
 | ||||
|         let gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "gps_location")[0] | ||||
|         if(gpsLayerDef === undefined){ | ||||
|         if (gpsLayerDef === undefined) { | ||||
|             return | ||||
|         } | ||||
|         this.currentUserLocation = new SimpleFeatureSource(gpsLayerDef, Tiles.tile_index(0, 0, 0)); | ||||
|  | @ -269,7 +269,7 @@ export default class MapState extends UserRelatedState { | |||
| 
 | ||||
| 
 | ||||
|         let gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "gps_location_history")[0] | ||||
|         if(gpsLayerDef !== undefined){ | ||||
|         if (gpsLayerDef !== undefined) { | ||||
|             this.historicalUserLocations = new SimpleFeatureSource(gpsLayerDef, Tiles.tile_index(0, 0, 0), features); | ||||
|         } | ||||
| 
 | ||||
|  | @ -299,7 +299,7 @@ export default class MapState extends UserRelatedState { | |||
|             }] | ||||
|         }) | ||||
|         let gpsLineLayerDef: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "gps_track")[0] | ||||
|         if(gpsLineLayerDef !== undefined){ | ||||
|         if (gpsLineLayerDef !== undefined) { | ||||
|             this.historicalUserLocationsTrack = new SimpleFeatureSource(gpsLineLayerDef, Tiles.tile_index(0, 0, 0), asLine); | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -96,14 +96,14 @@ export default class UserRelatedState extends ElementsState { | |||
|                     // We wait till we are logged in
 | ||||
|                     return | ||||
|                 } | ||||
|                  | ||||
|                 if(self.osmConnection.isLoggedIn.data == false){ | ||||
| 
 | ||||
|                 if (self.osmConnection.isLoggedIn.data == false) { | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 if (currentThemes.some(installed => installed.id === this.layoutToUse.id)) { | ||||
|                     // Already added to the 'installed theme' list
 | ||||
|                     return;  | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 console.log("Current installed themes are", this.installedThemes.data) | ||||
|  | @ -115,13 +115,11 @@ export default class UserRelatedState extends ElementsState { | |||
|                 }) | ||||
|                 self.installedThemes.ping() | ||||
|                 console.log("Registered " + self.layoutToUse.id + " as installed themes") | ||||
|              | ||||
| 
 | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme
 | ||||
|         this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") | ||||
|             .syncWith(this.osmConnection.GetLongPreference("favouriteLayers")) | ||||
|  |  | |||
|  | @ -117,7 +117,7 @@ export class And extends TagsFilter { | |||
|         } | ||||
|         return result; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     AsJson() { | ||||
|         return { | ||||
|             and: this.and.map(a => a.AsJson()) | ||||
|  |  | |||
|  | @ -38,7 +38,7 @@ export default class ComparingTag implements TagsFilter { | |||
|     usedKeys(): string[] { | ||||
|         return [this._key]; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     AsJson() { | ||||
|         return this.asHumanString(false, false, {}) | ||||
|     } | ||||
|  |  | |||
|  | @ -65,7 +65,7 @@ export class Or extends TagsFilter { | |||
|         } | ||||
|         return result; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     AsJson() { | ||||
|         return { | ||||
|             or: this.or.map(o => o.AsJson()) | ||||
|  |  | |||
|  | @ -46,13 +46,13 @@ export class Tag extends TagsFilter { | |||
|         if (shorten) { | ||||
|             v = Utils.EllipsesAfter(v, 25); | ||||
|         } | ||||
|         if(v === "" || v === undefined){ | ||||
|         if (v === "" || v === undefined) { | ||||
|             // This tag will be removed if in the properties, so we indicate this with special rendering
 | ||||
|             if(currentProperties !== undefined && (currentProperties[this.key] ?? "") === ""){ | ||||
|             if (currentProperties !== undefined && (currentProperties[this.key] ?? "") === "") { | ||||
|                 // This tag is not present in the current properties, so this tag doesn't change anything
 | ||||
|                 return "" | ||||
|             } | ||||
|             return "<span class='line-through'>"+this.key+"</span>" | ||||
|             return "<span class='line-through'>" + this.key + "</span>" | ||||
|         } | ||||
|         if (linkToWiki) { | ||||
|             return `<a href='https://wiki.openstreetmap.org/wiki/Key:${this.key}' target='_blank'>${this.key}</a>` + | ||||
|  | @ -83,7 +83,7 @@ export class Tag extends TagsFilter { | |||
|     asChange(properties: any): { k: string; v: string }[] { | ||||
|         return [{k: this.key, v: this.value}]; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     AsJson() { | ||||
|         return this.asHumanString(false, false) | ||||
|     } | ||||
|  |  | |||
|  | @ -245,7 +245,7 @@ export class TagUtils { | |||
|                 } | ||||
|                 return new RegexTag( | ||||
|                     split[0], | ||||
|                    new RegExp("^" + split[1] + "$"), | ||||
|                     new RegExp("^" + split[1] + "$"), | ||||
|                     true | ||||
|                 ); | ||||
|             } | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ export class UIEventSource<T> { | |||
|     constructor(data: T, tag: string = "") { | ||||
|         this.tag = tag; | ||||
|         this.data = data; | ||||
|         if(tag === undefined || tag === ""){ | ||||
|         if (tag === undefined || tag === "") { | ||||
|             const callstack = new Error().stack.split("\n") | ||||
|             this.tag = callstack[1] | ||||
|         } | ||||
|  | @ -42,7 +42,7 @@ export class UIEventSource<T> { | |||
|         source.addCallback((latestData) => { | ||||
|             sink.setData(latestData?.data); | ||||
|             latestData.addCallback(data => { | ||||
|                 if(source.data !== latestData){ | ||||
|                 if (source.data !== latestData) { | ||||
|                     return true; | ||||
|                 } | ||||
|                 sink.setData(data) | ||||
|  | @ -243,8 +243,8 @@ export class UIEventSource<T> { | |||
|             } | ||||
|         } | ||||
|         let endTime = new Date().getTime() / 1000 | ||||
|         if((endTime - startTime) > 500){ | ||||
|             console.trace("Warning: a ping of ",this.tag," took more then 500ms; this is probably a performance issue") | ||||
|         if ((endTime - startTime) > 500) { | ||||
|             console.trace("Warning: a ping of ", this.tag, " took more then 500ms; this is probably a performance issue") | ||||
|         } | ||||
|         if (toDelete !== undefined) { | ||||
|             for (const toDeleteElement of toDelete) { | ||||
|  | @ -297,10 +297,10 @@ export class UIEventSource<T> { | |||
| 
 | ||||
|         const stack = new Error().stack.split("\n"); | ||||
|         const callee = stack[1] | ||||
|          | ||||
| 
 | ||||
|         const newSource = new UIEventSource<J>( | ||||
|             f(this.data), | ||||
|             "map(" + this.tag + ")@"+callee | ||||
|             "map(" + this.tag + ")@" + callee | ||||
|         ); | ||||
| 
 | ||||
|         const update = function () { | ||||
|  |  | |||
|  | @ -7,24 +7,24 @@ import {Utils} from "../../Utils"; | |||
|  */ | ||||
| export class IdbLocalStorage { | ||||
| 
 | ||||
|      | ||||
|     public static Get<T>(key: string, options?: { defaultValue?: T , whenLoaded?: (t: T) => void}): UIEventSource<T>{ | ||||
|         const src = new UIEventSource<T>(options?.defaultValue, "idb-local-storage:"+key) | ||||
|         if(Utils.runningFromConsole){ | ||||
| 
 | ||||
|     public static Get<T>(key: string, options?: { defaultValue?: T, whenLoaded?: (t: T) => void }): UIEventSource<T> { | ||||
|         const src = new UIEventSource<T>(options?.defaultValue, "idb-local-storage:" + key) | ||||
|         if (Utils.runningFromConsole) { | ||||
|             return src; | ||||
|         } | ||||
|         idb.get(key).then(v => { | ||||
|             src.setData(v ?? options?.defaultValue); | ||||
|             if(options?.whenLoaded !== undefined){ | ||||
|             if (options?.whenLoaded !== undefined) { | ||||
|                 options?.whenLoaded(v) | ||||
|             } | ||||
|         }) | ||||
|         src.addCallback(v => idb.set(key, v)) | ||||
|         return src; | ||||
|          | ||||
| 
 | ||||
|     } | ||||
|      | ||||
|     public static SetDirectly(key: string, value){ | ||||
| 
 | ||||
|     public static SetDirectly(key: string, value) { | ||||
|         idb.set(key, value) | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,15 +7,12 @@ import {Utils} from "../../Utils"; | |||
| 
 | ||||
| export class QueryParameters { | ||||
| 
 | ||||
|     static defaults = {} | ||||
|     static documentation = {} | ||||
|     private static order: string [] = ["layout", "test", "z", "lat", "lon"]; | ||||
|     private static _wasInitialized: Set<string> = new Set() | ||||
|     private static knownSources = {}; | ||||
|     private static initialized = false; | ||||
|     static defaults = {} | ||||
| 
 | ||||
|     static documentation = {} | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     public static GetQueryParameter(key: string, deflt: string, documentation?: string): UIEventSource<string> { | ||||
|         if (!this.initialized) { | ||||
|  | @ -40,7 +37,6 @@ export class QueryParameters { | |||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     public static wasInitialized(key: string): boolean { | ||||
|         return QueryParameters._wasInitialized.has(key) | ||||
|     } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue