From ef1d2c9f56fc07f87255c557b05daf5e0451fdc5 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 17 Dec 2024 03:31:28 +0100 Subject: [PATCH] Fix(linkeddata): velopark deals with sections, fix image loading --- assets/themes/velopark/velopark.json | 12 +- scripts/velopark/veloParkToGeojson.ts | 106 +++++++++++--- src/Logic/Maproulette.ts | 37 ++--- src/Logic/State/UserSettingsMetaTagging.ts | 48 ++----- src/Logic/Web/LinkedDataLoader.ts | 131 +++++++++++++----- src/Logic/Web/TypedSparql.ts | 6 +- .../MapRoulette/MaprouletteSetStatus.svelte | 7 +- .../ImportButtons/PointImportButtonViz.ts | 18 ++- 8 files changed, 242 insertions(+), 123 deletions(-) diff --git a/assets/themes/velopark/velopark.json b/assets/themes/velopark/velopark.json index 428ea0b51..9466b0f79 100644 --- a/assets/themes/velopark/velopark.json +++ b/assets/themes/velopark/velopark.json @@ -17,7 +17,7 @@ "nl": "Een hulpmiddel om data van velopark.be in OpenStreetMap in te laden" }, "descriptionTail": { - "*": "

Maintainer tools

" + "*": "

Maintainer tools

" }, "icon": "./assets/themes/velopark/velopark.svg", "startZoom": 18, @@ -31,7 +31,7 @@ "description": "Maproulette challenge containing velopark data", "source": { "osmTags": "mr_taskId~*", - "geoJson": "https://maproulette.org/api/v2/challenge/view/43282", + "geoJson": "https://maproulette.org/api/v2/challenge/view/50552", "idKey": "mr_taskId" }, "title": { @@ -161,6 +161,7 @@ "en": "Create a new bicycle parking in OSM. This parking will have the link, you'll be able to copy the attributes in the next step", "nl": "Maak een nieuwe parking aan in OSM. Deze parking zal gelinkt zijn met Velopark en je kan in de volgende stap de attributen overzetten" }, + "to_point": "yes", "maproulette_id": "mr_taskId" } } @@ -238,7 +239,12 @@ } } ], - "lineRendering": [], + "lineRendering": [ + { + "color": "#bb9922", + "lineWidth": 2 + } + ], "filter": [ { "id": "created-only", diff --git a/scripts/velopark/veloParkToGeojson.ts b/scripts/velopark/veloParkToGeojson.ts index c5dc7fb9d..9634df49a 100644 --- a/scripts/velopark/veloParkToGeojson.ts +++ b/scripts/velopark/veloParkToGeojson.ts @@ -2,17 +2,18 @@ import Script from "../Script" import fs from "fs" import LinkedDataLoader from "../../src/Logic/Web/LinkedDataLoader" import { Utils } from "../../src/Utils" -import { Feature } from "geojson" +import { Feature, FeatureCollection, Point } from "geojson" import { BBox } from "../../src/Logic/BBox" import { Overpass } from "../../src/Logic/Osm/Overpass" import { RegexTag } from "../../src/Logic/Tags/RegexTag" import { ImmutableStore } from "../../src/Logic/UIEventSource" import Constants from "../../src/Models/Constants" +import { MaprouletteStatus } from "../../src/Logic/Maproulette" class VeloParkToGeojson extends Script { constructor() { super( - "Downloads the latest Velopark data and converts it to a geojson, which will be saved at the current directory" + "Downloads the latest Velopark data and converts it to a geojson, which will be saved at the current directory", ) } @@ -23,13 +24,13 @@ class VeloParkToGeojson extends Script { JSON.stringify( extension === ".geojson" ? { - type: "FeatureCollection", - features, - } + type: "FeatureCollection", + features, + } : features, null, - " " - ) + " ", + ), ) console.log("Written", file, "(" + features.length, " features)") } @@ -44,12 +45,15 @@ class VeloParkToGeojson extends Script { const linkedData = await LinkedDataLoader.fetchVeloparkEntry(url) const allVelopark: Feature[] = [] + if (linkedData.length > 1) { + console.log("Detected multiple sections in:", url) + } for (const sectionId in linkedData) { const sectionInfo = linkedData[sectionId] if (Object.keys(sectionInfo).length === 0) { console.warn("No result for", url) } - if (!sectionInfo.geometry?.coordinates) { + if (!sectionInfo.geometry?.["coordinates"]) { throw "Invalid properties!" } allVelopark.push(sectionInfo) @@ -62,8 +66,8 @@ class VeloParkToGeojson extends Script { console.log("Downloading velopark data") // Download data for NIS-code 1000. 1000 means: all of belgium const url = "https://www.velopark.be/api/parkings/1000" - const allVeloparkRaw: { url: string }[] = <{ url: string }[]>await Utils.downloadJson(url) - + const allVeloparkRaw= (await Utils.downloadJson<{ url: string }[]>(url)) + // Example multi-entry: https://data.velopark.be/data/Stad-Izegem_IZE_015 let failed = 0 console.log("Got", allVeloparkRaw.length, "items") const allVelopark: Feature[] = [] @@ -82,14 +86,15 @@ class VeloParkToGeojson extends Script { console.error("Loading ", f.url, " failed due to", e) failed++ } - }) + }), ) + console.log("Batch complete:", i) } console.log( "Fetching data done, got ", allVelopark.length + "/" + allVeloparkRaw.length, "failed:", - failed + failed, ) VeloParkToGeojson.exportGeojsonTo("velopark_all", allVelopark) @@ -135,7 +140,37 @@ class VeloParkToGeojson extends Script { } } + private static async fetchMapRouletteClosedItems() { + const challenges = ["https://maproulette.org/api/v2/challenge/view/43282"] + const solvedRefs: Set = new Set(); + for (const url of challenges) { + const data = await Utils.downloadJson>(url) + for (const challenge of data.features) { + const status = challenge.properties.mr_taskStatus + const isClosed = status === "Fixed" || status === "False_positive" || status === "Already fixed" || status === "Too_Hard" || status === "Deleted" + if(isClosed){ + const ref = challenge.properties["ref:velopark"] + solvedRefs .add(ref) + } + } + } + console.log("Detected", solvedRefs,"as closed on mapRoulette") + return solvedRefs + } + + /** + * Creates an extra version where all bicycle parkings which are already linked are removed. + * Fetches the latest OSM-data from overpass + * @param allVelopark + * @private + */ private static async createDiff(allVelopark: Feature[]) { + console.log("Creating diff...") const bboxBelgium = new BBox([ [2.51357303225, 49.5294835476], [6.15665815596, 51.4750237087], @@ -146,42 +181,71 @@ class VeloParkToGeojson extends Script { [], Constants.defaultOverpassUrls[0], new ImmutableStore(60 * 5), - false + false, ) const alreadyLinkedFeatures = (await alreadyLinkedQuery.queryGeoJson(bboxBelgium))[0] const seenIds = new Set( - alreadyLinkedFeatures.features.map((f) => f.properties?.["ref:velopark"]) + alreadyLinkedFeatures.features.map((f) => f.properties?.["ref:velopark"]), ) this.exportGeojsonTo("osm_with_velopark_link", alreadyLinkedFeatures.features) console.log("OpenStreetMap contains", seenIds.size, "bicycle parkings with a velopark ref") const features: Feature[] = allVelopark.filter( - (f) => !seenIds.has(f.properties["ref:velopark"]) + (f) => !seenIds.has(f.properties["ref:velopark"]), ) VeloParkToGeojson.exportGeojsonTo("velopark_nonsynced", features) + const synced =await this.fetchMapRouletteClosedItems() + const featuresMoreFiltered = features.filter( + (f) => !synced.has(f.properties["ref:velopark"]) + ) + VeloParkToGeojson.exportGeojsonTo("velopark_nonsynced_nonclosed", featuresMoreFiltered) + + + + const featuresMoreFilteredFailed = features.filter( + (f) => synced.has(f.properties["ref:velopark"]) + ) + VeloParkToGeojson.exportGeojsonTo("velopark_nonsynced_human_import_failed", featuresMoreFilteredFailed) + const allProperties = new Set() - for (const feature of features) { - Object.keys(feature).forEach((k) => allProperties.add(k)) + for (const feature of featuresMoreFiltered) { + Object.keys(feature.properties).forEach((k) => allProperties.add(k)) } allProperties.delete("ref:velopark") - for (const feature of features) { + for (const feature of featuresMoreFiltered) { allProperties.forEach((k) => { - delete feature[k] + if(k === "ref:velopark"){ + return + } + delete feature.properties[k] }) } - this.exportGeojsonTo("velopark_nonsynced_id_only", features) + this.exportGeojsonTo("velopark_nonsynced_nonclosed_id_only", featuresMoreFiltered) + } + + public static async findMultiSection(): Promise { + const url = "https://www.velopark.be/api/parkings/1000" + const raw = await Utils.downloadJson<{"@graph": {}[], url: string}[]>(url) + const multiEntries: string[] = [] + for (const entry of raw) { + if(entry["@graph"].length > 1){ + multiEntries.push(entry.url) + } + } + return multiEntries } async main(): Promise { + // const multiEntries = new Set(await VeloParkToGeojson.findMultiSection()) const allVelopark = VeloParkToGeojson.loadFromFile() ?? (await VeloParkToGeojson.downloadData()) console.log("Got", allVelopark.length, " items") VeloParkToGeojson.exportExtraAmenities(allVelopark) await VeloParkToGeojson.createDiff(allVelopark) console.log( - "Use vite-node scripts/velopark/compare.ts to compare the results and generate a diff file" + "Use vite-node scripts/velopark/compare.ts to compare the results and generate a diff file", ) } } diff --git a/src/Logic/Maproulette.ts b/src/Logic/Maproulette.ts index fd561ede7..51f45cd52 100644 --- a/src/Logic/Maproulette.ts +++ b/src/Logic/Maproulette.ts @@ -6,6 +6,17 @@ export interface MaprouletteTask { description: string instruction: string } +export const maprouletteStatus = ["Open", + "Fixed", + "False_positive", + "Skipped", + "Deleted", + "Already fixed", + "Too_Hard", + "Disabled", +] as const + +export type MaprouletteStatus = typeof maprouletteStatus[number] export default class Maproulette { public static readonly defaultEndpoint = "https://maproulette.org/api/v2" @@ -19,16 +30,7 @@ export default class Maproulette { public static readonly STATUS_TOO_HARD = 6 public static readonly STATUS_DISABLED = 9 - public static readonly STATUS_MEANING = { - 0: "Open", - 1: "Fixed", - 2: "False_positive", - 3: "Skipped", - 4: "Deleted", - 5: "Already fixed", - 6: "Too_Hard", - 9: "Disabled" - } + public static singleton = new Maproulette() /* * The API endpoint to use @@ -62,12 +64,11 @@ export default class Maproulette { if (code === "Created") { return Maproulette.STATUS_OPEN } - for (let i = 0; i < 9; i++) { - if (Maproulette.STATUS_MEANING["" + i] === code) { - return i - } + const i = maprouletteStatus.findIndex( code) + if(i < 0){ + return undefined } - return undefined + return i } /** @@ -87,7 +88,7 @@ export default class Maproulette { tags?: string requestReview?: boolean completionResponses?: Record - } + }, ): Promise { console.log("Maproulette: setting", `${this.endpoint}/task/${taskId}/${status}`, options) options ??= {} @@ -97,9 +98,9 @@ export default class Maproulette { method: "PUT", headers: { "Content-Type": "application/json", - apiKey: this.apiKey + apiKey: this.apiKey, }, - body: JSON.stringify(options) + body: JSON.stringify(options), }) if (response.status !== 204) { console.log(`Failed to close task: ${response.status}`) diff --git a/src/Logic/State/UserSettingsMetaTagging.ts b/src/Logic/State/UserSettingsMetaTagging.ts index 6e568c5c3..33a5ae85b 100644 --- a/src/Logic/State/UserSettingsMetaTagging.ts +++ b/src/Logic/State/UserSettingsMetaTagging.ts @@ -1,42 +1,14 @@ import { Utils } from "../../Utils" /** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */ export class ThemeMetaTagging { - public static readonly themeName = "usersettings" + public static readonly themeName = "usersettings" - public metaTaggging_for_usersettings(feat: { properties: Record }) { - Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () => - feat.properties._description - .match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/) - ?.at(1) - ) - Utils.AddLazyProperty( - feat.properties, - "_d", - () => feat.properties._description?.replace(/</g, "<")?.replace(/>/g, ">") ?? "" - ) - Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () => - ((feat) => { - const e = document.createElement("div") - e.innerHTML = feat.properties._d - return Array.from(e.getElementsByTagName("a")).filter( - (a) => a.href.match(/mastodon|en.osm.town/) !== null - )[0]?.href - })(feat) - ) - Utils.AddLazyProperty(feat.properties, "_mastodon_link", () => - ((feat) => { - const e = document.createElement("div") - e.innerHTML = feat.properties._d - return Array.from(e.getElementsByTagName("a")).filter( - (a) => a.getAttribute("rel")?.indexOf("me") >= 0 - )[0]?.href - })(feat) - ) - Utils.AddLazyProperty( - feat.properties, - "_mastodon_candidate", - () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a - ) - feat.properties["__current_backgroun"] = "initial_value" - } -} + public metaTaggging_for_usersettings(feat: {properties: Record}) { + Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) ) + Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? '' ) + Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) ) + Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) ) + Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a ) + feat.properties['__current_backgroun'] = 'initial_value' + } +} \ No newline at end of file diff --git a/src/Logic/Web/LinkedDataLoader.ts b/src/Logic/Web/LinkedDataLoader.ts index 268fd4865..21a8ab650 100644 --- a/src/Logic/Web/LinkedDataLoader.ts +++ b/src/Logic/Web/LinkedDataLoader.ts @@ -68,7 +68,7 @@ export default class LinkedDataLoader { coors .trim() .split(" ") - .map((n) => Number(n)) + .map((n) => Number(n)), ), ], } @@ -156,7 +156,7 @@ export default class LinkedDataLoader { } const compacted = await jsonld.compact( openingHoursSpecification, - LinkedDataLoader.COMPACTING_CONTEXT_OH + LinkedDataLoader.COMPACTING_CONTEXT_OH, ) const spec: object = compacted["@graph"] if (!spec) { @@ -190,12 +190,12 @@ export default class LinkedDataLoader { const compacted = await jsonld.compact(data, LinkedDataLoader.COMPACTING_CONTEXT) compacted["opening_hours"] = await LinkedDataLoader.ohToOsmFormat( - compacted["opening_hours"] + compacted["opening_hours"], ) if (compacted["openingHours"]) { const ohspec: string[] = compacted["openingHours"] compacted["opening_hours"] = OH.simplify( - ohspec.map((r) => LinkedDataLoader.ohStringToOsmFormat(r)).join("; ") + ohspec.map((r) => LinkedDataLoader.ohStringToOsmFormat(r)).join("; "), ) delete compacted["openingHours"] } @@ -236,7 +236,7 @@ export default class LinkedDataLoader { static async fetchJsonLd( url: string, options?: JsonLdLoaderOptions, - mode?: "fetch-lod" | "fetch-raw" | "proxy" + mode?: "fetch-lod" | "fetch-raw" | "proxy", ): Promise { mode ??= "fetch-lod" if (mode === "proxy") { @@ -251,7 +251,7 @@ export default class LinkedDataLoader { const div = document.createElement("div") div.innerHTML = htmlContent const script = Array.from(div.getElementsByTagName("script")).find( - (script) => script.type === "application/ld+json" + (script) => script.type === "application/ld+json", ) const snippet = JSON.parse(script.textContent) @@ -266,7 +266,7 @@ export default class LinkedDataLoader { */ static removeDuplicateData( externalData: Record, - currentData: Record + currentData: Record, ): Record { const d = { ...externalData } delete d["@context"] @@ -332,7 +332,7 @@ export default class LinkedDataLoader { } private static patchVeloparkProperties( - input: Record> + input: Record>, ): Record { const output: Record = {} for (const k in input) { @@ -472,7 +472,7 @@ export default class LinkedDataLoader { audience, "for", input["ref:velopark"], - " assuming yes" + " assuming yes", ) return "yes" }) @@ -516,8 +516,11 @@ export default class LinkedDataLoader { private static async fetchVeloparkProperty( url: string, property: string, - variable?: string + variable?: string, ): Promise> { + if(property === "schema:photos"){ + console.log(">> Getting photos") + } const results = await new TypedSparql().typedSparql( { schema: "http://schema.org/", @@ -529,17 +532,25 @@ export default class LinkedDataLoader { [url], undefined, " ?parking a ", - "?parking " + property + " " + (variable ?? "") + "?parking " + property + " " + (variable ?? ""), ) + return results } + /** + * + * @param url + * @param property + * @param subExpr + * @private + */ private static async fetchVeloparkGraphProperty( url: string, property: string, - subExpr?: string + subExpr?: string, ): Promise> { - return await new TypedSparql().typedSparql( + const result = await new TypedSparql().typedSparql( { schema: "http://schema.org/", mv: "http://schema.mobivoc.org/", @@ -551,8 +562,10 @@ export default class LinkedDataLoader { "g", " ?parking a ", - S.graph("g", "?section " + property + " " + (subExpr ?? ""), "?section a ?type") + S.graph("g", "?section " + property + " " + (subExpr ?? ""), "?section a ?type", "BIND(STR(?section) AS ?id)"), ) + + return result } /** @@ -569,26 +582,67 @@ export default class LinkedDataLoader { continue } for (const sectionKey in subResult) { - if (!r[sectionKey]) { - r[sectionKey] = {} - } - const section = subResult[sectionKey] - for (const key in section) { - r[sectionKey][key] ??= section[key] + if (sectionKey === "default") { + r["default"] ??= {} + const section = subResult["default"] + for (const key in section) { + r["default"][key] ??= section[key] + } + } else { + const section = subResult[sectionKey] + const actualId = Array.from(section["id"] ?? [])[0] ?? sectionKey + r[actualId] ??= {} + for (const key in section) { + r[actualId][key] ??= section[key] + } } } } - if (r["default"] !== undefined && Object.keys(r).length > 1) { + /** + * Copy all values from the section with name "key" into the other sections, + * remove section "key" afterwards + * @param key + */ + function spreadSection(key: string){ for (const section in r) { - if (section === "default") { + if (section === key) { continue } - for (const k in r.default) { - r[section][k] ??= r.default[k] + for (const k in r[key]) { + r[section][k] ??= r[key][k] } } - delete r.default + delete r[key] + } + + // The "default" part of the result contains all general info + // The other 'sections' need to get those copied! Then, we delete the "default"-section + if (r["default"] !== undefined && Object.keys(r).length > 1) { + spreadSection("default") + + } + if (Object.keys(r).length > 1) { + // This result has multiple sections + // We should check that the naked URL got distributed and scrapped + const keys = Object.keys(r) + if (Object.keys(r).length > 2) { + console.log("Multiple sections detected: ", JSON.stringify(keys)) + } + const shortestKeyLength: number = Math.min(...keys.map(k => k.length)) + const key = keys.find(k => k.length === shortestKeyLength) + if (keys.some(k => !k.startsWith(key))) { + throw "Invalid multi-object: the shortest key is not the start of all the others: " + JSON.stringify(keys) + } + spreadSection(key) + } + if (Object.keys(r).length == 1) { + const key = Object.keys(r)[0] + if(key.indexOf("#")>0){ + const newKey = key.split("#")[0] + r[newKey] = r[key] + delete r[key] + } } return r } @@ -597,7 +651,7 @@ export default class LinkedDataLoader { directUrl: string, propertiesWithoutGraph: PropertiesSpec, propertiesInGraph: PropertiesSpec, - extra?: string[] + extra?: string[], ): Promise> { const allPartialResults: SparqlResult[] = [] for (const propertyName in propertiesWithoutGraph) { @@ -607,7 +661,7 @@ export default class LinkedDataLoader { const result = await this.fetchVeloparkProperty( directUrl, propertyName, - "?" + variableName + "?" + variableName, ) allPartialResults.push(result) } else { @@ -616,7 +670,7 @@ export default class LinkedDataLoader { const result = await this.fetchVeloparkProperty( directUrl, propertyName, - `[${subProperty} ?${variableName}] ` + `[${subProperty} ?${variableName}] `, ) allPartialResults.push(result) } @@ -634,7 +688,7 @@ export default class LinkedDataLoader { const result = await this.fetchVeloparkGraphProperty( directUrl, propertyName, - variableName + variableName, ) allPartialResults.push(result) } @@ -646,7 +700,7 @@ export default class LinkedDataLoader { const result = await this.fetchVeloparkGraphProperty( directUrl, propertyName, - variableName + variableName, ) allPartialResults.push(result) } else { @@ -655,7 +709,7 @@ export default class LinkedDataLoader { const result = await this.fetchVeloparkGraphProperty( directUrl, propertyName, - `[${subProperty} ?${variableName}] ` + `[${subProperty} ?${variableName}] `, ) allPartialResults.push(result) } @@ -675,16 +729,18 @@ export default class LinkedDataLoader { /** * Fetches all data relevant to velopark. * The id will be saved as `ref:velopark` + * If the entry has multiple sections, this will return multiple items * @param url */ public static async fetchVeloparkEntry( url: string, - includeExtras: boolean = false + includeExtras: boolean = false, ): Promise { const cacheKey = includeExtras + url if (this.veloparkCache[cacheKey]) { return this.veloparkCache[cacheKey] } + // Note: the proxy doesn't make any changes in this case const withProxyUrl = Constants.linkedDataProxy.replace("{url}", encodeURIComponent(url)) const optionalPaths: Record> = { "schema:interactionService": { @@ -697,6 +753,7 @@ export default class LinkedDataLoader { "schema:email": "email", "schema:telephone": "phone", }, + // "schema:photos": "images", "schema:dateModified": "_last_edit_timestamp", } if (includeExtras) { @@ -738,8 +795,16 @@ export default class LinkedDataLoader { withProxyUrl, optionalPaths, graphOptionalPaths, - extra + extra, ) + for (const unpatchedKey in unpatched) { + // Dirty hack + const rawData = await Utils.downloadJsonCached(url, 1000*60*60) + const images = rawData["photos"].map(ph => ph.image) + unpatched[unpatchedKey].images = new Set(images) + } + + console.log("Got unpatched:", unpatched) const patched: Feature[] = [] for (const section in unpatched) { const p = LinkedDataLoader.patchVeloparkProperties(unpatched[section]) diff --git a/src/Logic/Web/TypedSparql.ts b/src/Logic/Web/TypedSparql.ts index c87991205..4cda88ce4 100644 --- a/src/Logic/Web/TypedSparql.ts +++ b/src/Logic/Web/TypedSparql.ts @@ -67,13 +67,11 @@ export default class TypedSparql { bindings.forEach((item) => { const result = >>{} item.forEach((value, key) => { - if (!result[key.value]) { - result[key.value] = new Set() - } + result[key.value] ??= new Set() result[key.value].add(value.value) }) if (graphVariable && result[graphVariable]?.size > 0) { - const id = Array.from(result[graphVariable])?.[0] ?? "default" + const id: string = ( Array.from(result["id"] ?? [])?.[0] ?? Array.from(result[graphVariable] ?? [])?.[0]) ?? "default" resultAllGraphs[id] = result } else { resultAllGraphs["default"] = result diff --git a/src/UI/MapRoulette/MaprouletteSetStatus.svelte b/src/UI/MapRoulette/MaprouletteSetStatus.svelte index 3ea6056e2..56619f6b7 100644 --- a/src/UI/MapRoulette/MaprouletteSetStatus.svelte +++ b/src/UI/MapRoulette/MaprouletteSetStatus.svelte @@ -5,7 +5,7 @@ import Tr from "../Base/Tr.svelte" import Translations from "../i18n/Translations" import Icon from "../Map/Icon.svelte" - import Maproulette from "../../Logic/Maproulette" + import Maproulette, { maprouletteStatus } from "../../Logic/Maproulette" import LoginToggle from "../Base/LoginToggle.svelte" /** @@ -38,10 +38,11 @@ async function apply() { const maproulette_id = tags.data[maproulette_id_key] ?? tags.data.mr_taskId ?? tags.data.id try { - await Maproulette.singleton.closeTask(Number(maproulette_id), Number(statusToSet), state, { + const statusIndex = Maproulette.codeToIndex(statusToSet) ?? Number(statusToSet) + await Maproulette.singleton.closeTask(Number(maproulette_id), statusIndex, state, { comment: feedback, }) - tags.data["mr_taskStatus"] = Maproulette.STATUS_MEANING[Number(statusToSet)] + tags.data["mr_taskStatus"] = maprouletteStatus[statusIndex] tags.data.status = statusToSet tags.ping() } catch (e) { diff --git a/src/UI/Popup/ImportButtons/PointImportButtonViz.ts b/src/UI/Popup/ImportButtons/PointImportButtonViz.ts index e5addb9e6..8be4e3f46 100644 --- a/src/UI/Popup/ImportButtons/PointImportButtonViz.ts +++ b/src/UI/Popup/ImportButtons/PointImportButtonViz.ts @@ -9,6 +9,7 @@ import { PointImportFlowArguments, PointImportFlowState } from "./PointImportFlo import { Utils } from "../../../Utils" import { ImportFlowUtils } from "./ImportFlow" import Translations from "../../i18n/Translations" +import { GeoOperations } from "../../../Logic/GeoOperations" /** * The wrapper to make the special visualisation for the PointImportFlow @@ -44,6 +45,10 @@ export class PointImportButtonViz implements SpecialVisualization { name: "maproulette_id", doc: "The property name of the maproulette_id - this is probably `mr_taskId`. If given, the maproulette challenge will be marked as fixed. Only use this if part of a maproulette-layer.", }, + { + name: "to_point", + doc: "If set, a feature will be converted to a centerpoint", + }, ] } @@ -51,10 +56,17 @@ export class PointImportButtonViz implements SpecialVisualization { state: SpecialVisualizationState, tagSource: UIEventSource>, argument: string[], - feature: Feature + feature: Feature, ): BaseUIElement { + + const to_point_index = this.args.findIndex(arg => arg.name === "to_point") + const summarizePointArg = argument[to_point_index].toLowerCase() if (feature.geometry.type !== "Point") { - return Translations.t.general.add.import.wrongType.SetClass("alert") + if (summarizePointArg !== "no" && summarizePointArg !== "false") { + feature = GeoOperations.centerpoint(feature) + } else { + return Translations.t.general.add.import.wrongType.SetClass("alert") + } } const baseArgs: PointImportFlowArguments = Utils.ParseVisArgs(this.args, argument) const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, baseArgs) @@ -63,7 +75,7 @@ export class PointImportButtonViz implements SpecialVisualization { >feature, baseArgs, tagsToApply, - tagSource + tagSource, ) return new SvelteUIElement(PointImportFlow, {