perliedman / geojson-path-finder

Find shortest path through a network of GeoJSON
https://www.liedman.net/geojson-path-finder/
ISC License
300 stars 86 forks source link

Getting a path along a river on a LineLayer with multiple rivers? #88

Open Tasemu opened 1 year ago

Tasemu commented 1 year ago

Hiya, I am using this geojson data source which supplied canals in the UK.

https://data-canalrivertrust.opendata.arcgis.com/datasets/CanalRiverTrust::canals-km-view-public/explore

I was hoping to be able to use this library to be able to highlight a section of one of the rivers to show for example a "cruise" between two locations. However when trying to run the function I receive undefined from the return value of findPath().

Am i doing something wrong, or is there a way to achieve this? Thanks so much for any and all advice!

Code:

import {
  Feature,
  FeatureCollection,
  LineString,
  Point,
  nearestPointOnLine,
  point,
} from "@turf/turf";

// Here is the pathFinder import
import PathFinder, { pathToGeoJSON } from "geojson-path-finder";

// Custom LatLngAlt type
type LatLngAlt = {
  lat: number;
  lng: number;
  alt?: number;
};

// This is just to get the coords for the below function
const toCoordinate = (latlng: LatLngAlt): [number, number] => {
  return [latlng.lng, latlng.lat];
};

/* I found this function in another issue, it just ensures that the points get snapped to the nearest line just in case the GPS coords are slightly off */
const insertProjectedPointToClosestLine = (
  latlng: LatLngAlt,
  collection: FeatureCollection<LineString>
) => {
  let closestLine = null;
  let nearestPoint = null;
  let minimalDistance = Infinity;
  let coordinates = toCoordinate(latlng);

  for (let feature of collection.features) {
    let point = nearestPointOnLine(feature, coordinates);

    if (point.properties.dist < minimalDistance) {
      minimalDistance = point.properties.dist;
      closestLine = feature;
      nearestPoint = point;
    }
  }

  closestLine.geometry.coordinates.splice(
    nearestPoint.properties.index + 1,
    0,
    nearestPoint.geometry.coordinates
  );

  return nearestPoint.geometry.coordinates as [number, number];
};

// This is the function that is returning undefined from findPath()
export function getShortestPath(
  lineData: FeatureCollection<LineString>,
  start: Feature<Point>,
  end: Feature<Point>
) {
  const pathFinder = new PathFinder(lineData);

  const startPointCoords = insertProjectedPointToClosestLine(
    { lat: start.geometry.coordinates[1], lng: start.geometry.coordinates[0] },
    lineData
  );
  const endPointCoords = insertProjectedPointToClosestLine(
    { lat: end.geometry.coordinates[1], lng: end.geometry.coordinates[0] },
    lineData
  );

  const startPointFeature = point(startPointCoords);
  const endPointFeature = point(endPointCoords);

  const pathResult = pathFinder.findPath(startPointFeature, endPointFeature);

  if (!pathResult) {
    console.error("Path not found");
  }

  return pathToGeoJSON(pathResult);
}
Tasemu commented 1 year ago

Looks like it was just a matter of changing the tolerance to 1e-3! Though it does simplify the line a little so it doesn't hug the original line when zoomed in unfortunately. I guess there's no way around this?

pbvahlst commented 1 year ago

If the original lines' vertices snap it should work without adjusting the tolerance

Tasemu commented 1 year ago

If the original lines' vertices snap it should work without adjusting the tolerance

I'm honestly not sure, i'm quite certain that the points have snapped as when viewing them visually on maximum zoom, they are dead centre on the line. But without adjusting the tolerance i just get back an undefined value. Maybe its the lineString itself... I'm a little lost. :)

pbvahlst commented 1 year ago

It is not enough that the vertex of one line snaps to the somewhere on another line between two vertices on that line. They need to meet on a vertex.

This is not enough...

         x
         |
x--------|
         |
         x

Needs to be...

        x       
        |
x-------x
        |
        x
Tasemu commented 1 year ago

It is not enough that the vertex of one line snaps to the somewhere on another line between two vertices on that line. They need to meet on a vertex.

This is not enough...

         x
         |
x--------|
         |
         x

Needs to be...

        x       
        |
x-------x
        |
        x

I think I understand, i'm required to increase the tolerance because the line data i'm using from the url has minute gaps between each segment of the line, or at least do not share vertices?

pbvahlst commented 1 year ago

Yes, to get accuracy the lines need to have the same vertices. Increasing tolerance expands the radius so two vertices close to each other is considered equal but this the reduces accuracy. Maybe you can preprocess the data with turf.js adding extra vertices on every overlapning lines.

Tasemu commented 1 year ago

Yes, to get accuracy the lines need to have the same vertices. Increasing tolerance expands the radius so two vertices close to each other is considered equal but this the reduces accuracy. Maybe you can preprocess the data with turf.js adding extra vertices on every overlapning lines.

I've been working on it and have been preprocessing the data, it turns out there are no overlapping lines and i cannot see any gaps at all. Could this issue be the start/end points being too far away from the lines?

pbvahlst commented 1 year ago

The coordinate of start and endpoints should also be a vertex on the line.

This is good (notice they meet at (0.1))

line1: [(0,0), (0,1), (0,2)]
line2: [(-1,1), (0,1), (1.1)]

this is not good (they cross but do not have the same identical coordinate where they meet which is needed to for the path-finder to join them)

line1: [(0,0), (0,1), (0,2)]
line2: [(-1,1), (1.1), (2,1)]

I expect that your data i suffering from this. The start/end-point is on the line but there is no vertex where they meet on the line