mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
feat(search): Nominatim as the last-resort geocoder (#573)
Supersedes #350 by @sendmebits, ported onto the current component layout with fixes from review. Verified before merge: format, package boundaries, unit suite, build, and the browser tracking suite; a browser harness for the change where one applies; adversarial review applied. Co-authored-by: sendmebits <[email protected]>
This commit is contained in:
1 parent
cca6442997
commit
7fa0cfbb26
15 files changed
+1077
-48
No files matched your search
@@ -0,0 +1,175 @@
|
||||
// NOMINATIM RESULT MAPPING — what counts as a usable hit.
|
||||
//
|
||||
// Every numeric field arrives as a string and may be absent or nonsense, and
|
||||
// `Number(null)` is 0: a hit with no coordinates read as (0, 0) would fly the
|
||||
// camera to the Atlantic and look like a successful search. These cases pin
|
||||
// every field that must be refused rather than coerced.
|
||||
//
|
||||
// Run with: npm test
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
nominatimLatitude,
|
||||
nominatimLongitude,
|
||||
nominatimToGeocodeResult,
|
||||
nominatimViewboxFromBounds,
|
||||
typesFromNominatim,
|
||||
viewportFromNominatim,
|
||||
} from './nominatimGeocode.js';
|
||||
|
||||
test('a coordinate field is a number in range, or nothing', () => {
|
||||
assert.equal(nominatimLatitude('43.1731'), 43.1731);
|
||||
assert.equal(nominatimLatitude(-33.8568), -33.8568);
|
||||
assert.equal(nominatimLatitude('90'), 90);
|
||||
assert.equal(nominatimLongitude('-180'), -180);
|
||||
|
||||
for (const value of [
|
||||
null,
|
||||
undefined,
|
||||
'',
|
||||
' ',
|
||||
'abc',
|
||||
NaN,
|
||||
Infinity,
|
||||
true,
|
||||
false,
|
||||
[],
|
||||
['43'],
|
||||
{},
|
||||
'91',
|
||||
'-90.5',
|
||||
])
|
||||
assert.equal(
|
||||
nominatimLatitude(value),
|
||||
null,
|
||||
`${JSON.stringify(value)} is not a latitude`,
|
||||
);
|
||||
|
||||
for (const value of [null, '', '181', '-180.5', true, []])
|
||||
assert.equal(
|
||||
nominatimLongitude(value),
|
||||
null,
|
||||
`${JSON.stringify(value)} is not a longitude`,
|
||||
);
|
||||
});
|
||||
|
||||
test('a hit without real coordinates produces no result at all', () => {
|
||||
for (const hit of [
|
||||
null,
|
||||
undefined,
|
||||
{},
|
||||
{ lat: null, lon: null, display_name: 'Nowhere' },
|
||||
{ lat: '', lon: '', display_name: 'Nowhere' },
|
||||
{ lat: '43.17', lon: null, display_name: 'Half a place' },
|
||||
{ lat: null, lon: '-79.03', display_name: 'Half a place' },
|
||||
{ lat: '91', lon: '0', display_name: 'Off the pole' },
|
||||
{ lat: '0', lon: '999', display_name: 'Off the world' },
|
||||
// Coordinates but no name: nothing to show the operator.
|
||||
{ lat: '43.17', lon: '-79.03' },
|
||||
{ lat: '43.17', lon: '-79.03', display_name: ' ' },
|
||||
])
|
||||
assert.equal(
|
||||
nominatimToGeocodeResult(hit),
|
||||
null,
|
||||
`${JSON.stringify(hit)} must not become a result`,
|
||||
);
|
||||
});
|
||||
|
||||
test('a real hit carries its point, label, types and box', () => {
|
||||
const result = nominatimToGeocodeResult({
|
||||
lat: '43.1731',
|
||||
lon: '-79.0384',
|
||||
display_name: 'Niagara Falls, Ontario, Canada',
|
||||
name: 'Niagara Falls',
|
||||
addresstype: 'city',
|
||||
boundingbox: ['43.0500', '43.1500', '-79.1500', '-79.0000'],
|
||||
});
|
||||
assert.deepEqual(result.geometry.location, { lat: 43.1731, lng: -79.0384 });
|
||||
assert.equal(result.formatted_address, 'Niagara Falls, Ontario, Canada');
|
||||
assert.deepEqual(result.types, ['locality', 'political']);
|
||||
assert.deepEqual(result.geometry.bounds, {
|
||||
southwest: { lat: 43.05, lng: -79.15 },
|
||||
northeast: { lat: 43.15, lng: -79 },
|
||||
});
|
||||
assert.equal(result.address_components[0].long_name, 'Niagara Falls');
|
||||
});
|
||||
|
||||
test('a box with an impossible or inverted edge is dropped, not framed', () => {
|
||||
for (const boundingbox of [
|
||||
undefined,
|
||||
[],
|
||||
['43.05', '43.15', '-79.15'],
|
||||
['43.05', '43.15', '-79.15', '999'],
|
||||
['43.05', '43.15', '-999', '-79.0'],
|
||||
['-91', '43.15', '-79.15', '-79.0'],
|
||||
['43.05', '91', '-79.15', '-79.0'],
|
||||
[null, '43.15', '-79.15', '-79.0'],
|
||||
['', '43.15', '-79.15', '-79.0'],
|
||||
// South above north is not a box.
|
||||
['43.15', '43.05', '-79.15', '-79.0'],
|
||||
]) {
|
||||
assert.equal(
|
||||
viewportFromNominatim({ boundingbox }),
|
||||
null,
|
||||
`${JSON.stringify(boundingbox)} must not become a viewport`,
|
||||
);
|
||||
const result = nominatimToGeocodeResult({
|
||||
lat: '43.1731',
|
||||
lon: '-79.0384',
|
||||
display_name: 'Somewhere',
|
||||
boundingbox,
|
||||
});
|
||||
assert.ok(result, 'the point itself still stands');
|
||||
assert.equal(
|
||||
result.geometry.bounds,
|
||||
undefined,
|
||||
'a place with no usable box is framed as a point',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('types fall back to a place rather than guessing a category', () => {
|
||||
assert.deepEqual(typesFromNominatim({ addresstype: 'country' }), [
|
||||
'country',
|
||||
'political',
|
||||
]);
|
||||
assert.deepEqual(typesFromNominatim({ class: 'highway', type: 'primary' }), [
|
||||
'route',
|
||||
]);
|
||||
assert.deepEqual(typesFromNominatim({}), [
|
||||
'point_of_interest',
|
||||
'establishment',
|
||||
]);
|
||||
assert.deepEqual(typesFromNominatim(null), [
|
||||
'point_of_interest',
|
||||
'establishment',
|
||||
]);
|
||||
});
|
||||
|
||||
test('a viewport hint is sent only when every edge is a real coordinate', () => {
|
||||
assert.equal(
|
||||
nominatimViewboxFromBounds('30.1,-97.95|30.52,-97.55'),
|
||||
'-97.95,30.52,-97.55,30.1',
|
||||
);
|
||||
|
||||
for (const bounds of [
|
||||
null,
|
||||
undefined,
|
||||
'',
|
||||
'not bounds',
|
||||
'30.1,-97.95',
|
||||
// Out of range on each edge in turn.
|
||||
'-91,-97.95|30.52,-97.55',
|
||||
'30.1,-999|30.52,-97.55',
|
||||
'30.1,-97.95|91,-97.55',
|
||||
'30.1,-97.95|30.52,999',
|
||||
// Inverted latitudes are not a box.
|
||||
'30.52,-97.95|30.1,-97.55',
|
||||
'30.1,-97.95|30.1,-97.55',
|
||||
])
|
||||
assert.equal(
|
||||
nominatimViewboxFromBounds(bounds),
|
||||
null,
|
||||
`${JSON.stringify(bounds)} must not become a viewbox`,
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user