Skip to content

Commit

Permalink
Better poi index computation
Browse files Browse the repository at this point in the history
  • Loading branch information
fredj committed Aug 28, 2023
1 parent 352bee0 commit 35fcde4
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 8 deletions.
171 changes: 171 additions & 0 deletions src/closest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/**
* @module ol/geom/flat/closest
*/
import {lerp, squaredDistance as squaredDx} from 'ol/math.js';

/*
* Returns the point on the 2D line segment flatCoordinates[offset1] to
* flatCoordinates[offset2] that is closest to the point (x, y). Extra
* dimensions are linearly interpolated.
*/
function assignClosest(
flatCoordinates: number[],
offset1: number,
offset2: number,
stride: number,
x: number,
y: number,
closestPoint: number[]
) {
const x1 = flatCoordinates[offset1];
const y1 = flatCoordinates[offset1 + 1];
const dx = flatCoordinates[offset2] - x1;
const dy = flatCoordinates[offset2 + 1] - y1;
let offset;
if (dx === 0 && dy === 0) {
offset = offset1;
} else {
const t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy);
if (t > 1) {
offset = offset2;
} else if (t > 0) {
for (let i = 0; i < stride; ++i) {
closestPoint[i] = lerp(
flatCoordinates[offset1 + i],
flatCoordinates[offset2 + i],
t
);
}
closestPoint.length = stride;
return;
} else {
offset = offset1;
}
}
for (let i = 0; i < stride; ++i) {
closestPoint[i] = flatCoordinates[offset + i];
}
closestPoint.length = stride;
}

type ClosestPointResult = {
totalSquaredDistance: number;
squaredDistance: number;
closestPoint: number[];
}

export function getClosestPoint(
flatCoordinates: number[],
offset: number,
end: number,
stride: number,
maxDelta: number,
x: number,
y: number
): ClosestPointResult {
let minSquaredDistance = Infinity;
const closestPoint = [NaN, NaN];
let minIndex;
if (offset == end) {
return {
closestPoint: closestPoint,
squaredDistance: minSquaredDistance,
totalSquaredDistance: minSquaredDistance,
};
}
let i, squaredDistance;
if (maxDelta === 0) {
// All points are identical, so just test the first point.
squaredDistance = squaredDx(
x,
y,
flatCoordinates[offset],
flatCoordinates[offset + 1]
);
if (squaredDistance < minSquaredDistance) {
for (i = 0; i < stride; ++i) {
closestPoint[i] = flatCoordinates[offset + i];
}
closestPoint.length = stride;
return {
closestPoint: closestPoint,
squaredDistance: squaredDistance,
totalSquaredDistance: squaredDx(
flatCoordinates[offset],
flatCoordinates[offset + 1],
closestPoint[0],
closestPoint[1]
),
};
}
return {
closestPoint: closestPoint,
squaredDistance: minSquaredDistance,
totalSquaredDistance: minSquaredDistance,
};
}
const tmpPoint = [NaN, NaN];
let index = offset + stride;
while (index < end) {
assignClosest(
flatCoordinates,
index - stride,
index,
stride,
x,
y,
tmpPoint
);
squaredDistance = squaredDx(x, y, tmpPoint[0], tmpPoint[1]);
if (squaredDistance < minSquaredDistance) {
minSquaredDistance = squaredDistance;
minIndex = index;
for (i = 0; i < stride; ++i) {
closestPoint[i] = tmpPoint[i];
}
closestPoint.length = stride;
index += stride;
} else {
// console.log('no match', index);
// Skip ahead multiple points, because we know that all the skipped
// points cannot be any closer than the closest point we have found so
// far. We know this because we know how close the current point is, how
// close the closest point we have found so far is, and the maximum
// distance between consecutive points. For example, if we're currently
// at distance 10, the best we've found so far is 3, and that the maximum
// distance between consecutive points is 2, then we'll need to skip at
// least (10 - 3) / 2 == 3 (rounded down) points to have any chance of
// finding a closer point. We use Math.max(..., 1) to ensure that we
// always advance at least one point, to avoid an infinite loop.
index +=
stride *
Math.max(
((Math.sqrt(squaredDistance) - Math.sqrt(minSquaredDistance)) /
maxDelta) |
0,
1
);
}
}
let totalSquaredDistance = 0;
for (i = offset + stride; i < minIndex; ++i) {
totalSquaredDistance += squaredDx(
flatCoordinates[i - stride],
flatCoordinates[i - stride + 1],
flatCoordinates[i],
flatCoordinates[i + 1]
)
}
totalSquaredDistance += squaredDx(
flatCoordinates[minIndex],
flatCoordinates[minIndex + 1],
closestPoint[0],
closestPoint[1]
);

return {
totalSquaredDistance: totalSquaredDistance,
squaredDistance: minSquaredDistance,
closestPoint: closestPoint,
}
}
19 changes: 11 additions & 8 deletions src/interaction/TrackData.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {equals} from 'ol/coordinate.js';
import Feature from 'ol/Feature.js';
import LineString from 'ol/geom/LineString.js';
import MultiPoint from 'ol/geom/MultiPoint.js';
import {maxSquaredDelta} from 'ol/geom/flat/closest.js';
import {getClosestPoint} from '../closest.ts';
import type Point from 'ol/geom/Point.js';
import type {Coordinate} from 'ol/coordinate.js';


interface ParsedFeatures {
segments: Array<Feature<LineString>>;
controlPoints: Array<Feature<Point>>;
Expand Down Expand Up @@ -257,17 +259,18 @@ export default class TrackData {
}

updatePOIIndexes() {
// build a multi point geometry from all segments coordinates
const points = new MultiPoint(this.segments.map((s) => s.getGeometry().getCoordinates()).flat());
const pointsCoordinates = points.getCoordinates();
const lineFlatCoordinates = this.segments.map(s => s.getGeometry().getFlatCoordinates()).flat()
const lineMaxDelta = maxSquaredDelta(lineFlatCoordinates, 0, lineFlatCoordinates.length, 2, 0)

const sorted = this.pois.map((poi) => {
// find the closest point to the POI and returns its index; that's it's "distance" from the start
const closestPoint = points.getClosestPoint(poi.getGeometry().getCoordinates());
const point = poi.getGeometry().getCoordinates();
const {totalSquaredDistance} = getClosestPoint(lineFlatCoordinates, 0, lineFlatCoordinates.length, 2, lineMaxDelta, point[0], point[1]);

return {
poi: poi,
index: pointsCoordinates.findIndex((c) => equals(c, closestPoint))
distance: totalSquaredDistance
};
}).sort((a, b) => a.index - b.index);
}).sort((a, b) => a.distance - b.distance);
sorted.forEach((s, index) => s.poi.set('index', index));
}

Expand Down

0 comments on commit 35fcde4

Please sign in to comment.