mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
fix: center named places on their own location
A place's bounds can reach far from the place itself; Tokyo's include remote Pacific islands, so the middle of its box is open ocean. Keep the geocoder's point for a named place and use it as the area's center, while bounds still decide what lies inside the area.
This commit is contained in:
1 parent
469b1e8d89
commit
0dbd6375fc
3 files changed
+38
-5
No files matched your search
+10
-2
@@ -75,7 +75,11 @@ export async function resolveArea(area, { services, signal } = {}) {
|
|||||||
'invalid_arguments',
|
'invalid_arguments',
|
||||||
`No place matched "${area.place}"; try a more specific name or a bbox`,
|
`No place matched "${area.place}"; try a more specific name or a bbox`,
|
||||||
);
|
);
|
||||||
return { label: place.name || area.place, ...place.bounds };
|
return {
|
||||||
|
label: place.name || area.place,
|
||||||
|
...place.bounds,
|
||||||
|
...(place.point ? { point: place.point } : {}),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function circleArea(lat, lon, radiusKm) {
|
function circleArea(lat, lon, radiusKm) {
|
||||||
@@ -151,9 +155,13 @@ export function distanceKm(a, b) {
|
|||||||
return 2 * EARTH_RADIUS_KM * Math.asin(Math.min(1, Math.sqrt(h)));
|
return 2 * EARTH_RADIUS_KM * Math.asin(Math.min(1, Math.sqrt(h)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The center of a resolved area, handling boxes across the antimeridian. */
|
/**
|
||||||
|
* The center of a resolved area: a radius area's center, a named place's own
|
||||||
|
* point, or the middle of a box (handling boxes across the antimeridian).
|
||||||
|
*/
|
||||||
export function areaCenter(area) {
|
export function areaCenter(area) {
|
||||||
if (area.center) return { lat: area.center.lat, lon: area.center.lon };
|
if (area.center) return { lat: area.center.lat, lon: area.center.lon };
|
||||||
|
if (area.point) return { lat: area.point.lat, lon: area.point.lon };
|
||||||
const width =
|
const width =
|
||||||
area.west <= area.east
|
area.west <= area.east
|
||||||
? area.east - area.west
|
? area.east - area.west
|
||||||
|
|||||||
+18
-1
@@ -1,6 +1,6 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import { areaContains, distanceKm, resolveArea } from './area.js';
|
import { areaCenter, areaContains, distanceKm, resolveArea } from './area.js';
|
||||||
import { placeFromGeocodeResult } from './places.js';
|
import { placeFromGeocodeResult } from './places.js';
|
||||||
|
|
||||||
test('an area needs exactly one form', async () => {
|
test('an area needs exactly one form', async () => {
|
||||||
@@ -91,6 +91,7 @@ test('geocode results become bounded places', () => {
|
|||||||
{
|
{
|
||||||
name: 'Oslo, Norway',
|
name: 'Oslo, Norway',
|
||||||
bounds: { west: 10.5, south: 59.8, east: 10.9, north: 60 },
|
bounds: { west: 10.5, south: 59.8, east: 10.9, north: 60 },
|
||||||
|
point: { lat: 59.9, lon: 10.7 },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
@@ -100,3 +101,19 @@ test('geocode results become bounded places', () => {
|
|||||||
);
|
);
|
||||||
assert.equal(placeFromGeocodeResult({ geometry: {} }), null);
|
assert.equal(placeFromGeocodeResult({ geometry: {} }), null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a named place centers on its own point, not the middle of its bounds', async () => {
|
||||||
|
// Tokyo's prefecture bounds reach remote Pacific islands.
|
||||||
|
const places = {
|
||||||
|
resolve: async () => ({
|
||||||
|
name: 'Tokyo, Japan',
|
||||||
|
bounds: { west: 136.07, south: 20.21, east: 153.99, north: 35.9 },
|
||||||
|
point: { lat: 35.6769, lon: 139.7639 },
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const tokyo = await resolveArea({ place: 'Tokyo' }, { services: { places } });
|
||||||
|
assert.deepEqual(areaCenter(tokyo), { lat: 35.6769, lon: 139.7639 });
|
||||||
|
assert.equal(areaContains(tokyo, { lat: 27.09, lon: 142.19 }), true);
|
||||||
|
const box = await resolveArea({ bbox: [0, 0, 10, 10] });
|
||||||
|
assert.deepEqual(areaCenter(box), { lat: 5, lon: 5 });
|
||||||
|
});
|
||||||
+10
-2
@@ -13,7 +13,11 @@ import { ToolError } from './catalog.js';
|
|||||||
|
|
||||||
const POINT_RADIUS_DEGREES = 0.25;
|
const POINT_RADIUS_DEGREES = 0.25;
|
||||||
|
|
||||||
/** Resolve place names to `{ name, bounds: { west, south, east, north } }` or null. */
|
/**
|
||||||
|
* Resolve place names to `{ name, bounds: { west, south, east, north }, point }`
|
||||||
|
* or null. `point` is the place's own location, which can lie far from the
|
||||||
|
* middle of its bounds (a region with remote islands, for example).
|
||||||
|
*/
|
||||||
export function createGeocodePlaceService({
|
export function createGeocodePlaceService({
|
||||||
fetchImpl = (...args) => globalThis.fetch(...args),
|
fetchImpl = (...args) => globalThis.fetch(...args),
|
||||||
} = {}) {
|
} = {}) {
|
||||||
@@ -54,7 +58,11 @@ export function placeFromGeocodeResult(result) {
|
|||||||
east: Math.min(180, location.lng + POINT_RADIUS_DEGREES),
|
east: Math.min(180, location.lng + POINT_RADIUS_DEGREES),
|
||||||
north: Math.min(90, location.lat + POINT_RADIUS_DEGREES),
|
north: Math.min(90, location.lat + POINT_RADIUS_DEGREES),
|
||||||
};
|
};
|
||||||
return { name: result.formatted_address || null, bounds };
|
return {
|
||||||
|
name: result.formatted_address || null,
|
||||||
|
bounds,
|
||||||
|
point: { lat: location.lat, lon: location.lng },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Search points of interest; `configured` is false when the server has no search key. */
|
/** Search points of interest; `configured` is false when the server has no search key. */
|
||||||
|
|||||||
Reference in new issue
Block a user