forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			234 lines
		
	
	
	
		
			6.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			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));
 |