mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
fix: bound large boxes by their farthest point on the sphere
A box's radius was the distance to its farthest corner, which falls short for large boxes: a whole-world box measured about 10,000 km, so bike-share and transit selection never considered systems beyond it. The radius now checks whether the box holds the point opposite its center and otherwise samples every edge.
This commit is contained in:
1 parent
e1ac5ac270
commit
4cfca57cfb
3 files changed
+64
-8
No files matched your search
+32
-8
@@ -142,14 +142,38 @@ export async function resolvePoint(point, { services, signal } = {}) {
|
||||
export function areaRadiusKm(area) {
|
||||
if (area.center) return area.center.radiusKm;
|
||||
const center = areaCenter(area);
|
||||
return Math.max(
|
||||
...[
|
||||
{ lat: area.north, lon: area.west },
|
||||
{ lat: area.north, lon: area.east },
|
||||
{ lat: area.south, lon: area.west },
|
||||
{ lat: area.south, lon: area.east },
|
||||
].map((corner) => distanceKm(center, corner)),
|
||||
);
|
||||
// A box holding the point opposite its center reaches half the globe.
|
||||
const antipode = {
|
||||
lat: -center.lat,
|
||||
lon: center.lon > 0 ? center.lon - 180 : center.lon + 180,
|
||||
};
|
||||
if (areaContains(area, antipode)) return Math.PI * EARTH_RADIUS_KM;
|
||||
// Otherwise the farthest point is on the boundary: sample each edge, and
|
||||
// include where each parallel edge comes nearest the antipode's longitude.
|
||||
const width =
|
||||
area.west <= area.east
|
||||
? area.east - area.west
|
||||
: 360 - (area.west - area.east);
|
||||
const lonAt = (t) => area.west + width * t;
|
||||
const steps = 64;
|
||||
const points = [];
|
||||
for (let step = 0; step <= steps; step += 1) {
|
||||
const t = step / steps;
|
||||
const lat = area.south + (area.north - area.south) * t;
|
||||
points.push(
|
||||
{ lat, lon: area.west },
|
||||
{ lat, lon: area.east },
|
||||
{ lat: area.north, lon: lonAt(t) },
|
||||
{ lat: area.south, lon: lonAt(t) },
|
||||
);
|
||||
}
|
||||
const offset = (((antipode.lon - area.west) % 360) + 360) % 360;
|
||||
if (offset <= width)
|
||||
points.push(
|
||||
{ lat: area.north, lon: antipode.lon },
|
||||
{ lat: area.south, lon: antipode.lon },
|
||||
);
|
||||
return Math.max(...points.map((point) => distanceKm(center, point)));
|
||||
}
|
||||
|
||||
/** Great-circle distance in kilometers. */
|
||||
|
||||
@@ -3,6 +3,7 @@ import test from 'node:test';
|
||||
import {
|
||||
areaCenter,
|
||||
areaContains,
|
||||
areaRadiusKm,
|
||||
distanceKm,
|
||||
lineTouchesArea,
|
||||
polygonsTouchArea,
|
||||
@@ -245,3 +246,19 @@ test('polygons touch an area by vertex, crossing edge or enclosing it', async ()
|
||||
const circle = await resolveArea({ lat: 0, lon: 0, radius_km: 20 });
|
||||
assert.ok(polygonsTouchArea([square(5)], circle));
|
||||
});
|
||||
|
||||
test('a box radius reaches every point of the box, however large', async () => {
|
||||
const world = await resolveArea({ bbox: [-180, -90, 180, 90] });
|
||||
assert.ok(Math.abs(areaRadiusKm(world) - Math.PI * 6371.0088) < 1);
|
||||
const wide = await resolveArea({ bbox: [-60, -70, 100, 60] });
|
||||
const radius = areaRadiusKm(wide);
|
||||
const center = areaCenter(wide);
|
||||
for (let lat = -70; lat <= 60; lat += 2.5)
|
||||
for (let lon = -60; lon <= 100; lon += 2.5)
|
||||
assert.ok(
|
||||
distanceKm(center, { lat, lon }) <= radius + 0.5,
|
||||
`${lat},${lon}`,
|
||||
);
|
||||
const small = await resolveArea({ bbox: [-97.94, 30.1, -97.56, 30.52] });
|
||||
assert.ok(areaRadiusKm(small) < 30);
|
||||
});
|
||||
@@ -481,3 +481,18 @@ test('traffic counts only roads inside a radius area and reports missing tiles',
|
||||
assert.equal(partial.data.partial, true);
|
||||
assert.match(partial.summary, /Some map tiles did not load/);
|
||||
});
|
||||
|
||||
test('a whole-world bike-share search names every system it skipped', async () => {
|
||||
const empty = { data: { stations: [] } };
|
||||
const result = await composeCatalog({
|
||||
tools: coreTools,
|
||||
services: {
|
||||
bikeshare: {
|
||||
systems: GBFS_CITY_REGISTRY,
|
||||
getStations: async () => empty,
|
||||
},
|
||||
},
|
||||
}).call('get_bike_share', { area: { bbox: [-180, -90, 180, 90] } });
|
||||
const named = [...result.data.systems, ...result.data.systems_not_searched];
|
||||
assert.equal(named.length, GBFS_CITY_REGISTRY.length);
|
||||
});
|
||||
Reference in new issue
Block a user