2022-07-14 15:17:09 +02:00
|
|
|
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 {
|
2022-07-18 16:10:06 +02:00
|
|
|
const tags = {
|
|
|
|
tags: Object.keys(Constants.categories[category]).map((tag) => {
|
|
|
|
return `${tag}=${Constants.categories[category][tag]}`
|
|
|
|
}),
|
|
|
|
}
|
2022-07-14 15:17:09 +02:00
|
|
|
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 = {}
|
2022-07-18 16:10:06 +02:00
|
|
|
properties.tags = []
|
2022-07-19 20:54:05 +02:00
|
|
|
// Loop through the original item tags
|
2022-07-14 15:17:09 +02:00
|
|
|
for (const key in item) {
|
2022-07-19 20:54:05 +02:00
|
|
|
// Check if we need it and it's not a null value
|
2022-07-14 15:17:09 +02:00
|
|
|
if (Constants.names[key] && item[key]) {
|
2022-07-19 20:54:05 +02:00
|
|
|
// Name and id tags need to be in the properties
|
2022-07-18 16:10:06 +02:00
|
|
|
if (Constants.names[key] == "name" || Constants.names[key] == "id") {
|
|
|
|
properties[Constants.names[key]] = item[key]
|
|
|
|
}
|
2022-07-19 20:54:05 +02:00
|
|
|
// Other tags need to be in the tags variable
|
2022-07-18 16:10:06 +02:00
|
|
|
if (Constants.names[key] !== "id") {
|
2022-07-19 20:54:05 +02:00
|
|
|
// 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
|
2022-09-08 21:40:48 +02:00
|
|
|
}
|
2022-07-18 16:10:06 +02:00
|
|
|
properties.tags.push(Constants.names[key] + "=" + item[key])
|
2022-09-08 21:40:48 +02:00
|
|
|
}
|
2022-07-19 20:54:05 +02:00
|
|
|
}
|
2022-07-14 15:17:09 +02:00
|
|
|
}
|
|
|
|
return properties
|
|
|
|
}
|
|
|
|
|
2022-07-19 20:54:05 +02:00
|
|
|
/**
|
|
|
|
* Convert types to match the OSM standard
|
|
|
|
*
|
|
|
|
* @param properties The properties to convert
|
|
|
|
* @returns The converted properties
|
|
|
|
*/
|
2022-07-14 15:17:09 +02:00
|
|
|
function convertTypes(properties: GeoJsonProperties): GeoJsonProperties {
|
2022-07-19 12:09:04 +02:00
|
|
|
// Split the tags into a list
|
|
|
|
let tags = properties.tags.split(";")
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-19 12:09:04 +02:00
|
|
|
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)
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-19 12:09:04 +02:00
|
|
|
if (Constants.types[originalKey]) {
|
|
|
|
// We need to convert the value to the correct type
|
|
|
|
let newValue
|
|
|
|
switch (Constants.types[originalKey]) {
|
2022-07-14 15:17:09 +02:00
|
|
|
case "boolean":
|
2022-07-19 12:09:04 +02:00
|
|
|
newValue = value === "1" ? "yes" : "no"
|
2022-07-14 15:17:09 +02:00
|
|
|
break
|
|
|
|
default:
|
2022-07-19 12:09:04 +02:00
|
|
|
newValue = value
|
2022-07-14 15:17:09 +02:00
|
|
|
break
|
|
|
|
}
|
2022-07-19 12:09:04 +02:00
|
|
|
tags[tag] = `${key}=${newValue}`
|
2022-09-08 21:40:48 +02:00
|
|
|
}
|
2022-07-14 15:17:09 +02:00
|
|
|
}
|
2022-07-19 12:09:04 +02:00
|
|
|
|
|
|
|
// Rejoin the tags
|
|
|
|
properties.tags = tags.join(";")
|
|
|
|
|
|
|
|
// Return the properties
|
2022-07-14 15:17:09 +02:00
|
|
|
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 {
|
2022-07-19 12:09:04 +02:00
|
|
|
// Split the tags into a list
|
|
|
|
let tags = properties.tags.split(";")
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-19 12:09:04 +02:00
|
|
|
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)
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-14 15:17:09 +02:00
|
|
|
// Check if the property needs units, and doesn't already have them
|
2022-07-19 12:09:04 +02:00
|
|
|
if (Constants.units[originalKey] && value.match(/.*([A-z]).*/gi) === null) {
|
|
|
|
tags[tag] = `${key}=${value} ${Constants.units[originalKey]}`
|
2022-09-08 21:40:48 +02:00
|
|
|
}
|
2022-07-14 15:17:09 +02:00
|
|
|
}
|
2022-07-19 12:09:04 +02:00
|
|
|
|
|
|
|
// Rejoin the tags
|
|
|
|
properties.tags = tags.join(";")
|
|
|
|
|
|
|
|
// Return the properties
|
2022-07-14 15:17:09 +02:00
|
|
|
return properties
|
|
|
|
}
|
|
|
|
|
2022-07-18 16:10:06 +02:00
|
|
|
/**
|
|
|
|
* 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")}
|
2023-08-23 18:33:30 +02:00
|
|
|
You can also easily import this item using MapComplete: https://mapcomplete.org/onwheels.html#${
|
2022-07-18 16:10:06 +02:00
|
|
|
properties["id"]
|
|
|
|
}`
|
|
|
|
return properties
|
|
|
|
}
|
|
|
|
|
2022-07-14 15:17:09 +02:00
|
|
|
/**
|
|
|
|
* 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]
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-14 15:17:09 +02:00
|
|
|
// Create an empty list to store the converted features
|
|
|
|
var items: Feature[] = []
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-14 15:17:09 +02:00
|
|
|
// Read CSV file
|
|
|
|
const csv: Record<any, string>[] = parse(readFileSync(file), csvOptions)
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-14 15:17:09 +02:00
|
|
|
// Loop through all the entries
|
|
|
|
for (var i = 0; i < csv.length; i++) {
|
|
|
|
const item = csv[i]
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-14 15:17:09 +02:00
|
|
|
// Determine coordinates
|
|
|
|
const lat = Number(item["Latitude"])
|
|
|
|
const lon = Number(item["Longitude"])
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-14 15:17:09 +02:00
|
|
|
// Check if coordinates are valid
|
|
|
|
if (isNaN(lat) || isNaN(lon)) {
|
|
|
|
throw `Not a valid lat or lon for entry ${i}: ${JSON.stringify(item)}`
|
2022-09-08 21:40:48 +02:00
|
|
|
}
|
|
|
|
|
2022-07-14 15:17:09 +02:00
|
|
|
// Create a new collection to store the converted properties
|
|
|
|
var properties: GeoJsonProperties = {}
|
|
|
|
|
|
|
|
// Add standard tags for category
|
|
|
|
const category = item["Categorie"]
|
2022-07-18 16:10:06 +02:00
|
|
|
const tagsCategory = categoryTags(category)
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-14 15:17:09 +02:00
|
|
|
// Add the rest of the needed tags
|
|
|
|
properties = { ...properties, ...renameTags(item) }
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-18 16:10:06 +02:00
|
|
|
// Merge them together
|
|
|
|
properties.tags = [...tagsCategory.tags, ...properties.tags]
|
|
|
|
properties.tags = properties.tags.join(";")
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-14 15:17:09 +02:00
|
|
|
// Convert types
|
|
|
|
properties = convertTypes(properties)
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-14 15:17:09 +02:00
|
|
|
// Add units if necessary
|
2022-07-19 12:09:04 +02:00
|
|
|
properties = addUnits(properties)
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-18 16:10:06 +02:00
|
|
|
// Add Maproulette tags
|
|
|
|
properties = addMaprouletteTags(properties, item)
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-14 15:17:09 +02:00
|
|
|
// Create the new feature
|
|
|
|
const feature: Feature = {
|
|
|
|
type: "Feature",
|
|
|
|
id: item["ID"],
|
|
|
|
geometry: {
|
|
|
|
type: "Point",
|
|
|
|
coordinates: [lon, lat],
|
|
|
|
},
|
|
|
|
properties,
|
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-14 15:17:09 +02:00
|
|
|
// Push it to the list we created earlier
|
|
|
|
items.push(feature)
|
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-14 15:17:09 +02:00
|
|
|
// Make a FeatureCollection out of it
|
|
|
|
const featureCollection: FeatureCollection = {
|
|
|
|
type: "FeatureCollection",
|
|
|
|
features: items,
|
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-07-19 12:09:04 +02:00
|
|
|
// Write the data to a file or output to the console
|
2022-07-14 15:17:09 +02:00
|
|
|
if (output) {
|
2022-07-18 16:10:06 +02:00
|
|
|
writeFileSync(`${output}.geojson`, JSON.stringify(featureCollection, null, 2))
|
2022-07-19 12:09:04 +02:00
|
|
|
} else {
|
|
|
|
console.log(JSON.stringify(featureCollection))
|
2022-07-14 15:17:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute the main function, with the stripped arguments
|
|
|
|
main(process.argv.slice(2))
|