forked from MapComplete/MapComplete
		
	Fix: maproulette import flow
This commit is contained in:
		
							parent
							
								
									f80054558f
								
							
						
					
					
						commit
						5f7cc351c9
					
				
					 18 changed files with 331 additions and 114 deletions
				
			
		|  | @ -254,6 +254,7 @@ class ClosestNObjectFunc implements ExtraFunction { | ||||||
|         const maxDistance = options?.maxDistance ?? 500 |         const maxDistance = options?.maxDistance ?? 500 | ||||||
|         const uniqueTag: string | undefined = options?.uniqueTag |         const uniqueTag: string | undefined = options?.uniqueTag | ||||||
|         let allFeatures: Feature[][] |         let allFeatures: Feature[][] | ||||||
|  |         console.log("Calculating closest", options?.maxFeatures, "features around", feature, "in layer", features) | ||||||
|         if (typeof features === "string") { |         if (typeof features === "string") { | ||||||
|             const name = features |             const name = features | ||||||
|             const bbox = GeoOperations.bbox( |             const bbox = GeoOperations.bbox( | ||||||
|  | @ -414,7 +415,7 @@ class GetParsed implements ExtraFunction { | ||||||
|             if (value === undefined) { |             if (value === undefined) { | ||||||
|                 return undefined |                 return undefined | ||||||
|             } |             } | ||||||
|             if(typeof value !== "string"){ |             if (typeof value !== "string") { | ||||||
|                 return value |                 return value | ||||||
|             } |             } | ||||||
|             try { |             try { | ||||||
|  |  | ||||||
|  | @ -105,6 +105,10 @@ export default class GeoJsonSource implements FeatureSource { | ||||||
|         let i = 0 |         let i = 0 | ||||||
|         let skipped = 0 |         let skipped = 0 | ||||||
|         for (const feature of json.features) { |         for (const feature of json.features) { | ||||||
|  |             if(feature.geometry.type === "Point"){ | ||||||
|  |                 // See https://github.com/maproulette/maproulette-backend/issues/242
 | ||||||
|  |                 feature.geometry.coordinates = feature.geometry.coordinates.map(Number) | ||||||
|  |             } | ||||||
|             const props = feature.properties |             const props = feature.properties | ||||||
|             for (const key in props) { |             for (const key in props) { | ||||||
|                 if (props[key] === null) { |                 if (props[key] === null) { | ||||||
|  |  | ||||||
|  | @ -72,4 +72,23 @@ export default class Maproulette { | ||||||
|             throw `Failed to close task: ${response.status}` |             throw `Failed to close task: ${response.status}` | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Converts a status text into the corresponding number | ||||||
|  |      * | ||||||
|  |      * Maproulette.codeToIndex("Created") // => 0
 | ||||||
|  |      * Maproulette.codeToIndex("qdsf") // => undefined
 | ||||||
|  |      * | ||||||
|  |      */ | ||||||
|  |     public static codeToIndex(code: string) : number | undefined{ | ||||||
|  |         if(code === "Created"){ | ||||||
|  |             return Maproulette.STATUS_OPEN | ||||||
|  |         } | ||||||
|  |         for (let i = 0; i < 9; i++) { | ||||||
|  |             if(Maproulette.STATUS_MEANING[""+i] === code){ | ||||||
|  |                 return i | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return undefined | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -8,7 +8,6 @@ import {GeoIndexedStoreForLayer} from "./FeatureSource/Actors/GeoIndexedStore" | ||||||
| import {IndexedFeatureSource} from "./FeatureSource/FeatureSource" | import {IndexedFeatureSource} from "./FeatureSource/FeatureSource" | ||||||
| import OsmObjectDownloader from "./Osm/OsmObjectDownloader" | import OsmObjectDownloader from "./Osm/OsmObjectDownloader" | ||||||
| import {Utils} from "../Utils"; | import {Utils} from "../Utils"; | ||||||
| import {GeoJSONFeature} from "maplibre-gl"; |  | ||||||
| import {UIEventSource} from "./UIEventSource"; | import {UIEventSource} from "./UIEventSource"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -206,13 +205,13 @@ export default class MetaTagging { | ||||||
|     private static createFunctionForFeature([key, code, isStrict]: [string, string, boolean], |     private static createFunctionForFeature([key, code, isStrict]: [string, string, boolean], | ||||||
|                                             helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>, |                                             helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>, | ||||||
|                                             layerId: string = "unkown layer" |                                             layerId: string = "unkown layer" | ||||||
|     ): ((feature: GeoJSONFeature, propertiesStore?: UIEventSource<any>) => void) | undefined { |     ): ((feature: Feature, propertiesStore?: UIEventSource<any>) => void) | undefined { | ||||||
|         if (code === undefined) { |         if (code === undefined) { | ||||||
|             return undefined |             return undefined | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const calculateAndAssign: ((feat: GeoJSONFeature, store?: UIEventSource<any>) => string | any) = (feat, store) => { |         const calculateAndAssign: ((feat: Feature, store?: UIEventSource<any>) => string | any) = (feat, store) => { | ||||||
|             try { |             try { | ||||||
|                 let result = new Function("feat", "{" + ExtraFunctions.types.join(", ") + "}", "return " + code + ";")(feat, helperFunctions) |                 let result = new Function("feat", "{" + ExtraFunctions.types.join(", ") + "}", "return " + code + ";")(feat, helperFunctions) | ||||||
|                 if (result === "") { |                 if (result === "") { | ||||||
|  | @ -259,7 +258,7 @@ export default class MetaTagging { | ||||||
|         if (isStrict) { |         if (isStrict) { | ||||||
|             return calculateAndAssign |             return calculateAndAssign | ||||||
|         } |         } | ||||||
|         return (feature: any, store?: UIEventSource<any>) => { |         return (feature: Feature, store?: UIEventSource<any>) => { | ||||||
|             delete feature.properties[key] |             delete feature.properties[key] | ||||||
|             Utils.AddLazyProperty(feature.properties, key, () => calculateAndAssign(feature, store)) |             Utils.AddLazyProperty(feature.properties, key, () => calculateAndAssign(feature, store)) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -132,6 +132,10 @@ class CountryTagger extends SimpleMetaTagger { | ||||||
|         CountryTagger.coder |         CountryTagger.coder | ||||||
|             .GetCountryCodeAsync(lon, lat) |             .GetCountryCodeAsync(lon, lat) | ||||||
|             .then((countries) => { |             .then((countries) => { | ||||||
|  |                 if(!countries){ | ||||||
|  |                     console.warn("Country coder returned ", countries) | ||||||
|  |                     return | ||||||
|  |                 } | ||||||
|                 const oldCountry = feature.properties["_country"] |                 const oldCountry = feature.properties["_country"] | ||||||
|                 const newCountry = countries[0].trim().toLowerCase() |                 const newCountry = countries[0].trim().toLowerCase() | ||||||
|                 if (oldCountry !== newCountry) { |                 if (oldCountry !== newCountry) { | ||||||
|  |  | ||||||
|  | @ -538,7 +538,6 @@ export default class TagRenderingConfig { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if ( |         if ( | ||||||
|             this.id === "questions" || |  | ||||||
|             this.freeform?.key === undefined || |             this.freeform?.key === undefined || | ||||||
|             tags[this.freeform.key] !== undefined |             tags[this.freeform.key] !== undefined | ||||||
|         ) { |         ) { | ||||||
|  |  | ||||||
|  | @ -116,11 +116,12 @@ | ||||||
|                     currentFlowStep = "imported" |                     currentFlowStep = "imported" | ||||||
|                     dispatch("confirm") |                     dispatch("confirm") | ||||||
|                 }}> |                 }}> | ||||||
|                     <span slot="image"> |                     <span slot="image" class="w-8 h-8 pr-4"> | ||||||
|                          |                         {#if importFlow.args.icon} | ||||||
|                     {#if importFlow.args.icon} |                             <img src={importFlow.args.icon}> | ||||||
|                         <img src={importFlow.args.icon}> |                         {:else} | ||||||
|                     {/if} |                             <ToSvelte construct={Svg.confirm_svg().SetClass("w-8 h-8 pr-4")}/> | ||||||
|  |                         {/if} | ||||||
|                     </span> |                     </span> | ||||||
|                     <slot name="confirm-text"> |                     <slot name="confirm-text"> | ||||||
|                         {importFlow.args.text} |                         {importFlow.args.text} | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import {SpecialVisualizationState} from "../../SpecialVisualization"; | import {SpecialVisualizationState} from "../../SpecialVisualization"; | ||||||
| import {Utils} from "../../../Utils"; | import {Utils} from "../../../Utils"; | ||||||
| import {Store, UIEventSource} from "../../../Logic/UIEventSource"; | import {ImmutableStore, Store, UIEventSource} from "../../../Logic/UIEventSource"; | ||||||
| import {Tag} from "../../../Logic/Tags/Tag"; | import {Tag} from "../../../Logic/Tags/Tag"; | ||||||
| import TagApplyButton from "../TagApplyButton"; | import TagApplyButton from "../TagApplyButton"; | ||||||
| import {PointImportFlowArguments} from "./PointImportFlowState"; | import {PointImportFlowArguments} from "./PointImportFlowState"; | ||||||
|  | @ -11,6 +11,7 @@ import FilteredLayer from "../../../Models/FilteredLayer"; | ||||||
| import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; | ||||||
| import {LayerConfigJson} from "../../../Models/ThemeConfig/Json/LayerConfigJson"; | import {LayerConfigJson} from "../../../Models/ThemeConfig/Json/LayerConfigJson"; | ||||||
| import conflation_json from "../../../assets/layers/conflation/conflation.json"; | import conflation_json from "../../../assets/layers/conflation/conflation.json"; | ||||||
|  | import {And} from "../../../Logic/Tags/And"; | ||||||
| 
 | 
 | ||||||
| export interface ImportFlowArguments { | export interface ImportFlowArguments { | ||||||
|     readonly  text: string |     readonly  text: string | ||||||
|  | @ -85,6 +86,15 @@ ${Utils.special_visualizations_importRequirementDocs} | ||||||
|                 "] of this object, namely ", |                 "] of this object, namely ", | ||||||
|                 items |                 items | ||||||
|             ) |             ) | ||||||
|  | 
 | ||||||
|  |             if(items.startsWith("{")){ | ||||||
|  |                 // This is probably a JSON
 | ||||||
|  |                 const properties: Record<string, string> = JSON.parse(items) | ||||||
|  |                 const keys = Object.keys(properties) | ||||||
|  |                 const tags = keys.map(k => new Tag(k, properties[k])) | ||||||
|  |                 return new ImmutableStore((tags)) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             newTags = TagApplyButton.generateTagsToApply(items, originalFeatureTags) |             newTags = TagApplyButton.generateTagsToApply(items, originalFeatureTags) | ||||||
|         } else { |         } else { | ||||||
|             newTags = TagApplyButton.generateTagsToApply(tags, originalFeatureTags) |             newTags = TagApplyButton.generateTagsToApply(tags, originalFeatureTags) | ||||||
|  |  | ||||||
|  | @ -1,22 +1,23 @@ | ||||||
| import { AutoAction } from "./AutoApplyButton" | import {AutoAction} from "./AutoApplyButton" | ||||||
| import Translations from "../i18n/Translations" | import Translations from "../i18n/Translations" | ||||||
| import { VariableUiElement } from "../Base/VariableUIElement" | import {VariableUiElement} from "../Base/VariableUIElement" | ||||||
| import BaseUIElement from "../BaseUIElement" | import BaseUIElement from "../BaseUIElement" | ||||||
| import { FixedUiElement } from "../Base/FixedUiElement" | import {FixedUiElement} from "../Base/FixedUiElement" | ||||||
| import { Store, UIEventSource } from "../../Logic/UIEventSource" | import {Store, UIEventSource} from "../../Logic/UIEventSource" | ||||||
| import { SubtleButton } from "../Base/SubtleButton" | import {SubtleButton} from "../Base/SubtleButton" | ||||||
| import Combine from "../Base/Combine" | import Combine from "../Base/Combine" | ||||||
| import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" | import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" | ||||||
| import { And } from "../../Logic/Tags/And" | import {And} from "../../Logic/Tags/And" | ||||||
| import Toggle from "../Input/Toggle" | import Toggle from "../Input/Toggle" | ||||||
| import { Utils } from "../../Utils" | import {Utils} from "../../Utils" | ||||||
| import { Tag } from "../../Logic/Tags/Tag" | import {Tag} from "../../Logic/Tags/Tag" | ||||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | ||||||
| import { Changes } from "../../Logic/Osm/Changes" | import {Changes} from "../../Logic/Osm/Changes" | ||||||
| import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | import {SpecialVisualization, SpecialVisualizationState} from "../SpecialVisualization" | ||||||
| import {IndexedFeatureSource} from "../../Logic/FeatureSource/FeatureSource"; | import {IndexedFeatureSource} from "../../Logic/FeatureSource/FeatureSource"; | ||||||
| import {Feature} from "geojson"; | import {Feature} from "geojson"; | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
|  | import Maproulette from "../../Logic/Maproulette"; | ||||||
| 
 | 
 | ||||||
| export default class TagApplyButton implements AutoAction, SpecialVisualization { | export default class TagApplyButton implements AutoAction, SpecialVisualization { | ||||||
|     public readonly funcName = "tag_apply" |     public readonly funcName = "tag_apply" | ||||||
|  | @ -27,7 +28,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization | ||||||
|     public readonly args = [ |     public readonly args = [ | ||||||
|         { |         { | ||||||
|             name: "tags_to_apply", |             name: "tags_to_apply", | ||||||
|             doc: "A specification of the tags to apply", |             doc: "A specification of the tags to apply. This is either hardcoded in the layer or the `$name` of a property containing the tags to apply. If redirected and the value of the linked property starts with `{`, the other property will be interpreted as a json object", | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             name: "message", |             name: "message", | ||||||
|  | @ -42,10 +43,62 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization | ||||||
|             defaultValue: undefined, |             defaultValue: undefined, | ||||||
|             doc: "If specified, applies the the tags onto _another_ object. The id will be read from properties[id_of_object_to_apply_this_one] of the selected object. The tags are still calculated based on the tags of the _selected_ element", |             doc: "If specified, applies the the tags onto _another_ object. The id will be read from properties[id_of_object_to_apply_this_one] of the selected object. The tags are still calculated based on the tags of the _selected_ element", | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |             name:"maproulette_task_id", | ||||||
|  |             defaultValue: undefined, | ||||||
|  |             doc: "If specified, this maproulette-challenge will be closed when the tags are applied" | ||||||
|  |         } | ||||||
|     ] |     ] | ||||||
|     public readonly example = |     public readonly example = | ||||||
|         "`{tag_apply(survey_date=$_now:date, Surveyed today!)}`, `{tag_apply(addr:street=$addr:street, Apply the address, apply_icon.svg, _closest_osm_id)" |         "`{tag_apply(survey_date=$_now:date, Surveyed today!)}`, `{tag_apply(addr:street=$addr:street, Apply the address, apply_icon.svg, _closest_osm_id)" | ||||||
| 
 | 
 | ||||||
|  |     public static generateTagsToApply( | ||||||
|  |         spec: string, | ||||||
|  |         tagSource: Store<Record<string, string>> | ||||||
|  |     ): Store<Tag[]> { | ||||||
|  |         // Check whether we need to look up a single value
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         if (!spec.includes(";") && !spec.includes("=") && spec.startsWith("$")) { | ||||||
|  |             // We seem to be dealing with a single value, fetch it
 | ||||||
|  |             spec = tagSource.data[spec.replace("$", "")] | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let tgsSpec: [string, string][] | ||||||
|  | 
 | ||||||
|  |         if (spec.startsWith("{")) { | ||||||
|  |             const properties = JSON.parse(spec) | ||||||
|  |             tgsSpec = [] | ||||||
|  |             for (const key of Object.keys(properties)) { | ||||||
|  |                 tgsSpec.push([key, properties[key]]) | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             tgsSpec = TagApplyButton.parseTagSpec(spec) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return tagSource.map((tags) => { | ||||||
|  |             const newTags: Tag[] = [] | ||||||
|  |             for (const [key, value] of tgsSpec) { | ||||||
|  |                 if (value.indexOf("$") >= 0) { | ||||||
|  |                     let parts = value.split("$") | ||||||
|  |                     // THe first of the split won't start with a '$', so no substitution needed
 | ||||||
|  |                     let actualValue = parts[0] | ||||||
|  |                     parts.shift() | ||||||
|  | 
 | ||||||
|  |                     for (const part of parts) { | ||||||
|  |                         const [_, varName, leftOver] = part.match(/([a-zA-Z0-9_:]*)(.*)/) | ||||||
|  |                         actualValue += (tags[varName] ?? "") + leftOver | ||||||
|  |                     } | ||||||
|  |                     newTags.push(new Tag(key, actualValue)) | ||||||
|  |                 } else { | ||||||
|  |                     newTags.push(new Tag(key, value)) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return newTags | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Parses a tag specification |      * Parses a tag specification | ||||||
|      * |      * | ||||||
|  | @ -79,41 +132,6 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization | ||||||
|         return tgsSpec |         return tgsSpec | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static generateTagsToApply( |  | ||||||
|         spec: string, |  | ||||||
|         tagSource: Store<Record<string, string>> |  | ||||||
|     ): Store<Tag[]> { |  | ||||||
|         // Check whether we need to look up a single value
 |  | ||||||
| 
 |  | ||||||
|         if (!spec.includes(";") && !spec.includes("=") && spec.includes("$")) { |  | ||||||
|             // We seem to be dealing with a single value, fetch it
 |  | ||||||
|             spec = tagSource.data[spec.replace("$", "")] |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const tgsSpec = TagApplyButton.parseTagSpec(spec) |  | ||||||
| 
 |  | ||||||
|         return tagSource.map((tags) => { |  | ||||||
|             const newTags: Tag[] = [] |  | ||||||
|             for (const [key, value] of tgsSpec) { |  | ||||||
|                 if (value.indexOf("$") >= 0) { |  | ||||||
|                     let parts = value.split("$") |  | ||||||
|                     // THe first of the split won't start with a '$', so no substitution needed
 |  | ||||||
|                     let actualValue = parts[0] |  | ||||||
|                     parts.shift() |  | ||||||
| 
 |  | ||||||
|                     for (const part of parts) { |  | ||||||
|                         const [_, varName, leftOver] = part.match(/([a-zA-Z0-9_:]*)(.*)/) |  | ||||||
|                         actualValue += (tags[varName] ?? "") + leftOver |  | ||||||
|                     } |  | ||||||
|                     newTags.push(new Tag(key, actualValue)) |  | ||||||
|                 } else { |  | ||||||
|                     newTags.push(new Tag(key, value)) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return newTags |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async applyActionOn( |     public async applyActionOn( | ||||||
|         feature: Feature, |         feature: Feature, | ||||||
|         state: { |         state: { | ||||||
|  | @ -123,7 +141,6 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization | ||||||
|         }, |         }, | ||||||
|         tags: UIEventSource<any>, |         tags: UIEventSource<any>, | ||||||
|         args: string[], |         args: string[], | ||||||
| 
 |  | ||||||
|     ): Promise<void> { |     ): Promise<void> { | ||||||
|         const tagsToApply = TagApplyButton.generateTagsToApply(args[0], tags) |         const tagsToApply = TagApplyButton.generateTagsToApply(args[0], tags) | ||||||
|         const targetIdKey = args[3] |         const targetIdKey = args[3] | ||||||
|  | @ -139,6 +156,13 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|         await state.changes.applyAction(changeAction) |         await state.changes.applyAction(changeAction) | ||||||
|  |         const maproulette_id_key = args[4] | ||||||
|  |         if(maproulette_id_key){ | ||||||
|  |             const maproulette_id = Number(tags.data[maproulette_id_key]) | ||||||
|  |             await Maproulette.singleton.closeTask(maproulette_id, Maproulette.STATUS_FIXED,   { | ||||||
|  |                 comment: "Tags are copied onto "+targetId+" with MapComplete" | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public constr( |     public constr( | ||||||
|  | @ -163,7 +187,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization | ||||||
|                 let el: BaseUIElement = new FixedUiElement(tagsStr) |                 let el: BaseUIElement = new FixedUiElement(tagsStr) | ||||||
|                 if (targetIdKey !== undefined) { |                 if (targetIdKey !== undefined) { | ||||||
|                     const targetId = tags.data[targetIdKey] ?? tags.data.id |                     const targetId = tags.data[targetIdKey] ?? tags.data.id | ||||||
|                     el = t.appliedOnAnotherObject.Subs({ tags: tagsStr, id: targetId }) |                     el = t.appliedOnAnotherObject.Subs({tags: tagsStr, id: targetId}) | ||||||
|                 } |                 } | ||||||
|                 return el |                 return el | ||||||
|             }) |             }) | ||||||
|  |  | ||||||
|  | @ -11,27 +11,27 @@ | ||||||
| 
 | 
 | ||||||
|   export let tags: UIEventSource<Record<string, string> | undefined>; |   export let tags: UIEventSource<Record<string, string> | undefined>; | ||||||
|   let _tags: Record<string, string>; |   let _tags: Record<string, string>; | ||||||
|   onDestroy(tags.addCallbackAndRun(tags => { |   let trs: { then: Translation; icon?: string; iconClass?: string }[]; | ||||||
|     _tags = tags; | 
 | ||||||
|   })); |  | ||||||
|   export let state: SpecialVisualizationState; |   export let state: SpecialVisualizationState; | ||||||
|   export let selectedElement: Feature; |   export let selectedElement: Feature; | ||||||
|  |   export let layer: LayerConfig; | ||||||
|   export let config: TagRenderingConfig; |   export let config: TagRenderingConfig; | ||||||
|  |   export let extraClasses: string= "" | ||||||
|  |    | ||||||
|   if (config === undefined) { |   if (config === undefined) { | ||||||
|     throw "Config is undefined in tagRenderingAnswer"; |     throw "Config is undefined in tagRenderingAnswer"; | ||||||
|   } |   } | ||||||
|   export let layer: LayerConfig; |   onDestroy(tags.addCallbackAndRun(tags => { | ||||||
|   let trs: { then: Translation; icon?: string; iconClass?: string }[]; |     _tags = tags; | ||||||
|   $:{ |  | ||||||
|     trs = Utils.NoNull(config?.GetRenderValues(_tags)); |     trs = Utils.NoNull(config?.GetRenderValues(_tags)); | ||||||
|   } |   })); | ||||||
|   export let extraClasses: string= "" |  | ||||||
|   let classes = "" |   let classes = "" | ||||||
|   $:classes = config?.classes?.join(" ") ?? ""; |   $:classes = config?.classes?.join(" ") ?? ""; | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| {#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties(_tags))} | {#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties(_tags))} | ||||||
|   <div class={"link-underline flex flex-col w-full overflow-hidden "+classes+" "+extraClasses}> |   <div class={"link-underline flex flex-col w-full "+classes+" "+extraClasses}> | ||||||
|     {#if trs.length === 1} |     {#if trs.length === 1} | ||||||
|       <TagRenderingMapping mapping={trs[0]} {tags} {state} {selectedElement} {layer}></TagRenderingMapping> |       <TagRenderingMapping mapping={trs[0]} {tags} {state} {selectedElement} {layer}></TagRenderingMapping> | ||||||
|     {/if} |     {/if} | ||||||
|  |  | ||||||
|  | @ -78,7 +78,7 @@ | ||||||
|                 <XCircleIcon slot="upper-right" class="w-8 h-8 cursor-pointer" on:click={() => {editMode = false}}/> |                 <XCircleIcon slot="upper-right" class="w-8 h-8 cursor-pointer" on:click={() => {editMode = false}}/> | ||||||
|             </TagRenderingQuestion> |             </TagRenderingQuestion> | ||||||
|         {:else} |         {:else} | ||||||
|             <div class="flex justify-between low-interaction items-center rounded px-2"> |             <div class="flex justify-between low-interaction items-center rounded px-2 overflow-hidden"> | ||||||
|                 <TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/> |                 <TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/> | ||||||
|                 <button on:click={() => {editMode = true}} |                 <button on:click={() => {editMode = true}} | ||||||
|                         class="shrink-0 w-8 h-8 rounded-full p-1 secondary self-start"> |                         class="shrink-0 w-8 h-8 rounded-full p-1 secondary self-start"> | ||||||
|  | @ -87,6 +87,8 @@ | ||||||
|             </div> |             </div> | ||||||
|         {/if} |         {/if} | ||||||
|     {:else } |     {:else } | ||||||
|  |         <div class="p-2 overflow-hidden"> | ||||||
|         <TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/> |         <TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/> | ||||||
|  |         </div> | ||||||
|     {/if} |     {/if} | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  | @ -959,24 +959,6 @@ export default class SpecialVisualizations { | ||||||
|                         defaultValue: "id", |                         defaultValue: "id", | ||||||
|                     }, |                     }, | ||||||
|                 ], |                 ], | ||||||
|                 example: |  | ||||||
|                     " The following example sets the status to '2' (false positive)\n" + |  | ||||||
|                     "\n" + |  | ||||||
|                     "```json\n" + |  | ||||||
|                     "{\n" + |  | ||||||
|                     '   "id": "mark_duplicate",\n' + |  | ||||||
|                     '   "render": {\n' + |  | ||||||
|                     '      "special": {\n' + |  | ||||||
|                     '         "type": "maproulette_set_status",\n' + |  | ||||||
|                     '         "message": {\n' + |  | ||||||
|                     '            "en": "Mark as not found or false positive"\n' + |  | ||||||
|                     "         },\n" + |  | ||||||
|                     '         "status": "2",\n' + |  | ||||||
|                     '         "image": "close"\n' + |  | ||||||
|                     "      }\n" + |  | ||||||
|                     "   }\n" + |  | ||||||
|                     "}\n" + |  | ||||||
|                     "```", |  | ||||||
|                 constr: (state, tags, args) => { |                 constr: (state, tags, args) => { | ||||||
|                     const isUploading = new UIEventSource(false) |                     const isUploading = new UIEventSource(false) | ||||||
|                     const t = Translations.t.notes |                     const t = Translations.t.notes | ||||||
|  | @ -1080,6 +1062,24 @@ export default class SpecialVisualizations { | ||||||
|             { |             { | ||||||
|                 funcName: "maproulette_set_status", |                 funcName: "maproulette_set_status", | ||||||
|                 docs: "Change the status of the given MapRoulette task", |                 docs: "Change the status of the given MapRoulette task", | ||||||
|  |                 example: | ||||||
|  |                     " The following example sets the status to '2' (false positive)\n" + | ||||||
|  |                     "\n" + | ||||||
|  |                     "```json\n" + | ||||||
|  |                     "{\n" + | ||||||
|  |                     '   "id": "mark_duplicate",\n' + | ||||||
|  |                     '   "render": {\n' + | ||||||
|  |                     '      "special": {\n' + | ||||||
|  |                     '         "type": "maproulette_set_status",\n' + | ||||||
|  |                     '         "message": {\n' + | ||||||
|  |                     '            "en": "Mark as not found or false positive"\n' + | ||||||
|  |                     "         },\n" + | ||||||
|  |                     '         "status": "2",\n' + | ||||||
|  |                     '         "image": "close"\n' + | ||||||
|  |                     "      }\n" + | ||||||
|  |                     "   }\n" + | ||||||
|  |                     "}\n" + | ||||||
|  |                     "```", | ||||||
|                 args: [ |                 args: [ | ||||||
|                     { |                     { | ||||||
|                         name: "message", |                         name: "message", | ||||||
|  | @ -1110,11 +1110,14 @@ export default class SpecialVisualizations { | ||||||
|                     if (image === "") { |                     if (image === "") { | ||||||
|                         image = "confirm" |                         image = "confirm" | ||||||
|                     } |                     } | ||||||
|  |                     if(maproulette_id_key === "" || maproulette_id_key === undefined){ | ||||||
|  |                         maproulette_id_key = "mr_taskId" | ||||||
|  |                     } | ||||||
|                     if (Svg.All[image] !== undefined || Svg.All[image + ".svg"] !== undefined) { |                     if (Svg.All[image] !== undefined || Svg.All[image + ".svg"] !== undefined) { | ||||||
|                         if (image.endsWith(".svg")) { |                         if (image.endsWith(".svg")) { | ||||||
|                             image = image.substring(0, image.length - 4) |                             image = image.substring(0, image.length - 4) | ||||||
|                         } |                         } | ||||||
|                         image = Svg[image + "_ui"]() |                         image = Svg[image + "_svg"]() | ||||||
|                     } |                     } | ||||||
|                     const failed = new UIEventSource(false) |                     const failed = new UIEventSource(false) | ||||||
| 
 | 
 | ||||||
|  | @ -1122,7 +1125,7 @@ export default class SpecialVisualizations { | ||||||
|                         Translations.t.general.loading, |                         Translations.t.general.loading, | ||||||
|                         async () => { |                         async () => { | ||||||
|                             const maproulette_id = |                             const maproulette_id = | ||||||
|                                 tagsSource.data[maproulette_id_key] ?? tagsSource.data.id |                                 tagsSource.data[maproulette_id_key] ?? tagsSource.data.mr_taskId ?? tagsSource.data.id | ||||||
|                             try { |                             try { | ||||||
|                                 await Maproulette.singleton.closeTask( |                                 await Maproulette.singleton.closeTask( | ||||||
|                                     Number(maproulette_id), |                                     Number(maproulette_id), | ||||||
|  | @ -1150,13 +1153,19 @@ export default class SpecialVisualizations { | ||||||
|                     return new VariableUiElement( |                     return new VariableUiElement( | ||||||
|                         tagsSource |                         tagsSource | ||||||
|                             .map( |                             .map( | ||||||
|                                 (tgs) => |                                 (tgs) => { | ||||||
|                                     tgs["status"] ?? |                                     if(tgs["status"]){ | ||||||
|                                     Maproulette.STATUS_MEANING[tgs["mr_taskStatus"]] |                                         return tgs["status"] | ||||||
|  |                                     } | ||||||
|  |                                     const code = tgs["mr_taskStatus"] | ||||||
|  |                                     console.log("Code is", code, Maproulette.codeToIndex(code)) | ||||||
|  |                                     return Maproulette.codeToIndex(code) | ||||||
|  |                                 } | ||||||
|                             ) |                             ) | ||||||
|                             .map(Number) |                             .map(Number) | ||||||
|                             .map( |                             .map( | ||||||
|                                 (status) => { |                                 (status) => { | ||||||
|  |                                     console.log("Close MR button: status is", status) | ||||||
|                                     if (failed.data) { |                                     if (failed.data) { | ||||||
|                                         return new FixedUiElement( |                                         return new FixedUiElement( | ||||||
|                                             "ERROR - could not close the MapRoulette task" |                                             "ERROR - could not close the MapRoulette task" | ||||||
|  |  | ||||||
|  | @ -60,7 +60,6 @@ | ||||||
|   "tagRenderings": [ |   "tagRenderings": [ | ||||||
|     { |     { | ||||||
|       "id": "status", |       "id": "status", | ||||||
|       "render": "Current status: {status}", |  | ||||||
|       "mappings": [ |       "mappings": [ | ||||||
|         { |         { | ||||||
|           "if": "status=0", |           "if": "status=0", | ||||||
|  | @ -130,6 +129,7 @@ | ||||||
|       ] |       ] | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|  |       "labels": ["controls"], | ||||||
|       "id": "mark_fixed", |       "id": "mark_fixed", | ||||||
|       "render": { |       "render": { | ||||||
|         "special": { |         "special": { | ||||||
|  | @ -144,6 +144,7 @@ | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "id": "mark_duplicate", |       "id": "mark_duplicate", | ||||||
|  |       "labels": ["controls"], | ||||||
|       "render": { |       "render": { | ||||||
|         "special": { |         "special": { | ||||||
|           "type": "maproulette_set_status", |           "type": "maproulette_set_status", | ||||||
|  | @ -159,6 +160,7 @@ | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "id": "mark_too_hard", |       "id": "mark_too_hard", | ||||||
|  |       "labels": ["controls"], | ||||||
|       "render": { |       "render": { | ||||||
|         "special": { |         "special": { | ||||||
|           "type": "maproulette_set_status", |           "type": "maproulette_set_status", | ||||||
|  | @ -306,4 +308,4 @@ | ||||||
|       ] |       ] | ||||||
|     } |     } | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -77,7 +77,6 @@ | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "id": "status", |       "id": "status", | ||||||
|       "render": "Current status: {status}", |  | ||||||
|       "mappings": [ |       "mappings": [ | ||||||
|         { |         { | ||||||
|           "if": "mr_taskStatus=Created", |           "if": "mr_taskStatus=Created", | ||||||
|  |  | ||||||
|  | @ -34,6 +34,7 @@ | ||||||
|       "override": { |       "override": { | ||||||
|         "id": "banks_with_atm", |         "id": "banks_with_atm", | ||||||
|         "name": null, |         "name": null, | ||||||
|  |         "minzoom": 14, | ||||||
|         "source": { |         "source": { | ||||||
|           "osmTags": { |           "osmTags": { | ||||||
|             "and+": [ |             "and+": [ | ||||||
|  | @ -54,6 +55,68 @@ | ||||||
|           "sameAs": "bank" |           "sameAs": "bank" | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "builtin": "maproulette_challenge", | ||||||
|  |       "override": { | ||||||
|  |         "minzoom": 5, | ||||||
|  |         "source": { | ||||||
|  |           "geoJson": "https://maproulette.org/api/v2/challenge/view/39519" | ||||||
|  |         }, | ||||||
|  |         "isShown": "mr_taskStatus=Created", | ||||||
|  |         "calculatedTags": [ | ||||||
|  |           "_closest_osm_poi=closest(feat)('atm')?.properties?.id", | ||||||
|  |           "_closest_osm_poi_distance=Math.round(distanceTo(feat)(feat.properties._closest_osm_poi))", | ||||||
|  |           "_has_closeby_feature=Number(feat.properties._closest_osm_poi_distance) < 50 ? 'yes' : 'no'" | ||||||
|  |         ], | ||||||
|  |         "=tagRenderings": [ | ||||||
|  |           { | ||||||
|  |             "id": "import-button", | ||||||
|  |             "condition": "_has_closeby_feature=no", | ||||||
|  |             "render": { | ||||||
|  |               "special": { | ||||||
|  |                 "type": "import_button", | ||||||
|  |                 "targetLayer": "atm", | ||||||
|  |                 "tags": "tags", | ||||||
|  |                 "maproulette_id": "mr_taskId", | ||||||
|  |                 "text": { | ||||||
|  |                   "en": "Import this ATM" | ||||||
|  |                 }, | ||||||
|  |                 "icon": "./assets/svg/addSmall.svg" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             "id": "closeness-indicator", | ||||||
|  |             "condition": "_has_closeby_feature=yes", | ||||||
|  |             "render": { | ||||||
|  |               "en": "OpenStreetMap knows about <a href='#{_closest_osm_poi}'>an ATM which is {_closest_osm_poi_distance} meter away.</a> " | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             "id": "tag-apply-button", | ||||||
|  |             "condition": "_has_closeby_feature=yes", | ||||||
|  |             "render": { | ||||||
|  |               "special": { | ||||||
|  |                 "type": "tag_apply", | ||||||
|  |                 "tags_to_apply": "$tags", | ||||||
|  |                 "id_of_object_to_apply_this_one": "_closest_osm_poi", | ||||||
|  |                 "message": { | ||||||
|  |                   "en": "Add all the suggested tags to the closest ATM" | ||||||
|  |                 }, | ||||||
|  |                 "image": "./assets/svg/addSmall.svg", | ||||||
|  |                 "maproulette_task_id": "mr_taskId" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           "maproulette.controls", | ||||||
|  |           { | ||||||
|  |             "id": "minimap_with_atm", | ||||||
|  |             "render": "{minimap(18, id, _closest_osm_poi)}" | ||||||
|  |           }, | ||||||
|  |           "all_tags" | ||||||
|  |         ] | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -356,7 +356,12 @@ class LayerOverviewUtils extends Script { | ||||||
|             const context = "While building builtin layer " + sharedLayerPath |             const context = "While building builtin layer " + sharedLayerPath | ||||||
|             const fixed = prepLayer.convertStrict(parsed, context) |             const fixed = prepLayer.convertStrict(parsed, context) | ||||||
| 
 | 
 | ||||||
|             if (typeof fixed.source !== "string" && fixed.source["osmTags"]["and"] === undefined) { |             if(!fixed.source){ | ||||||
|  |                 console.error(sharedLayerPath,"has no source configured:",fixed) | ||||||
|  |                 throw sharedLayerPath+" layer has no source configured" | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (typeof fixed.source !== "string" && fixed.source["osmTags"] && fixed.source["osmTags"]["and"] === undefined) { | ||||||
|                 fixed.source["osmTags"] = { and: [fixed.source["osmTags"]] } |                 fixed.source["osmTags"] = { and: [fixed.source["osmTags"]] } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										92
									
								
								scripts/importscripts/cash.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								scripts/importscripts/cash.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | ||||||
|  | import fs from "fs" | ||||||
|  | import {OH} from "../../UI/OpeningHours/OpeningHours"; | ||||||
|  | 
 | ||||||
|  | const cashpunten = JSON.parse(fs.readFileSync("/home/pietervdvn/Downloads/cash_punten.json", "utf8")).data | ||||||
|  | 
 | ||||||
|  | const features: any[] = [] | ||||||
|  | const weekdays = [ | ||||||
|  |     "MO", | ||||||
|  |     "TU", | ||||||
|  |     "WE", | ||||||
|  |     "TH", | ||||||
|  |     "FR", | ||||||
|  |     "SA", | ||||||
|  |     "SU" | ||||||
|  | ] | ||||||
|  | for (const atm of cashpunten) { | ||||||
|  |     const properties = { | ||||||
|  |         "amenity": "atm", | ||||||
|  |         "addr:street": atm.adr_street, | ||||||
|  |         "addr:housenumber": atm.adr_street_number, | ||||||
|  |         "phone": <string>atm.phone_number, | ||||||
|  |         "operator": "Batopin", | ||||||
|  |         network: "CASH", | ||||||
|  |         fee: "no", | ||||||
|  |         "speech_output": "yes", | ||||||
|  |         "brand": "CASH", | ||||||
|  |         website: "https://batopin.be", | ||||||
|  |         "source": "https://batopin.be", | ||||||
|  |         "brand:wikidata": "Q112875867", | ||||||
|  |         "operator:wikidata": "Q97142699", | ||||||
|  |         "currency:EUR": "yes" | ||||||
|  |     } | ||||||
|  |     features.push({ | ||||||
|  |         geometry: {type: "Point", coordinates: [atm.adr_longitude, atm.adr_latitude]}, | ||||||
|  |         properties: { | ||||||
|  |             tags: properties | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |     switch (atm.accessibility) { | ||||||
|  |         case "Green": | ||||||
|  |             properties["wheelchair"] = "yes"; | ||||||
|  |             break; | ||||||
|  |         case "Orange": | ||||||
|  |             properties["wheelchair"] = "limited"; | ||||||
|  |             break; | ||||||
|  |         case "Red": | ||||||
|  |             properties["wheelchair"] = "no"; | ||||||
|  |             break; | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |     } | ||||||
|  |     delete atm.accessibility | ||||||
|  | 
 | ||||||
|  |     if (atm.deposit_cash) { | ||||||
|  |         properties["cash_in"] = atm.deposit_cash === "1" ? "yes" : "no" | ||||||
|  |         delete atm.deposit_cash | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!weekdays.some(wd => atm.regular_hours[wd] !== "00:00-00:00")) { | ||||||
|  |         properties["opening_hours"] = "24/7" | ||||||
|  |         delete atm.regular_hours | ||||||
|  |     } else { | ||||||
|  |         const rules = weekdays.filter(wd => atm.regular_hours[wd] !== undefined).map(wd => wd[0] + wd.toLowerCase()[1] + " " + atm.regular_hours[wd]).join(";") | ||||||
|  |         properties["opening_hours"] = OH.ToString(OH.MergeTimes(OH.Parse(rules))) | ||||||
|  |         delete atm.regular_hours | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     delete atm.special_hours // Only one data point has this
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     delete atm.location_language | ||||||
|  |     delete atm.location_name | ||||||
|  |     delete atm.shop_code | ||||||
|  |     delete atm.id | ||||||
|  |     delete atm.adr_longitude | ||||||
|  |     delete atm.adr_latitude | ||||||
|  |     delete atm.adr_street_number | ||||||
|  |     delete atm.adr_street | ||||||
|  |     delete atm.adr_zipcode | ||||||
|  |     delete atm.adr_city | ||||||
|  |     delete atm.adr_country | ||||||
|  |     delete atm.phone_number | ||||||
|  |     if (Object.keys(atm).length == 0) { | ||||||
|  |         continue | ||||||
|  |     } | ||||||
|  |     console.log(atm, properties) | ||||||
|  |     break | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | fs.writeFileSync("atms.geojson", JSON.stringify({type: "FeatureCollection", features})) | ||||||
							
								
								
									
										16
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										16
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -3,9 +3,6 @@ import * as theme from "./assets/generated/themes/bookcases.json" | ||||||
| import ThemeViewState from "./Models/ThemeViewState" | import ThemeViewState from "./Models/ThemeViewState" | ||||||
| import Combine from "./UI/Base/Combine" | import Combine from "./UI/Base/Combine" | ||||||
| import SpecialVisualizations from "./UI/SpecialVisualizations" | import SpecialVisualizations from "./UI/SpecialVisualizations" | ||||||
| import {VariableUiElement} from "./UI/Base/VariableUIElement" |  | ||||||
| import {SvgToPdf} from "./Utils/svgToPdf" |  | ||||||
| import {Utils} from "./Utils" |  | ||||||
| 
 | 
 | ||||||
| function testspecial() { | function testspecial() { | ||||||
|     const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ?  : new AllKnownLayoutsLazy().get(qp.data)
 |     const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ?  : new AllKnownLayoutsLazy().get(qp.data)
 | ||||||
|  | @ -18,19 +15,6 @@ function testspecial() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| async function testPdf() { |  | ||||||
|     const svgs = await Promise.all( |  | ||||||
|         SvgToPdf.templates["flyer_a4"].pages.map((url) => Utils.download(url)) |  | ||||||
|     ) |  | ||||||
|     console.log("Building svg") |  | ||||||
|     const pdf = new SvgToPdf("Test", svgs, { |  | ||||||
|         freeComponentId:"extradiv" |  | ||||||
|     }) |  | ||||||
|     new VariableUiElement(pdf.status).AttachTo("maindiv") |  | ||||||
|     await pdf.ExportPdf("nl") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| testPdf().then((_) => console.log("All done")) |  | ||||||
| /*/ | /*/ | ||||||
| testspecial() | testspecial() | ||||||
| //*/
 | //*/
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue