fix: Draw tracks and routes crossing antimeridian without jumping across the globe#364
Conversation
2775992 to
207bc61
Compare
|
What happens when you edit (move points) of a route that crosses the anti-meridian? |
207bc61 to
31e4a74
Compare
|
Good catch. I think I found a solution, but need to review it once again to make sure I didn't miss anything. |
Splits a lon/lat LineString at every ±180° crossing into segments that each stay within [-180, 180]. Interpolated endpoints are inserted at exactly ±180° so OpenLayers' wrapX mechanism can clone each segment into adjacent world copies correctly.
Replace the mapifyCoords + LineString approach with splitAtAntimeridian + MultiLineString for both the local (self) trail and the server trail. Each trail segment is split at ±180° so every piece stays within [-180, 180]. OL's wrapX then clones each piece into all visible world copies, eliminating the screen-crossing jump.
…unwrapping The old implementation used an inDLCrossingZone heuristic that only kicked in when a point was within a configurable zone near ±180°. This broke for routes that crossed the antimeridian at non-extreme longitudes, and for routes that crossed it multiple times. The new algorithm: 1. Normalises the first point into [-180, 180]. 2. For each subsequent point, adjusts by ±360 until it is within 180° of its predecessor — producing a continuous, unwrapped coordinate sequence. inDLCrossingZone is removed; it had no callers outside mapifyCoords.
toLonLat() normalises longitude back into [-180, 180], discarding which world copy the user clicked in. Replace with transform() so that the raw longitude is preserved (e.g. 190° for world +1, -190° for world -1). Also switch the right-click handler from getEventCoordinate() to getEventCoordinateInternal() for the same reason: the internal method returns the raw EPSG:3857 coordinate without clamping. Downstream code that calls fromLonLat() on the resulting lonlat will then produce a Mercator X in the correct world copy, which is a prerequisite for placing popups and editing handles in the right location.
OL's Modify interaction attaches vertex handles to the primary-world Feature (longitude in [-180, 180]). When the user clicks a route on a non-primary world copy, the handle would snap to the wrong location — potentially 360° away. Fix: when entering modify mode for a route, build a scratch LineString from mapifyCoords()-unwrapped coordinates and shift its Mercator x by the number of world widths needed to place the nearest vertex at the click position. OL Modify then operates on this shifted scratch geometry, so the drag handle appears where the user clicked. The scratch geometry is invisible (Modify attaches no VectorLayer to draw.features). Waypoint positions are extracted from it on each modifyEnd via transformCoordsArray().
Routes crossing the antimeridian previously rendered as a straight Mercator line jumping across the screen, because OL drew a single LineString between the last point in one world and the first point in the next.
31e4a74 to
6c21135
Compare
This PR proses now to build editing routes around moving waypoints. For making this happen, this PR customizes the editing interaction. The inserted support points on the anti-meridian will not appear as draggable waypoints. After each moved waypoint, the resulting route is recalculated. Moving a waypoint might lead to a segment "switching direction" and going east instead of west and vice versa if the direction of the shortest path changed. I couldn't make this recalculation happen while dragging, but only when the drag finished. This meant that the intermediate dragged geometry as drawn by openlayers would be misleading (and also expose the support points on the antimeridian if the user changes the segment crossing it). For this reason, I propose not to show the misleading line while dragging, but just update the drawn route whenever a waypoint was updated. Not ideal, but it works. As a bonus, it paves the way to drawing routes segments as great-circle connections (separate PR comes soon). |
|
The commits:
|
|
Apologies for the wall of commits and PRs. Please let me know whether you're actually interested in the proposed fixed and features and if you'd prefer them in a different format. |
|
Thanks for working through this. The screenshots make the underlying problem really clear, and I agree this is a real correctness issue for dateline / Pacific / offshore users rather than just a visual nicety. Before this can land, I'd like to tighten up a couple of things:
I'm interested in getting this fix in; I just want the route-editing invariants and scope to be clear before merge. |
mapifyCoordsis responsible for (un-)wrapping coordinates of line segments around the antimeridian. I.e. a line from [0, 179] → [0, -179] would be translated to [0, 179] → [0, 181].It had two bugs:
This lead to tracks and routes "jumping" around the world when they would cross the antimeridian if the first segment was further away or the crossing segment was a large one.
Additionally, there was an issue with how openlayers draws objects outside [-180,180]. A track that spans the globe, let's say from 0E to 0E, actually becomes a track from 0E to 360E with the above fix. The expected view would show the track seamlessly spanning the world. This requires the track actually to be drawn twice: from 360W to 0E and from 0E to 360E.
Openlayers seemingly has a logic that objects within [-180,180] are drawn multiple times, but objects outside this range are only drawn once. This lead to a problem where a track going beyond the antimeridian that spans the world would jump depending on where the view center is and mostly seem to miss some parts.
This PR thus proposes a fix by keeping tracks withing [-180, 180]. The function mapifyCoords is replaced by splitAtAntimeridian which splits line segments [179,-179] crossing the antimeridian and replaces it by a first segment to the antimeridian [179,180] and a second segment from the antimeridian onwards [-180,-179]. Since the track stays within [-180,180], openlayers duplicates both segments and the track still looks continuous.
Broken



Fixed
Broken:
Fixed

Route broken:


Route fixed: