diff --git a/Docs/SpecialRenderings.md b/Docs/SpecialRenderings.md index c4ed606d5..d2419f526 100644 --- a/Docs/SpecialRenderings.md +++ b/Docs/SpecialRenderings.md @@ -347,11 +347,12 @@ snap_onto_layers | _undefined_ | If a way of the given layer(s) is closeby, will max_snap_distance | 5 | The maximum distance that the imported point will be moved to snap onto a way in an already existing layer (in meters). This is previewed to the contributor, similar to the 'add new point'-action of MapComplete note_id | _undefined_ | If given, this key will be read. The corresponding note on OSM will be closed, stating 'imported' location_picker | photo | Chooses the background for the precise location picker, options are 'map', 'photo' or 'osmbasedmap' or 'none' if the precise input picker should be disabled +maproulette_id | _undefined_ | If given, the maproulette challenge will be marked as fixed #### Example usage of import_button - `{import_button(,,Import this data into OpenStreetMap,./assets/svg/addSmall.svg,,5,,photo)}` + `{import_button(,,Import this data into OpenStreetMap,./assets/svg/addSmall.svg,,5,,photo,)}` diff --git a/Logic/Maproulette.ts b/Logic/Maproulette.ts new file mode 100644 index 000000000..470bb75d8 --- /dev/null +++ b/Logic/Maproulette.ts @@ -0,0 +1,39 @@ +import Constants from "../Models/Constants"; + +export default class Maproulette { + /** + * The API endpoint to use + */ + endpoint: string; + + /** + * The API key to use for all requests + */ + private apiKey: string; + + /** + * Creates a new Maproulette instance + * @param endpoint The API endpoint to use + */ + constructor(endpoint: string = "https://maproulette.org/api/v2") { + this.endpoint = endpoint; + this.apiKey = Constants.MaprouletteApiKey; + } + + /** + * Close a task + * @param taskId The task to close + */ + async closeTask(taskId: number): Promise { + const response = await fetch(`${this.endpoint}/task/${taskId}/1`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + "apiKey": this.apiKey, + }, + }); + if (response.status !== 304) { + console.log(`Failed to close task: ${response.status}`); + } + } +} diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index 8fbd9c5df..0fc43ebc8 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -13,6 +13,7 @@ import ChangeToElementsActor from "../Actors/ChangeToElementsActor"; import PendingChangesUploader from "../Actors/PendingChangesUploader"; import * as translators from "../../assets/translators.json" import {post} from "jquery"; +import Maproulette from "../Maproulette"; /** * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, @@ -34,6 +35,11 @@ export default class UserRelatedState extends ElementsState { */ public mangroveIdentity: MangroveIdentity; + /** + * Maproulette connection + */ + public maprouletteConnection: Maproulette; + public readonly isTranslator : Store; public readonly installedUserThemes: Store @@ -80,6 +86,8 @@ export default class UserRelatedState extends ElementsState { this.osmConnection.GetLongPreference("identity", "mangrove") ); + this.maprouletteConnection = new Maproulette(); + if (layoutToUse?.hideFromOverview) { this.osmConnection.isLoggedIn.addCallbackAndRunD(loggedIn => { if (loggedIn) { diff --git a/Models/Constants.ts b/Models/Constants.ts index b10e8b872..b296af714 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -6,6 +6,8 @@ export default class Constants { public static ImgurApiKey = '7070e7167f0a25a' public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85" + // Currently there is no user-friendly way to get the user's API key. See https://github.com/maproulette/maproulette2/issues/476 for more information. + public static readonly MaprouletteApiKey = ""; public static defaultOverpassUrls = [ // The official instance, 10000 queries per day per project allowed diff --git a/UI/Popup/ImportButton.ts b/UI/Popup/ImportButton.ts index e72d60129..42f9241f3 100644 --- a/UI/Popup/ImportButton.ts +++ b/UI/Popup/ImportButton.ts @@ -550,15 +550,21 @@ export class ImportPointButton extends AbstractImportButton { name: "note_id", doc: "If given, this key will be read. The corresponding note on OSM will be closed, stating 'imported'" }, - {name:"location_picker", + { + name:"location_picker", defaultValue: "photo", - doc: "Chooses the background for the precise location picker, options are 'map', 'photo' or 'osmbasedmap' or 'none' if the precise input picker should be disabled"}], + doc: "Chooses the background for the precise location picker, options are 'map', 'photo' or 'osmbasedmap' or 'none' if the precise input picker should be disabled" + }, + { + name: "maproulette_id", + doc: "If given, the maproulette challenge will be marked as fixed" + }], { showRemovedTags: false} ) } private static createConfirmPanelForPoint( - args: { max_snap_distance: string, snap_onto_layers: string, icon: string, text: string, newTags: UIEventSource, targetLayer: string, note_id: string }, + args: { max_snap_distance: string, snap_onto_layers: string, icon: string, text: string, newTags: UIEventSource, targetLayer: string, note_id: string, maproulette_id: string }, state: FeaturePipelineState, guiState: DefaultGuiState, originalFeatureTags: UIEventSource, @@ -600,6 +606,14 @@ export class ImportPointButton extends AbstractImportButton { originalFeatureTags.data["closed_at"] = new Date().toISOString() originalFeatureTags.ping() } + + let maproulette_id = originalFeatureTags.data[args.maproulette_id]; + console.log("Checking if we need to mark a maproulette challenge as fixed (" + maproulette_id + ")") + if (maproulette_id !== undefined) { + // Fetch MapRoulette API key, then use it to mark the challenge as fixed + console.log("Marking maproulette challenge as fixed") + state.maprouletteConnection.closeTask(Number(maproulette_id)); + } } let preciseInputOption = args["location_picker"] diff --git a/assets/layers/doctors/doctors.json b/assets/layers/doctors/doctors.json index 8979e43c0..eefedaf8d 100644 --- a/assets/layers/doctors/doctors.json +++ b/assets/layers/doctors/doctors.json @@ -1,147 +1,147 @@ { - "id": "doctors", - "name": { - "en": "doctors" + "id": "doctors", + "name": { + "en": "doctors" + }, + "description": { + "en": "This layer shows doctor offices, dentists and other healthcare facilities" + }, + "source": { + "osmTags": { + "or": [ + "amenity=doctors", + "amenity=dentist", + "healthcare=physiotherapist" + ] + } + }, + "title": { + "render": { + "en": "Doctors Office {name}" }, - "description": { - "en": "This layer shows doctor offices, dentists and other healthcare facilities" - }, - "source": { - "osmTags": { - "or": [ - "amenity=doctors", - "amenity=dentist", - "healthcare=physiotherapist" - ] - } - }, - "title": { - "render": { - "en": "Doctors Office {name}" - }, - "mappings": [ - { - "if": "amenity=doctors", - "then": "Doctors Office {name}" - }, - { - "if": "amenity=dentist", - "then": "Dentists office {name}" - }, - { - "if": "healthcare=physiotherapist", - "then": "Physiotherapists office {name}" - } - ] - }, - "minzoom": 13, - "tagRenderings": [ - "images", - "opening_hours", - "phone", - "email", - "website", - { - "question": { - "en": "What is the name of this doctors place?" - }, - "render": { - "en": "This doctors place is called {name}" - }, - "freeform": { - "key": "name" - }, - "id": "name" - }, - { - "condition": "amenity=doctors", - "id": "specialty", - "render": { - "en": "This doctor is specialized in {healthcare:speciality}" - }, - "question": { - "en": "What is this doctor specialized in?" - }, - "freeform": { - "key": "healthcare:speciality" - }, - "mappings": [ - { - "if": "healthcare:speciality=general", - "then": { - "en": "This is a general practitioner" - } - }, - { - "if": "healthcare:speciality=gynaecology", - "then": { - "en": "This is a gynaecologist" - } - }, - { - "if": "healthcare:speciality=psychiatry", - "then": { - "en": "This is a psychiatrist" - } - }, - { - "if": "healthcare:speciality=paediatrics", - "then": { - "en": "This is a paediatrician" - } - } - ] - } - ], - "presets": [ - { - "title": { - "en": "a doctors office" - }, - "tags": [ - "amenity=doctors" - ] - }, - { - "title": { - "en": "a dentists office" - }, - "tags": [ - "amenity=dentist" - ] - }, - { - "title": { - "en": "a physiotherapists office" - }, - "tags": [ - "healthcare=physiotherapist" - ] - } - ], - "filter": [ - { - "id": "opened-now", - "options": [ - { - "question": { - "en": "Opened now" - }, - "osmTags": "_isOpen=yes" - } - ] - } - ], - "mapRendering": [ - { - "icon": { - "render": "circle:white;./assets/layers/doctors/doctors.svg" - }, - "iconSize": "40,40,center", - "location": [ - "point", - "centroid" - ] - } + "mappings": [ + { + "if": "amenity=doctors", + "then": "Doctors Office {name}" + }, + { + "if": "amenity=dentist", + "then": "Dentists office {name}" + }, + { + "if": "healthcare=physiotherapist", + "then": "Physiotherapists office {name}" + } ] + }, + "minzoom": 13, + "tagRenderings": [ + "images", + "opening_hours", + "phone", + "email", + "website", + { + "question": { + "en": "What is the name of this doctors place?" + }, + "render": { + "en": "This doctors place is called {name}" + }, + "freeform": { + "key": "name" + }, + "id": "name" + }, + { + "condition": "amenity=doctors", + "id": "specialty", + "render": { + "en": "This doctor is specialized in {healthcare:speciality}" + }, + "question": { + "en": "What is this doctor specialized in?" + }, + "freeform": { + "key": "healthcare:speciality" + }, + "mappings": [ + { + "if": "healthcare:speciality=general", + "then": { + "en": "This is a general practitioner" + } + }, + { + "if": "healthcare:speciality=gynaecology", + "then": { + "en": "This is a gynaecologist" + } + }, + { + "if": "healthcare:speciality=psychiatry", + "then": { + "en": "This is a psychiatrist" + } + }, + { + "if": "healthcare:speciality=paediatrics", + "then": { + "en": "This is a paediatrician" + } + } + ] + } + ], + "presets": [ + { + "title": { + "en": "a doctors office" + }, + "tags": [ + "amenity=doctors" + ] + }, + { + "title": { + "en": "a dentists office" + }, + "tags": [ + "amenity=dentist" + ] + }, + { + "title": { + "en": "a physiotherapists office" + }, + "tags": [ + "healthcare=physiotherapist" + ] + } + ], + "filter": [ + { + "id": "opened-now", + "options": [ + { + "question": { + "en": "Opened now" + }, + "osmTags": "_isOpen=yes" + } + ] + } + ], + "mapRendering": [ + { + "icon": { + "render": "circle:white;./assets/layers/doctors/doctors.svg" + }, + "iconSize": "40,40,center", + "location": [ + "point", + "centroid" + ] + } + ] } \ No newline at end of file diff --git a/assets/layers/rainbow_crossings/rainbow_crossings.json b/assets/layers/rainbow_crossings/rainbow_crossings.json index 35c99d8d9..d13fe3c03 100644 --- a/assets/layers/rainbow_crossings/rainbow_crossings.json +++ b/assets/layers/rainbow_crossings/rainbow_crossings.json @@ -7,8 +7,7 @@ "en": "A layer showing pedestrian crossings with rainbow paintings" }, "source": { - "osmTags": - "highway=crossing" + "osmTags": "highway=crossing" }, "minzoom": 17, "title": { @@ -38,7 +37,7 @@ ], "tagRenderings": [ "images", - { + { "id": "crossing-with-rainbow", "question": { "en": "Does this crossing has rainbow paintings?" @@ -77,10 +76,12 @@ { "icon": { "render": "./assets/themes/rainbow_crossings/crossing.svg", - "mappings": [{ - "if": "crossing:marking=rainbow", - "then": "./assets/themes/rainbow_crossings/logo.svg" - }] + "mappings": [ + { + "if": "crossing:marking=rainbow", + "then": "./assets/themes/rainbow_crossings/logo.svg" + } + ] }, "iconSize": "40,40,center", "location": [ @@ -89,4 +90,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/assets/themes/onwheels/onwheels.json b/assets/themes/onwheels/onwheels.json index 881729e71..920ce1e1c 100644 --- a/assets/themes/onwheels/onwheels.json +++ b/assets/themes/onwheels/onwheels.json @@ -1,10 +1,10 @@ { "id": "onwheels", "title": { - "en": "OnWheels" + "en": "OnWheels" }, "description": { - "en": "On this map, publicly weelchair accessible places are shown and can be easily added" + "en": "On this map, publicly weelchair accessible places are shown and can be easily added" }, "maintainer": "MapComplete", "icon": "./assets/themes/onwheels/crest.svg", @@ -29,11 +29,11 @@ "viewpoint", "doctors" ], - "overrideAll" : { - "minzoom" : "15", - "mapRendering" : [ + "overrideAll": { + "minzoom": "15", + "mapRendering": [ { - "label" : null + "label": null } ] } diff --git a/assets/themes/rainbow_crossings/rainbow_crossings.json b/assets/themes/rainbow_crossings/rainbow_crossings.json index 32fe470a3..1a63df6fc 100644 --- a/assets/themes/rainbow_crossings/rainbow_crossings.json +++ b/assets/themes/rainbow_crossings/rainbow_crossings.json @@ -24,7 +24,9 @@ "=presets": [], "source": { "osmTags": { - "and+": ["crossing:marking=rainbow"] + "and+": [ + "crossing:marking=rainbow" + ] } } } @@ -38,6 +40,4 @@ } } ] -} - - +} \ No newline at end of file diff --git a/assets/themes/street_lighting/street_lighting_assen.json b/assets/themes/street_lighting/street_lighting_assen.json index 646a82550..0ca7388ed 100644 --- a/assets/themes/street_lighting/street_lighting_assen.json +++ b/assets/themes/street_lighting/street_lighting_assen.json @@ -51,6 +51,43 @@ "tagRenderings": [ "all_tags" ] + }, + { + "id": "maproulette", + "name": "Maproulette Tasks", + "source": { + "osmTags": "id~*", + "geoJson": "https://maproulette.org/api/v2/challenge/view/27971", + "isOsmCache": false + }, + "calculatedTags": [ + "_closest_osm_street_lamp=feat.closest('street_lamps')?.properties?.id", + "_closest_osm_street_lamp_distance=feat.distanceTo(feat.properties._closest_osm_street_lamp)", + "_has_closeby_feature=Number(feat.properties._closest_osm_street_lamp_distance) < 5 ? 'yes' : 'no'" + ], + "title": "Straatlantaarn in Maproulette", + "mapRendering": [ + { + "location": [ + "point", + "centroid" + ], + "icon": "circle:black", + "iconSize": "20,20,center" + } + ], + "tagRenderings": [ + "all_tags", + { + "id": "link", + "render": "View this task" + }, + { + "id": "import", + + "render": "{import_button(street_lamps,tags,Import,./assets/svg/addSmall.svg,,,,photo,mr_taskId)}" + } + ] } ], "hideFromOverview": true diff --git a/langs/layers/en.json b/langs/layers/en.json index 305bfde70..764a8669d 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -3256,6 +3256,7 @@ "name": "Direction visualization" }, "doctors": { + "description": "This layer shows doctor offices, dentists and other healthcare facilities", "filter": { "0": { "options": { @@ -3278,6 +3279,10 @@ } }, "tagRenderings": { + "name": { + "question": "What is the name of this doctors place?", + "render": "This doctors place is called {name}" + }, "specialty": { "mappings": { "0": { @@ -5074,6 +5079,35 @@ "render": "Bookcase" } }, + "rainbow_crossings": { + "description": "A layer showing pedestrian crossings with rainbow paintings", + "name": "Crossings with rainbow paintings", + "presets": { + "0": { + "description": "Pedestrian crossing", + "title": "a crossing" + } + }, + "tagRenderings": { + "crossing-with-rainbow": { + "mappings": { + "0": { + "then": "This crossing has rainbow paintings" + }, + "1": { + "then": "No rainbow paintings here" + }, + "2": { + "then": "No rainbow paintings here" + } + }, + "question": "Does this crossing has rainbow paintings?" + } + }, + "title": { + "render": "Crossing" + } + }, "recycling": { "description": "A layer with recycling containers and centres", "filter": { diff --git a/langs/themes/en.json b/langs/themes/en.json index 7b3cd083c..011d63f1b 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -745,6 +745,10 @@ "shortDescription": "Publicly accessible towers to enjoy the view", "title": "Observation towers" }, + "onwheels": { + "description": "On this map, publicly weelchair accessible places are shown and can be easily added", + "title": "OnWheels" + }, "openwindpowermap": { "description": "A map for showing and editing wind turbines.", "title": "OpenWindPowerMap" @@ -867,6 +871,10 @@ "shortDescription": "A map showing postboxes and post offices", "title": "Postbox and Post Office Map" }, + "rainbow_crossings": { + "description": "On this map, rainbow-painted pedestrian crossings are shown and can be easily added", + "title": "Rainbow pedestrian crossings" + }, "shops": { "description": "On this map, one can mark basic information about shops, add opening hours and phone numbers", "shortDescription": "An editable map with basic shop information",