Source: togeojson.js

import { feature } from "topojson-client";
const topojson = Object.assign({}, { feature });
import { table2geo } from "./helpers/table2geo";
import {
  isTwoValues,
  isArrayOfCoords,
  isArrayOfObjects,
  isGeometry,
  isGeometries,
  isFeature,
  isFeatures,
  isFeatureCollection,
  isTopology,
} from "./helpers/helpers";

/**
 * @function togeojson
 * @summary Retrieve a FeatureCollection. The `featurecollection()` function allows to retrieve a FeatureCollection from a
 - a topoJSON ✅ 
- an array of features ✅ 
- a single feature ✅ 
- an array of geometries ✅ 
- a single geometry ✅ 
- an array of objects with coordinates (points) ✅
- an array coordinates (points) ✅ 
- a couple of coordinates (points) ✅
 * @param {object} data - A GeoJSON FeatureCollection
 * @param {object} options - Optional parameters
 * @param {string} [options.lat = undefined] - Field containing latitude coordinates. `lat`, `Lat`, `LAT`, `Latitude`, `latitude` are set by default.
 * @param {string} [options.latitude = undefined] - Field containing latitude coordinates. `lat`, `Lat`, `LAT`, `Latitude`, `latitude` are set by default.
 * @param {string} [options.lon = undefined] - Field containing longitude coordinates. `lon`, `Lon`, `LON`, `lng`, `Lng`, `LNG`, `Longitude`, `longitude` are set by default.
 * @param {string} [options.longitude = undefined] - Field containing longitude coordinates. `lon`, `Lon`, `LON`, `lng`, `Lng`, `LNG`, `Longitude`, `longitude` are set by default.
 * @param {string} [options.coords = undefined] - If the coordinates are in a single column, this also works.  The fields  `coords`, `coord`, `Coords`, `Coordinates`, `coordinates`, `Coordinate`, `coordinate` are set by default but, as previsously, you can specify in option with `coordinates` or `coords`.
 * @param {string} [options.coordinates = undefined] - If the coordinates are in a single column, this also works.  The fields  `coords`, `coord`, `Coords`, `Coordinates`, `coordinates`, `Coordinate`, `coordinate` are set by default but, as previsously, you can specify in option with `coordinates` or `coords`.
 * @param {boolean} [options.reperse = false] - If the latitude and longitude coordinates are inverted, you can use the `reverse = true`.
 * @param {Array} [options.properties = undefined] -  With the `properties` option, you can choose which fields you'd like to keep.
 * @param {Array} [options.rename = undefined] -  With the `rename` option, you can even rename them.
 * @param {function} [options.filter = undefined] - with `filter`, you can filter data.
 * @param {boolean} [options.mutate = false] - Use `true` to update the input data. With false, you create a new object, but the input object remains the same.
 * @example
 * // Array of coordinates
 * geoclean.togeojson([
  [32.33, 45.66],
  [10, 10]
])
  *
 * // A geometry  
  * geoclean.togeojson({
  type: "Polygon",
  coordinates: [
    [
      [100.0, 0.0],
      [101.0, 0.0],
      [101.0, 1.0],
      [100.0, 1.0],
      [100.0, 0.0]
    ]
  ]
})
  *
   * // An array of objects containing coordinates
   * geoclean.togeojson(*a JSON*, {lat: "lat", lon: "lon"})
   * 
   * // Data handling
   * geoclean.togeojson(*a geoJSON*, {
      filter: (d) => d.properties.pop2018 >= 200,
      properties: ["id", "capital", "pop2018"],
      rename: ["code", "name", "pop"
  })  
   *  
   * 
 */

export function togeojson(
  data,
  {
    lat = undefined,
    lon = undefined,
    latitude = undefined,
    longitude = undefined,
    coords = undefined,
    coordinates = undefined,
    reverse = false,
    properties = undefined,
    rename = undefined,
    filter = undefined,
    mutate = false,
  } = {}
) {
  lat = lat || latitude;
  lon = lon || longitude;
  coords = coords || coordinates;

  // deep copy ?
  let x;
  if (!mutate) {
    x = JSON.parse(JSON.stringify(data));
  } else {
    x = data;
  }

  if (isFeatureCollection(x)) {
    // nothing to do
  } else if (isTopology(x)) {
    x = topojson.feature(x, Object.keys(x.objects)[0]);
  } else if (isFeatures(x)) {
    x = { type: "FeatureCollection", features: x };
  } else if (isFeature(x)) {
    x = { type: "FeatureCollection", features: [x] };
  } else if (isGeometries(x)) {
    x = {
      type: "FeatureCollection",
      features: x.map((d) => ({
        type: "Feature",
        properties: {},
        geometry: d,
      })),
    };
  } else if (isGeometry(x)) {
    x = {
      type: "FeatureCollection",
      features: [{ type: "Feature", properties: {}, geometry: x }],
    };
  } else if (isArrayOfObjects(x)) {
    x = table2geo(x, lat, lon, coords, reverse);
  } else if (isArrayOfCoords(x)) {
    x = {
      type: "FeatureCollection",
      features: x.map((d) => ({
        type: "Feature",
        properties: {},
        geometry: {
          type: "Point",
          coordinates: reverse
            ? [parseFloat(d[1]), parseFloat(d[0])]
            : [parseFloat(d[0]), parseFloat(d[1])],
        },
      })),
    };
  } else if (isTwoValues(x)) {
    x = {
      type: "FeatureCollection",
      features: [
        {
          type: "Feature",
          properties: {},
          geometry: { type: "Point", coordinates: x },
        },
      ],
    };
  }

  // Filter
  if (filter != undefined && typeof filter == "function") {
    x.features = x.features.filter(filter);
  }

  if (properties != undefined && Array.isArray(properties)) {
    // Rename ?
    if (
      rename != undefined &&
      Array.isArray(rename) &&
      rename.length == properties.length
    ) {
      // Select and rename properties
      const fields = properties.map((d, i) => [d, rename[i]]);
      x.features.forEach((d) => {
        d.properties = Object.fromEntries(
          fields.map((k) => [k[1], d?.properties[k[0]]])
        );
      });
    } else {
      // Select properties
      x.features.forEach((d) => {
        d.properties = Object.fromEntries(
          properties.map((k) => [k, d?.properties[k]])
        );
      });
    }
  }

  // Output
  return x;
}

// Helpers