Files
gods-eye-view/src/nominatimGeocode.test.mjs
T
Bilawal Sidhuandsendmebits 7fa0cfbb26 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]>
2026-09-14 21:22:08 -05:00

176 lines
5.0 KiB
JavaScript

// 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`,
);
});