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 uniqueTag: string | undefined = options?.uniqueTag | ||||
|         let allFeatures: Feature[][] | ||||
|         console.log("Calculating closest", options?.maxFeatures, "features around", feature, "in layer", features) | ||||
|         if (typeof features === "string") { | ||||
|             const name = features | ||||
|             const bbox = GeoOperations.bbox( | ||||
|  | @ -414,7 +415,7 @@ class GetParsed implements ExtraFunction { | |||
|             if (value === undefined) { | ||||
|                 return undefined | ||||
|             } | ||||
|             if(typeof value !== "string"){ | ||||
|             if (typeof value !== "string") { | ||||
|                 return value | ||||
|             } | ||||
|             try { | ||||
|  |  | |||
|  | @ -105,6 +105,10 @@ export default class GeoJsonSource implements FeatureSource { | |||
|         let i = 0 | ||||
|         let skipped = 0 | ||||
|         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 | ||||
|             for (const key in props) { | ||||
|                 if (props[key] === null) { | ||||
|  |  | |||
|  | @ -72,4 +72,23 @@ export default class Maproulette { | |||
|             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 OsmObjectDownloader from "./Osm/OsmObjectDownloader" | ||||
| import {Utils} from "../Utils"; | ||||
| import {GeoJSONFeature} from "maplibre-gl"; | ||||
| import {UIEventSource} from "./UIEventSource"; | ||||
| 
 | ||||
| /** | ||||
|  | @ -206,13 +205,13 @@ export default class MetaTagging { | |||
|     private static createFunctionForFeature([key, code, isStrict]: [string, string, boolean], | ||||
|                                             helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>, | ||||
|                                             layerId: string = "unkown layer" | ||||
|     ): ((feature: GeoJSONFeature, propertiesStore?: UIEventSource<any>) => void) | undefined { | ||||
|     ): ((feature: Feature, propertiesStore?: UIEventSource<any>) => void) | undefined { | ||||
|         if (code === 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 { | ||||
|                 let result = new Function("feat", "{" + ExtraFunctions.types.join(", ") + "}", "return " + code + ";")(feat, helperFunctions) | ||||
|                 if (result === "") { | ||||
|  | @ -259,7 +258,7 @@ export default class MetaTagging { | |||
|         if (isStrict) { | ||||
|             return calculateAndAssign | ||||
|         } | ||||
|         return (feature: any, store?: UIEventSource<any>) => { | ||||
|         return (feature: Feature, store?: UIEventSource<any>) => { | ||||
|             delete feature.properties[key] | ||||
|             Utils.AddLazyProperty(feature.properties, key, () => calculateAndAssign(feature, store)) | ||||
|         } | ||||
|  |  | |||
|  | @ -132,6 +132,10 @@ class CountryTagger extends SimpleMetaTagger { | |||
|         CountryTagger.coder | ||||
|             .GetCountryCodeAsync(lon, lat) | ||||
|             .then((countries) => { | ||||
|                 if(!countries){ | ||||
|                     console.warn("Country coder returned ", countries) | ||||
|                     return | ||||
|                 } | ||||
|                 const oldCountry = feature.properties["_country"] | ||||
|                 const newCountry = countries[0].trim().toLowerCase() | ||||
|                 if (oldCountry !== newCountry) { | ||||
|  |  | |||
|  | @ -538,7 +538,6 @@ export default class TagRenderingConfig { | |||
|         } | ||||
| 
 | ||||
|         if ( | ||||
|             this.id === "questions" || | ||||
|             this.freeform?.key === undefined || | ||||
|             tags[this.freeform.key] !== undefined | ||||
|         ) { | ||||
|  |  | |||
|  | @ -116,11 +116,12 @@ | |||
|                     currentFlowStep = "imported" | ||||
|                     dispatch("confirm") | ||||
|                 }}> | ||||
|                     <span slot="image"> | ||||
|                          | ||||
|                     {#if importFlow.args.icon} | ||||
|                         <img src={importFlow.args.icon}> | ||||
|                     {/if} | ||||
|                     <span slot="image" class="w-8 h-8 pr-4"> | ||||
|                         {#if importFlow.args.icon} | ||||
|                             <img src={importFlow.args.icon}> | ||||
|                         {:else} | ||||
|                             <ToSvelte construct={Svg.confirm_svg().SetClass("w-8 h-8 pr-4")}/> | ||||
|                         {/if} | ||||
|                     </span> | ||||
|                     <slot name="confirm-text"> | ||||
|                         {importFlow.args.text} | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import {SpecialVisualizationState} from "../../SpecialVisualization"; | ||||
| import {Utils} from "../../../Utils"; | ||||
| import {Store, UIEventSource} from "../../../Logic/UIEventSource"; | ||||
| import {ImmutableStore, Store, UIEventSource} from "../../../Logic/UIEventSource"; | ||||
| import {Tag} from "../../../Logic/Tags/Tag"; | ||||
| import TagApplyButton from "../TagApplyButton"; | ||||
| import {PointImportFlowArguments} from "./PointImportFlowState"; | ||||
|  | @ -11,6 +11,7 @@ import FilteredLayer from "../../../Models/FilteredLayer"; | |||
| import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; | ||||
| import {LayerConfigJson} from "../../../Models/ThemeConfig/Json/LayerConfigJson"; | ||||
| import conflation_json from "../../../assets/layers/conflation/conflation.json"; | ||||
| import {And} from "../../../Logic/Tags/And"; | ||||
| 
 | ||||
| export interface ImportFlowArguments { | ||||
|     readonly  text: string | ||||
|  | @ -85,6 +86,15 @@ ${Utils.special_visualizations_importRequirementDocs} | |||
|                 "] of this object, namely ", | ||||
|                 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) | ||||
|         } else { | ||||
|             newTags = TagApplyButton.generateTagsToApply(tags, originalFeatureTags) | ||||
|  |  | |||
|  | @ -1,22 +1,23 @@ | |||
| import { AutoAction } from "./AutoApplyButton" | ||||
| import {AutoAction} from "./AutoApplyButton" | ||||
| import Translations from "../i18n/Translations" | ||||
| import { VariableUiElement } from "../Base/VariableUIElement" | ||||
| import {VariableUiElement} from "../Base/VariableUIElement" | ||||
| import BaseUIElement from "../BaseUIElement" | ||||
| import { FixedUiElement } from "../Base/FixedUiElement" | ||||
| import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
| import { SubtleButton } from "../Base/SubtleButton" | ||||
| import {FixedUiElement} from "../Base/FixedUiElement" | ||||
| import {Store, UIEventSource} from "../../Logic/UIEventSource" | ||||
| import {SubtleButton} from "../Base/SubtleButton" | ||||
| import Combine from "../Base/Combine" | ||||
| 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 { Utils } from "../../Utils" | ||||
| import { Tag } from "../../Logic/Tags/Tag" | ||||
| import {Utils} from "../../Utils" | ||||
| import {Tag} from "../../Logic/Tags/Tag" | ||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | ||||
| import { Changes } from "../../Logic/Osm/Changes" | ||||
| import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | ||||
| import {Changes} from "../../Logic/Osm/Changes" | ||||
| import {SpecialVisualization, SpecialVisualizationState} from "../SpecialVisualization" | ||||
| import {IndexedFeatureSource} from "../../Logic/FeatureSource/FeatureSource"; | ||||
| import {Feature} from "geojson"; | ||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||
| import Maproulette from "../../Logic/Maproulette"; | ||||
| 
 | ||||
| export default class TagApplyButton implements AutoAction, SpecialVisualization { | ||||
|     public readonly funcName = "tag_apply" | ||||
|  | @ -27,7 +28,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization | |||
|     public readonly args = [ | ||||
|         { | ||||
|             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", | ||||
|  | @ -42,10 +43,62 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization | |||
|             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", | ||||
|         }, | ||||
|         { | ||||
|             name:"maproulette_task_id", | ||||
|             defaultValue: undefined, | ||||
|             doc: "If specified, this maproulette-challenge will be closed when the tags are applied" | ||||
|         } | ||||
|     ] | ||||
|     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)" | ||||
| 
 | ||||
|     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 | ||||
|      * | ||||
|  | @ -79,41 +132,6 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization | |||
|         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( | ||||
|         feature: Feature, | ||||
|         state: { | ||||
|  | @ -123,7 +141,6 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization | |||
|         }, | ||||
|         tags: UIEventSource<any>, | ||||
|         args: string[], | ||||
| 
 | ||||
|     ): Promise<void> { | ||||
|         const tagsToApply = TagApplyButton.generateTagsToApply(args[0], tags) | ||||
|         const targetIdKey = args[3] | ||||
|  | @ -139,6 +156,13 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization | |||
|             } | ||||
|         ) | ||||
|         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( | ||||
|  | @ -163,7 +187,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization | |||
|                 let el: BaseUIElement = new FixedUiElement(tagsStr) | ||||
|                 if (targetIdKey !== undefined) { | ||||
|                     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 | ||||
|             }) | ||||
|  |  | |||
|  | @ -11,27 +11,27 @@ | |||
| 
 | ||||
|   export let tags: UIEventSource<Record<string, string> | undefined>; | ||||
|   let _tags: Record<string, string>; | ||||
|   onDestroy(tags.addCallbackAndRun(tags => { | ||||
|     _tags = tags; | ||||
|   })); | ||||
|   let trs: { then: Translation; icon?: string; iconClass?: string }[]; | ||||
| 
 | ||||
|   export let state: SpecialVisualizationState; | ||||
|   export let selectedElement: Feature; | ||||
|   export let layer: LayerConfig; | ||||
|   export let config: TagRenderingConfig; | ||||
|   export let extraClasses: string= "" | ||||
|    | ||||
|   if (config === undefined) { | ||||
|     throw "Config is undefined in tagRenderingAnswer"; | ||||
|   } | ||||
|   export let layer: LayerConfig; | ||||
|   let trs: { then: Translation; icon?: string; iconClass?: string }[]; | ||||
|   $:{ | ||||
|   onDestroy(tags.addCallbackAndRun(tags => { | ||||
|     _tags = tags; | ||||
|     trs = Utils.NoNull(config?.GetRenderValues(_tags)); | ||||
|   } | ||||
|   export let extraClasses: string= "" | ||||
|   })); | ||||
|   let classes = "" | ||||
|   $:classes = config?.classes?.join(" ") ?? ""; | ||||
| </script> | ||||
| 
 | ||||
| {#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} | ||||
|       <TagRenderingMapping mapping={trs[0]} {tags} {state} {selectedElement} {layer}></TagRenderingMapping> | ||||
|     {/if} | ||||
|  |  | |||
|  | @ -78,7 +78,7 @@ | |||
|                 <XCircleIcon slot="upper-right" class="w-8 h-8 cursor-pointer" on:click={() => {editMode = false}}/> | ||||
|             </TagRenderingQuestion> | ||||
|         {: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}/> | ||||
|                 <button on:click={() => {editMode = true}} | ||||
|                         class="shrink-0 w-8 h-8 rounded-full p-1 secondary self-start"> | ||||
|  | @ -87,6 +87,8 @@ | |||
|             </div> | ||||
|         {/if} | ||||
|     {:else } | ||||
|         <div class="p-2 overflow-hidden"> | ||||
|         <TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/> | ||||
|         </div> | ||||
|     {/if} | ||||
| </div> | ||||
|  |  | |||
|  | @ -959,24 +959,6 @@ export default class SpecialVisualizations { | |||
|                         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) => { | ||||
|                     const isUploading = new UIEventSource(false) | ||||
|                     const t = Translations.t.notes | ||||
|  | @ -1080,6 +1062,24 @@ export default class SpecialVisualizations { | |||
|             { | ||||
|                 funcName: "maproulette_set_status", | ||||
|                 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: [ | ||||
|                     { | ||||
|                         name: "message", | ||||
|  | @ -1110,11 +1110,14 @@ export default class SpecialVisualizations { | |||
|                     if (image === "") { | ||||
|                         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 (image.endsWith(".svg")) { | ||||
|                             image = image.substring(0, image.length - 4) | ||||
|                         } | ||||
|                         image = Svg[image + "_ui"]() | ||||
|                         image = Svg[image + "_svg"]() | ||||
|                     } | ||||
|                     const failed = new UIEventSource(false) | ||||
| 
 | ||||
|  | @ -1122,7 +1125,7 @@ export default class SpecialVisualizations { | |||
|                         Translations.t.general.loading, | ||||
|                         async () => { | ||||
|                             const maproulette_id = | ||||
|                                 tagsSource.data[maproulette_id_key] ?? tagsSource.data.id | ||||
|                                 tagsSource.data[maproulette_id_key] ?? tagsSource.data.mr_taskId ?? tagsSource.data.id | ||||
|                             try { | ||||
|                                 await Maproulette.singleton.closeTask( | ||||
|                                     Number(maproulette_id), | ||||
|  | @ -1150,13 +1153,19 @@ export default class SpecialVisualizations { | |||
|                     return new VariableUiElement( | ||||
|                         tagsSource | ||||
|                             .map( | ||||
|                                 (tgs) => | ||||
|                                     tgs["status"] ?? | ||||
|                                     Maproulette.STATUS_MEANING[tgs["mr_taskStatus"]] | ||||
|                                 (tgs) => { | ||||
|                                     if(tgs["status"]){ | ||||
|                                         return tgs["status"] | ||||
|                                     } | ||||
|                                     const code = tgs["mr_taskStatus"] | ||||
|                                     console.log("Code is", code, Maproulette.codeToIndex(code)) | ||||
|                                     return Maproulette.codeToIndex(code) | ||||
|                                 } | ||||
|                             ) | ||||
|                             .map(Number) | ||||
|                             .map( | ||||
|                                 (status) => { | ||||
|                                     console.log("Close MR button: status is", status) | ||||
|                                     if (failed.data) { | ||||
|                                         return new FixedUiElement( | ||||
|                                             "ERROR - could not close the MapRoulette task" | ||||
|  |  | |||
|  | @ -60,7 +60,6 @@ | |||
|   "tagRenderings": [ | ||||
|     { | ||||
|       "id": "status", | ||||
|       "render": "Current status: {status}", | ||||
|       "mappings": [ | ||||
|         { | ||||
|           "if": "status=0", | ||||
|  | @ -130,6 +129,7 @@ | |||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "labels": ["controls"], | ||||
|       "id": "mark_fixed", | ||||
|       "render": { | ||||
|         "special": { | ||||
|  | @ -144,6 +144,7 @@ | |||
|     }, | ||||
|     { | ||||
|       "id": "mark_duplicate", | ||||
|       "labels": ["controls"], | ||||
|       "render": { | ||||
|         "special": { | ||||
|           "type": "maproulette_set_status", | ||||
|  | @ -159,6 +160,7 @@ | |||
|     }, | ||||
|     { | ||||
|       "id": "mark_too_hard", | ||||
|       "labels": ["controls"], | ||||
|       "render": { | ||||
|         "special": { | ||||
|           "type": "maproulette_set_status", | ||||
|  | @ -306,4 +308,4 @@ | |||
|       ] | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -77,7 +77,6 @@ | |||
|     }, | ||||
|     { | ||||
|       "id": "status", | ||||
|       "render": "Current status: {status}", | ||||
|       "mappings": [ | ||||
|         { | ||||
|           "if": "mr_taskStatus=Created", | ||||
|  |  | |||
|  | @ -34,6 +34,7 @@ | |||
|       "override": { | ||||
|         "id": "banks_with_atm", | ||||
|         "name": null, | ||||
|         "minzoom": 14, | ||||
|         "source": { | ||||
|           "osmTags": { | ||||
|             "and+": [ | ||||
|  | @ -54,6 +55,68 @@ | |||
|           "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 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"]] } | ||||
|             } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										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 Combine from "./UI/Base/Combine" | ||||
| import SpecialVisualizations from "./UI/SpecialVisualizations" | ||||
| import {VariableUiElement} from "./UI/Base/VariableUIElement" | ||||
| import {SvgToPdf} from "./Utils/svgToPdf" | ||||
| import {Utils} from "./Utils" | ||||
| 
 | ||||
| function testspecial() { | ||||
|     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() | ||||
| //*/
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue