mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
refactor(studio): one great circle distance for the map bows and the road fetch
TravelElements ramped how far a leg bows by a local km helper and roadRoute refused long legs by a second copy of it. Both now call legKm from legKm.ts, the same formula with the same clamp.
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
type LucideIcon,
|
||||
} from 'lucide-react'
|
||||
import { fontStack } from './bookFonts'
|
||||
import { legKm } from './legKm'
|
||||
import { brightness } from './folioColour'
|
||||
import { COUNTRY_SHAPES, countryParts, countryWorldPath, projectMercator, unprojectMercator } from './countryShapes'
|
||||
import { projectOntoTiles, tileView, usableStaticUrl } from './mapTiles'
|
||||
@@ -358,16 +359,6 @@ function MapView({ el, frameStyle, big = false }: {
|
||||
*/
|
||||
const bowed = el.routeArc === 'bow' && trail.length === 0 && points.length > 1
|
||||
|
||||
/** Kilometres between two coordinates, for the ramp. */
|
||||
const km = (a: { lat: number; lng: number }, b: { lat: number; lng: number }) => {
|
||||
const r = (d: number) => (d * Math.PI) / 180
|
||||
const dLat = r(b.lat - a.lat)
|
||||
const dLng = r(b.lng - a.lng)
|
||||
const h = Math.sin(dLat / 2) ** 2
|
||||
+ Math.cos(r(a.lat)) * Math.cos(r(b.lat)) * Math.sin(dLng / 2) ** 2
|
||||
return 12742 * Math.asin(Math.min(1, Math.sqrt(h)))
|
||||
}
|
||||
|
||||
const meanLat = el.points.length
|
||||
? el.points.reduce((sum, p) => sum + p.lat, 0) / el.points.length
|
||||
: 0
|
||||
@@ -389,7 +380,7 @@ function MapView({ el, frameStyle, big = false }: {
|
||||
const d = Math.hypot(dx, dy)
|
||||
if (d < 1e-6) return { k: 0, nx: 0, ny: 0 }
|
||||
|
||||
const u = Math.min(1, Math.max(0, Math.log(km(a, b) / 150) / Math.log(10)))
|
||||
const u = Math.min(1, Math.max(0, Math.log(legKm(a, b) / 150) / Math.log(10)))
|
||||
const ramp = u * u * (3 - 2 * u)
|
||||
// How much of the leg runs east-west, which is where a bow is truthful.
|
||||
const eastWest = 0.35 + 0.65 * Math.abs(dx / d)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { legKm } from './legKm';
|
||||
|
||||
describe('legKm', () => {
|
||||
it('is zero between a point and itself', () => {
|
||||
expect(legKm({ lat: 48.1, lng: 11.6 }, { lat: 48.1, lng: 11.6 })).toBe(0);
|
||||
});
|
||||
|
||||
it('measures a degree of latitude as about 111 km', () => {
|
||||
expect(legKm({ lat: 0, lng: 0 }, { lat: 1, lng: 0 })).toBeCloseTo(111.19, 1);
|
||||
});
|
||||
|
||||
it('is the same both ways', () => {
|
||||
const a = { lat: 35.68, lng: 139.69 };
|
||||
const b = { lat: 34.05, lng: -118.24 };
|
||||
expect(legKm(a, b)).toBeCloseTo(legKm(b, a), 9);
|
||||
expect(legKm(a, b)).toBeGreaterThan(8700);
|
||||
expect(legKm(a, b)).toBeLessThan(8900);
|
||||
});
|
||||
|
||||
it('stays defined for antipodal points', () => {
|
||||
expect(legKm({ lat: 0, lng: 0 }, { lat: 0, lng: 180 })).toBeCloseTo((12742 * Math.PI) / 2, 3);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Kilometres between two coordinates, along the great circle.
|
||||
*
|
||||
* Shared by the map element, which ramps how far a leg bows by it, and by the
|
||||
* road fetch, which refuses to route a leg past its ceiling. The clamp keeps
|
||||
* `asin` defined when rounding pushes two near antipodal points a hair past 1.
|
||||
*/
|
||||
export function legKm(a: { lat: number; lng: number }, b: { lat: number; lng: number }): number {
|
||||
const r = (d: number) => (d * Math.PI) / 180;
|
||||
const dLat = r(b.lat - a.lat);
|
||||
const dLng = r(b.lng - a.lng);
|
||||
const h = Math.sin(dLat / 2) ** 2 + Math.cos(r(a.lat)) * Math.cos(r(b.lat)) * Math.sin(dLng / 2) ** 2;
|
||||
return 12742 * Math.asin(Math.min(1, Math.sqrt(h)));
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { calculateRouteWithLegs } from '../Map/RouteCalculator'
|
||||
import { legKm } from './legKm'
|
||||
|
||||
/**
|
||||
* The roads between a book's stops.
|
||||
@@ -63,15 +64,6 @@ export interface RoadStop {
|
||||
lng: number
|
||||
}
|
||||
|
||||
function km(a: RoadStop, b: RoadStop): number {
|
||||
const r = (d: number) => (d * Math.PI) / 180
|
||||
const dLat = r(b.lat - a.lat)
|
||||
const dLng = r(b.lng - a.lng)
|
||||
const h = Math.sin(dLat / 2) ** 2
|
||||
+ Math.cos(r(a.lat)) * Math.cos(r(b.lat)) * Math.sin(dLng / 2) ** 2
|
||||
return 12742 * Math.asin(Math.min(1, Math.sqrt(h)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Thin a leg to what a printed line can show.
|
||||
*
|
||||
@@ -107,7 +99,7 @@ export async function fetchRoads(
|
||||
const a = stops[i]
|
||||
const b = stops[i + 1]
|
||||
|
||||
if (km(a, b) > ROAD_CEILING_KM) {
|
||||
if (legKm(a, b) > ROAD_CEILING_KM) {
|
||||
opts.onProgress?.(i + 1, legs)
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user