forked from MapComplete/MapComplete
		
	Bump version number
This commit is contained in:
		
						commit
						a875e2f767
					
				
					 35 changed files with 361 additions and 178 deletions
				
			
		|  | @ -1,11 +1,11 @@ | |||
| import {AndOrTagConfigJson} from "./TagConfigJson"; | ||||
| import {Or} from "../../Logic/Or"; | ||||
| 
 | ||||
| import {Utils} from "../../Utils"; | ||||
| import {TagsFilter} from "../../Logic/TagsFilter"; | ||||
| import {RegexTag} from "../../Logic/RegexTag"; | ||||
| import {Tag} from "../../Logic/Tag"; | ||||
| import {And} from "../../Logic/And"; | ||||
| import {RegexTag} from "../../Logic/Tags/RegexTag"; | ||||
| import {Or} from "../../Logic/Tags/Or"; | ||||
| import {And} from "../../Logic/Tags/And"; | ||||
| import {Tag} from "../../Logic/Tags/Tag"; | ||||
| import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | ||||
| import SubstitutingTag from "../../Logic/Tags/SubstitutingTag"; | ||||
| 
 | ||||
| export class FromJSON { | ||||
| 
 | ||||
|  | @ -13,11 +13,12 @@ export class FromJSON { | |||
|         const tag = Utils.SplitFirst(json, "="); | ||||
|         return new Tag(tag[0], tag[1]); | ||||
|     } | ||||
| 
 | ||||
|     public static Tag(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter { | ||||
|         try{ | ||||
|         try { | ||||
|             return this.TagUnsafe(json, context); | ||||
|         }catch(e){ | ||||
|             console.error("Could not parse tag", json,"in context",context,"due to ", e) | ||||
|         } catch (e) { | ||||
|             console.error("Could not parse tag", json, "in context", context, "due to ", e) | ||||
|             throw e; | ||||
|         } | ||||
|     } | ||||
|  | @ -50,6 +51,11 @@ export class FromJSON { | |||
|                     new RegExp("^" + split[1] + "$") | ||||
|                 ); | ||||
|             } | ||||
|             if(tag.indexOf(":=") >= 0){ | ||||
|                 const split = Utils.SplitFirst(tag, ":="); | ||||
|                 return new SubstitutingTag(split[0], split[1]); | ||||
|             } | ||||
|              | ||||
|             if (tag.indexOf("!=") >= 0) { | ||||
|                 const split = Utils.SplitFirst(tag, "!="); | ||||
|                 if (split[1] === "*") { | ||||
|  |  | |||
|  | @ -15,8 +15,8 @@ import {FixedUiElement} from "../../UI/Base/FixedUiElement"; | |||
| import {UIElement} from "../../UI/UIElement"; | ||||
| import {SubstitutedTranslation} from "../../UI/SubstitutedTranslation"; | ||||
| import SourceConfig from "./SourceConfig"; | ||||
| import {TagsFilter} from "../../Logic/TagsFilter"; | ||||
| import {Tag} from "../../Logic/Tag"; | ||||
| import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | ||||
| import {Tag} from "../../Logic/Tags/Tag"; | ||||
| 
 | ||||
| export default class LayerConfig { | ||||
| 
 | ||||
|  | @ -81,7 +81,7 @@ export default class LayerConfig { | |||
| 
 | ||||
|             this.source = new SourceConfig({ | ||||
|                 osmTags: osmTags, | ||||
|                 geojsonSource: json.source["geoJsonSource"], | ||||
|                 geojsonSource: json.source["geoJson"], | ||||
|                 overpassScript: json.source["overpassScript"], | ||||
|             }); | ||||
|         } else { | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; | ||||
| import {AndOrTagConfigJson} from "./TagConfigJson"; | ||||
| import TagRenderingConfig from "./TagRenderingConfig"; | ||||
| 
 | ||||
| /** | ||||
|  * Configuration for a single layer | ||||
|  | @ -40,7 +39,9 @@ export interface LayerConfigJson { | |||
|      * NOTE: the previous format was 'overpassTags: AndOrTagCOnfigJson | string', which is interpreted as a shorthand for source: {osmTags: "key=value"} | ||||
|      *  While still supported, this is considered deprecated | ||||
|      */ | ||||
|     source: {osmTags: AndOrTagConfigJson | string} | {geoJsonSource: string} | {overpassScript: string} | ||||
|     source: { osmTags: AndOrTagConfigJson | string } | | ||||
|         { osmTags: AndOrTagConfigJson | string, geoJson: string } | | ||||
|         { osmTags: AndOrTagConfigJson | string, overpassScript: string } | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|  | @ -53,7 +54,7 @@ export interface LayerConfigJson { | |||
|      * ] | ||||
|      * | ||||
|      */ | ||||
|     calculatedTags? : string[]; | ||||
|     calculatedTags?: string[]; | ||||
| 
 | ||||
|     /** | ||||
|      * If set, this layer will not query overpass; but it'll still match the tags above which are by chance returned by other layers. | ||||
|  | @ -115,7 +116,7 @@ export interface LayerConfigJson { | |||
|      * | ||||
|      * Note: strings are interpreted as icons, so layering and substituting is supported | ||||
|      */ | ||||
|     iconOverlays?: {if: string | AndOrTagConfigJson, then: string | TagRenderingConfigJson, badge?: boolean}[] | ||||
|     iconOverlays?: { if: string | AndOrTagConfigJson, then: string | TagRenderingConfigJson, badge?: boolean }[] | ||||
| 
 | ||||
|     /** | ||||
|      * A string containing "width,height" or "width,height,anchorpoint" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ... | ||||
|  | @ -157,7 +158,7 @@ export interface LayerConfigJson { | |||
|      * If set, this layer will pass all the features it receives onto the next layer. | ||||
|      * This is ideal for decoration, e.g. directionss on cameras | ||||
|      */ | ||||
|     passAllFeatures?:boolean | ||||
|     passAllFeatures?: boolean | ||||
| 
 | ||||
|     /** | ||||
|      * Presets for this layer. | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import {TagsFilter} from "../../Logic/TagsFilter"; | ||||
| import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | ||||
| 
 | ||||
| export default class SourceConfig { | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,9 +4,9 @@ import {FromJSON} from "./FromJSON"; | |||
| import ValidatedTextField from "../../UI/Input/ValidatedTextField"; | ||||
| import {Translation} from "../../UI/i18n/Translation"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import {TagsFilter} from "../../Logic/TagsFilter"; | ||||
| import {And} from "../../Logic/And"; | ||||
| import {TagUtils} from "../../Logic/TagUtils"; | ||||
| import {TagUtils} from "../../Logic/Tags/TagUtils"; | ||||
| import {And} from "../../Logic/Tags/And"; | ||||
| import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | ||||
| 
 | ||||
| /*** | ||||
|  * The parsed version of TagRenderingConfigJSON | ||||
|  |  | |||
|  | @ -35,3 +35,32 @@ Regex equals | |||
| A tag can also be tested against a regex with `key~regex`. Note that this regex __must match__ the entire value. If the value is allowed to appear anywhere as substring, use `key~.*regex.*` | ||||
| 
 | ||||
| Equivalently, `key!~regex` can be used if you _don't_ want to match the regex in order to appear. | ||||
| 
 | ||||
| 
 | ||||
| Using other tags as variables | ||||
| ----------------------------- | ||||
| 
 | ||||
| **This is an advanced feature - use with caution** | ||||
| 
 | ||||
| Some tags are automatically set or calculated - see [CalculatedTags](CalculatedTags.md) for an entire overview. | ||||
| If one wants to apply such a value as tag, use a substituting-tag such, for example`survey:date:={_date:now}`. Note that the separator between key and value here is `:=`. | ||||
| The text between `{` and `}` is interpreted as a key, and the respective value is substituted into the string. | ||||
| 
 | ||||
| One can also append, e.g. `key:={some_key} fixed text {some_other_key}`. | ||||
| 
 | ||||
| An assigning tag _cannot_ be used to query OpenStreetMap/Overpass. | ||||
| 
 | ||||
| If using a key or variable which might not be defined, add a condition in the mapping to hide the option. | ||||
| This is because, if `some_other_key` is not defined, one might actually upload the literal text `key={some_other_key}` to OSM - which we do not want. | ||||
| 
 | ||||
| To mitigate this, use: | ||||
| 
 | ||||
| ``` | ||||
| "mappings": [ | ||||
| { | ||||
|     "if":"key:={some_other_key}" | ||||
|     "then": "...", | ||||
|     "hideInAnswer": "some_other_key=" | ||||
| } | ||||
| ] | ||||
| ``` | ||||
|  | @ -1,2 +1,5 @@ | |||
| <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> | ||||
| 	<s:String x:Key="/Default/CodeInspection/Highlighting/SweaWarningsMode/@EntryValue">ShowAndRun</s:String></wpf:ResourceDictionary> | ||||
| 	<s:String x:Key="/Default/CodeInspection/Highlighting/SweaWarningsMode/@EntryValue">DoNotShowAndRun</s:String> | ||||
| 	<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=d2f95dca_002Defa2_002D40b6_002D8190_002D724496f13a75/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="Session" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> | ||||
|   <Nothing /> | ||||
| </SessionState></s:String></wpf:ResourceDictionary> | ||||
|  | @ -1,12 +1,12 @@ | |||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import Loc from "../../Models/Loc"; | ||||
| import {Or} from "../Or"; | ||||
| import {Or} from "../Tags/Or"; | ||||
| import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; | ||||
| import {Overpass} from "../Osm/Overpass"; | ||||
| import Bounds from "../../Models/Bounds"; | ||||
| import FeatureSource from "../FeatureSource/FeatureSource"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import {TagsFilter} from "../TagsFilter"; | ||||
| import {TagsFilter} from "../Tags/TagsFilter"; | ||||
| 
 | ||||
| 
 | ||||
| export default class UpdateFromOverpass implements FeatureSource { | ||||
|  |  | |||
|  | @ -61,13 +61,59 @@ Some advanced functions are available on <b>feat</b> as well: | |||
|         "Calculates the distance between the feature and a specified point", | ||||
|         ["longitude", "latitude"], | ||||
|         (featuresPerLayer, feature) => { | ||||
|             return (lon, lat) => { | ||||
|             return (arg0, lat) => { | ||||
|                 if(typeof arg0 === "number"){ | ||||
|                     const lon = arg0 | ||||
|                     // Feature._lon and ._lat is conveniently place by one of the other metatags
 | ||||
|                     return GeoOperations.distanceBetween([lon, lat], [feature._lon, feature._lat]); | ||||
|                 }else{ | ||||
|                     // arg0 is probably a feature
 | ||||
|                     return GeoOperations.distanceBetween(GeoOperations.centerpointCoordinates(arg0),[feature._lon, feature._lat]) | ||||
|                 } | ||||
|                | ||||
|             } | ||||
|         } | ||||
|     ) | ||||
|     private static readonly allFuncs: ExtraFunction[] = [ExtraFunction.DistanceToFunc, ExtraFunction.OverlapFunc]; | ||||
| 
 | ||||
|     private static ClosestObjectFunc = new ExtraFunction( | ||||
|         "closest", | ||||
|         "Given either a list of geojson features or a single layer name, gives the single object which is nearest to the feature. In the case of ways/polygons, only the centerpoint is considered.", | ||||
|         ["list of features"], | ||||
|         (featuresPerLayer, feature) => { | ||||
|             return (features) => { | ||||
|                 if (typeof features === "string") { | ||||
|                     features = featuresPerLayer.get(features) | ||||
|                 } | ||||
|                 let closestFeature = undefined; | ||||
|                 let closestDistance = undefined; | ||||
|                 for (const otherFeature of features) { | ||||
|                     if(otherFeature == feature){ | ||||
|                         continue; // We ignore self
 | ||||
|                     } | ||||
|                     let distance = undefined; | ||||
|                     if (otherFeature._lon !== undefined && otherFeature._lat !== undefined) { | ||||
|                         distance = GeoOperations.distanceBetween([otherFeature._lon, otherFeature._lat], [feature._lon, feature._lat]); | ||||
|                     } else { | ||||
|                         distance = GeoOperations.distanceBetween( | ||||
|                             GeoOperations.centerpointCoordinates(otherFeature), | ||||
|                             [feature._lon, feature._lat] | ||||
|                         ) | ||||
|                     } | ||||
|                     if(distance === undefined){ | ||||
|                         throw "Undefined distance!" | ||||
|                     } | ||||
|                     if(closestFeature === undefined || distance < closestDistance){ | ||||
|                         closestFeature = otherFeature | ||||
|                         closestDistance = distance; | ||||
|                     } | ||||
|                 } | ||||
|                 return closestFeature; | ||||
|             } | ||||
|         } | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
|     private static readonly allFuncs: ExtraFunction[] = [ExtraFunction.DistanceToFunc, ExtraFunction.OverlapFunc, ExtraFunction.ClosestObjectFunc]; | ||||
|     private readonly _name: string; | ||||
|     private readonly _args: string[]; | ||||
|     private readonly _doc: string; | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| import FeatureSource from "./FeatureSource"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import * as $ from "jquery"; | ||||
| import {Layer} from "leaflet"; | ||||
| 
 | ||||
| /** | ||||
|  * Fetches a geojson file somewhere and passes it along | ||||
|  |  | |||
|  | @ -16,9 +16,7 @@ export class GeoOperations { | |||
|     } | ||||
|      | ||||
|     static centerpointCoordinates(feature: any){ | ||||
|         const coordinates = turf.center(feature).geometry.coordinates; | ||||
|         coordinates.reverse(); | ||||
|         return coordinates; | ||||
|         return turf.center(feature).geometry.coordinates; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -4,9 +4,8 @@ import {Utils} from "../../Utils"; | |||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import Constants from "../../Models/Constants"; | ||||
| import FeatureSource from "../FeatureSource/FeatureSource"; | ||||
| import {TagsFilter} from "../TagsFilter"; | ||||
| import {Tag} from "../Tag"; | ||||
| import {And} from "../And"; | ||||
| import {TagsFilter} from "../Tags/TagsFilter"; | ||||
| import {Tag} from "../Tags/Tag"; | ||||
| 
 | ||||
| /** | ||||
|  * Handles all changes made to OSM. | ||||
|  | @ -29,7 +28,9 @@ export class Changes implements FeatureSource{ | |||
|     /** | ||||
|      * Adds a change to the pending changes | ||||
|      */ | ||||
|     private static checkChange(key: string, value: string): { k: string, v: string } { | ||||
|     private static checkChange(kv: {k: string, v: string}): { k: string, v: string } { | ||||
|         const key = kv.k; | ||||
|         const value = kv.v; | ||||
|         if (key === undefined || key === null) { | ||||
|             console.log("Invalid key"); | ||||
|             return undefined; | ||||
|  | @ -43,22 +44,19 @@ export class Changes implements FeatureSource{ | |||
|             console.warn("Tag starts with or ends with a space - trimming anyway") | ||||
|         } | ||||
| 
 | ||||
|         key = key.trim(); | ||||
|         value = value.trim(); | ||||
| 
 | ||||
|         return {k: key, v: value}; | ||||
|         return {k: key.trim(), v: value.trim()}; | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|      | ||||
|     addTag(elementId: string, tagsFilter: TagsFilter, | ||||
|            tags?: UIEventSource<any>) { | ||||
|         const changes = this.tagToChange(tagsFilter); | ||||
|         const eventSource = tags ?? State.state?.allElements.getEventSourceById(elementId); | ||||
|         const elementTags = eventSource.data; | ||||
|         const changes = tagsFilter.asChange(elementTags).map(Changes.checkChange) | ||||
|         if (changes.length == 0) { | ||||
|             return; | ||||
|         } | ||||
|         const eventSource = tags ?? State.state?.allElements.getEventSourceById(elementId); | ||||
|         const elementTags = eventSource.data; | ||||
|         for (const change of changes) { | ||||
|             if (elementTags[change.k] !== change.v) { | ||||
|                 elementTags[change.k] = change.v; | ||||
|  | @ -132,28 +130,6 @@ export class Changes implements FeatureSource{ | |||
|         return geojson; | ||||
|     } | ||||
| 
 | ||||
|     private tagToChange(tagsFilter: TagsFilter) { | ||||
|         let changes: { k: string, v: string }[] = []; | ||||
| 
 | ||||
|         if (tagsFilter instanceof Tag) { | ||||
|             const tag = tagsFilter as Tag; | ||||
|             if (typeof tag.value !== "string") { | ||||
|                 throw "Invalid value" | ||||
|             } | ||||
|             return [Changes.checkChange(tag.key, tag.value)]; | ||||
|         } | ||||
| 
 | ||||
|         if (tagsFilter instanceof And) { | ||||
|             const and = tagsFilter as And; | ||||
|             for (const tag of and.and) { | ||||
|                 changes = changes.concat(this.tagToChange(tag)); | ||||
|             } | ||||
|             return changes; | ||||
|         } | ||||
|         console.log("Unsupported tagsfilter element to addTag", tagsFilter); | ||||
|         throw "Unsupported tagsFilter element"; | ||||
|     } | ||||
| 
 | ||||
|     private uploadChangesWithLatestVersions( | ||||
|         knownElements, newElements: OsmObject[], pending: { elementId: string; key: string; value: string }[]) { | ||||
|         // Here, inside the continuation, we know that all 'neededIds' are loaded in 'knownElements', which maps the ids onto the elements
 | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| import {GeoOperations} from "./GeoOperations"; | ||||
| import State from "../State"; | ||||
| import {And} from "./And"; | ||||
| import {Tag} from "./Tag"; | ||||
| import {Or} from "./Or"; | ||||
| import {And} from "./Tags/And"; | ||||
| import {Tag} from "./Tags/Tag"; | ||||
| import {Or} from "./Tags/Or"; | ||||
| import {Utils} from "../Utils"; | ||||
| import opening_hours from "opening_hours"; | ||||
| import {UIElement} from "../UI/UIElement"; | ||||
|  |  | |||
|  | @ -46,16 +46,8 @@ export class And extends TagsFilter { | |||
|         return allChoices; | ||||
|     } | ||||
| 
 | ||||
|     substituteValues(tags: any): TagsFilter { | ||||
|         const newChoices = []; | ||||
|         for (const c of this.and) { | ||||
|             newChoices.push(c.substituteValues(tags)); | ||||
|         } | ||||
|         return new And(newChoices); | ||||
|     } | ||||
| 
 | ||||
|     asHumanString(linkToWiki: boolean, shorten: boolean) { | ||||
|         return this.and.map(t => t.asHumanString(linkToWiki, shorten)).join("&"); | ||||
|     asHumanString(linkToWiki: boolean, shorten: boolean, properties) { | ||||
|         return this.and.map(t => t.asHumanString(linkToWiki, shorten, properties)).join("&"); | ||||
|     } | ||||
| 
 | ||||
|     isUsableAsAnswer(): boolean { | ||||
|  | @ -116,4 +108,12 @@ export class And extends TagsFilter { | |||
|     usedKeys(): string[] { | ||||
|         return [].concat(...this.and.map(subkeys => subkeys.usedKeys())); | ||||
|     } | ||||
| 
 | ||||
|     asChange(properties: any): { k: string; v: string }[] { | ||||
|         const result = [] | ||||
|         for (const tagsFilter of this.and) { | ||||
|             result.push(...tagsFilter.asChange(properties)) | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
| } | ||||
|  | @ -30,16 +30,8 @@ export class Or extends TagsFilter { | |||
|         return choices; | ||||
|     } | ||||
| 
 | ||||
|     substituteValues(tags: any): TagsFilter { | ||||
|         const newChoices = []; | ||||
|         for (const c of this.or) { | ||||
|             newChoices.push(c.substituteValues(tags)); | ||||
|         } | ||||
|         return new Or(newChoices); | ||||
|     } | ||||
| 
 | ||||
|     asHumanString(linkToWiki: boolean, shorten: boolean) { | ||||
|         return this.or.map(t => t.asHumanString(linkToWiki, shorten)).join("|"); | ||||
|     asHumanString(linkToWiki: boolean, shorten: boolean, properties) { | ||||
|         return this.or.map(t => t.asHumanString(linkToWiki, shorten, properties)).join("|"); | ||||
|     } | ||||
| 
 | ||||
|     isUsableAsAnswer(): boolean { | ||||
|  | @ -67,6 +59,10 @@ export class Or extends TagsFilter { | |||
|     usedKeys(): string[] { | ||||
|         return [].concat(...this.or.map(subkeys => subkeys.usedKeys())); | ||||
|     } | ||||
| 
 | ||||
|     asChange(properties: any): { k: string; v: string }[] { | ||||
|        throw "Can not convert an 'or' into a changeset" | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -55,10 +55,6 @@ export class RegexTag extends TagsFilter { | |||
|         return this.invert; | ||||
|     } | ||||
| 
 | ||||
|     substituteValues(tags: any): TagsFilter { | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     asHumanString() { | ||||
|         if (typeof this.key === "string") { | ||||
|             return `${this.key}${this.invert ? "!" : ""}~${RegexTag.source(this.value)}`; | ||||
|  | @ -82,4 +78,8 @@ export class RegexTag extends TagsFilter { | |||
|         } | ||||
|         throw "Key cannot be determined as it is a regex" | ||||
|     } | ||||
| 
 | ||||
|     asChange(properties: any): { k: string; v: string }[] { | ||||
|         throw "Cannot convert a regex-tag into a change"; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										67
									
								
								Logic/Tags/SubstitutingTag.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								Logic/Tags/SubstitutingTag.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | |||
| import {TagsFilter} from "./TagsFilter"; | ||||
| 
 | ||||
| /** | ||||
|  * The substituting-tag uses the tags of a feature a variables and replaces them. | ||||
|  * | ||||
|  * e.g. key:={other_key}_{ref} will match an object that has at least 'key'. | ||||
|  * If {other_key} is _not_ defined, it will not be substituted. | ||||
|  * | ||||
|  * The 'key' is always fixed and should not contain substitutions. | ||||
|  * This cannot be used to query features | ||||
|  */ | ||||
| export default class SubstitutingTag implements TagsFilter { | ||||
|     private readonly _key: string; | ||||
|     private readonly _value: string; | ||||
| 
 | ||||
|     constructor(key: string, value: string) { | ||||
|         this._key = key; | ||||
|         this._value = value; | ||||
|     } | ||||
| 
 | ||||
|     private static substituteString(template: string, dict: any): string { | ||||
|         for (const k in dict) { | ||||
|             template = template.replace(new RegExp("\\{" + k + "\\}", 'g'), dict[k]) | ||||
|         } | ||||
|         return template; | ||||
|     } | ||||
| 
 | ||||
|     asHumanString(linkToWiki: boolean, shorten: boolean, properties) { | ||||
|         return this._key + "=" + SubstitutingTag.substituteString(this._value, properties); | ||||
|     } | ||||
| 
 | ||||
|     asOverpass(): string[] { | ||||
|         throw "A variable with substitution can not be used to query overpass" | ||||
|     } | ||||
| 
 | ||||
|     isEquivalent(other: TagsFilter): boolean { | ||||
|         if (!(other instanceof SubstitutingTag)) { | ||||
|             return false; | ||||
|         } | ||||
|         return other._key === this._key && other._value === this._value; | ||||
|     } | ||||
| 
 | ||||
|     isUsableAsAnswer(): boolean { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     matchesProperties(properties: any): boolean { | ||||
|         const value = properties[this._key]; | ||||
|         if (value === undefined || value === "") { | ||||
|             return false; | ||||
|         } | ||||
|         const expectedValue = SubstitutingTag.substituteString(this._value, properties); | ||||
|         return value === expectedValue; | ||||
|     } | ||||
| 
 | ||||
|     usedKeys(): string[] { | ||||
|         return [this._key]; | ||||
|     } | ||||
| 
 | ||||
|     asChange(properties: any): { k: string; v: string }[] { | ||||
|         const v = SubstitutingTag.substituteString(this._value, properties); | ||||
|         if (v.match(/{.*}/) !== null) { | ||||
|             throw "Could not calculate all the substitutions: still have " + v | ||||
|         } | ||||
|         return [{k: this._key, v: v}]; | ||||
|     } | ||||
| } | ||||
|  | @ -1,4 +1,4 @@ | |||
| import {Utils} from "../Utils"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import {RegexTag} from "./RegexTag"; | ||||
| import {TagsFilter} from "./TagsFilter"; | ||||
| import {TagUtils} from "./TagUtils"; | ||||
|  | @ -81,4 +81,8 @@ export class Tag extends TagsFilter { | |||
|     usedKeys(): string[] { | ||||
|         return [this.key]; | ||||
|     } | ||||
| 
 | ||||
|     asChange(properties: any): { k: string; v: string }[] { | ||||
|         return [{k: this.key,  v: this.value}]; | ||||
|     } | ||||
| } | ||||
|  | @ -1,7 +1,7 @@ | |||
| import {Tag} from "./Tag"; | ||||
| import {TagsFilter} from "./TagsFilter"; | ||||
| import {And} from "./And"; | ||||
| import {Utils} from "../Utils"; | ||||
| import {Utils} from "../../Utils"; | ||||
| 
 | ||||
| export class TagUtils { | ||||
|     static ApplyTemplate(template: string, tags: any): string { | ||||
|  | @ -2,23 +2,20 @@ export abstract class TagsFilter { | |||
| 
 | ||||
|     abstract asOverpass(): string[] | ||||
| 
 | ||||
|     abstract substituteValues(tags: any): TagsFilter; | ||||
| 
 | ||||
|     abstract isUsableAsAnswer(): boolean; | ||||
| 
 | ||||
|     abstract isEquivalent(other: TagsFilter): boolean; | ||||
| 
 | ||||
|     abstract matchesProperties(properties: any): boolean; | ||||
| 
 | ||||
|     abstract asHumanString(linkToWiki: boolean, shorten: boolean); | ||||
|     abstract asHumanString(linkToWiki: boolean, shorten: boolean, properties: any); | ||||
| 
 | ||||
|     abstract usedKeys(): string[]; | ||||
| 
 | ||||
|     public matches(tags: { k: string, v: string }[]) { | ||||
|         const properties = {}; | ||||
|         for (const kv of tags) { | ||||
|             properties[kv.k] = kv.v; | ||||
|         } | ||||
|         return this.matchesProperties(properties); | ||||
|     } | ||||
|     /** | ||||
|      * Converts the tagsFilter into a list of key-values that should be uploaded to OSM. | ||||
|      * Throws an error if not applicable | ||||
|      */ | ||||
|     abstract asChange(properties:any): {k: string, v:string}[] | ||||
|      | ||||
| } | ||||
|  | @ -2,7 +2,7 @@ import { Utils } from "../Utils"; | |||
| 
 | ||||
| export default class Constants { | ||||
|      | ||||
|     public static vNumber = "0.6.3b"; | ||||
|     public static vNumber = "0.6.4"; | ||||
| 
 | ||||
|     // The user journey states thresholds when a new feature gets unlocked
 | ||||
|     public static userJourney = { | ||||
|  |  | |||
|  | @ -12,8 +12,8 @@ import {FixedUiElement} from "../Base/FixedUiElement"; | |||
| import Translations from "../i18n/Translations"; | ||||
| import Constants from "../../Models/Constants"; | ||||
| import LayerConfig from "../../Customizations/JSON/LayerConfig"; | ||||
| import {Tag} from "../../Logic/Tag"; | ||||
| import {TagUtils} from "../../Logic/TagUtils"; | ||||
| import {Tag} from "../../Logic/Tags/Tag"; | ||||
| import {TagUtils} from "../../Logic/Tags/TagUtils"; | ||||
| 
 | ||||
| export default class SimpleAddUI extends UIElement { | ||||
|     private readonly _loginButton: UIElement; | ||||
|  | @ -158,7 +158,7 @@ export default class SimpleAddUI extends UIElement { | |||
|         let tagInfo = ""; | ||||
|         const csCount = State.state.osmConnection.userDetails.data.csCount; | ||||
|         if (csCount > Constants.userJourney.tagsVisibleAt) { | ||||
|             tagInfo = this._confirmPreset.data.tags.map(t => t.asHumanString(csCount > Constants.userJourney.tagsVisibleAndWikiLinked, true)).join("&"); | ||||
|             tagInfo = this._confirmPreset.data.tags.map(t => t.asHumanString(csCount > Constants.userJourney.tagsVisibleAndWikiLinked, true, {})).join("&"); | ||||
|             tagInfo = `<br/>More information about the preset: ${tagInfo}` | ||||
|         } | ||||
| 
 | ||||
|  | @ -186,7 +186,7 @@ export default class SimpleAddUI extends UIElement { | |||
|                 const csCount = State.state.osmConnection.userDetails.data.csCount; | ||||
|                 let tagInfo = undefined; | ||||
|                 if (csCount > Constants.userJourney.tagsVisibleAt) { | ||||
|                     const presets = preset.tags.map(t => new Combine ([t.asHumanString(false, true), " "]).SetClass("subtle break-words") ) | ||||
|                     const presets = preset.tags.map(t => new Combine ([t.asHumanString(false, true, {}), " "]).SetClass("subtle break-words") ) | ||||
|                     tagInfo = new Combine(presets) | ||||
|                 } | ||||
|                 const button: UIElement = | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import CheckBox from "../Input/CheckBox"; | |||
| import Combine from "../Base/Combine"; | ||||
| import State from "../../State"; | ||||
| import Svg from "../../Svg"; | ||||
| import {Tag} from "../../Logic/Tag"; | ||||
| import {Tag} from "../../Logic/Tags/Tag"; | ||||
| 
 | ||||
| 
 | ||||
| export default class DeleteImage extends UIElement { | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ import {Imgur} from "../../Logic/Web/Imgur"; | |||
| import {DropDown} from "../Input/DropDown"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import Svg from "../../Svg"; | ||||
| import {Tag} from "../../Logic/Tag"; | ||||
| import {Tag} from "../../Logic/Tags/Tag"; | ||||
| 
 | ||||
| export class ImageUploadFlow extends UIElement { | ||||
|     private readonly _licensePicker: UIElement; | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ import Combine from "../Base/Combine"; | |||
| import TagRenderingAnswer from "./TagRenderingAnswer"; | ||||
| import State from "../../State"; | ||||
| import Svg from "../../Svg"; | ||||
| import {TagUtils} from "../../Logic/TagUtils"; | ||||
| 
 | ||||
| export default class EditableTagRendering extends UIElement { | ||||
|     private readonly _tags: UIEventSource<any>; | ||||
|  |  | |||
|  | @ -8,8 +8,7 @@ import TagRenderingAnswer from "./TagRenderingAnswer"; | |||
| import State from "../../State"; | ||||
| import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig"; | ||||
| import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import {Tag} from "../../Logic/Tag"; | ||||
| import {Tag} from "../../Logic/Tags/Tag"; | ||||
| 
 | ||||
| export default class FeatureInfoBox extends ScrollableFullScreen { | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import {Utils} from "../../Utils"; | |||
| import Combine from "../Base/Combine"; | ||||
| import {SubstitutedTranslation} from "../SubstitutedTranslation"; | ||||
| import {Translation} from "../i18n/Translation"; | ||||
| import {TagUtils} from "../../Logic/TagUtils"; | ||||
| import {TagUtils} from "../../Logic/Tags/TagUtils"; | ||||
| 
 | ||||
| /*** | ||||
|  * Displays the correct value for a known tagrendering | ||||
|  |  | |||
|  | @ -18,10 +18,10 @@ import {FixedUiElement} from "../Base/FixedUiElement"; | |||
| import {Translation} from "../i18n/Translation"; | ||||
| import Constants from "../../Models/Constants"; | ||||
| import {SubstitutedTranslation} from "../SubstitutedTranslation"; | ||||
| import {TagsFilter} from "../../Logic/TagsFilter"; | ||||
| import {Tag} from "../../Logic/Tag"; | ||||
| import {And} from "../../Logic/And"; | ||||
| import {TagUtils} from "../../Logic/TagUtils"; | ||||
| import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | ||||
| import {Tag} from "../../Logic/Tags/Tag"; | ||||
| import {And} from "../../Logic/Tags/And"; | ||||
| import {TagUtils} from "../../Logic/Tags/TagUtils"; | ||||
| 
 | ||||
| /** | ||||
|  * Shows the question element. | ||||
|  | @ -57,9 +57,8 @@ export default class TagRenderingQuestion extends UIElement { | |||
|         this._inputElement = this.GenerateInputElement() | ||||
|         const self = this; | ||||
|         const save = () => { | ||||
|             console.log("Save clicked!") | ||||
|             const selection = self._inputElement.GetValue().data; | ||||
|             console.log("Selection is", selection) | ||||
|             console.log("Save button clicked, the tags are is", selection) | ||||
|             if (selection) { | ||||
|                 (State.state?.changes ?? new Changes()) | ||||
|                     .addTag(tags.data.id, selection, tags); | ||||
|  | @ -86,10 +85,10 @@ export default class TagRenderingQuestion extends UIElement { | |||
|                         return Translations.t.general.noTagsSelected.SetClass("subtle").Render(); | ||||
|                     } | ||||
|                     if (csCount < Constants.userJourney.tagsVisibleAndWikiLinked) { | ||||
|                         const tagsStr = tags.asHumanString(false, true); | ||||
|                         const tagsStr = tags.asHumanString(false, true, self._tags.data); | ||||
|                         return new FixedUiElement(tagsStr).SetClass("subtle").Render(); | ||||
|                     } | ||||
|                     return tags.asHumanString(true, true); | ||||
|                     return tags.asHumanString(true, true, self._tags.data); | ||||
|                 } | ||||
|             ) | ||||
|         ).SetClass("block") | ||||
|  |  | |||
|  | @ -27,8 +27,10 @@ export default class ShowDataLayer { | |||
| 
 | ||||
|         layoutToUse.addCallbackAndRun(layoutToUse => { | ||||
|             for (const layer of layoutToUse.layers) { | ||||
|                 if (self._layerDict[layer.id] === undefined) { | ||||
|                     self._layerDict[layer.id] = layer; | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         let geoLayer = undefined; | ||||
|  |  | |||
|  | @ -227,6 +227,24 @@ | |||
|           } | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "question":{ | ||||
|         "en": "When was this bench last surveyed?" | ||||
|       }, | ||||
|       "render": { | ||||
|         "en": "This bench was last surveyed on {survey:date}" | ||||
|       }, | ||||
|       "freeform": { | ||||
|         "key": "survey:date", | ||||
|         "type": "date" | ||||
|       }, | ||||
|       "mappings": [ | ||||
|         { | ||||
|           "if": "survey:date:={_now:date}", | ||||
|           "then": "Surveyed today!" | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   ], | ||||
|   "hideUnderlayingFeaturesMinPercentage": 0, | ||||
|  |  | |||
|  | @ -41,6 +41,10 @@ | |||
|       ] | ||||
|     } | ||||
|   }, | ||||
|   "calculatedTags": [ | ||||
|     "_closest_other_drinking_water_id=feat.closest('drinking_water').id", | ||||
|     "_closest_other_drinking_water_distance=Math.floor(feat.distanceTo(feat.closest('drinking_water')) * 1000)" | ||||
|   ], | ||||
|   "minzoom": 13, | ||||
|   "wayHandling": 1, | ||||
|   "presets": [ | ||||
|  | @ -122,6 +126,12 @@ | |||
|           } | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "render":{ | ||||
|         "en": "<a href='#{_closest_other_drinking_water_id}'>There is another drinking water fountain at {_closest_other_drinking_water_distance} meter</a>", | ||||
|         "nl": "<a href='#{_closest_other_drinking_water_id}'>Er bevindt zich een ander drinkwaterpunt op {_closest_other_drinking_water_distance} meter</a>" | ||||
|       } | ||||
|     } | ||||
|   ] | ||||
| } | ||||
|  | @ -95,7 +95,7 @@ | |||
|       "name": { | ||||
|         "nl": "Fietsstraten" | ||||
|       }, | ||||
|       "minzoom": 9, | ||||
|       "minzoom": 7, | ||||
|       "source": { | ||||
|         "osmTags": { | ||||
|           "and": [ | ||||
|  |  | |||
|  | @ -8,7 +8,10 @@ | |||
|     "en": "On this map, publicly accessible drinkging water spots are shown and can be easily added", | ||||
|     "nl": "Op deze kaart staan publiek toegankelijke drinkwaterpunten en kan je makkelijk een nieuw drinkwaterpunt toevoegen" | ||||
|   }, | ||||
|   "language": ["en", "nl"], | ||||
|   "language": [ | ||||
|     "en", | ||||
|     "nl" | ||||
|   ], | ||||
|   "maintainer": "MapComplete", | ||||
|   "icon": "./assets/themes/drinking_water/logo.svg", | ||||
|   "version": "0", | ||||
|  | @ -17,6 +20,8 @@ | |||
|   "startLon": 4.3516970, | ||||
|   "startZoom": 16, | ||||
|   "widenFactor": 0.05, | ||||
|   "layers": ["drinking_water"], | ||||
|   "layers": [ | ||||
|     "drinking_water" | ||||
|   ], | ||||
|   "roamingRenderings": [] | ||||
| } | ||||
							
								
								
									
										51
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										51
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -5729,11 +5729,18 @@ | |||
|       } | ||||
|     }, | ||||
|     "debug": { | ||||
|       "version": "2.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", | ||||
|       "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", | ||||
|       "version": "2.6.9", | ||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|       "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|       "requires": { | ||||
|         "ms": "0.7.1" | ||||
|         "ms": "2.0.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "ms": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|           "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "decamelize": { | ||||
|  | @ -8214,18 +8221,11 @@ | |||
|       } | ||||
|     }, | ||||
|     "mkdirp": { | ||||
|       "version": "0.5.1", | ||||
|       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", | ||||
|       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", | ||||
|       "version": "0.5.5", | ||||
|       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", | ||||
|       "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", | ||||
|       "requires": { | ||||
|         "minimist": "0.0.8" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "minimist": { | ||||
|           "version": "0.0.8", | ||||
|           "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", | ||||
|           "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" | ||||
|         } | ||||
|         "minimist": "^1.2.5" | ||||
|       } | ||||
|     }, | ||||
|     "mkdirp-classic": { | ||||
|  | @ -8255,6 +8255,14 @@ | |||
|           "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", | ||||
|           "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=" | ||||
|         }, | ||||
|         "debug": { | ||||
|           "version": "2.2.0", | ||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", | ||||
|           "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", | ||||
|           "requires": { | ||||
|             "ms": "0.7.1" | ||||
|           } | ||||
|         }, | ||||
|         "escape-string-regexp": { | ||||
|           "version": "1.0.2", | ||||
|           "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", | ||||
|  | @ -8278,6 +8286,19 @@ | |||
|             "sigmund": "~1.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "minimist": { | ||||
|           "version": "0.0.8", | ||||
|           "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", | ||||
|           "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" | ||||
|         }, | ||||
|         "mkdirp": { | ||||
|           "version": "0.5.1", | ||||
|           "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", | ||||
|           "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", | ||||
|           "requires": { | ||||
|             "minimist": "0.0.8" | ||||
|           } | ||||
|         }, | ||||
|         "supports-color": { | ||||
|           "version": "1.2.0", | ||||
|           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", | ||||
|  |  | |||
|  | @ -12,8 +12,8 @@ import {Translation} from "../UI/i18n/Translation"; | |||
| import {OH, OpeningHour} from "../UI/OpeningHours/OpeningHours"; | ||||
| import PublicHolidayInput from "../UI/OpeningHours/PublicHolidayInput"; | ||||
| import {SubstitutedTranslation} from "../UI/SubstitutedTranslation"; | ||||
| import {Tag} from "../Logic/Tag"; | ||||
| import {And} from "../Logic/And"; | ||||
| import {Tag} from "../Logic/Tags/Tag"; | ||||
| import {And} from "../Logic/Tags/And"; | ||||
| 
 | ||||
| 
 | ||||
| new T("Tags", [ | ||||
|  | @ -79,6 +79,14 @@ new T("Tags", [ | |||
|         equal(nameStartsWith.matchesProperties({"name": "Sint-Anna"}), false) | ||||
|         equal(nameStartsWith.matchesProperties({"name": ""}), false) | ||||
|          | ||||
|          | ||||
|         const assign = FromJSON.Tag("survey:date:={_date:now}") | ||||
|         equal(assign.matchesProperties({"survey:date":"2021-03-29", "_date:now":"2021-03-29"}), true); | ||||
|         equal(assign.matchesProperties({"survey:date":"2021-03-29", "_date:now":"2021-01-01"}), false); | ||||
|         equal(assign.matchesProperties({"survey:date":"2021-03-29"}), false); | ||||
|         equal(assign.matchesProperties({"_date:now":"2021-03-29"}), false); | ||||
|         equal(assign.matchesProperties({"some_key":"2021-03-29"}), false); | ||||
| 
 | ||||
|     })], | ||||
|     ["Is equivalent test", (() => { | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue