2 minute read

This script returns the properties of the closest Feature in a FeatureCollection against a given Feature (GeoJSON RFC7946).

/**
 * @param Feature with type `Point`
 * @returns {boolean}
 */
function isPointFeature (Feature) {
  return (
    Feature.type === 'Feature'
    && Feature.geometry.type === 'Point'
  );
}

/**
 * @param {object} Feature
 * @param {object} FeatureCollection
 * @returns {(object|null)} Feature Properties or null if nothing found or malformed reference
 */
function getClosestFeature (Feature, FeatureCollection) {
  if (!isPointFeature(Feature)) {
    console.error('The Feature of reference is malformed.');
    return null;
  }

  const lat1 = Feature.geometry.coordinates[1];
  const lng1 = Feature.geometry.coordinates[0];
  const radLat1 = Math.PI * lat1 / 180;
  const nauticalMileToMile = Math.PI * 60 * 1.1515;

  let lowestValue = 12451; // Earth average longest semicircle
  let closestPlace = null;

  for (const i in FeatureCollection.features) {
    const thisFeature = FeatureCollection.features[i];

    if (!isPointFeature(thisFeature)) {
      continue;
    }

    const lng2 = thisFeature.geometry.coordinates[0];
    const lat2 = thisFeature.geometry.coordinates[1];
    const placeMeta = thisFeature.properties;

    const radLat2 = Math.PI * lat2 / 180;
    const theta = lng1 - lng2;
    const radTheta = Math.PI * theta / 180;

    let dist = Math.sin(radLat1) * Math.sin(radLat2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.cos(radTheta);
    dist = Math.acos(dist) * 180 / nauticalMileToMile;

    if (dist < lowestValue) {
      lowestValue = dist;
      closestPlace = placeMeta;
    }
  }

  return closestPlace;
}