forked from MapComplete/MapComplete
		
	Allow closing Maproulette tasks
This commit is contained in:
		
							parent
							
								
									a1bffc7b7f
								
							
						
					
					
						commit
						65997291bb
					
				
					 12 changed files with 309 additions and 165 deletions
				
			
		| 
						 | 
					@ -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
 | 
					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'
 | 
					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
 | 
					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 
 | 
					#### 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,)}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										39
									
								
								Logic/Maproulette.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								Logic/Maproulette.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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<void> {
 | 
				
			||||||
 | 
					    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}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,7 @@ import ChangeToElementsActor from "../Actors/ChangeToElementsActor";
 | 
				
			||||||
import PendingChangesUploader from "../Actors/PendingChangesUploader";
 | 
					import PendingChangesUploader from "../Actors/PendingChangesUploader";
 | 
				
			||||||
import * as translators from "../../assets/translators.json"
 | 
					import * as translators from "../../assets/translators.json"
 | 
				
			||||||
import {post} from "jquery";
 | 
					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,
 | 
					 * 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;
 | 
					    public mangroveIdentity: MangroveIdentity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Maproulette connection
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public maprouletteConnection: Maproulette;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public readonly isTranslator : Store<boolean>;
 | 
					    public readonly isTranslator : Store<boolean>;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    public readonly installedUserThemes: Store<string[]>
 | 
					    public readonly installedUserThemes: Store<string[]>
 | 
				
			||||||
