Merge pull request #1003 from pietervdvn/feature/maproulette
Maproulette tasks
This commit is contained in:
commit
c81d36fb8f
24 changed files with 1253 additions and 15 deletions
|
@ -349,11 +349,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,)}`
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -82,6 +82,28 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
|
|||
Utils.downloadJsonCached(url, 60 * 60)
|
||||
.then(json => {
|
||||
self.state.setData("loaded")
|
||||
// TODO: move somewhere else, just for testing
|
||||
// Check for maproulette data
|
||||
if (url.startsWith("https://maproulette.org/api/v2/tasks/box/")) {
|
||||
console.log("MapRoulette data detected")
|
||||
const data = json;
|
||||
let maprouletteFeatures: any[] = [];
|
||||
data.forEach(element => {
|
||||
maprouletteFeatures.push({
|
||||
type: "Feature",
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [element.point.lng, element.point.lat]
|
||||
},
|
||||
properties: {
|
||||
// Map all properties to the feature
|
||||
...element,
|
||||
}
|
||||
});
|
||||
});
|
||||
json.features = maprouletteFeatures;
|
||||
}
|
||||
|
||||
if (json.features === undefined || json.features === null) {
|
||||
return;
|
||||
}
|
||||
|
|
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 * 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<boolean>;
|
||||
|
||||
public readonly installedUserThemes: Store<string[]>
|
||||
|
@ -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) {
|
||||
|
|
|
@ -7,6 +7,15 @@ export default class Constants {
|
|||
public static ImgurApiKey = '7070e7167f0a25a'
|
||||
public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85"
|
||||
|
||||
/**
|
||||
* API key for Maproulette
|
||||
*
|
||||
* 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.
|
||||
* Using an empty string however does work for most actions, but will attribute all actions to the Superuser.
|
||||
*/
|
||||
public static readonly MaprouletteApiKey = "";
|
||||
|
||||
public static defaultOverpassUrls = [
|
||||
// The official instance, 10000 queries per day per project allowed
|
||||
"https://overpass-api.de/api/interpreter",
|
||||
|
|
|
@ -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<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,
|
||||
guiState: DefaultGuiState,
|
||||
originalFeatureTags: UIEventSource<any>,
|
||||
|
@ -600,6 +606,19 @@ 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 task as fixed (" + maproulette_id + ")")
|
||||
if (maproulette_id !== undefined) {
|
||||
if (state.featureSwitchIsTesting.data){
|
||||
console.log("Not marking maproulette task " + maproulette_id + " as fixed, because we are in testing mode")
|
||||
} else {
|
||||
console.log("Marking maproulette task as fixed")
|
||||
state.maprouletteConnection.closeTask(Number(maproulette_id));
|
||||
originalFeatureTags.data["mr_taskStatus"] = "Fixed";
|
||||
originalFeatureTags.ping();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let preciseInputOption = args["location_picker"]
|
||||
|
|
|
@ -42,6 +42,13 @@ export default class TagApplyButton implements AutoAction {
|
|||
|
||||
public static generateTagsToApply(spec: string, tagSource: Store<any>): 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 = spec.split(";").map(spec => {
|
||||
const kv = spec.split("=").map(s => s.trim());
|
||||
if (kv.length != 2) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Store, UIEventSource} from "../Logic/UIEventSource";
|
||||
import {Store, Stores, UIEventSource} from "../Logic/UIEventSource";
|
||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||
import LiveQueryHandler from "../Logic/Web/LiveQueryHandler";
|
||||
import {ImageCarousel} from "./Image/ImageCarousel";
|
||||
|
@ -57,8 +57,9 @@ import {SaveButton} from "./Popup/SaveButton";
|
|||
import {MapillaryLink} from "./BigComponents/MapillaryLink";
|
||||
import {CheckBox} from "./Input/Checkboxes";
|
||||
import Slider from "./Input/Slider";
|
||||
import {OsmFeature} from "../Models/OsmFeature";
|
||||
import List from "./Base/List";
|
||||
import StatisticsPanel from "./BigComponents/StatisticsPanel";
|
||||
import { OsmFeature } from "../Models/OsmFeature";
|
||||
|
||||
export interface SpecialVisualization {
|
||||
funcName: string,
|
||||
|
@ -1100,6 +1101,39 @@ export default class SpecialVisualizations {
|
|||
},
|
||||
new NearbyImageVis(),
|
||||
new MapillaryLinkVis(),
|
||||
{
|
||||
funcName: "maproulette_task",
|
||||
args: [],
|
||||
constr(state, tagSource, argument, guistate) {
|
||||
let parentId = tagSource.data.mr_challengeId;
|
||||
let challenge = Stores.FromPromise(Utils.downloadJsonCached(`https://maproulette.org/api/v2/challenge/${parentId}`,24*60*60*1000));
|
||||
|
||||
let details = new VariableUiElement( challenge.map(challenge => {
|
||||
let listItems: BaseUIElement[] = [];
|
||||
let title: BaseUIElement;
|
||||
|
||||
if (challenge?.name) {
|
||||
title = new Title(challenge.name);
|
||||
}
|
||||
|
||||
if (challenge?.description) {
|
||||
listItems.push(new FixedUiElement(challenge.description));
|
||||
}
|
||||
|
||||
if (challenge?.instruction) {
|
||||
listItems.push(new FixedUiElement(challenge.instruction));
|
||||
}
|
||||
|
||||
if(listItems.length === 0) {
|
||||
return undefined;
|
||||
} else {
|
||||
return [title, new List(listItems)];
|
||||
}
|
||||
}))
|
||||
return details;
|
||||
},
|
||||
docs: "Show details of a MapRoulette task"
|
||||
},
|
||||
{
|
||||
funcName: "statistics",
|
||||
docs: "Show general statistics about the elements currently in view. Intended to use on the `current_view`-layer",
|
||||
|
|
12
assets/layers/maproulette/license_info.json
Normal file
12
assets/layers/maproulette/license_info.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
[
|
||||
{
|
||||
"path": "logomark.svg",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
"MapRoulette"
|
||||
],
|
||||
"sources": [
|
||||
"https://github.com/maproulette/docs/blob/master/src/assets/svg/logo.svg"
|
||||
]
|
||||
}
|
||||
]
|
77
assets/layers/maproulette/logomark.svg
Normal file
77
assets/layers/maproulette/logomark.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.5 KiB |
225
assets/layers/maproulette/maproulette.json
Normal file
225
assets/layers/maproulette/maproulette.json
Normal file
|
@ -0,0 +1,225 @@
|
|||
{
|
||||
"id": "maproulette",
|
||||
"source": {
|
||||
"geoJson": "https://maproulette.org/api/v2/tasks/box/{x_min}/{y_min}/{x_max}/{y_max}",
|
||||
"geoJsonZoomLevel": 16,
|
||||
"osmTags": "id~*"
|
||||
},
|
||||
"mapRendering": [
|
||||
{
|
||||
"location": [
|
||||
"point",
|
||||
"centroid"
|
||||
],
|
||||
"icon": {
|
||||
"render": "./assets/layers/maproulette/logomark.svg",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "status=0",
|
||||
"then": "pin:#959DFF"
|
||||
},
|
||||
{
|
||||
"if": "status=1",
|
||||
"then": "pin:#65D2DA"
|
||||
},
|
||||
{
|
||||
"if": "status=2",
|
||||
"then": "pin:#F7BB59"
|
||||
},
|
||||
{
|
||||
"if": "status=3",
|
||||
"then": "pin:#F7BB59"
|
||||
},
|
||||
{
|
||||
"if": "status=4",
|
||||
"then": "pin:#737373"
|
||||
},
|
||||
{
|
||||
"if": "status=5",
|
||||
"then": "pin:#CCB186"
|
||||
},
|
||||
{
|
||||
"if": "status=6",
|
||||
"then": "pin:#FF5E63"
|
||||
},
|
||||
{
|
||||
"if": "status=9",
|
||||
"then": "pin:#FF349C"
|
||||
}
|
||||
]
|
||||
},
|
||||
"iconSize": "40,40,center"
|
||||
}
|
||||
],
|
||||
"tagRenderings": [
|
||||
{
|
||||
"id": "status",
|
||||
"render": "Current status: {status}",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "status=0",
|
||||
"then": {
|
||||
"en": "Task is created"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "status=1",
|
||||
"then": {
|
||||
"en": "Task is fixed"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "status=2",
|
||||
"then": {
|
||||
"en": "Task is a false positive"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "status=3",
|
||||
"then": {
|
||||
"en": "Task is skipped"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "status=4",
|
||||
"then": {
|
||||
"en": "Task is deleted"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "status=5",
|
||||
"then": {
|
||||
"en": "Task is already fixed"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "status=6",
|
||||
"then": {
|
||||
"en": "Task is marked as too hard"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "status=9",
|
||||
"then": {
|
||||
"en": "Task is disabled"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "blurb",
|
||||
"condition": "blurb~*",
|
||||
"render": "{blurb}"
|
||||
}
|
||||
],
|
||||
"description": {
|
||||
"en": "Layer showing all tasks in MapRoulette"
|
||||
},
|
||||
"minzoom": 15,
|
||||
"name": {
|
||||
"en": "MapRoulette Tasks"
|
||||
},
|
||||
"title": {
|
||||
"render": {
|
||||
"en": "MapRoulette Item: {parentName}"
|
||||
}
|
||||
},
|
||||
"titleIcons": [
|
||||
{
|
||||
"id": "maproulette",
|
||||
"render": "<a href='https://maproulette.org/challenge/{parentId}/task/{id}' target='_blank'><img src='./assets/layers/maproulette/logomark.svg'/></a>"
|
||||
}
|
||||
],
|
||||
"filter": [
|
||||
{
|
||||
"id": "status",
|
||||
"options": [
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks with all statuses"
|
||||
}
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are created"
|
||||
},
|
||||
"osmTags": "status=0"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are fixed"
|
||||
},
|
||||
"osmTags": "status=1"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are false positives"
|
||||
},
|
||||
"osmTags": "status=2"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are skipped"
|
||||
},
|
||||
"osmTags": "status=3"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are deleted"
|
||||
},
|
||||
"osmTags": "status=4"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are already fixed"
|
||||
},
|
||||
"osmTags": "status=5"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are marked as too hard"
|
||||
},
|
||||
"osmTags": "status=6"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are disabled"
|
||||
},
|
||||
"osmTags": "status=9"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "parent-name",
|
||||
"options": [
|
||||
{
|
||||
"osmTags": "parentName~i~.*{search}.*",
|
||||
"fields": [
|
||||
{
|
||||
"name": "search"
|
||||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "Challenge name contains {search}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "parent-id",
|
||||
"options": [
|
||||
{
|
||||
"osmTags": "parentId={search}",
|
||||
"fields": [
|
||||
{
|
||||
"name": "search"
|
||||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "Challenge ID matches {search}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
194
assets/layers/maproulette_challenge/maproulette_challenge.json
Normal file
194
assets/layers/maproulette_challenge/maproulette_challenge.json
Normal file
|
@ -0,0 +1,194 @@
|
|||
{
|
||||
"id": "maproulette_challenge",
|
||||
"name": null,
|
||||
"description": {
|
||||
"en": "Layer showing tasks of a MapRoulette challenge"
|
||||
},
|
||||
"source": {
|
||||
"osmTags": "id~*",
|
||||
"geoJson": "https://maproulette.org/api/v2/challenge/view/27971",
|
||||
"isOsmCache": false
|
||||
},
|
||||
"title": {
|
||||
"render": {
|
||||
"en": "Item in MapRoulette"
|
||||
}
|
||||
},
|
||||
"titleIcons": [
|
||||
{
|
||||
"id": "maproulette",
|
||||
"render": "<a href='https://maproulette.org/challenge/{mr_challengeId}/task/{mr_taskId}' target='_blank'><img src='./assets/layers/maproulette/logomark.svg'/></a>"
|
||||
}
|
||||
],
|
||||
"mapRendering": [
|
||||
{
|
||||
"location": [
|
||||
"point",
|
||||
"centroid"
|
||||
],
|
||||
"icon": {
|
||||
"render": "./assets/layers/maproulette/logomark.svg",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "mr_taskStatus=Created",
|
||||
"then": "pin:#959DFF"
|
||||
},
|
||||
{
|
||||
"if": "mr_taskStatus=Fixed",
|
||||
"then": "pin:#65D2DA"
|
||||
},
|
||||
{
|
||||
"if": "mr_taskStatus=False positive",
|
||||
"then": "pin:#F7BB59"
|
||||
},
|
||||
{
|
||||
"if": "mr_taskStatus=Skipped",
|
||||
"then": "pin:#F7BB59"
|
||||
},
|
||||
{
|
||||
"if": "mr_taskStatus=Deleted",
|
||||
"then": "pin:#737373"
|
||||
},
|
||||
{
|
||||
"if": "mr_taskStatus=Already fixed",
|
||||
"then": "pin:#CCB186"
|
||||
},
|
||||
{
|
||||
"if": "mr_taskStatus=Too hard",
|
||||
"then": "pin:#FF5E63"
|
||||
},
|
||||
{
|
||||
"if": "mr_taskStatus=Disabled",
|
||||
"then": "pin:#FF349C"
|
||||
}
|
||||
]
|
||||
},
|
||||
"iconSize": "40,40,bottom"
|
||||
}
|
||||
],
|
||||
"tagRenderings": [
|
||||
{
|
||||
"id": "details",
|
||||
"render": "{maproulette_task()}"
|
||||
},
|
||||
{
|
||||
"id": "status",
|
||||
"render": "Current status: {status}",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "mr_taskStatus=Created",
|
||||
"then": {
|
||||
"en": "Task is created"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "mr_taskStatus=Fixed",
|
||||
"then": {
|
||||
"en": "Task is fixed"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "mr_taskStatus=False positive",
|
||||
"then": {
|
||||
"en": "Task is a false positive"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "mr_taskStatus=Skipped",
|
||||
"then": {
|
||||
"en": "Task is skipped"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "mr_taskStatus=Deleted",
|
||||
"then": {
|
||||
"en": "Task is deleted"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "mr_taskStatus=Already fixed",
|
||||
"then": {
|
||||
"en": "Task is already fixed"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "mr_taskStatus=Too hard",
|
||||
"then": {
|
||||
"en": "Task is marked as too hard"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "mr_taskStatus=Disabled",
|
||||
"then": {
|
||||
"en": "Task is disabled"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "blurb",
|
||||
"condition": "blurb~*",
|
||||
"render": "{blurb}"
|
||||
}
|
||||
],
|
||||
"filter": [
|
||||
{
|
||||
"id": "status",
|
||||
"options": [
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks with all statuses"
|
||||
}
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are created"
|
||||
},
|
||||
"osmTags": "mr_taskStatus=Created"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are fixed"
|
||||
},
|
||||
"osmTags": "mr_taskStatus=Fixed"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are false positives"
|
||||
},
|
||||
"osmTags": "mr_taskStatus=False positive"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are skipped"
|
||||
},
|
||||
"osmTags": "mr_taskStatus=Skipped"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are deleted"
|
||||
},
|
||||
"osmTags": "mr_taskStatus=Deleted"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are already fixed"
|
||||
},
|
||||
"osmTags": "mr_taskStatus=Already fixed"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are marked as too hard"
|
||||
},
|
||||
"osmTags": "mr_taskStatus=Too hard"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Show tasks that are disabled"
|
||||
},
|
||||
"osmTags": "mr_taskStatus=Disabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -303,7 +303,7 @@
|
|||
},
|
||||
"allowMove": {
|
||||
"enableRelocation": false,
|
||||
"enableImproveAccuraccy": true
|
||||
"enableImproveAccuracy": true
|
||||
},
|
||||
"mapRendering": [
|
||||
{
|
||||
|
|
|
@ -246,6 +246,10 @@
|
|||
"if": "theme=mapcomplete-changes",
|
||||
"then": "./assets/svg/logo.svg"
|
||||
},
|
||||
{
|
||||
"if": "theme=maproulette",
|
||||
"then": "./assets/layers/maproulette/logomark.svg"
|
||||
},
|
||||
{
|
||||
"if": "theme=maps",
|
||||
"then": "./assets/themes/maps/logo.svg"
|
||||
|
|
19
assets/themes/maproulette/maproulette.json
Normal file
19
assets/themes/maproulette/maproulette.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"id": "maproulette",
|
||||
"title": {
|
||||
"en": "MapRoulette Tasks"
|
||||
},
|
||||
"description": {
|
||||
"en": "Theme showing MapRoulette tasks, allowing you to search, filter and fix them."
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"hideFromOverview": true,
|
||||
"icon": "./assets/layers/maproulette/logomark.svg",
|
||||
"maintainer": "",
|
||||
"startLat": 0,
|
||||
"startLon": 0,
|
||||
"startZoom": 4,
|
||||
"layers": [
|
||||
"maproulette"
|
||||
]
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
"widenFactor": 2,
|
||||
"hideFromOverview": false,
|
||||
"layers": [
|
||||
{
|
||||
{
|
||||
"builtin": "indoors",
|
||||
"override": {
|
||||
"minzoom": 19,
|
||||
|
@ -366,6 +366,53 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"builtin": "maproulette_challenge",
|
||||
"override": {
|
||||
"source": {
|
||||
"geoJson": "https://maproulette.org/api/v2/challenge/view/28012"
|
||||
},
|
||||
"calculatedTags": [
|
||||
"_closest_osm_hotel=feat.closest('hotel')?.properties?.id",
|
||||
"_closest_osm_hotel_distance=feat.distanceTo(feat.properties._closest_osm_hotel)",
|
||||
"_has_closeby_feature=Number(feat.properties._closest_osm_hotel_distance) < 50 ? 'yes' : 'no'"
|
||||
],
|
||||
"+tagRenderings": [
|
||||
{
|
||||
"id": "import-button",
|
||||
"condition": "_has_closeby_feature=no",
|
||||
"render": {
|
||||
"special": {
|
||||
"type": "import_button",
|
||||
"targetLayer": "hotel",
|
||||
"tags": "tags",
|
||||
"text": {
|
||||
"en": "Import"
|
||||
},
|
||||
"icon": "./assets/svg/addSmall.svg",
|
||||
"location_picker": "photo",
|
||||
"maproulette_id": "mr_taskId"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tag-apply-button",
|
||||
"condition": "_has_closeby_feature=yes",
|
||||
"render": {
|
||||
"special": {
|
||||
"type": "tag_apply",
|
||||
"tags_to_apply": "$tags",
|
||||
"message": {
|
||||
"en": "Add all the suggested tags"
|
||||
},
|
||||
"image": "./assets/svg/addSmall.svg",
|
||||
"id_of_object_to_apply_this_one": "_closest_osm_hotel"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"overrideAll": {
|
||||
|
|
|
@ -51,6 +51,22 @@
|
|||
"tagRenderings": [
|
||||
"all_tags"
|
||||
]
|
||||
},
|
||||
{
|
||||
"builtin": "maproulette_challenge",
|
||||
"override": {
|
||||
"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'"
|
||||
],
|
||||
"tagRenderings+": [
|
||||
{
|
||||
"id": "import",
|
||||
"render": "{import_button(street_lamps,tags,Import,./assets/svg/addSmall.svg,,,,photo,mr_taskId)}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"hideFromOverview": true
|
||||
|
|
|
@ -4464,6 +4464,159 @@
|
|||
"render": "Map"
|
||||
}
|
||||
},
|
||||
"maproulette": {
|
||||
"description": "Layer showing all tasks in MapRoulette",
|
||||
"filter": {
|
||||
"0": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Show tasks with all statuses"
|
||||
},
|
||||
"1": {
|
||||
"question": "Show tasks that are created"
|
||||
},
|
||||
"2": {
|
||||
"question": "Show tasks that are fixed"
|
||||
},
|
||||
"3": {
|
||||
"question": "Show tasks that are false positives"
|
||||
},
|
||||
"4": {
|
||||
"question": "Show tasks that are skipped"
|
||||
},
|
||||
"5": {
|
||||
"question": "Show tasks that are deleted"
|
||||
},
|
||||
"6": {
|
||||
"question": "Show tasks that are already fixed"
|
||||
},
|
||||
"7": {
|
||||
"question": "Show tasks that are marked as too hard"
|
||||
},
|
||||
"8": {
|
||||
"question": "Show tasks that are disabled"
|
||||
}
|
||||
}
|
||||
},
|
||||
"1": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Challenge name contains {search}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Challenge ID matches {search}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "MapRoulette Tasks",
|
||||
"tagRenderings": {
|
||||
"status": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Task is created"
|
||||
},
|
||||
"1": {
|
||||
"then": "Task is fixed"
|
||||
},
|
||||
"2": {
|
||||
"then": "Task is a false positive"
|
||||
},
|
||||
"3": {
|
||||
"then": "Task is skipped"
|
||||
},
|
||||
"4": {
|
||||
"then": "Task is deleted"
|
||||
},
|
||||
"5": {
|
||||
"then": "Task is already fixed"
|
||||
},
|
||||
"6": {
|
||||
"then": "Task is marked as too hard"
|
||||
},
|
||||
"7": {
|
||||
"then": "Task is disabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "MapRoulette Item: {parentName}"
|
||||
}
|
||||
},
|
||||
"maproulette_challenge": {
|
||||
"description": "Layer showing tasks of a MapRoulette challenge",
|
||||
"filter": {
|
||||
"0": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Show tasks with all statuses"
|
||||
},
|
||||
"1": {
|
||||
"question": "Show tasks that are created"
|
||||
},
|
||||
"2": {
|
||||
"question": "Show tasks that are fixed"
|
||||
},
|
||||
"3": {
|
||||
"question": "Show tasks that are false positives"
|
||||
},
|
||||
"4": {
|
||||
"question": "Show tasks that are skipped"
|
||||
},
|
||||
"5": {
|
||||
"question": "Show tasks that are deleted"
|
||||
},
|
||||
"6": {
|
||||
"question": "Show tasks that are already fixed"
|
||||
},
|
||||
"7": {
|
||||
"question": "Show tasks that are marked as too hard"
|
||||
},
|
||||
"8": {
|
||||
"question": "Show tasks that are disabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tagRenderings": {
|
||||
"status": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Task is created"
|
||||
},
|
||||
"1": {
|
||||
"then": "Task is fixed"
|
||||
},
|
||||
"2": {
|
||||
"then": "Task is a false positive"
|
||||
},
|
||||
"3": {
|
||||
"then": "Task is skipped"
|
||||
},
|
||||
"4": {
|
||||
"then": "Task is deleted"
|
||||
},
|
||||
"5": {
|
||||
"then": "Task is already fixed"
|
||||
},
|
||||
"6": {
|
||||
"then": "Task is marked as too hard"
|
||||
},
|
||||
"7": {
|
||||
"then": "Task is disabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Item in MapRoulette"
|
||||
}
|
||||
},
|
||||
"maxspeed": {
|
||||
"description": "Shows the allowed speed for every road",
|
||||
"name": "Maxspeed",
|
||||
|
|
|
@ -725,6 +725,10 @@
|
|||
"shortDescription": "Shows changes made by MapComplete",
|
||||
"title": "Changes made with MapComplete"
|
||||
},
|
||||
"maproulette": {
|
||||
"description": "Theme showing MapRoulette tasks, allowing you to search, filter and fix them.",
|
||||
"title": "MapRoulette Tasks"
|
||||
},
|
||||
"maps": {
|
||||
"description": "On this map you can find all maps OpenStreetMap knows - typically a big map on an information board showing the area, city or region, e.g. a tourist map on the back of a billboard, a map of a nature reserve, a map of cycling networks in the region, ...) <br/><br/>If a map is missing, you can easily map this map on OpenStreetMap.",
|
||||
"shortDescription": "This theme shows all (touristic) maps that OpenStreetMap knows of",
|
||||
|
|
23
package-lock.json
generated
23
package-lock.json
generated
|
@ -18,6 +18,7 @@
|
|||
"@turf/length": "^6.5.0",
|
||||
"@turf/turf": "^6.5.0",
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/geojson": "^7946.0.10",
|
||||
"@types/jquery": "^3.5.5",
|
||||
"@types/leaflet-markercluster": "^1.0.3",
|
||||
"@types/leaflet-providers": "^1.2.0",
|
||||
|
@ -3224,9 +3225,9 @@
|
|||
"integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw=="
|
||||
},
|
||||
"node_modules/@types/geojson": {
|
||||
"version": "7946.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz",
|
||||
"integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA=="
|
||||
"version": "7946.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
|
||||
"integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA=="
|
||||
},
|
||||
"node_modules/@types/jquery": {
|
||||
"version": "3.5.5",
|
||||
|
@ -7176,6 +7177,11 @@
|
|||
"rbush": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/geojson-rbush/node_modules/@types/geojson": {
|
||||
"version": "7946.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz",
|
||||
"integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA=="
|
||||
},
|
||||
"node_modules/geojson-rbush/node_modules/quickselect": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
|
||||
|
@ -19255,9 +19261,9 @@
|
|||
"integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw=="
|
||||
},
|
||||
"@types/geojson": {
|
||||
"version": "7946.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz",
|
||||
"integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA=="
|
||||
"version": "7946.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
|
||||
"integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA=="
|
||||
},
|
||||
"@types/jquery": {
|
||||
"version": "3.5.5",
|
||||
|
@ -22428,6 +22434,11 @@
|
|||
"rbush": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/geojson": {
|
||||
"version": "7946.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz",
|
||||
"integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA=="
|
||||
},
|
||||
"quickselect": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
"@turf/length": "^6.5.0",
|
||||
"@turf/turf": "^6.5.0",
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/geojson": "^7946.0.10",
|
||||
"@types/jquery": "^3.5.5",
|
||||
"@types/leaflet-markercluster": "^1.0.3",
|
||||
"@types/leaflet-providers": "^1.2.0",
|
||||
|
|
102
scripts/onwheels/constants.ts
Normal file
102
scripts/onwheels/constants.ts
Normal file
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* Class containing all constants and tables used in the script
|
||||
*
|
||||
* @class Constants
|
||||
*/
|
||||
export default class Constants {
|
||||
/**
|
||||
* Table used to determine tags for the category
|
||||
*
|
||||
* Keys are the original category names,
|
||||
* values are an object containing the tags
|
||||
*/
|
||||
public static categories = {
|
||||
restaurant: {
|
||||
amenity: "restaurant",
|
||||
},
|
||||
parking: {
|
||||
amenity: "parking",
|
||||
},
|
||||
hotel: {
|
||||
tourism: "hotel",
|
||||
},
|
||||
wc: {
|
||||
amenity: "toilets",
|
||||
},
|
||||
winkel: {
|
||||
shop: "yes",
|
||||
},
|
||||
apotheek: {
|
||||
amenity: "pharmacy",
|
||||
healthcare: "pharmacy",
|
||||
},
|
||||
ziekenhuis: {
|
||||
amenity: "hospital",
|
||||
healthcare: "hospital",
|
||||
},
|
||||
bezienswaardigheid: {
|
||||
tourism: "attraction",
|
||||
},
|
||||
ontspanning: {
|
||||
fixme: "Needs proper tags",
|
||||
},
|
||||
cafe: {
|
||||
amenity: "cafe",
|
||||
},
|
||||
dienst: {
|
||||
fixme: "Needs proper tags",
|
||||
},
|
||||
bank: {
|
||||
amenity: "bank",
|
||||
},
|
||||
gas: {
|
||||
amenity: "fuel",
|
||||
},
|
||||
medical: {
|
||||
fixme: "Needs proper tags",
|
||||
},
|
||||
obstacle: {
|
||||
fixme: "Needs proper tags",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Table used to rename original Onwheels properties to their corresponding OSM properties
|
||||
*
|
||||
* Keys are the original Onwheels properties, values are the corresponding OSM properties
|
||||
*/
|
||||
public static names = {
|
||||
ID: "id",
|
||||
Naam: "name",
|
||||
Straat: "addr:street",
|
||||
Nummer: "addr:housenumber",
|
||||
Postcode: "addr:postcode",
|
||||
Plaats: "addr:city",
|
||||
Website: "website",
|
||||
Email: "email",
|
||||
"Aantal aangepaste parkeerplaatsen": "capacity:disabled",
|
||||
"Aantal treden": "step_count",
|
||||
"Hellend vlak aanwezig": "ramp",
|
||||
"Baby verzorging aanwezig": "changing_table",
|
||||
"Totale hoogte van de treden": "kerb:height",
|
||||
"Deurbreedte": "door:width",
|
||||
};
|
||||
|
||||
/**
|
||||
* In some cases types might need to be converted as well
|
||||
*
|
||||
* Keys are the OSM properties, values are the wanted type
|
||||
*/
|
||||
public static types = {
|
||||
"Hellend vlak aanwezig": "boolean",
|
||||
"Baby verzorging aanwezig": "boolean",
|
||||
};
|
||||
|
||||
/**
|
||||
* Some tags also need to have units added
|
||||
*/
|
||||
public static units = {
|
||||
"Totale hoogte van de treden": "cm",
|
||||
"Deurbreedte": "cm",
|
||||
};
|
||||
}
|
234
scripts/onwheels/convertData.ts
Normal file
234
scripts/onwheels/convertData.ts
Normal file
|
@ -0,0 +1,234 @@
|
|||
import { parse } from "csv-parse/sync";
|
||||
import { readFileSync, writeFileSync } from "fs";
|
||||
import { Feature, FeatureCollection, GeoJsonProperties } from "geojson";
|
||||
import Constants from "./constants";
|
||||
|
||||
/**
|
||||
* Function to determine the tags for a category
|
||||
*
|
||||
* @param category The category of the item
|
||||
* @returns List of tags for the category
|
||||
*/
|
||||
function categoryTags(category: string): GeoJsonProperties {
|
||||
const tags = {
|
||||
tags: Object.keys(Constants.categories[category]).map((tag) => {
|
||||
return `${tag}=${Constants.categories[category][tag]}`;
|
||||
}),
|
||||
};
|
||||
if (!tags) {
|
||||
throw `Unknown category: ${category}`;
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename tags to match the OSM standard
|
||||
*
|
||||
* @param item The item to convert
|
||||
* @returns GeoJsonProperties for the item
|
||||
*/
|
||||
function renameTags(item): GeoJsonProperties {
|
||||
const properties: GeoJsonProperties = {};
|
||||
properties.tags = [];
|
||||
// Loop through the original item tags
|
||||
for (const key in item) {
|
||||
// Check if we need it and it's not a null value
|
||||
if (Constants.names[key] && item[key]) {
|
||||
// Name and id tags need to be in the properties
|
||||
if (Constants.names[key] == "name" || Constants.names[key] == "id") {
|
||||
properties[Constants.names[key]] = item[key];
|
||||
}
|
||||
// Other tags need to be in the tags variable
|
||||
if (Constants.names[key] !== "id") {
|
||||
// Website needs to have at least any = encoded
|
||||
if(Constants.names[key] == "website") {
|
||||
let website = item[key];
|
||||
// Encode URL
|
||||
website = website.replace("=", "%3D");
|
||||
item[key] = website;
|
||||
}
|
||||
properties.tags.push(Constants.names[key] + "=" + item[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert types to match the OSM standard
|
||||
*
|
||||
* @param properties The properties to convert
|
||||
* @returns The converted properties
|
||||
*/
|
||||
function convertTypes(properties: GeoJsonProperties): GeoJsonProperties {
|
||||
// Split the tags into a list
|
||||
let tags = properties.tags.split(";");
|
||||
|
||||
for (const tag in tags) {
|
||||
// Split the tag into key and value
|
||||
const key = tags[tag].split("=")[0];
|
||||
const value = tags[tag].split("=")[1];
|
||||
const originalKey = Object.keys(Constants.names).find(
|
||||
(tag) => Constants.names[tag] === key
|
||||
);
|
||||
|
||||
if (Constants.types[originalKey]) {
|
||||
// We need to convert the value to the correct type
|
||||
let newValue;
|
||||
switch (Constants.types[originalKey]) {
|
||||
case "boolean":
|
||||
newValue = value === "1" ? "yes" : "no";
|
||||
break;
|
||||
default:
|
||||
newValue = value;
|
||||
break;
|
||||
}
|
||||
tags[tag] = `${key}=${newValue}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Rejoin the tags
|
||||
properties.tags = tags.join(";");
|
||||
|
||||
// Return the properties
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to add units to the properties if necessary
|
||||
*
|
||||
* @param properties The properties to add units to
|
||||
* @returns The properties with units added
|
||||
*/
|
||||
function addUnits(properties: GeoJsonProperties): GeoJsonProperties {
|
||||
// Split the tags into a list
|
||||
let tags = properties.tags.split(";");
|
||||
|
||||
for (const tag in tags) {
|
||||
const key = tags[tag].split("=")[0];
|
||||
const value = tags[tag].split("=")[1];
|
||||
const originalKey = Object.keys(Constants.names).find(
|
||||
(tag) => Constants.names[tag] === key
|
||||
);
|
||||
|
||||
// Check if the property needs units, and doesn't already have them
|
||||
if (Constants.units[originalKey] && value.match(/.*([A-z]).*/gi) === null) {
|
||||
tags[tag] = `${key}=${value} ${Constants.units[originalKey]}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Rejoin the tags
|
||||
properties.tags = tags.join(";");
|
||||
|
||||
// Return the properties
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that adds Maproulette instructions and blurb to each item
|
||||
*
|
||||
* @param properties The properties to add Maproulette tags to
|
||||
* @param item The original CSV item
|
||||
*/
|
||||
function addMaprouletteTags(properties: GeoJsonProperties, item: any): GeoJsonProperties {
|
||||
properties[
|
||||
"blurb"
|
||||
] = `This is feature out of the ${item["Categorie"]} category.
|
||||
It may match another OSM item, if so, you can add any missing tags to it.
|
||||
If it doesn't match any other OSM item, you can create a new one.
|
||||
Here is a list of tags that can be added:
|
||||
${properties["tags"].split(";").join("\n")}
|
||||
You can also easily import this item using MapComplete: https://mapcomplete.osm.be/onwheels.html#${properties["id"]}`;
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to convert original CSV into GeoJSON
|
||||
*
|
||||
* @param args List of arguments [input.csv]
|
||||
*/
|
||||
function main(args: string[]): void {
|
||||
const csvOptions = {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
trim: true,
|
||||
};
|
||||
const file = args[0];
|
||||
const output = args[1];
|
||||
|
||||
// Create an empty list to store the converted features
|
||||
var items: Feature[] = [];
|
||||
|
||||
// Read CSV file
|
||||
const csv: Record<any, string>[] = parse(readFileSync(file), csvOptions);
|
||||
|
||||
// Loop through all the entries
|
||||
for (var i = 0; i < csv.length; i++) {
|
||||
const item = csv[i];
|
||||
|
||||
// Determine coordinates
|
||||
const lat = Number(item["Latitude"]);
|
||||
const lon = Number(item["Longitude"]);
|
||||
|
||||
// Check if coordinates are valid
|
||||
if (isNaN(lat) || isNaN(lon)) {
|
||||
throw `Not a valid lat or lon for entry ${i}: ${JSON.stringify(item)}`;
|
||||
}
|
||||
|
||||
// Create a new collection to store the converted properties
|
||||
var properties: GeoJsonProperties = {};
|
||||
|
||||
// Add standard tags for category
|
||||
const category = item["Categorie"];
|
||||
const tagsCategory = categoryTags(category);
|
||||
|
||||
// Add the rest of the needed tags
|
||||
properties = { ...properties, ...renameTags(item) };
|
||||
|
||||
// Merge them together
|
||||
properties.tags = [...tagsCategory.tags, ...properties.tags];
|
||||
properties.tags = properties.tags.join(";");
|
||||
|
||||
// Convert types
|
||||
properties = convertTypes(properties);
|
||||
|
||||
// Add units if necessary
|
||||
properties = addUnits(properties);
|
||||
|
||||
// Add Maproulette tags
|
||||
properties = addMaprouletteTags(properties, item);
|
||||
|
||||
// Create the new feature
|
||||
const feature: Feature = {
|
||||
type: "Feature",
|
||||
id: item["ID"],
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [lon, lat],
|
||||
},
|
||||
properties,
|
||||
};
|
||||
|
||||
// Push it to the list we created earlier
|
||||
items.push(feature);
|
||||
}
|
||||
|
||||
// Make a FeatureCollection out of it
|
||||
const featureCollection: FeatureCollection = {
|
||||
type: "FeatureCollection",
|
||||
features: items,
|
||||
};
|
||||
|
||||
// Write the data to a file or output to the console
|
||||
if (output) {
|
||||
writeFileSync(
|
||||
`${output}.geojson`,
|
||||
JSON.stringify(featureCollection, null, 2)
|
||||
);
|
||||
} else {
|
||||
console.log(JSON.stringify(featureCollection));
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the main function, with the stripped arguments
|
||||
main(process.argv.slice(2));
|
|
@ -123,7 +123,7 @@ describe('RewriteSpecial', function () {
|
|||
const r = new RewriteSpecial().convert(tr, "test").result
|
||||
expect(r).to.deep.eq({
|
||||
"id": "uk_addresses_import_button",
|
||||
"render": {'*': "{import_button(address,urpn_count=$urpn_count;ref:GB:uprn=$ref:GB:uprn$,Add this address,./assets/themes/uk_addresses/housenumber_add.svg,,,,none)}"}
|
||||
"render": {'*': "{import_button(address,urpn_count=$urpn_count;ref:GB:uprn=$ref:GB:uprn$,Add this address,./assets/themes/uk_addresses/housenumber_add.svg,,,,none,)}"}
|
||||
})
|
||||
})
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue