Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better poi index computation #109

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions src/closest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/**
* @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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition is always true.

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;
Comment on lines +121 to +126
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could avoid these copies by defining 2 points and swapping between them appropriately:

let closestPoint = [];
let assignedPoint = [];
...
assignedPoint = assignClosest(..., assignedClosest);
if (squaredDistance < minSquaredDistance) {
   minSquaredDistance = squaredDistance;
   minIndex = index;
  [closestPoint, assignedPoint] = [assignedPoint, closestPoint]; // swap variables
...
}

index += stride;
} else {
// 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) |
Comment on lines +142 to +143
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an integer?

Also, instead of max we could do xxxx | 1 and that would also avoid te infinite loop.

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,
}
Comment on lines +149 to +169
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this part can be computed by the algorithm automatically:

  • from the initial coordinates;
  • add 1 dimension for the distanceFromTheStartToThisPoint;
  • then the assignClosest function will automatically compute the distance from the start of the track to the projected point.

}
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
2 changes: 2 additions & 0 deletions src/interaction/TrackManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ class TrackManager {
setZ(segment, segmentProfile[0][2], segmentProfile[segmentProfile.length - 1][2]);
}
}
this.trackData_.updatePOIIndexes();
this.onTrackChanged_();
}
});
Expand Down Expand Up @@ -300,6 +301,7 @@ class TrackManager {
segmentUpdates.push(this.updater_.computeAdjacentSegmentsProfile(pointAfter));
}
Promise.all(segmentUpdates).then(() => {
this.trackData_.updatePOIIndexes();
this.onTrackChanged_();
});
});
Expand Down