mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-13 07:40:59 +00:00
51af575c9a
A right-click, click or drag on a repeated world copy reported the longitude it has on that copy (274.9 for -85.1). The place was saved there, the geocoder found nothing, and a route to a place on the real world ran across the whole map (#2976). Longitudes are now brought back onto [-180, 180] where the map hands them out (Leaflet and GL), where they are stored (places, booking endpoints, collection places, so REST, MCP, plugin RPC and importers alike) and before reverse geocoding. A migration moves the ones already stored. Leaflet draws a layer at its literal longitude only, so a trip from Los Angeles to Auckland had its pins at opposite ends of the world while the flight ran off the edge to the next copy (#2975). A trip that crosses the antimeridian is now drawn in one longitude frame, seamed in the widest stretch it leaves empty, so pins, routes, flights and every overlay lie on one copy and the map opens across the Pacific. Every other trip has no frame and draws exactly as before. The GL day fit also takes the short way. A new install now starts with Journey and Collections switched on, next to the trip addons (all but Road trip), Atlas, Vacay and the Naver import that already were. Existing installs keep their addons as they are. Closes #2975 Closes #2976
91 lines
3.5 KiB
TypeScript
91 lines
3.5 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { greatCircle, unwrapLngs, geodesicArcs } from './flightGeodesy'
|
|
import { tripLngFrame } from '../../utils/lngFrame'
|
|
|
|
const YVR: [number, number] = [49.19, -123.18]
|
|
const ICN: [number, number] = [37.46, 126.44]
|
|
const FRA: [number, number] = [50.03, 8.57]
|
|
const JFK: [number, number] = [40.64, -73.78]
|
|
|
|
const maxConsecutiveDeltaLng = (pts: [number, number][]) =>
|
|
pts.reduce((max, p, i) => (i === 0 ? 0 : Math.max(max, Math.abs(p[1] - pts[i - 1][1]))), 0)
|
|
|
|
describe('flightGeodesy (#1411)', () => {
|
|
it('a date-line crossing unwraps into one continuous arc plus a shifted copy for Leaflet', () => {
|
|
const arcs = geodesicArcs(YVR, ICN, true)
|
|
expect(arcs).toHaveLength(2)
|
|
const [base, shifted] = arcs
|
|
// continuous: no ±360 jump anywhere
|
|
expect(maxConsecutiveDeltaLng(base)).toBeLessThan(180)
|
|
// endpoints: starts at YVR, ends at ICN unwrapped westwards (126.44 - 360)
|
|
expect(base[0][0]).toBeCloseTo(YVR[0], 5)
|
|
expect(base[0][1]).toBeCloseTo(YVR[1], 5)
|
|
expect(base[base.length - 1][1]).toBeCloseTo(ICN[1] - 360, 5)
|
|
// the copy is the base shifted by exactly +360
|
|
expect(shifted).toHaveLength(base.length)
|
|
shifted.forEach(([lat, lng], i) => {
|
|
expect(lat).toBe(base[i][0])
|
|
expect(lng).toBeCloseTo(base[i][1] + 360, 10)
|
|
})
|
|
})
|
|
|
|
it('an eastbound crossing unwraps upwards and shifts by -360', () => {
|
|
const arcs = geodesicArcs(ICN, YVR, true)
|
|
expect(arcs).toHaveLength(2)
|
|
const [base, shifted] = arcs
|
|
expect(maxConsecutiveDeltaLng(base)).toBeLessThan(180)
|
|
expect(base[base.length - 1][1]).toBeCloseTo(YVR[1] + 360, 5)
|
|
expect(shifted[0][1]).toBeCloseTo(base[0][1] - 360, 10)
|
|
})
|
|
|
|
it('a non-crossing flight stays a single in-range arc', () => {
|
|
const arcs = geodesicArcs(FRA, JFK, true)
|
|
expect(arcs).toHaveLength(1)
|
|
for (const [, lng] of arcs[0]) {
|
|
expect(lng).toBeGreaterThanOrEqual(-180)
|
|
expect(lng).toBeLessThanOrEqual(180)
|
|
}
|
|
})
|
|
|
|
it('GL mode always returns exactly one continuous arc (world copies handle the wrap)', () => {
|
|
const arcs = geodesicArcs(YVR, ICN, false)
|
|
expect(arcs).toHaveLength(1)
|
|
expect(maxConsecutiveDeltaLng(arcs[0])).toBeLessThan(180)
|
|
})
|
|
|
|
it('a zero-distance leg passes the endpoints through', () => {
|
|
expect(greatCircle(YVR, YVR)).toEqual([YVR, YVR])
|
|
})
|
|
|
|
it('unwrapLngs keeps already-continuous input unchanged', () => {
|
|
const pts: [number, number][] = [[0, 170], [0, 175], [0, 179]]
|
|
expect(unwrapLngs(pts)).toEqual(pts)
|
|
})
|
|
})
|
|
|
|
describe('flightGeodesy in a longitude frame (#2975)', () => {
|
|
const LAX: [number, number] = [33.94, -118.41]
|
|
const AKL: [number, number] = [-37.01, 174.79]
|
|
const frame = tripLngFrame([LAX[1], AKL[1]])
|
|
|
|
it('draws LAX to Auckland as one arc on one world copy, between the two pins as the frame places them', () => {
|
|
expect(frame).not.toBeNull()
|
|
const arcs = geodesicArcs([LAX[0], LAX[1] + 360], AKL, true, frame)
|
|
expect(arcs).toHaveLength(1)
|
|
const [arc] = arcs
|
|
expect(maxConsecutiveDeltaLng(arc)).toBeLessThan(180)
|
|
expect(arc[0][1]).toBeCloseTo(LAX[1] + 360, 5)
|
|
expect(arc[arc.length - 1][1]).toBeCloseTo(AKL[1], 5)
|
|
})
|
|
|
|
it('still copies an arc that leaves the frame, so both of its ends keep a line', () => {
|
|
const arcs = geodesicArcs(FRA, JFK, true, { seam: -30 })
|
|
expect(arcs).toHaveLength(2)
|
|
expect(arcs[1][0][1]).toBeCloseTo(arcs[0][0][1] + 360, 10)
|
|
})
|
|
|
|
it('without a frame draws what it drew before', () => {
|
|
expect(geodesicArcs(YVR, ICN, true, null)).toEqual(geodesicArcs(YVR, ICN, true))
|
|
})
|
|
})
|