forked from MapComplete/MapComplete
		
	Add switch to hide certain features, cleanup of code
This commit is contained in:
		
							parent
							
								
									aa0989b72a
								
							
						
					
					
						commit
						1b1ec9f15d
					
				
					 18 changed files with 230 additions and 173 deletions
				
			
		|  | @ -24,6 +24,7 @@ export default class LayerConfig { | ||||||
|     static WAYHANDLING_DEFAULT = 0; |     static WAYHANDLING_DEFAULT = 0; | ||||||
|     static WAYHANDLING_CENTER_ONLY = 1; |     static WAYHANDLING_CENTER_ONLY = 1; | ||||||
|     static WAYHANDLING_CENTER_AND_WAY = 2; |     static WAYHANDLING_CENTER_AND_WAY = 2; | ||||||
|  |      | ||||||
|     id: string; |     id: string; | ||||||
|     name: Translation |     name: Translation | ||||||
|     description: Translation; |     description: Translation; | ||||||
|  | @ -31,6 +32,7 @@ export default class LayerConfig { | ||||||
|     calculatedTags: [string, string][] |     calculatedTags: [string, string][] | ||||||
|     doNotDownload: boolean; |     doNotDownload: boolean; | ||||||
|     passAllFeatures: boolean; |     passAllFeatures: boolean; | ||||||
|  |     isShown: TagRenderingConfig; | ||||||
|     minzoom: number; |     minzoom: number; | ||||||
|     maxzoom: number; |     maxzoom: number; | ||||||
|     title?: TagRenderingConfig; |     title?: TagRenderingConfig; | ||||||
|  | @ -205,6 +207,7 @@ export default class LayerConfig { | ||||||
|                 throw "Builtin SVG asset not found: " + iconPath |                 throw "Builtin SVG asset not found: " + iconPath | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         this.isShown = tr("isShown", "yes"); | ||||||
|         this.iconSize = tr("iconSize", "40,40,center"); |         this.iconSize = tr("iconSize", "40,40,center"); | ||||||
|         this.color = tr("color", "#0000ff"); |         this.color = tr("color", "#0000ff"); | ||||||
|         this.width = tr("width", "7"); |         this.width = tr("width", "7"); | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; | import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; | ||||||
| import {AndOrTagConfigJson} from "./TagConfigJson"; | import {AndOrTagConfigJson} from "./TagConfigJson"; | ||||||
|  | import TagRenderingConfig from "./TagRenderingConfig"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Configuration for a single layer |  * Configuration for a single layer | ||||||
|  | @ -52,6 +53,14 @@ export interface LayerConfigJson { | ||||||
|      */ |      */ | ||||||
|     doNotDownload?: boolean; |     doNotDownload?: boolean; | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * This tagrendering should either be 'yes' or 'no'. If 'no' is returned, then the feature will be hidden from view. | ||||||
|  |      * This is useful to hide certain features from view | ||||||
|  |      * The default value is 'yes' | ||||||
|  |      */ | ||||||
|  |     isShown?: TagRenderingConfigJson; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * The zoomlevel at which point the data is shown and loaded. |      * The zoomlevel at which point the data is shown and loaded. | ||||||
|      * Default: 0 |      * Default: 0 | ||||||
|  |  | ||||||
|  | @ -384,26 +384,7 @@ export class InitUiElements { | ||||||
|         State.state.layerUpdater = updater; |         State.state.layerUpdater = updater; | ||||||
|         const source = new FeaturePipeline(state.filteredLayers, updater, state.layoutToUse, state.changes, state.locationControl); |         const source = new FeaturePipeline(state.filteredLayers, updater, state.layoutToUse, state.changes, state.locationControl); | ||||||
| 
 | 
 | ||||||
| 
 |         new ShowDataLayer(source.features, State.state.leafletMap,            State.state.layoutToUse); | ||||||
|         source.features.addCallbackAndRun((featuresFreshness: { feature: any, freshness: Date }[]) => { |  | ||||||
|             if (featuresFreshness === undefined) { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|             featuresFreshness.forEach(featureFresh => { |  | ||||||
|                 const feature = featureFresh.feature; |  | ||||||
|                 State.state.allElements.addOrGetElement(feature); |  | ||||||
| 
 |  | ||||||
|                 if (Hash.hash.data === feature.properties.id) { |  | ||||||
|                     State.state.selectedElement.setData(feature); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|             }) |  | ||||||
| 
 |  | ||||||
|             MetaTagging.addMetatags(featuresFreshness, state.layoutToUse.data.layers); |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         new ShowDataLayer(source.features, State.state.leafletMap, |  | ||||||
|             State.state.layoutToUse); |  | ||||||
| 
 | 
 | ||||||
|         new SelectedFeatureHandler(Hash.hash, State.state.selectedElement, source); |         new SelectedFeatureHandler(Hash.hash, State.state.selectedElement, source); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -43,7 +43,7 @@ class TitleElement extends UIElement { | ||||||
|             } |             } | ||||||
|             if (layer.source.osmTags.matchesProperties(properties)) { |             if (layer.source.osmTags.matchesProperties(properties)) { | ||||||
|                 const title = new TagRenderingAnswer( |                 const title = new TagRenderingAnswer( | ||||||
|                     this._allElementsStorage.getEventSourceFor(feature), |                     this._allElementsStorage.addOrGetElement(feature), | ||||||
|                     layer.title |                     layer.title | ||||||
|                 ) |                 ) | ||||||
|                 return new Combine([defaultTitle, " | ", title]).Render(); |                 return new Combine([defaultTitle, " | ", title]).Render(); | ||||||
|  |  | ||||||
|  | @ -15,30 +15,42 @@ export class ElementStorage { | ||||||
|         this._elements[id] = eventSource; |         this._elements[id] = eventSource; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     addElement(element): UIEventSource<any> { |     /** | ||||||
|         const eventSource = new UIEventSource<any>(element.properties, "tags of "+element.properties.id); |      * Creates a UIEventSource for the tags of the given feature. | ||||||
|         this._elements[element.properties.id] = eventSource; |      * If an UIEventsource has been created previously, the same UIEventSource will be returned | ||||||
|         return eventSource; |      * | ||||||
|     } |      * Note: it will cleverly merge the tags, if needed | ||||||
| 
 |      */ | ||||||
|     addOrGetElement(element: any) : UIEventSource<any>{ |     addOrGetElement(feature: any): UIEventSource<any> { | ||||||
|         const elementId = element.properties.id; |         const elementId = feature.properties.id; | ||||||
|         if (elementId in this._elements) { |         if (elementId in this._elements) { | ||||||
|             const es = this._elements[elementId]; |             const es = this._elements[elementId]; | ||||||
|  |             if (es.data == feature.properties) { | ||||||
|  |                 // Reference comparison gives the same object! we can just return the event source
 | ||||||
|  |                 return es; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|             const keptKeys = es.data; |             const keptKeys = es.data; | ||||||
|             // The element already exists
 |             // The element already exists
 | ||||||
|             // We add all the new keys to the old keys
 |             // We add all the new keys to the old keys
 | ||||||
|             for (const k in element.properties) { |             let somethingChanged = false; | ||||||
|                 const v = element.properties[k]; |             for (const k in feature.properties) { | ||||||
|  |                 const v = feature.properties[k]; | ||||||
|                 if (keptKeys[k] !== v) { |                 if (keptKeys[k] !== v) { | ||||||
|                     keptKeys[k] = v; |                     keptKeys[k] = v; | ||||||
|                     es.ping(); |                     somethingChanged = true; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             if (somethingChanged) { | ||||||
|  |                 es.ping(); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             return es; |             return es; | ||||||
|         }else{ |         } else { | ||||||
|             return this.addElement(element); |             const eventSource = new UIEventSource<any>(feature.properties, "tags of " + feature.properties.id); | ||||||
|  |             this._elements[feature.properties.id] = eventSource; | ||||||
|  |             return eventSource; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -48,8 +60,4 @@ export class ElementStorage { | ||||||
|         } |         } | ||||||
|         console.error("Can not find eventsource with id ", elementId); |         console.error("Can not find eventsource with id ", elementId); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     getEventSourceFor(feature): UIEventSource<any> { |  | ||||||
|         return this.getEventSourceById(feature.properties.id); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
							
								
								
									
										81
									
								
								Logic/ExtraFunction.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								Logic/ExtraFunction.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,81 @@ | ||||||
|  | import {GeoOperations} from "./GeoOperations"; | ||||||
|  | import {UIElement} from "../UI/UIElement"; | ||||||
|  | import Combine from "../UI/Base/Combine"; | ||||||
|  | 
 | ||||||
|  | export class ExtraFunction { | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     private static DistanceToFunc = new ExtraFunction( | ||||||
|  |         "distanceTo", | ||||||
|  |         "Calculates the distance between the feature and a specified point", | ||||||
|  |         ["longitude", "latitude"], | ||||||
|  |         (feature) => { | ||||||
|  |             return (lon, lat) => { | ||||||
|  |                 // Feature._lon and ._lat is conveniently place by one of the other metatags
 | ||||||
|  |                 return GeoOperations.distanceBetween([lon, lat], [feature._lon, feature._lat]); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     private static readonly allFuncs: ExtraFunction[] = [ExtraFunction.DistanceToFunc]; | ||||||
|  |     private readonly _name: string; | ||||||
|  |     private readonly _args: string[]; | ||||||
|  |     private readonly _doc: string; | ||||||
|  |     private readonly _f: (feat: any) => any; | ||||||
|  | 
 | ||||||
|  |     constructor(name: string, doc: string, args: string[], f: ((feat: any) => any)) { | ||||||
|  |         this._name = name; | ||||||
|  |         this._doc = doc; | ||||||
|  |         this._args = args; | ||||||
|  |         this._f = f; | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static FullPatchFeature(feature) { | ||||||
|  |         for (const func of ExtraFunction.allFuncs) { | ||||||
|  |             func.PatchFeature(feature); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static HelpText(): UIElement { | ||||||
|  |         return new Combine([ | ||||||
|  |             ExtraFunction.intro, | ||||||
|  |             ...ExtraFunction.allFuncs.map(func => | ||||||
|  |                 new Combine([ | ||||||
|  |                     "<h3>" + func._name + "</h3>", | ||||||
|  |                     func._doc, | ||||||
|  |                     "<ul>", | ||||||
|  |                     ...func._args.map(arg => "<li>" + arg + "</li>"), | ||||||
|  |                     "</ul>" | ||||||
|  |                 ]) | ||||||
|  |             ) | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public PatchFeature(feature: any) { | ||||||
|  |         feature[this._name] = this._f(feature); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static readonly intro = `<h2>Calculating tags with Javascript</h2>
 | ||||||
|  | 
 | ||||||
|  | <p>In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. <b>_lat</b>, <b>lon</b>, <b>_country</b>), as detailed above.</p> | ||||||
|  | 
 | ||||||
|  | <p>It is also possible to calculate your own tags - but this requires some javascript knowledge. </p> | ||||||
|  | 
 | ||||||
|  | Before proceeding, some warnings: | ||||||
|  | 
 | ||||||
|  | <ul> | ||||||
|  | <li> DO NOT DO THIS AS BEGINNER</li> | ||||||
|  | <li> <b>Only do this if all other techniques fail</b>. This should <i>not</i> be done to create a rendering effect, only to calculate a specific value</li> | ||||||
|  | <li> <b>THIS MIGHT BE DISABLED WITHOUT ANY NOTICE ON UNOFFICIAL THEMES</b>. As unofficial themes might be loaded from the internet, this is the equivalent of injecting arbitrary code into the client. It'll be disabled if abuse occurs.</li> | ||||||
|  | </ul> | ||||||
|  | In the layer object, add a field <b>calculatedTags</b>, e.g.: | ||||||
|  | 
 | ||||||
|  | <div class="code"> | ||||||
|  |   "calculatedTags": { | ||||||
|  |     "_someKey": "javascript-expression", | ||||||
|  |     "name": "feat.properties.name ?? feat.properties.ref ?? feat.properties.operator", | ||||||
|  |     "_distanceCloserThen3Km": "feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'"  | ||||||
|  |   } | ||||||
|  | </div> | ||||||
|  | ` | ||||||
|  | } | ||||||
|  | @ -6,7 +6,7 @@ import LayerConfig from "../../Customizations/JSON/LayerConfig"; | ||||||
| /** | /** | ||||||
|  * In some rare cases, some elements are shown on multiple layers (when 'passthrough' is enabled) |  * In some rare cases, some elements are shown on multiple layers (when 'passthrough' is enabled) | ||||||
|  * If this is the case, multiple objects with a different _matching_layer_id are generated. |  * If this is the case, multiple objects with a different _matching_layer_id are generated. | ||||||
|  * If not, the _feature_layter_id is added |  * In any case, this featureSource marks the objects with _matching_layer_id | ||||||
|  */ |  */ | ||||||
| export default class FeatureDuplicatorPerLayer implements FeatureSource { | export default class FeatureDuplicatorPerLayer implements FeatureSource { | ||||||
|     public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; |     public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ import LocalStorageSource from "./LocalStorageSource"; | ||||||
| import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; | import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; | ||||||
| import Loc from "../../Models/Loc"; | import Loc from "../../Models/Loc"; | ||||||
| import GeoJsonSource from "./GeoJsonSource"; | import GeoJsonSource from "./GeoJsonSource"; | ||||||
|  | import MetaTaggingFeatureSource from "./MetaTaggingFeatureSource"; | ||||||
| 
 | 
 | ||||||
| export default class FeaturePipeline implements FeatureSource { | export default class FeaturePipeline implements FeatureSource { | ||||||
| 
 | 
 | ||||||
|  | @ -25,42 +26,42 @@ export default class FeaturePipeline implements FeatureSource { | ||||||
| 
 | 
 | ||||||
|         const amendedOverpassSource = |         const amendedOverpassSource = | ||||||
|             new RememberingSource( |             new RememberingSource( | ||||||
|                 new WayHandlingApplyingFeatureSource(flayers, |  | ||||||
|                 new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, |                 new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, | ||||||
|                     new LocalStorageSaver(updater, layout))) |                     new LocalStorageSaver(updater, layout))) | ||||||
|                 ) |  | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|         const geojsonSources: GeoJsonSource [] = [] |         const geojsonSources: GeoJsonSource [] = [] | ||||||
|         for (const flayer of flayers.data) { |         for (const flayer of flayers.data) { | ||||||
|             const sourceUrl = flayer.layerDef.source.geojsonSource |             const sourceUrl = flayer.layerDef.source.geojsonSource | ||||||
|             if (sourceUrl !== undefined) { |             if (sourceUrl !== undefined) { | ||||||
|                 geojsonSources.push(new WayHandlingApplyingFeatureSource(flayers,  |                 geojsonSources.push( | ||||||
|                     new GeoJsonSource(flayer.layerDef.id, sourceUrl))) |                     new GeoJsonSource(flayer.layerDef.id, sourceUrl)) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const amendedLocalStorageSource = |         const amendedLocalStorageSource = | ||||||
|             new RememberingSource( |             new RememberingSource( | ||||||
|                 new WayHandlingApplyingFeatureSource(flayers, |  | ||||||
|                 new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, new LocalStorageSource(layout))) |                 new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, new LocalStorageSource(layout))) | ||||||
|                 )); |             ); | ||||||
| 
 | 
 | ||||||
|         newPoints = new FeatureDuplicatorPerLayer(flayers, newPoints); |         newPoints = new FeatureDuplicatorPerLayer(flayers, newPoints); | ||||||
| 
 | 
 | ||||||
|         const merged = new FeatureSourceMerger([ |         const merged = | ||||||
|  |             new MetaTaggingFeatureSource( | ||||||
|  |                 new FeatureSourceMerger([ | ||||||
|                     amendedOverpassSource, |                     amendedOverpassSource, | ||||||
|                     amendedLocalStorageSource, |                     amendedLocalStorageSource, | ||||||
|                     newPoints, |                     newPoints, | ||||||
|                     ...geojsonSources |                     ...geojsonSources | ||||||
|         ]); |                 ])); | ||||||
| 
 | 
 | ||||||
|         const source = |         const source = | ||||||
|  |             new WayHandlingApplyingFeatureSource(flayers, | ||||||
|                 new FilteringFeatureSource( |                 new FilteringFeatureSource( | ||||||
|                     flayers, |                     flayers, | ||||||
|                     locationControl, |                     locationControl, | ||||||
|                     merged |                     merged | ||||||
|             ); |                 )); | ||||||
|         this.features = source.features; |         this.features = source.features; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ export default class FilteringFeatureSource implements FeatureSource { | ||||||
| 
 | 
 | ||||||
|             const newFeatures = features.filter(f => { |             const newFeatures = features.filter(f => { | ||||||
|                 const layerId = f.feature._matching_layer_id; |                 const layerId = f.feature._matching_layer_id; | ||||||
|  |                  | ||||||
|                 if (layerId !== undefined) { |                 if (layerId !== undefined) { | ||||||
|                     const layer: { |                     const layer: { | ||||||
|                         isDisplayed: UIEventSource<boolean>, |                         isDisplayed: UIEventSource<boolean>, | ||||||
|  | @ -38,6 +39,17 @@ export default class FilteringFeatureSource implements FeatureSource { | ||||||
|                         console.error("No layer found with id ", layerId); |                         console.error("No layer found with id ", layerId); | ||||||
|                         return true; |                         return true; | ||||||
|                     } |                     } | ||||||
|  |                      | ||||||
|  |                     const isShown = layer.layerDef.isShown | ||||||
|  |                     const tags = f.feature.properties; | ||||||
|  |                     console.log("Is shown: ", isShown," known? ", isShown.IsKnown(tags), " result: ", isShown.GetRenderValue(tags).txt) | ||||||
|  |                     if(isShown.IsKnown(tags)){ | ||||||
|  |                         const result = layer.layerDef.isShown.GetRenderValue(f.feature.properties).txt; | ||||||
|  |                         if(result !== "yes"){ | ||||||
|  |                             return false; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                      | ||||||
|                     if (FilteringFeatureSource.showLayer(layer, location)) { |                     if (FilteringFeatureSource.showLayer(layer, location)) { | ||||||
|                         return true; |                         return true; | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
							
								
								
									
										30
									
								
								Logic/FeatureSource/MetaTaggingFeatureSource.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Logic/FeatureSource/MetaTaggingFeatureSource.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | ||||||
|  | import FeatureSource from "./FeatureSource"; | ||||||
|  | import {UIEventSource} from "../UIEventSource"; | ||||||
|  | import State from "../../State"; | ||||||
|  | import Hash from "../Web/Hash"; | ||||||
|  | import MetaTagging from "../MetaTagging"; | ||||||
|  | 
 | ||||||
|  | export default class MetaTaggingFeatureSource implements FeatureSource { | ||||||
|  |     features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{feature: any; freshness: Date}[]>(undefined); | ||||||
|  |      | ||||||
|  |     constructor(source: FeatureSource) { | ||||||
|  |         const self = this; | ||||||
|  |         source.features.addCallbackAndRun((featuresFreshness: { feature: any, freshness: Date }[]) => { | ||||||
|  |                 if (featuresFreshness === undefined) { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 featuresFreshness.forEach(featureFresh => { | ||||||
|  |                     const feature = featureFresh.feature; | ||||||
|  |                     State.state.allElements.addOrGetElement(feature); | ||||||
|  | 
 | ||||||
|  |                     if (Hash.hash.data === feature.properties.id) { | ||||||
|  |                         State.state.selectedElement.setData(feature); | ||||||
|  |                     } | ||||||
|  |                 }) | ||||||
|  | 
 | ||||||
|  |                 MetaTagging.addMetatags(featuresFreshness, State.state.layoutToUse.data.layers); | ||||||
|  |                 self.features.setData(featuresFreshness); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
|  | @ -3,6 +3,9 @@ import {UIEventSource} from "../UIEventSource"; | ||||||
| import LayerConfig from "../../Customizations/JSON/LayerConfig"; | import LayerConfig from "../../Customizations/JSON/LayerConfig"; | ||||||
| import {GeoOperations} from "../GeoOperations"; | import {GeoOperations} from "../GeoOperations"; | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * This is the part of the pipeline which introduces extra points at the center of an area (but only if this is demanded by the wayhandling) | ||||||
|  |  */ | ||||||
| export default class WayHandlingApplyingFeatureSource implements FeatureSource { | export default class WayHandlingApplyingFeatureSource implements FeatureSource { | ||||||
|     features: UIEventSource<{ feature: any; freshness: Date }[]>; |     features: UIEventSource<{ feature: any; freshness: Date }[]>; | ||||||
| 
 | 
 | ||||||
|  | @ -46,6 +49,7 @@ export default class WayHandlingApplyingFeatureSource implements FeatureSource { | ||||||
|                         continue; |                         continue; | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|  |                     // Create the copy
 | ||||||
|                     const centerPoint = GeoOperations.centerpoint(feat); |                     const centerPoint = GeoOperations.centerpoint(feat); | ||||||
|                     centerPoint._matching_layer_id = feat._matching_layer_id; |                     centerPoint._matching_layer_id = feat._matching_layer_id; | ||||||
|                     newFeatures.push({feature: centerPoint, freshness: f.freshness}); |                     newFeatures.push({feature: centerPoint, freshness: f.freshness}); | ||||||
|  |  | ||||||
|  | @ -1,85 +1,6 @@ | ||||||
| import {GeoOperations} from "./GeoOperations"; |  | ||||||
| import LayerConfig from "../Customizations/JSON/LayerConfig"; | import LayerConfig from "../Customizations/JSON/LayerConfig"; | ||||||
| import SimpleMetaTagger from "./SimpleMetaTagger"; | import SimpleMetaTagger from "./SimpleMetaTagger"; | ||||||
| import {UIElement} from "../UI/UIElement"; | import {ExtraFunction} from "./ExtraFunction"; | ||||||
| import Combine from "../UI/Base/Combine"; |  | ||||||
| 
 |  | ||||||
| export class ExtraFunction { |  | ||||||
| 
 |  | ||||||
|     static readonly intro = `<h2>Calculating tags with Javascript</h2>
 |  | ||||||
| 
 |  | ||||||
| <p>In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. <b>_lat</b>, <b>lon</b>, <b>_country</b>), as detailed above.</p> |  | ||||||
| 
 |  | ||||||
| <p>It is also possible to calculate your own tags - but this requires some javascript knowledge. </p> |  | ||||||
| 
 |  | ||||||
| Before proceeding, some warnings: |  | ||||||
| 
 |  | ||||||
| <ul> |  | ||||||
| <li> DO NOT DO THIS AS BEGINNER</li> |  | ||||||
| <li> <b>Only do this if all other techniques fail</b>. This should <i>not</i> be done to create a rendering effect, only to calculate a specific value</li> |  | ||||||
| <li> <b>THIS MIGHT BE DISABLED WITHOUT ANY NOTICE ON UNOFFICIAL THEMES</b>. As unofficial themes might be loaded from the internet, this is the equivalent of injecting arbitrary code into the client. It'll be disabled if abuse occurs.</li> |  | ||||||
| </ul> |  | ||||||
| In the layer object, add a field <b>calculatedTags</b>, e.g.: |  | ||||||
| 
 |  | ||||||
| <div class="code"> |  | ||||||
|   "calculatedTags": { |  | ||||||
|     "_someKey": "javascript-expression", |  | ||||||
|     "name": "feat.properties.name ?? feat.properties.ref ?? feat.properties.operator", |  | ||||||
|     "_distanceCloserThen3Km": "feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'"  |  | ||||||
|   } |  | ||||||
| </div> |  | ||||||
| ` |  | ||||||
| 
 |  | ||||||
|     private static DistanceToFunc = new ExtraFunction( |  | ||||||
|         "distanceTo", |  | ||||||
|         "Calculates the distance between the feature and a specified point", |  | ||||||
|         ["longitude", "latitude"], |  | ||||||
|         (feature) => { |  | ||||||
|             return (lon, lat) => { |  | ||||||
|                 // Feature._lon and ._lat is conveniently place by one of the other metatags
 |  | ||||||
|                 return GeoOperations.distanceBetween([lon, lat], [feature._lon, feature._lat]); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     ) |  | ||||||
|     private static readonly allFuncs: ExtraFunction[] = [ExtraFunction.DistanceToFunc]; |  | ||||||
|     private readonly _name: string; |  | ||||||
|     private readonly _args: string[]; |  | ||||||
|     private readonly _doc: string; |  | ||||||
|     private readonly _f: (feat: any) => any; |  | ||||||
| 
 |  | ||||||
|     constructor(name: string, doc: string, args: string[], f: ((feat: any) => any)) { |  | ||||||
|         this._name = name; |  | ||||||
|         this._doc = doc; |  | ||||||
|         this._args = args; |  | ||||||
|         this._f = f; |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static FullPatchFeature(feature) { |  | ||||||
|         for (const func of ExtraFunction.allFuncs) { |  | ||||||
|             func.PatchFeature(feature); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static HelpText(): UIElement { |  | ||||||
|         return new Combine([ |  | ||||||
|             ExtraFunction.intro, |  | ||||||
|             ...ExtraFunction.allFuncs.map(func => |  | ||||||
|                 new Combine([ |  | ||||||
|                     "<h3>" + func._name + "</h3>", |  | ||||||
|                     func._doc, |  | ||||||
|                     "<ul>", |  | ||||||
|                     ...func._args.map(arg => "<li>" + arg + "</li>"), |  | ||||||
|                     "</ul>" |  | ||||||
|                 ]) |  | ||||||
|             ) |  | ||||||
|         ]); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public PatchFeature(feature: any) { |  | ||||||
|         feature[this._name] = this._f(feature); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ... |  * Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ... | ||||||
|  | @ -163,4 +84,6 @@ export default class MetaTagging { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -7,8 +7,8 @@ import {TagsFilter} from "../TagsFilter"; | ||||||
|  * Interfaces overpass to get all the latest data |  * Interfaces overpass to get all the latest data | ||||||
|  */ |  */ | ||||||
| export class Overpass { | export class Overpass { | ||||||
|     private _filter: TagsFilter |  | ||||||
|     public static testUrl: string = null |     public static testUrl: string = null | ||||||
|  |     private _filter: TagsFilter | ||||||
|     private readonly _extraScripts: string[]; |     private readonly _extraScripts: string[]; | ||||||
| 
 | 
 | ||||||
|     constructor(filter: TagsFilter, extraScripts: string[]) { |     constructor(filter: TagsFilter, extraScripts: string[]) { | ||||||
|  | @ -16,21 +16,6 @@ export class Overpass { | ||||||
|         this._extraScripts = extraScripts; |         this._extraScripts = extraScripts; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|      |  | ||||||
|     private buildQuery(bbox: string): string { |  | ||||||
|         const filters = this._filter.asOverpass() |  | ||||||
|         let filter = "" |  | ||||||
|         for (const filterOr of filters) { |  | ||||||
|             filter += 'nwr' + filterOr + ';' |  | ||||||
|         } |  | ||||||
|         for (const extraScript of this._extraScripts){ |  | ||||||
|             filter += '('+extraScript+');'; |  | ||||||
|         } |  | ||||||
|         const query = |  | ||||||
|             '[out:json][timeout:25]' + bbox + ';(' + filter + ');out body;>;out skel qt;' |  | ||||||
|         return "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query) |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     queryGeoJson(bounds: Bounds, continuation: ((any, date: Date) => void), onFail: ((reason) => void)): void { |     queryGeoJson(bounds: Bounds, continuation: ((any, date: Date) => void), onFail: ((reason) => void)): void { | ||||||
| 
 | 
 | ||||||
|         let query = this.buildQuery("[bbox:" + bounds.south + "," + bounds.west + "," + bounds.north + "," + bounds.east + "]") |         let query = this.buildQuery("[bbox:" + bounds.south + "," + bounds.west + "," + bounds.north + "," + bounds.east + "]") | ||||||
|  | @ -52,11 +37,27 @@ export class Overpass { | ||||||
|                     onFail("Runtime error (timeout)") |                     onFail("Runtime error (timeout)") | ||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
|  | 
 | ||||||
|                 // @ts-ignore
 |                 // @ts-ignore
 | ||||||
|                 const geojson = OsmToGeoJson.default(json); |                 const geojson = OsmToGeoJson.default(json); | ||||||
|                 console.log("Received geojson", geojson) |                 console.log("Received geojson", geojson) | ||||||
|                 const osmTime = new Date(json.osm3s.timestamp_osm_base); |                 const osmTime = new Date(json.osm3s.timestamp_osm_base); | ||||||
|                 continuation(geojson, osmTime); |                 continuation(geojson, osmTime); | ||||||
|  | 
 | ||||||
|             }).fail(onFail) |             }).fail(onFail) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     private buildQuery(bbox: string): string { | ||||||
|  |         const filters = this._filter.asOverpass() | ||||||
|  |         let filter = "" | ||||||
|  |         for (const filterOr of filters) { | ||||||
|  |             filter += 'nwr' + filterOr + ';' | ||||||
|  |         } | ||||||
|  |         for (const extraScript of this._extraScripts) { | ||||||
|  |             filter += '(' + extraScript + ');'; | ||||||
|  |         } | ||||||
|  |         const query = | ||||||
|  |             '[out:json][timeout:25]' + bbox + ';(' + filter + ');out body;>;out skel qt;' | ||||||
|  |         return "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -64,7 +64,7 @@ export default class SimpleMetaTagger { | ||||||
|             SimpleMetaTagger.GetCountryCodeFor(lon, lat, (countries) => { |             SimpleMetaTagger.GetCountryCodeFor(lon, lat, (countries) => { | ||||||
|                 try { |                 try { | ||||||
|                     feature.properties["_country"] = countries[0].trim().toLowerCase(); |                     feature.properties["_country"] = countries[0].trim().toLowerCase(); | ||||||
|                     const tagsSource = State.state.allElements.getEventSourceFor(feature); |                     const tagsSource = State.state.allElements.addOrGetElement(feature); | ||||||
|                     tagsSource.ping(); |                     tagsSource.ping(); | ||||||
|                 } catch (e) { |                 } catch (e) { | ||||||
|                     console.warn(e) |                     console.warn(e) | ||||||
|  | @ -77,7 +77,7 @@ export default class SimpleMetaTagger { | ||||||
|         "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')", |         "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')", | ||||||
|         (feature => { |         (feature => { | ||||||
| 
 | 
 | ||||||
|             const tagsSource = State.state.allElements.getEventSourceFor(feature); |             const tagsSource = State.state.allElements.addOrGetElement(feature); | ||||||
|             tagsSource.addCallbackAndRun(tags => { |             tagsSource.addCallbackAndRun(tags => { | ||||||
|                 if (tags.opening_hours === undefined || tags._country === undefined) { |                 if (tags.opening_hours === undefined || tags._country === undefined) { | ||||||
|                     return; |                     return; | ||||||
|  |  | ||||||
|  | @ -86,7 +86,7 @@ export default class ShowDataLayer { | ||||||
|             marker.openPopup(); |             marker.openPopup(); | ||||||
| 
 | 
 | ||||||
|             const popup = marker.getPopup(); |             const popup = marker.getPopup(); | ||||||
|             const tags = State.state.allElements.getEventSourceFor(selected); |             const tags = State.state.allElements.addOrGetElement(selected); | ||||||
|             const layer: LayerConfig = this._layerDict[selected._matching_layer_id]; |             const layer: LayerConfig = this._layerDict[selected._matching_layer_id]; | ||||||
|             const infoBox = FeatureInfoBox.construct(tags, layer); |             const infoBox = FeatureInfoBox.construct(tags, layer); | ||||||
| 
 | 
 | ||||||
|  | @ -105,7 +105,7 @@ export default class ShowDataLayer { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     private createStyleFor(feature) { |     private createStyleFor(feature) { | ||||||
|         const tagsSource = State.state.allElements.getEventSourceFor(feature); |         const tagsSource = State.state.allElements.addOrGetElement(feature); | ||||||
|         // Every object is tied to exactly one layer
 |         // Every object is tied to exactly one layer
 | ||||||
|         const layer = this._layerDict[feature._matching_layer_id]; |         const layer = this._layerDict[feature._matching_layer_id]; | ||||||
|         return layer?.GenerateLeafletStyle(tagsSource, layer._showOnPopup !== undefined); |         return layer?.GenerateLeafletStyle(tagsSource, layer._showOnPopup !== undefined); | ||||||
|  |  | ||||||
|  | @ -15,9 +15,6 @@ | ||||||
|   "source": { |   "source": { | ||||||
|     "osmTags": "amenity=public_bookcase" |     "osmTags": "amenity=public_bookcase" | ||||||
|   }, |   }, | ||||||
|   "calculatedTags": { |  | ||||||
|     "_distanceToPietervdn": "feat.distanceTo(3.704388, 51.05281) < 1 ? 'closeby' : 'faraway'" |  | ||||||
|   }, |  | ||||||
|   "minzoom": 12, |   "minzoom": 12, | ||||||
|   "wayHandling": 2, |   "wayHandling": 2, | ||||||
|   "title": { |   "title": { | ||||||
|  |  | ||||||
|  | @ -6,6 +6,8 @@ | ||||||
|   "minzoom": 16, |   "minzoom": 16, | ||||||
|   "source": { |   "source": { | ||||||
|     "osmTags": { |     "osmTags": { | ||||||
|  |       "and": [ | ||||||
|  |         { | ||||||
|           "or": [ |           "or": [ | ||||||
|             "highway=pedestrian", |             "highway=pedestrian", | ||||||
|             "highway=footway", |             "highway=footway", | ||||||
|  | @ -14,6 +16,10 @@ | ||||||
|             "highway=living_street", |             "highway=living_street", | ||||||
|             "highway=track" |             "highway=track" | ||||||
|           ] |           ] | ||||||
|  |         }, | ||||||
|  |         "access!=no", | ||||||
|  |         "access!=private" | ||||||
|  |       ] | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "title": { |   "title": { | ||||||
|  | @ -51,7 +57,8 @@ | ||||||
|           "nl": "Woonerf" |           "nl": "Woonerf" | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|       {"if": "highway=path", |       { | ||||||
|  |         "if": "highway=path", | ||||||
|         "then": "Klein pad" |         "then": "Klein pad" | ||||||
|       } |       } | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  | @ -4,8 +4,8 @@ import SpecialVisualizations from "../UI/SpecialVisualizations"; | ||||||
| import {writeFileSync} from "fs"; | import {writeFileSync} from "fs"; | ||||||
| import {UIElement} from "../UI/UIElement"; | import {UIElement} from "../UI/UIElement"; | ||||||
| import SimpleMetaTagger from "../Logic/SimpleMetaTagger"; | import SimpleMetaTagger from "../Logic/SimpleMetaTagger"; | ||||||
| import {ExtraFunction} from "../Logic/MetaTagging"; |  | ||||||
| import Combine from "../UI/Base/Combine"; | import Combine from "../UI/Base/Combine"; | ||||||
|  | import {ExtraFunction} from "../Logic/ExtraFunction"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue