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 {AndOrTagConfigJson} from "./TagConfigJson"; | ||||||
| import {Or} from "../../Logic/Or"; |  | ||||||
| 
 |  | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import {TagsFilter} from "../../Logic/TagsFilter"; | import {RegexTag} from "../../Logic/Tags/RegexTag"; | ||||||
| import {RegexTag} from "../../Logic/RegexTag"; | import {Or} from "../../Logic/Tags/Or"; | ||||||
| import {Tag} from "../../Logic/Tag"; | import {And} from "../../Logic/Tags/And"; | ||||||
| import {And} from "../../Logic/And"; | import {Tag} from "../../Logic/Tags/Tag"; | ||||||
|  | import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | ||||||
|  | import SubstitutingTag from "../../Logic/Tags/SubstitutingTag"; | ||||||
| 
 | 
 | ||||||
| export class FromJSON { | export class FromJSON { | ||||||
| 
 | 
 | ||||||
|  | @ -13,6 +13,7 @@ export class FromJSON { | ||||||
|         const tag = Utils.SplitFirst(json, "="); |         const tag = Utils.SplitFirst(json, "="); | ||||||
|         return new Tag(tag[0], tag[1]); |         return new Tag(tag[0], tag[1]); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     public static Tag(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter { |     public static Tag(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter { | ||||||
|         try { |         try { | ||||||
|             return this.TagUnsafe(json, context); |             return this.TagUnsafe(json, context); | ||||||
|  | @ -50,6 +51,11 @@ export class FromJSON { | ||||||
|                     new RegExp("^" + split[1] + "$") |                     new RegExp("^" + split[1] + "$") | ||||||
|                 ); |                 ); | ||||||
|             } |             } | ||||||
|  |             if(tag.indexOf(":=") >= 0){ | ||||||
|  |                 const split = Utils.SplitFirst(tag, ":="); | ||||||
|  |                 return new SubstitutingTag(split[0], split[1]); | ||||||
|  |             } | ||||||
|  |              | ||||||
|             if (tag.indexOf("!=") >= 0) { |             if (tag.indexOf("!=") >= 0) { | ||||||
|                 const split = Utils.SplitFirst(tag, "!="); |                 const split = Utils.SplitFirst(tag, "!="); | ||||||
|                 if (split[1] === "*") { |                 if (split[1] === "*") { | ||||||
|  |  | ||||||
|  | @ -15,8 +15,8 @@ import {FixedUiElement} from "../../UI/Base/FixedUiElement"; | ||||||
| import {UIElement} from "../../UI/UIElement"; | import {UIElement} from "../../UI/UIElement"; | ||||||
| import {SubstitutedTranslation} from "../../UI/SubstitutedTranslation"; | import {SubstitutedTranslation} from "../../UI/SubstitutedTranslation"; | ||||||
| import SourceConfig from "./SourceConfig"; | import SourceConfig from "./SourceConfig"; | ||||||
| import {TagsFilter} from "../../Logic/TagsFilter"; | import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | ||||||
| import {Tag} from "../../Logic/Tag"; | import {Tag} from "../../Logic/Tags/Tag"; | ||||||
| 
 | 
 | ||||||
| export default class LayerConfig { | export default class LayerConfig { | ||||||
| 
 | 
 | ||||||
|  | @ -81,7 +81,7 @@ export default class LayerConfig { | ||||||
| 
 | 
 | ||||||
|             this.source = new SourceConfig({ |             this.source = new SourceConfig({ | ||||||
|                 osmTags: osmTags, |                 osmTags: osmTags, | ||||||
|                 geojsonSource: json.source["geoJsonSource"], |                 geojsonSource: json.source["geoJson"], | ||||||
|                 overpassScript: json.source["overpassScript"], |                 overpassScript: json.source["overpassScript"], | ||||||
|             }); |             }); | ||||||
|         } else { |         } else { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| 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 | ||||||
|  | @ -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"} |      * 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 |      *  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 } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * |      * | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {TagsFilter} from "../../Logic/TagsFilter"; | import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | ||||||
| 
 | 
 | ||||||
| export default class SourceConfig { | export default class SourceConfig { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,9 +4,9 @@ import {FromJSON} from "./FromJSON"; | ||||||
| import ValidatedTextField from "../../UI/Input/ValidatedTextField"; | import ValidatedTextField from "../../UI/Input/ValidatedTextField"; | ||||||
| import {Translation} from "../../UI/i18n/Translation"; | import {Translation} from "../../UI/i18n/Translation"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import {TagsFilter} from "../../Logic/TagsFilter"; | import {TagUtils} from "../../Logic/Tags/TagUtils"; | ||||||
| import {And} from "../../Logic/And"; | import {And} from "../../Logic/Tags/And"; | ||||||
| import {TagUtils} from "../../Logic/TagUtils"; | import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | ||||||
| 
 | 
 | ||||||
| /*** | /*** | ||||||
|  * The parsed version of TagRenderingConfigJSON |  * 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.*` | 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. | 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"> | <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 {UIEventSource} from "../UIEventSource"; | ||||||
| import Loc from "../../Models/Loc"; | import Loc from "../../Models/Loc"; | ||||||
| import {Or} from "../Or"; | import {Or} from "../Tags/Or"; | ||||||
| import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; | import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; | ||||||
| import {Overpass} from "../Osm/Overpass"; | import {Overpass} from "../Osm/Overpass"; | ||||||
| import Bounds from "../../Models/Bounds"; | import Bounds from "../../Models/Bounds"; | ||||||
| import FeatureSource from "../FeatureSource/FeatureSource"; | import FeatureSource from "../FeatureSource/FeatureSource"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import {TagsFilter} from "../TagsFilter"; | import {TagsFilter} from "../Tags/TagsFilter"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export default class UpdateFromOverpass implements FeatureSource { | 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", |         "Calculates the distance between the feature and a specified point", | ||||||
|         ["longitude", "latitude"], |         ["longitude", "latitude"], | ||||||
|         (featuresPerLayer, feature) => { |         (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
 |                     // Feature._lon and ._lat is conveniently place by one of the other metatags
 | ||||||
|                     return GeoOperations.distanceBetween([lon, lat], [feature._lon, feature._lat]); |                     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 _name: string; | ||||||
|     private readonly _args: string[]; |     private readonly _args: string[]; | ||||||
|     private readonly _doc: string; |     private readonly _doc: string; | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| import FeatureSource from "./FeatureSource"; | import FeatureSource from "./FeatureSource"; | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {UIEventSource} from "../UIEventSource"; | ||||||
| import * as $ from "jquery"; | import * as $ from "jquery"; | ||||||
| import {Layer} from "leaflet"; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Fetches a geojson file somewhere and passes it along |  * Fetches a geojson file somewhere and passes it along | ||||||
|  |  | ||||||
|  | @ -16,9 +16,7 @@ export class GeoOperations { | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     static centerpointCoordinates(feature: any){ |     static centerpointCoordinates(feature: any){ | ||||||
|         const coordinates = turf.center(feature).geometry.coordinates; |         return turf.center(feature).geometry.coordinates; | ||||||
|         coordinates.reverse(); |  | ||||||
|         return coordinates; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  |  | ||||||
|  | @ -4,9 +4,8 @@ import {Utils} from "../../Utils"; | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {UIEventSource} from "../UIEventSource"; | ||||||
| import Constants from "../../Models/Constants"; | import Constants from "../../Models/Constants"; | ||||||
| import FeatureSource from "../FeatureSource/FeatureSource"; | import FeatureSource from "../FeatureSource/FeatureSource"; | ||||||
| import {TagsFilter} from "../TagsFilter"; | import {TagsFilter} from "../Tags/TagsFilter"; | ||||||
| import {Tag} from "../Tag"; | import {Tag} from "../Tags/Tag"; | ||||||
| import {And} from "../And"; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handles all changes made to OSM. |  * Handles all changes made to OSM. | ||||||
|  | @ -29,7 +28,9 @@ export class Changes implements FeatureSource{ | ||||||
|     /** |     /** | ||||||
|      * Adds a change to the pending changes |      * 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) { |         if (key === undefined || key === null) { | ||||||
|             console.log("Invalid key"); |             console.log("Invalid key"); | ||||||
|             return undefined; |             return undefined; | ||||||
|  | @ -43,22 +44,19 @@ export class Changes implements FeatureSource{ | ||||||
|             console.warn("Tag starts with or ends with a space - trimming anyway") |             console.warn("Tag starts with or ends with a space - trimming anyway") | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         key = key.trim(); |         return {k: key.trim(), v: value.trim()}; | ||||||
|         value = value.trim(); |  | ||||||
| 
 |  | ||||||
|         return {k: key, v: value}; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|      |      | ||||||
|      |      | ||||||
|     addTag(elementId: string, tagsFilter: TagsFilter, |     addTag(elementId: string, tagsFilter: TagsFilter, | ||||||
|            tags?: UIEventSource<any>) { |            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) { |         if (changes.length == 0) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         const eventSource = tags ?? State.state?.allElements.getEventSourceById(elementId); |  | ||||||
|         const elementTags = eventSource.data; |  | ||||||
|         for (const change of changes) { |         for (const change of changes) { | ||||||
|             if (elementTags[change.k] !== change.v) { |             if (elementTags[change.k] !== change.v) { | ||||||
|                 elementTags[change.k] = change.v; |                 elementTags[change.k] = change.v; | ||||||
|  | @ -132,28 +130,6 @@ export class Changes implements FeatureSource{ | ||||||
|         return geojson; |         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( |     private uploadChangesWithLatestVersions( | ||||||
|         knownElements, newElements: OsmObject[], pending: { elementId: string; key: string; value: string }[]) { |         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
 |         // 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 {GeoOperations} from "./GeoOperations"; | ||||||
| import State from "../State"; | import State from "../State"; | ||||||
| import {And} from "./And"; | import {And} from "./Tags/And"; | ||||||
| import {Tag} from "./Tag"; | import {Tag} from "./Tags/Tag"; | ||||||
| import {Or} from "./Or"; | import {Or} from "./Tags/Or"; | ||||||
| import {Utils} from "../Utils"; | import {Utils} from "../Utils"; | ||||||
| import opening_hours from "opening_hours"; | import opening_hours from "opening_hours"; | ||||||
| import {UIElement} from "../UI/UIElement"; | import {UIElement} from "../UI/UIElement"; | ||||||
|  |  | ||||||
|  | @ -46,16 +46,8 @@ export class And extends TagsFilter { | ||||||
|         return allChoices; |         return allChoices; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     substituteValues(tags: any): TagsFilter { |     asHumanString(linkToWiki: boolean, shorten: boolean, properties) { | ||||||
|         const newChoices = []; |         return this.and.map(t => t.asHumanString(linkToWiki, shorten, properties)).join("&"); | ||||||
|         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("&"); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     isUsableAsAnswer(): boolean { |     isUsableAsAnswer(): boolean { | ||||||
|  | @ -116,4 +108,12 @@ export class And extends TagsFilter { | ||||||
|     usedKeys(): string[] { |     usedKeys(): string[] { | ||||||
|         return [].concat(...this.and.map(subkeys => subkeys.usedKeys())); |         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; |         return choices; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     substituteValues(tags: any): TagsFilter { |     asHumanString(linkToWiki: boolean, shorten: boolean, properties) { | ||||||
|         const newChoices = []; |         return this.or.map(t => t.asHumanString(linkToWiki, shorten, properties)).join("|"); | ||||||
|         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("|"); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     isUsableAsAnswer(): boolean { |     isUsableAsAnswer(): boolean { | ||||||
|  | @ -67,6 +59,10 @@ export class Or extends TagsFilter { | ||||||
|     usedKeys(): string[] { |     usedKeys(): string[] { | ||||||
|         return [].concat(...this.or.map(subkeys => subkeys.usedKeys())); |         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; |         return this.invert; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     substituteValues(tags: any): TagsFilter { |  | ||||||
|         return this; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     asHumanString() { |     asHumanString() { | ||||||
|         if (typeof this.key === "string") { |         if (typeof this.key === "string") { | ||||||
|             return `${this.key}${this.invert ? "!" : ""}~${RegexTag.source(this.value)}`; |             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" |         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 {RegexTag} from "./RegexTag"; | ||||||
| import {TagsFilter} from "./TagsFilter"; | import {TagsFilter} from "./TagsFilter"; | ||||||
| import {TagUtils} from "./TagUtils"; | import {TagUtils} from "./TagUtils"; | ||||||
|  | @ -81,4 +81,8 @@ export class Tag extends TagsFilter { | ||||||
|     usedKeys(): string[] { |     usedKeys(): string[] { | ||||||
|         return [this.key]; |         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 {Tag} from "./Tag"; | ||||||
| import {TagsFilter} from "./TagsFilter"; | import {TagsFilter} from "./TagsFilter"; | ||||||
| import {And} from "./And"; | import {And} from "./And"; | ||||||
| import {Utils} from "../Utils"; | import {Utils} from "../../Utils"; | ||||||
| 
 | 
 | ||||||
| export class TagUtils { | export class TagUtils { | ||||||
|     static ApplyTemplate(template: string, tags: any): string { |     static ApplyTemplate(template: string, tags: any): string { | ||||||
|  | @ -2,23 +2,20 @@ export abstract class TagsFilter { | ||||||
| 
 | 
 | ||||||
|     abstract asOverpass(): string[] |     abstract asOverpass(): string[] | ||||||
| 
 | 
 | ||||||
|     abstract substituteValues(tags: any): TagsFilter; |  | ||||||
| 
 |  | ||||||
|     abstract isUsableAsAnswer(): boolean; |     abstract isUsableAsAnswer(): boolean; | ||||||
| 
 | 
 | ||||||
|     abstract isEquivalent(other: TagsFilter): boolean; |     abstract isEquivalent(other: TagsFilter): boolean; | ||||||
| 
 | 
 | ||||||
|     abstract matchesProperties(properties: any): boolean; |     abstract matchesProperties(properties: any): boolean; | ||||||
| 
 | 
 | ||||||
|     abstract asHumanString(linkToWiki: boolean, shorten: boolean); |     abstract asHumanString(linkToWiki: boolean, shorten: boolean, properties: any); | ||||||
| 
 | 
 | ||||||
|     abstract usedKeys(): string[]; |     abstract usedKeys(): string[]; | ||||||
| 
 | 
 | ||||||
|     public matches(tags: { k: string, v: string }[]) { |     /** | ||||||
|         const properties = {}; |      * Converts the tagsFilter into a list of key-values that should be uploaded to OSM. | ||||||
|         for (const kv of tags) { |      * Throws an error if not applicable | ||||||
|             properties[kv.k] = kv.v; |      */ | ||||||
|         } |     abstract asChange(properties:any): {k: string, v:string}[] | ||||||
|         return this.matchesProperties(properties); |      | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | @ -2,7 +2,7 @@ import { Utils } from "../Utils"; | ||||||
| 
 | 
 | ||||||
| export default class Constants { | 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
 |     // The user journey states thresholds when a new feature gets unlocked
 | ||||||
|     public static userJourney = { |     public static userJourney = { | ||||||
|  |  | ||||||
|  | @ -12,8 +12,8 @@ import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import Constants from "../../Models/Constants"; | import Constants from "../../Models/Constants"; | ||||||
| import LayerConfig from "../../Customizations/JSON/LayerConfig"; | import LayerConfig from "../../Customizations/JSON/LayerConfig"; | ||||||
| import {Tag} from "../../Logic/Tag"; | import {Tag} from "../../Logic/Tags/Tag"; | ||||||
| import {TagUtils} from "../../Logic/TagUtils"; | import {TagUtils} from "../../Logic/Tags/TagUtils"; | ||||||
| 
 | 
 | ||||||
| export default class SimpleAddUI extends UIElement { | export default class SimpleAddUI extends UIElement { | ||||||
|     private readonly _loginButton: UIElement; |     private readonly _loginButton: UIElement; | ||||||
|  | @ -158,7 +158,7 @@ export default class SimpleAddUI extends UIElement { | ||||||
|         let tagInfo = ""; |         let tagInfo = ""; | ||||||
|         const csCount = State.state.osmConnection.userDetails.data.csCount; |         const csCount = State.state.osmConnection.userDetails.data.csCount; | ||||||
|         if (csCount > Constants.userJourney.tagsVisibleAt) { |         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}` |             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; |                 const csCount = State.state.osmConnection.userDetails.data.csCount; | ||||||
|                 let tagInfo = undefined; |                 let tagInfo = undefined; | ||||||
|                 if (csCount > Constants.userJourney.tagsVisibleAt) { |                 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) |                     tagInfo = new Combine(presets) | ||||||
|                 } |                 } | ||||||
|                 const button: UIElement = |                 const button: UIElement = | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import CheckBox from "../Input/CheckBox"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import State from "../../State"; | import State from "../../State"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import {Tag} from "../../Logic/Tag"; | import {Tag} from "../../Logic/Tags/Tag"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export default class DeleteImage extends UIElement { | export default class DeleteImage extends UIElement { | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ import {Imgur} from "../../Logic/Web/Imgur"; | ||||||
| import {DropDown} from "../Input/DropDown"; | import {DropDown} from "../Input/DropDown"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import {Tag} from "../../Logic/Tag"; | import {Tag} from "../../Logic/Tags/Tag"; | ||||||
| 
 | 
 | ||||||
| export class ImageUploadFlow extends UIElement { | export class ImageUploadFlow extends UIElement { | ||||||
|     private readonly _licensePicker: UIElement; |     private readonly _licensePicker: UIElement; | ||||||
|  |  | ||||||
|  | @ -7,7 +7,6 @@ import Combine from "../Base/Combine"; | ||||||
| import TagRenderingAnswer from "./TagRenderingAnswer"; | import TagRenderingAnswer from "./TagRenderingAnswer"; | ||||||
| import State from "../../State"; | import State from "../../State"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import {TagUtils} from "../../Logic/TagUtils"; |  | ||||||
| 
 | 
 | ||||||
| export default class EditableTagRendering extends UIElement { | export default class EditableTagRendering extends UIElement { | ||||||
|     private readonly _tags: UIEventSource<any>; |     private readonly _tags: UIEventSource<any>; | ||||||
|  |  | ||||||
|  | @ -8,8 +8,7 @@ import TagRenderingAnswer from "./TagRenderingAnswer"; | ||||||
| import State from "../../State"; | import State from "../../State"; | ||||||
| import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig"; | import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig"; | ||||||
| import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | ||||||
| import {Utils} from "../../Utils"; | import {Tag} from "../../Logic/Tags/Tag"; | ||||||
| import {Tag} from "../../Logic/Tag"; |  | ||||||
| 
 | 
 | ||||||
| export default class FeatureInfoBox extends ScrollableFullScreen { | export default class FeatureInfoBox extends ScrollableFullScreen { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import {Utils} from "../../Utils"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {SubstitutedTranslation} from "../SubstitutedTranslation"; | import {SubstitutedTranslation} from "../SubstitutedTranslation"; | ||||||
| import {Translation} from "../i18n/Translation"; | import {Translation} from "../i18n/Translation"; | ||||||
| import {TagUtils} from "../../Logic/TagUtils"; | import {TagUtils} from "../../Logic/Tags/TagUtils"; | ||||||
| 
 | 
 | ||||||
| /*** | /*** | ||||||
|  * Displays the correct value for a known tagrendering |  * Displays the correct value for a known tagrendering | ||||||
|  |  | ||||||
|  | @ -18,10 +18,10 @@ import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| import {Translation} from "../i18n/Translation"; | import {Translation} from "../i18n/Translation"; | ||||||
| import Constants from "../../Models/Constants"; | import Constants from "../../Models/Constants"; | ||||||
| import {SubstitutedTranslation} from "../SubstitutedTranslation"; | import {SubstitutedTranslation} from "../SubstitutedTranslation"; | ||||||
| import {TagsFilter} from "../../Logic/TagsFilter"; | import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | ||||||
| import {Tag} from "../../Logic/Tag"; | import {Tag} from "../../Logic/Tags/Tag"; | ||||||
| import {And} from "../../Logic/And"; | import {And} from "../../Logic/Tags/And"; | ||||||
| import {TagUtils} from "../../Logic/TagUtils"; | import {TagUtils} from "../../Logic/Tags/TagUtils"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Shows the question element. |  * Shows the question element. | ||||||
|  | @ -57,9 +57,8 @@ export default class TagRenderingQuestion extends UIElement { | ||||||
|         this._inputElement = this.GenerateInputElement() |         this._inputElement = this.GenerateInputElement() | ||||||
|         const self = this; |         const self = this; | ||||||
|         const save = () => { |         const save = () => { | ||||||
|             console.log("Save clicked!") |  | ||||||
|             const selection = self._inputElement.GetValue().data; |             const selection = self._inputElement.GetValue().data; | ||||||
|             console.log("Selection is", selection) |             console.log("Save button clicked, the tags are is", selection) | ||||||
|             if (selection) { |             if (selection) { | ||||||
|                 (State.state?.changes ?? new Changes()) |                 (State.state?.changes ?? new Changes()) | ||||||
|                     .addTag(tags.data.id, selection, tags); |                     .addTag(tags.data.id, selection, tags); | ||||||
|  | @ -86,10 +85,10 @@ export default class TagRenderingQuestion extends UIElement { | ||||||
|                         return Translations.t.general.noTagsSelected.SetClass("subtle").Render(); |                         return Translations.t.general.noTagsSelected.SetClass("subtle").Render(); | ||||||
|                     } |                     } | ||||||
|                     if (csCount < Constants.userJourney.tagsVisibleAndWikiLinked) { |                     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 new FixedUiElement(tagsStr).SetClass("subtle").Render(); | ||||||
|                     } |                     } | ||||||
|                     return tags.asHumanString(true, true); |                     return tags.asHumanString(true, true, self._tags.data); | ||||||
|                 } |                 } | ||||||
|             ) |             ) | ||||||
|         ).SetClass("block") |         ).SetClass("block") | ||||||
|  |  | ||||||
|  | @ -27,8 +27,10 @@ export default class ShowDataLayer { | ||||||
| 
 | 
 | ||||||
|         layoutToUse.addCallbackAndRun(layoutToUse => { |         layoutToUse.addCallbackAndRun(layoutToUse => { | ||||||
|             for (const layer of layoutToUse.layers) { |             for (const layer of layoutToUse.layers) { | ||||||
|  |                 if (self._layerDict[layer.id] === undefined) { | ||||||
|                     self._layerDict[layer.id] = layer; |                     self._layerDict[layer.id] = layer; | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         let geoLayer = undefined; |         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, |   "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, |   "minzoom": 13, | ||||||
|   "wayHandling": 1, |   "wayHandling": 1, | ||||||
|   "presets": [ |   "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": { |       "name": { | ||||||
|         "nl": "Fietsstraten" |         "nl": "Fietsstraten" | ||||||
|       }, |       }, | ||||||
|       "minzoom": 9, |       "minzoom": 7, | ||||||
|       "source": { |       "source": { | ||||||
|         "osmTags": { |         "osmTags": { | ||||||
|           "and": [ |           "and": [ | ||||||
|  |  | ||||||
|  | @ -8,7 +8,10 @@ | ||||||
|     "en": "On this map, publicly accessible drinkging water spots are shown and can be easily added", |     "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" |     "nl": "Op deze kaart staan publiek toegankelijke drinkwaterpunten en kan je makkelijk een nieuw drinkwaterpunt toevoegen" | ||||||
|   }, |   }, | ||||||
|   "language": ["en", "nl"], |   "language": [ | ||||||
|  |     "en", | ||||||
|  |     "nl" | ||||||
|  |   ], | ||||||
|   "maintainer": "MapComplete", |   "maintainer": "MapComplete", | ||||||
|   "icon": "./assets/themes/drinking_water/logo.svg", |   "icon": "./assets/themes/drinking_water/logo.svg", | ||||||
|   "version": "0", |   "version": "0", | ||||||
|  | @ -17,6 +20,8 @@ | ||||||
|   "startLon": 4.3516970, |   "startLon": 4.3516970, | ||||||
|   "startZoom": 16, |   "startZoom": 16, | ||||||
|   "widenFactor": 0.05, |   "widenFactor": 0.05, | ||||||
|   "layers": ["drinking_water"], |   "layers": [ | ||||||
|  |     "drinking_water" | ||||||
|  |   ], | ||||||
|   "roamingRenderings": [] |   "roamingRenderings": [] | ||||||
| } | } | ||||||
							
								
								
									
										51
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										51
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -5729,11 +5729,18 @@ | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "debug": { |     "debug": { | ||||||
|       "version": "2.2.0", |       "version": "2.6.9", | ||||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", |       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||||
|       "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", |       "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||||
|       "requires": { |       "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": { |     "decamelize": { | ||||||
|  | @ -8214,18 +8221,11 @@ | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "mkdirp": { |     "mkdirp": { | ||||||
|       "version": "0.5.1", |       "version": "0.5.5", | ||||||
|       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", |       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", | ||||||
|       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", |       "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "minimist": "0.0.8" |         "minimist": "^1.2.5" | ||||||
|       }, |  | ||||||
|       "dependencies": { |  | ||||||
|         "minimist": { |  | ||||||
|           "version": "0.0.8", |  | ||||||
|           "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", |  | ||||||
|           "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "mkdirp-classic": { |     "mkdirp-classic": { | ||||||
|  | @ -8255,6 +8255,14 @@ | ||||||
|           "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", |           "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", | ||||||
|           "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=" |           "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": { |         "escape-string-regexp": { | ||||||
|           "version": "1.0.2", |           "version": "1.0.2", | ||||||
|           "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", |           "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", | ||||||
|  | @ -8278,6 +8286,19 @@ | ||||||
|             "sigmund": "~1.0.0" |             "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": { |         "supports-color": { | ||||||
|           "version": "1.2.0", |           "version": "1.2.0", | ||||||
|           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", |           "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 {OH, OpeningHour} from "../UI/OpeningHours/OpeningHours"; | ||||||
| import PublicHolidayInput from "../UI/OpeningHours/PublicHolidayInput"; | import PublicHolidayInput from "../UI/OpeningHours/PublicHolidayInput"; | ||||||
| import {SubstitutedTranslation} from "../UI/SubstitutedTranslation"; | import {SubstitutedTranslation} from "../UI/SubstitutedTranslation"; | ||||||
| import {Tag} from "../Logic/Tag"; | import {Tag} from "../Logic/Tags/Tag"; | ||||||
| import {And} from "../Logic/And"; | import {And} from "../Logic/Tags/And"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| new T("Tags", [ | new T("Tags", [ | ||||||
|  | @ -79,6 +79,14 @@ new T("Tags", [ | ||||||
|         equal(nameStartsWith.matchesProperties({"name": "Sint-Anna"}), false) |         equal(nameStartsWith.matchesProperties({"name": "Sint-Anna"}), false) | ||||||
|         equal(nameStartsWith.matchesProperties({"name": ""}), 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", (() => { |     ["Is equivalent test", (() => { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue