Source: filter.js

import { isarrayofobjects, isgeojson } from "./helpers/helpers.js";

/**
 * @function filter
 * @summary Filter a dataset. The functions allows to subset a geoJSON or an array of objects
 * @param {object|array} data - A GeoJSON FeatureCollection or an array of objects
 * @param {object} options - Optional parameters
 * @param {string|function} [options.func] - A function to filter the datset. But you can aslo use un string like "ISO3 = FRA" ou "pop > 1000"
 * @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.
 * @returns {object|array} -  A GeoJSON FeatureCollection or an array of objects. (it depends on what you've set as `data`).
 * @example
 * geotoolbox.filter(*a geojson or an array of objects*, {filter: "gdp >= 1000000" })
 */
export function filter(data, { func, mutate = false } = {}) {
  let x = data;
  // geoJSON
  if (isgeojson(data)) {
    if (!mutate) {
      x = JSON.parse(JSON.stringify(data));
    }

    if (typeof func == "function") {
      x.features = x.features.filter(func);
    } else if (typeof func == "string") {
      const prop = [
        ...new Set(
          x.features
            .map((d) => d.properties)
            .map((d) => Object.keys(d))
            .flat()
        ),
      ];
      const newprop = prop.map((d) => "d.properties['" + d + "']");

      func =
        "d => " +
        replaceEquals(
          addQuotesIfString(
            prop.reduce(
              (acc, mot, i) => acc.replace(new RegExp(mot, "g"), newprop[i]),
              func
            )
          )
        );

      x.features = x.features.filter(eval(func));
    }
  } else if (isarrayofobjects(data)) {
    if (typeof func == "function") {
      x = x.filter(func);
    } else if (typeof func == "string") {
      const prop = [...new Set(x.map((d) => Object.keys(d)).flat())];
      const newprop = prop.map((d) => "d['" + d + "']");

      const func =
        "d => " +
        replaceEquals(
          addQuotesIfString(
            prop.reduce(
              (acc, mot, i) => acc.replace(new RegExp(mot, "g"), newprop[i]),
              func
            )
          )
        );
      x = x.filter(eval(func));
    }

    if (mutate) {
      data.splice(0, data.length, ...x);
    }
  }

  return x;
}

function replaceEquals(str) {
  return str.replace(/(?<![><!])=(?![=])/g, "==");
}

function addQuotesIfString(expression) {
  return expression.replace(
    /([><=!]=?|==)\s*([A-Za-z_]+)/g,
    (match, operator, value) => {
      return `${operator} '${value}'`;
    }
  );
}