mapcomplete/scripts/onwheels/convertData.ts
2022-07-19 20:54:05 +02:00

234 lines
6.5 KiB
TypeScript

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));