diff --git a/Logic/ExtraFunctions.ts b/Logic/ExtraFunctions.ts index c889787629..7da38b9af3 100644 --- a/Logic/ExtraFunctions.ts +++ b/Logic/ExtraFunctions.ts @@ -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 { diff --git a/Logic/FeatureSource/Sources/GeoJsonSource.ts b/Logic/FeatureSource/Sources/GeoJsonSource.ts index 02c76c4edb..4a244683b7 100644 --- a/Logic/FeatureSource/Sources/GeoJsonSource.ts +++ b/Logic/FeatureSource/Sources/GeoJsonSource.ts @@ -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) { diff --git a/Logic/Maproulette.ts b/Logic/Maproulette.ts index 03e3777360..161fc9b9d9 100644 --- a/Logic/Maproulette.ts +++ b/Logic/Maproulette.ts @@ -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 + } } diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index 1e6351f44c..0e0129b9be 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -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 Function>, layerId: string = "unkown layer" - ): ((feature: GeoJSONFeature, propertiesStore?: UIEventSource) => void) | undefined { + ): ((feature: Feature, propertiesStore?: UIEventSource) => void) | undefined { if (code === undefined) { return undefined } - const calculateAndAssign: ((feat: GeoJSONFeature, store?: UIEventSource) => string | any) = (feat, store) => { + const calculateAndAssign: ((feat: Feature, store?: UIEventSource) => 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) => { + return (feature: Feature, store?: UIEventSource) => { delete feature.properties[key] Utils.AddLazyProperty(feature.properties, key, () => calculateAndAssign(feature, store)) } diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index c3e34653e2..cc3565a49b 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -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) { diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index b0890ab5a3..cf88c2ed69 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -538,7 +538,6 @@ export default class TagRenderingConfig { } if ( - this.id === "questions" || this.freeform?.key === undefined || tags[this.freeform.key] !== undefined ) { diff --git a/UI/Popup/ImportButtons/ImportFlow.svelte b/UI/Popup/ImportButtons/ImportFlow.svelte index 436c7f82d7..980c1e2445 100644 --- a/UI/Popup/ImportButtons/ImportFlow.svelte +++ b/UI/Popup/ImportButtons/ImportFlow.svelte @@ -116,11 +116,12 @@ currentFlowStep = "imported" dispatch("confirm") }}> - - - {#if importFlow.args.icon} - - {/if} + + {#if importFlow.args.icon} + + {:else} + + {/if} {importFlow.args.text} diff --git a/UI/Popup/ImportButtons/ImportFlow.ts b/UI/Popup/ImportButtons/ImportFlow.ts index 1644da27a3..4edfda589d 100644 --- a/UI/Popup/ImportButtons/ImportFlow.ts +++ b/UI/Popup/ImportButtons/ImportFlow.ts @@ -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 = 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) diff --git a/UI/Popup/TagApplyButton.ts b/UI/Popup/TagApplyButton.ts index 85612e9e87..be0d4566b0 100644 --- a/UI/Popup/TagApplyButton.ts +++ b/UI/Popup/TagApplyButton.ts @@ -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> + ): Store { + // 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> - ): Store { - // 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, args: string[], - ): Promise { 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 }) diff --git a/UI/Popup/TagRendering/TagRenderingAnswer.svelte b/UI/Popup/TagRendering/TagRenderingAnswer.svelte index 09f6b47a02..df950c93b5 100644 --- a/UI/Popup/TagRendering/TagRenderingAnswer.svelte +++ b/UI/Popup/TagRendering/TagRenderingAnswer.svelte @@ -11,27 +11,27 @@ export let tags: UIEventSource | undefined>; let _tags: Record; - 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(" ") ?? ""; {#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties(_tags))} -