mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
fix(search): normalize geocoder bias across the antimeridian (#1272)
Keep geocoder viewport bias near the antimeridian and omit misleading wrapped viewboxes. Contribution by @asturksever.
This commit is contained in:
1 parent
ba292ce475
commit
a43ff8619e
6 files changed
+50
-7
No files matched your search
@@ -0,0 +1 @@
|
||||
- Normalize geocoder bias across the antimeridian (asturksever, #1272).
|
||||
@@ -5197,9 +5197,14 @@ HTTP refusals and outages remain retryable. Searches share a 12-second total
|
||||
deadline, with Photon requests capped at six seconds each. Caller/application
|
||||
cancellation stops retries and late cache writes. Replacing a location search
|
||||
cancels the previous lookup; disposing its controls cancels the active lookup. Photon uses a soft proximity bias, up to five
|
||||
candidates, and an unbiased retry for name mismatches. Invalid/wrapped bounds
|
||||
candidates, and an unbiased retry for name mismatches. Invalid or wrapped bounds returned by providers
|
||||
are omitted rather than framing the wrong part of the globe.
|
||||
|
||||
For request viewport bias, Photon unwraps antimeridian-crossing longitude bounds
|
||||
before computing their midpoint, then wraps that midpoint back into ±180 degrees.
|
||||
Malformed viewport bounds add no proximity bias. Nominatim omits a crossing
|
||||
request viewbox and continues searching without that geographic preference.
|
||||
|
||||
### Map Stack Switcher (June 2026)
|
||||
|
||||
- `src/mapStackController.js` switches between Google Photorealistic 3D (`photoreal`, the default when a Google or ion key is present), keyless Esri World Imagery (the zero-key default landing, with keyless terrain), Bing Aerial / Aerial-with-Labels via Cesium ion world imagery (require `CESIUM_ION_TOKEN`), and OSM tile fallback. Bing Road is **retired**: it is gone from `MAP_STACKS`, from the `set_map_stack` enum, and from the voice aliases (road phrasings now resolve to OSM, the one shipped road basemap). An old `map=bing-road` link is simply an unknown id and takes `setStack()`'s existing photoreal fallback with the Google 3D tile lit — pinned live in `scripts/qa-map-source-tray.mjs`.
|
||||
|
||||
+20
-5
@@ -206,12 +206,27 @@ export function photonSearchUrl(
|
||||
url.searchParams.set('limit', String(limit));
|
||||
|
||||
const corners = String(bias ?? '').split('|');
|
||||
if (corners.length === 2) {
|
||||
const [swLat, swLng] = corners[0].split(',').map(Number);
|
||||
const [neLat, neLng] = corners[1].split(',').map(Number);
|
||||
if ([swLat, swLng, neLat, neLng].every(Number.isFinite)) {
|
||||
const tokens = corners.flatMap((corner) => corner.split(','));
|
||||
if (
|
||||
corners.length === 2 &&
|
||||
corners.every((corner) => corner.split(',').length === 2) &&
|
||||
tokens.every((token) => token.trim() !== '')
|
||||
) {
|
||||
const [swLat, swLng, neLat, neLng] = tokens.map(Number);
|
||||
if (
|
||||
[swLat, swLng, neLat, neLng].every(Number.isFinite) &&
|
||||
Math.abs(swLat) <= 90 &&
|
||||
Math.abs(neLat) <= 90 &&
|
||||
swLat <= neLat &&
|
||||
Math.abs(swLng) <= 180 &&
|
||||
Math.abs(neLng) <= 180
|
||||
) {
|
||||
const midpoint = (swLng + neLng + (swLng > neLng ? 360 : 0)) / 2;
|
||||
url.searchParams.set('lat', String((swLat + neLat) / 2));
|
||||
url.searchParams.set('lon', String((swLng + neLng) / 2));
|
||||
url.searchParams.set(
|
||||
'lon',
|
||||
String(midpoint > 180 ? midpoint - 360 : midpoint),
|
||||
);
|
||||
}
|
||||
}
|
||||
return url.toString();
|
||||
|
||||
@@ -452,3 +452,14 @@ function readGeocodingConsumer(file) {
|
||||
? [file, 'annotations/resolver.js'] : [file];
|
||||
return files.map((name) => fs.readFileSync(path.join(ROOT, 'src', name), 'utf8')).join('\n');
|
||||
}
|
||||
|
||||
|
||||
test('wrapped view bias stays near the antimeridian and malformed coordinates are omitted', () => {
|
||||
for (const [bias, expected] of [['-20,170|-10,-171', 179.5], ['50,175|60,-165', -175], ['0,180|10,-180', 180], ['0,-180|10,180', 0]]) {
|
||||
const params = new URL(photonSearchUrl('island', { bias })).searchParams;
|
||||
assert.equal(Number(params.get('lon')), expected);
|
||||
}
|
||||
for (const bias of ['-91,0|0,1', '0,181|1,0', '10,0|0,1', ',|,', '0,1,2|3,4', '0,1|Infinity,2']) {
|
||||
assert.equal(new URL(photonSearchUrl('island', { bias })).searchParams.has('lon'), false);
|
||||
}
|
||||
});
|
||||
@@ -166,6 +166,8 @@ export function nominatimViewboxFromBounds(bounds) {
|
||||
const neLat = nominatimLatitude(match[3]);
|
||||
const neLng = nominatimLongitude(match[4]);
|
||||
if ([swLat, swLng, neLat, neLng].some((value) => value === null)) return null;
|
||||
if (swLat >= neLat) return null;
|
||||
// One Nominatim viewbox cannot express a wrapped interval. Omit this soft
|
||||
// preference rather than biasing the search toward the opposite hemisphere.
|
||||
if (swLat >= neLat || swLng > neLng) return null;
|
||||
return `${swLng},${neLat},${neLng},${swLat}`;
|
||||
}
|
||||
@@ -173,3 +173,12 @@ test('a viewport hint is sent only when every edge is a real coordinate', () =>
|
||||
`${JSON.stringify(bounds)} must not become a viewbox`,
|
||||
);
|
||||
});
|
||||
|
||||
test('wrapped viewports omit the Nominatim bias without rejecting the search', () => {
|
||||
assert.equal(nominatimViewboxFromBounds('-20,170|-10,-171'), null);
|
||||
assert.equal(nominatimViewboxFromBounds('50,175|60,-165'), null);
|
||||
assert.equal(
|
||||
nominatimViewboxFromBounds('-20,170|-10,180'),
|
||||
'170,-10,180,-20',
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user