| 
						 | 
					@ -80,6 +86,8 @@ export default class UserRelatedState extends ElementsState {
 | 
				
			||||||
            this.osmConnection.GetLongPreference("identity", "mangrove")
 | 
					            this.osmConnection.GetLongPreference("identity", "mangrove")
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.maprouletteConnection = new Maproulette();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (layoutToUse?.hideFromOverview) {
 | 
					        if (layoutToUse?.hideFromOverview) {
 | 
				
			||||||
            this.osmConnection.isLoggedIn.addCallbackAndRunD(loggedIn => {
 | 
					            this.osmConnection.isLoggedIn.addCallbackAndRunD(loggedIn => {
 | 
				
			||||||
                if (loggedIn) {
 | 
					                if (loggedIn) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,8 @@ export default class Constants {
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    public static ImgurApiKey = '7070e7167f0a25a'
 | 
					    public static ImgurApiKey = '7070e7167f0a25a'
 | 
				
			||||||
    public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85"
 | 
					    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 = [
 | 
					    public static defaultOverpassUrls = [
 | 
				
			||||||
        // The official instance, 10000 queries per day per project allowed
 | 
					        // The official instance, 10000 queries per day per project allowed
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -550,15 +550,21 @@ export class ImportPointButton extends AbstractImportButton {
 | 
				
			||||||
                    name: "note_id",
 | 
					                    name: "note_id",
 | 
				
			||||||
                    doc: "If given, this key will be read. The corresponding note on OSM will be closed, stating 'imported'"
 | 
					                    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",
 | 
					                    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}
 | 
					            { showRemovedTags: false}
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static createConfirmPanelForPoint(
 | 
					    private static createConfirmPanelForPoint(
 | 
				
			||||||
        args: { max_snap_distance: string, snap_onto_layers: string, icon: string, text: string, newTags: UIEventSource<any>, targetLayer: string, note_id: string },
 | 
					        args: { max_snap_distance: string, snap_onto_layers: string, icon: string, text: string, newTags: UIEventSource<any>, targetLayer: string, note_id: string, maproulette_id: string },
 | 
				
			||||||
        state: FeaturePipelineState,
 | 
					        state: FeaturePipelineState,
 | 
				
			||||||
        guiState: DefaultGuiState,
 | 
					        guiState: DefaultGuiState,
 | 
				
			||||||
        originalFeatureTags: UIEventSource<any>,
 | 
					        originalFeatureTags: UIEventSource<any>,
 | 
				
			||||||
| 
						 | 
					@ -600,6 +606,14 @@ export class ImportPointButton extends AbstractImportButton {
 | 
				
			||||||
                originalFeatureTags.data["closed_at"] = new Date().toISOString()
 | 
					                originalFeatureTags.data["closed_at"] = new Date().toISOString()
 | 
				
			||||||
                originalFeatureTags.ping()
 | 
					                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"]
 | 
					        let preciseInputOption = args["location_picker"]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,8 +7,7 @@
 | 
				
			||||||
    "en": "A layer showing pedestrian crossings with rainbow paintings"
 | 
					    "en": "A layer showing pedestrian crossings with rainbow paintings"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "source": {
 | 
					  "source": {
 | 
				
			||||||
    "osmTags": 
 | 
					    "osmTags": "highway=crossing"
 | 
				
			||||||
      "highway=crossing"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "minzoom": 17,
 | 
					  "minzoom": 17,
 | 
				
			||||||
  "title": {
 | 
					  "title": {
 | 
				
			||||||
| 
						 | 
					@ -77,10 +76,12 @@
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "icon": {
 | 
					      "icon": {
 | 
				
			||||||
        "render": "./assets/themes/rainbow_crossings/crossing.svg",
 | 
					        "render": "./assets/themes/rainbow_crossings/crossing.svg",
 | 
				
			||||||
        "mappings": [{
 | 
					        "mappings": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
            "if": "crossing:marking=rainbow",
 | 
					            "if": "crossing:marking=rainbow",
 | 
				
			||||||
            "then": "./assets/themes/rainbow_crossings/logo.svg"
 | 
					            "then": "./assets/themes/rainbow_crossings/logo.svg"
 | 
				
			||||||
        }]
 | 
					          }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "iconSize": "40,40,center",
 | 
					      "iconSize": "40,40,center",
 | 
				
			||||||
      "location": [
 | 
					      "location": [
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,11 +29,11 @@
 | 
				
			||||||
    "viewpoint",
 | 
					    "viewpoint",
 | 
				
			||||||
    "doctors"
 | 
					    "doctors"
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "overrideAll" : {
 | 
					  "overrideAll": {
 | 
				
			||||||
    "minzoom" : "15",
 | 
					    "minzoom": "15",
 | 
				
			||||||
    "mapRendering" : [
 | 
					    "mapRendering": [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        "label" : null
 | 
					        "label": null
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,9 @@
 | 
				
			||||||
        "=presets": [],
 | 
					        "=presets": [],
 | 
				
			||||||
        "source": {
 | 
					        "source": {
 | 
				
			||||||
          "osmTags": {
 | 
					          "osmTags": {
 | 
				
			||||||
            "and+": ["crossing:marking=rainbow"]
 | 
					            "and+": [
 | 
				
			||||||
 | 
					              "crossing:marking=rainbow"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -39,5 +41,3 @@
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,6 +51,43 @@
 | 
				
			||||||
      "tagRenderings": [
 | 
					      "tagRenderings": [
 | 
				
			||||||
        "all_tags"
 | 
					        "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": "<a href='https://maproulette.org/challenge/{mr_challengeId}/task/{mr_taskId}'>View this task</a>"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "id": "import",
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          "render": "{import_button(street_lamps,tags,Import,./assets/svg/addSmall.svg,,,,photo,mr_taskId)}"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "hideFromOverview": true
 | 
					  "hideFromOverview": true
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3256,6 +3256,7 @@
 | 
				
			||||||
        "name": "Direction visualization"
 | 
					        "name": "Direction visualization"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "doctors": {
 | 
					    "doctors": {
 | 
				
			||||||
 | 
					        "description": "This layer shows doctor offices, dentists and other healthcare facilities",
 | 
				
			||||||
        "filter": {
 | 
					        "filter": {
 | 
				
			||||||
            "0": {
 | 
					            "0": {
 | 
				
			||||||
                "options": {
 | 
					                "options": {
 | 
				
			||||||
| 
						 | 
					@ -3278,6 +3279,10 @@
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "tagRenderings": {
 | 
					        "tagRenderings": {
 | 
				
			||||||
 | 
					            "name": {
 | 
				
			||||||
 | 
					                "question": "What is the name of this doctors place?",
 | 
				
			||||||
 | 
					                "render": "This doctors place is called {name}"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
            "specialty": {
 | 
					            "specialty": {
 | 
				
			||||||
                "mappings": {
 | 
					                "mappings": {
 | 
				
			||||||
                    "0": {
 | 
					                    "0": {
 | 
				
			||||||
| 
						 | 
					@ -5074,6 +5079,35 @@
 | 
				
			||||||
            "render": "Bookcase"
 | 
					            "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": {
 | 
					    "recycling": {
 | 
				
			||||||
        "description": "A layer with recycling containers and centres",
 | 
					        "description": "A layer with recycling containers and centres",
 | 
				
			||||||
        "filter": {
 | 
					        "filter": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -745,6 +745,10 @@
 | 
				
			||||||
        "shortDescription": "Publicly accessible towers to enjoy the view",
 | 
					        "shortDescription": "Publicly accessible towers to enjoy the view",
 | 
				
			||||||
        "title": "Observation towers"
 | 
					        "title": "Observation towers"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "onwheels": {
 | 
				
			||||||
 | 
					        "description": "On this map, publicly weelchair accessible places are shown and can be easily added",
 | 
				
			||||||
 | 
					        "title": "OnWheels"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "openwindpowermap": {
 | 
					    "openwindpowermap": {
 | 
				
			||||||
        "description": "A map for showing and editing wind turbines.",
 | 
					        "description": "A map for showing and editing wind turbines.",
 | 
				
			||||||
        "title": "OpenWindPowerMap"
 | 
					        "title": "OpenWindPowerMap"
 | 
				
			||||||
| 
						 | 
					@ -867,6 +871,10 @@
 | 
				
			||||||
        "shortDescription": "A map showing postboxes and post offices",
 | 
					        "shortDescription": "A map showing postboxes and post offices",
 | 
				
			||||||
        "title": "Postbox and Post Office Map"
 | 
					        "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": {
 | 
					    "shops": {
 | 
				
			||||||
        "description": "On this map, one can mark basic information about shops, add opening hours and phone numbers",
 | 
					        "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",
 | 
					        "shortDescription": "An editable map with basic shop information",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue