view client/src/application/lib/geo.js @ 677:3605af94d1ee

fix: prepare profile calculation algorithm fixed The x-value of a point is determined according to its reference point * in case of the first segment, it is the first element of the line string * in case of consecutive segments it is the last point of the previous segment
author Thomas Junk <thomas.junk@intevation.de>
date Wed, 19 Sep 2018 16:37:03 +0200
parents 4c36b0e39d78
children 87ea9d267c2b
line wrap: on
line source

/**
 *
 * Distance calculations
 * JS transposition of cross.go functions
 *
 */

const EARTHRADIUS = 6378137.0;

/**
 * Converts to radiant
 * @param {degree} d
 */
const deg2rad = d => {
  return (d * Math.PI) / 180.0;
};

/**
 * Calculates the difference between two points in m
 *
 * Points are given with {lat: $lat, lon: $lon}
 *
 * @param {object} P1
 * @param {object} P2
 */
const distanceBetween = (P1, P2) => {
  const dLat = deg2rad(P2.lat - P1.lat);
  let dLng = Math.abs(deg2rad(P2.lon - P1.lon));
  if (dLng > Math.PI) {
    dLng = 2 * Math.PI - dLng;
  }
  const x = dLng * Math.cos(deg2rad((P1.lat + P2.lat) / 2.0));
  return Math.sqrt(dLat * dLat + x * x) * EARTHRADIUS;
};

/**
 * Takes a triple of [lat, long, alt] and generates
 * an object with according attributes
 * {
 *  lat: $lat,
 *  lon: $lon,
 *  alt: $alt
 * }
 *
 * @param {array} coords
 */
const generatePoint = coords => {
  return {
    lon: coords[0],
    lat: coords[1],
    alt: coords[2]
  };
};

/**
 * Has geoJSON as its input and transforms
 * given coordinates
 *
 * a) extracting the minimum altitude
 * b) extracting the maximum altitude
 * c) calculating the total length of the given profile
 * d) transposes the datapoints given to the first point of the first section
 *
 * The calculation of total equals the sum of partial distances between points
 * The x-value of a point is determined according to its reference point
 * in case of the first segment, it is the first element of the line string
 * in case of consecutive segments it is the last point of the previous segment
 *
 * @param {object} feature
 */
const transform = feature => {
  const sections = feature.geometry.coordinates;
  let firstPoint = generatePoint(sections[0][0]);
  let coordinates = [];
  let totalLength = 0;
  let minAlt = firstPoint.alt;
  let maxAlt = firstPoint.alt;
  for (let section of sections) {
    let sectionCoordinates = [];
    let previousPoint = generatePoint(section[0]);
    let currentPoint = null;
    for (let coords of section) {
      currentPoint = generatePoint(coords);
      let x = distanceBetween(firstPoint, currentPoint);
      let y = coords[2];
      sectionCoordinates.push({
        x: x,
        y: y
      });
      totalLength += distanceBetween(currentPoint, previousPoint);
      previousPoint = currentPoint;
      if (y < minAlt) minAlt = y;
      if (y > maxAlt) maxAlt = y;
    }
    coordinates.push(sectionCoordinates);
    firstPoint = currentPoint;
  }
  return { coordinates, totalLength, minAlt, maxAlt };
};

/**
 * Prepare profile takes geoJSON data in form of
 * a MultiLineString, e.g.
 *
 * {
 *   type: "Feature",
 *   geometry: {
 *   type: "MultiLineString",
 *   coordinates: [
 *                 [
 *                  [16.53593398, 48.14694085, -146.52392755]
 *                  ...
 *                  ]]
 *
 * and transforms it to a structure representing the number of sections
 * where data is present with according lengths and the points
 *
 * {
 *  { points:
 *           [
 *            [ { x: 0.005798201616417183, y: -146.52419461 },
 *              { x: 0, y: -146.52394016 }
 *              ...
 *            ]
 *           ]
 *   totalLength: 160.06814078495722,
 *   minAlt: -146.73122231,
 *   maxAlt: -145.65155866
 *  }
 *
 * @param {object} geoJSON
 */
const prepareProfile = geoJSON => {
  const { coordinates, totalLength, minAlt, maxAlt } = transform(geoJSON);
  return {
    points: coordinates,
    totalLength: totalLength,
    minAlt: minAlt,
    maxAlt: maxAlt
  };
};

export { prepareProfile };