From f7506e07cd2cc8b69b8bba40dac64d5df3051b59 Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Thu, 14 Jul 2022 15:17:09 +0200 Subject: [PATCH] Add hotel layer, conversion scripts --- assets/layers/hotel/hotel.json | 53 ++++++ assets/layers/hotel/hotel.svg | 3 + assets/layers/hotel/license_info.json | 15 ++ .../mapcomplete-changes.json | 55 ++++-- assets/themes/onwheels/onwheels.json | 16 +- langs/layers/en.json | 13 ++ langs/layers/nl.json | 13 ++ package-lock.json | 23 ++- package.json | 1 + scripts/onwheels/constants.ts | 100 ++++++++++ scripts/onwheels/convertData.ts | 177 ++++++++++++++++++ 11 files changed, 448 insertions(+), 21 deletions(-) create mode 100644 assets/layers/hotel/hotel.json create mode 100644 assets/layers/hotel/hotel.svg create mode 100644 assets/layers/hotel/license_info.json create mode 100644 scripts/onwheels/constants.ts create mode 100644 scripts/onwheels/convertData.ts 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));