diff --git a/assets/layers/hotel/hotel.json b/assets/layers/hotel/hotel.json
new file mode 100644
index 000000000..8166b478b
--- /dev/null
+++ b/assets/layers/hotel/hotel.json
@@ -0,0 +1,53 @@
+{
+ "id": "hotel",
+ "name": {
+ "en": "Hotels",
+ "nl": "Hotels"
+ },
+ "description": {
+ "en": "Layer showing all hotels",
+ "nl": "Laag die alle hotels toont"
+ },
+ "source": {
+ "osmTags": "tourism=hotel"
+ },
+ "mapRendering": [
+ {
+ "location": [
+ "point",
+ "centroid"
+ ],
+ "icon": "pin:white"
+ }
+ ],
+ "tagRenderings": [
+ "images",
+ "reviews",
+ {
+ "id": "name",
+ "freeform": {
+ "key": "name",
+ "placeholder": {
+ "en": "Name of the hotel",
+ "nl": "Naam van het hotel"
+ }
+ },
+ "question": {
+ "en": "What is the name of this hotel?",
+ "nl": "Wat is de naam van dit hotel?"
+ },
+ "render": {
+ "en": "This hotel is called {name}",
+ "nl": "Dit hotel heet {name}"
+ }
+ },
+ "phone",
+ "email",
+ "website",
+ "wheelchair-access"
+ ],
+ "allowMove": {
+ "enableImproveAccuracy": true,
+ "enableRelocation": true
+ }
+}
\ No newline at end of file
diff --git a/assets/layers/hotel/hotel.svg b/assets/layers/hotel/hotel.svg
new file mode 100644
index 000000000..879d08301
--- /dev/null
+++ b/assets/layers/hotel/hotel.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/layers/hotel/license_info.json b/assets/layers/hotel/license_info.json
new file mode 100644
index 000000000..38f47cac6
--- /dev/null
+++ b/assets/layers/hotel/license_info.json
@@ -0,0 +1,15 @@
+[
+ {
+ "path": "hotel.svg",
+ "license": "",
+ "authors": [
+ "Andy Allan",
+ "Michael Glanznig",
+ "Adamant36",
+ "Paul Dicker"
+ ],
+ "sources": [
+ "https://github.com/gravitystorm/openstreetmap-carto/blob/master/symbols/tourism/hotel.svg"
+ ]
+ }
+]
\ No newline at end of file
diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json
index e8c3c1536..ca184a473 100644
--- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json
+++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json
@@ -1,13 +1,19 @@
{
"id": "mapcomplete-changes",
"title": {
- "en": "Changes made with MapComplete"
+ "en": "Changes made with MapComplete",
+ "de": "Mit MapComplete vorgenommene Änderungen",
+ "nl": "Wijzigingen gemaakt met MapComplete"
},
"shortDescription": {
- "en": "Shows changes made by MapComplete"
+ "en": "Shows changes made by MapComplete",
+ "de": "Zeigt die mit MapComplete vorgenommenen Änderungen",
+ "nl": "Toont wijzigingen gemaakt met MapComplete"
},
"description": {
- "en": "This maps shows all the changes made with MapComplete"
+ "en": "This maps shows all the changes made with MapComplete",
+ "de": "Diese Karte zeigt alle mit MapComplete vorgenommenen Änderungen",
+ "nl": "Deze kaart toont alle wijzigingen die met MapComplete werden gemaakt"
},
"maintainer": "",
"icon": "./assets/svg/logo.svg",
@@ -22,7 +28,8 @@
{
"id": "mapcomplete-changes",
"name": {
- "en": "Changeset centers"
+ "en": "Changeset centers",
+ "de": "Zentrum der Änderungssätze"
},
"minzoom": 0,
"source": {
@@ -36,35 +43,47 @@
],
"title": {
"render": {
- "en": "Changeset for {theme}"
+ "en": "Changeset for {theme}",
+ "de": "Änderungssatz für {theme}",
+ "nl": "Wijzigingset voor {theme}"
}
},
"description": {
- "en": "Shows all MapComplete changes"
+ "en": "Shows all MapComplete changes",
+ "de": "Zeigt alle MapComplete Änderungen",
+ "nl": "Toont alle wijzigingen met MapComplete"
},
"tagRenderings": [
{
"id": "render_id",
"render": {
- "en": "Changeset {id}"
+ "en": "Changeset {id}",
+ "de": "Änderungssatz {id}",
+ "nl": "Wijzigingset {id}"
}
},
{
"id": "contributor",
"render": {
- "en": "Change made by {_last_edit:contributor}"
+ "en": "Change made by {_last_edit:contributor}",
+ "de": "Geändert von {_last_edit:contributor}",
+ "nl": "Wijziging gemaakt door {_last_edit:contributor}"
}
},
{
"id": "theme",
"render": {
- "en": "Change with theme {theme}"
+ "en": "Change with theme {theme}",
+ "de": "Änderung mit Thema {theme}",
+ "nl": "Wijziging met thema {theme}"
},
"mappings": [
{
"if": "theme~http.*",
"then": {
- "en": "Change with unofficial theme {theme}"
+ "en": "Change with unofficial theme {theme}",
+ "de": "Änderung mit inoffiziellem Thema {theme}",
+ "nl": "Wijziging met officieus thema {theme}"
}
}
]
@@ -364,7 +383,9 @@
}
],
"question": {
- "en": "Themename contains {search}"
+ "en": "Themename contains {search}",
+ "de": "Themenname enthält {search}",
+ "nl": "Themanaam bevat {search}"
}
}
]
@@ -380,7 +401,9 @@
}
],
"question": {
- "en": "Made by contributor {search}"
+ "en": "Made by contributor {search}",
+ "de": "Erstellt von {search}",
+ "nl": "Gemaakt door bijdrager {search}"
}
}
]
@@ -396,7 +419,9 @@
}
],
"question": {
- "en": "Not made by contributor {search}"
+ "en": "Not made by contributor {search}",
+ "de": "Nicht erstellt von {search}",
+ "nl": "Niet gemaakt door bijdrager {search}"
}
}
]
@@ -411,7 +436,9 @@
{
"id": "link_to_more",
"render": {
- "en": "More statistics can be found here"
+ "en": "More statistics can be found here",
+ "de": "Weitere Statistiken finden Sie hier",
+ "nl": "Meer statistieken kunnen hier gevonden worden"
}
},
{
diff --git a/assets/themes/onwheels/onwheels.json b/assets/themes/onwheels/onwheels.json
index 920ce1e1c..703cf1924 100644
--- a/assets/themes/onwheels/onwheels.json
+++ b/assets/themes/onwheels/onwheels.json
@@ -27,7 +27,21 @@
"shops",
"toilet",
"viewpoint",
- "doctors"
+ "doctors",
+ "hotel",
+ {
+ "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'"
+ ]
+ }
+ }
],
"overrideAll": {
"minzoom": "15",
diff --git a/langs/layers/en.json b/langs/layers/en.json
index d0274d74d..deb5fa46e 100644
--- a/langs/layers/en.json
+++ b/langs/layers/en.json
@@ -4061,6 +4061,19 @@
"render": "Hospital"
}
},
+ "hotel": {
+ "description": "Layer showing all hotels",
+ "name": "Hotels",
+ "tagRenderings": {
+ "name": {
+ "freeform": {
+ "placeholder": "Name of the hotel"
+ },
+ "question": "What is the name of this hotel?",
+ "render": "This hotel is called {name}"
+ }
+ }
+ },
"hydrant": {
"description": "Map layer to show fire hydrants.",
"name": "Map of hydrants",
diff --git a/langs/layers/nl.json b/langs/layers/nl.json
index 11d154dcd..18c26b124 100644
--- a/langs/layers/nl.json
+++ b/langs/layers/nl.json
@@ -3937,6 +3937,19 @@
"render": "Hackerspace"
}
},
+ "hotel": {
+ "description": "Laag die alle hotels toont",
+ "name": "Hotels",
+ "tagRenderings": {
+ "name": {
+ "freeform": {
+ "placeholder": "Naam van het hotel"
+ },
+ "question": "Wat is de naam van dit hotel?",
+ "render": "Dit hotel heet {name}"
+ }
+ }
+ },
"hydrant": {
"description": "Kaartlaag met brandkranen.",
"name": "Kaart van brandkranen",
diff --git a/package-lock.json b/package-lock.json
index a7c3cdf0a..98f04eb85 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
@@ -3223,9 +3224,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",
@@ -7170,6 +7171,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",
@@ -19249,9 +19255,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",
@@ -22417,6 +22423,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",
diff --git a/package.json b/package.json
index 6d91d1aa2..18abddf4c 100644
--- a/package.json
+++ b/package.json
@@ -76,6 +76,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",
diff --git a/scripts/onwheels/constants.ts b/scripts/onwheels/constants.ts
new file mode 100644
index 000000000..8dd4ef94c
--- /dev/null
+++ b/scripts/onwheels/constants.ts
@@ -0,0 +1,100 @@
+/**
+ * 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: {
+ leisure: "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"
+ };
+
+ /**
+ * 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",
+ };
+}
diff --git a/scripts/onwheels/convertData.ts b/scripts/onwheels/convertData.ts
new file mode 100644
index 000000000..e35d6321f
--- /dev/null
+++ b/scripts/onwheels/convertData.ts
@@ -0,0 +1,177 @@
+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 = Constants.categories[category];
+ 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 = {};
+ for (const key in item) {
+ if (Constants.names[key] && item[key]) {
+ properties[Constants.names[key]] = item[key];
+ }
+ }
+ return properties;
+}
+
+function convertTypes(properties: GeoJsonProperties): GeoJsonProperties {
+ for (const property in properties) {
+ // Determine the original tag by looking at the value in the names table
+ const originalTag = Object.keys(Constants.names).find(
+ (tag) => Constants.names[tag] === property
+ );
+ // Check if we need to convert the value
+ if (Constants.types[originalTag]) {
+ switch (Constants.types[originalTag]) {
+ case "boolean":
+ properties[property] = properties[property] === "1" ? "yes" : "no";
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ 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 {
+ for (const property in properties) {
+ // Check if the property needs units, and doesn't already have them
+ if (Constants.units[property] && property.match(/.*([A-z]).*/gi) === null) {
+ properties[
+ property
+ ] = `${properties[property]} ${Constants.units[property]}`;
+ }
+ }
+ 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[] = 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"];
+ properties = { ...properties, ...categoryTags(category) };
+
+ // Add the rest of the needed tags
+ properties = { ...properties, ...renameTags(item) };
+
+ // Convert types
+ properties = convertTypes(properties);
+
+ // Loop through all the properties
+ // for (var key in item) {
+ // // Check if we need the property, and it's not empty
+ // if (Constants.names[key] && item[key]) {
+ // // Check if the type needs to be converted
+ // if (Constants.types[key]) {
+ // // Conversion necessary, use the typeTable
+ // switch (Constants.types[key]) {
+ // case "boolean":
+ // properties[Constants.names[key]] =
+ // item[key] === "1" ? "yes" : "no";
+ // break;
+ // default:
+ // properties[Constants.names[key]] = item[key];
+ // break;
+ // }
+ // } else {
+ // // No conversion necessary, we can just add the property
+ // properties[Constants.names[key]] = item[key];
+ // }
+ // }
+ // }
+
+ // Add units if necessary
+ addUnits(properties);
+
+ // 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,
+ };
+
+ // Output the data to the console
+ console.log(JSON.stringify(featureCollection));
+
+ // Write the data to a file
+ if (output) {
+ writeFileSync(`${output}.geojson`, JSON.stringify(featureCollection, null, 2));
+ }
+}
+
+// Execute the main function, with the stripped arguments
+main(process.argv.slice(2));