From 6c62b50ff0a872354d12f82fab227c43c73f245d Mon Sep 17 00:00:00 2001 From: RAM PRASAD ATLURI <116977270+ATLURI0001@users.noreply.github.com> Date: Tue, 10 Nov 2026 09:22:18 +0530 Subject: [PATCH] fix(geocode): expand feature type mappings, tolerate whitespace in bounds, and add tests --- src/nominatimGeocode.js | 22 ++++++++++++++++++---- src/nominatimGeocode.test.mjs | 17 +++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/nominatimGeocode.js b/src/nominatimGeocode.js index b7dd614..e506ca3 100644 --- a/src/nominatimGeocode.js +++ b/src/nominatimGeocode.js @@ -16,6 +16,7 @@ const ADDRESS_TYPE_MAP = Object.freeze({ region: ['administrative_area_level_1', 'political'], county: ['administrative_area_level_2', 'political'], district: ['administrative_area_level_2', 'political'], + subdivision: ['administrative_area_level_2', 'political'], municipality: ['locality', 'political'], city: ['locality', 'political'], town: ['locality', 'political'], @@ -24,6 +25,7 @@ const ADDRESS_TYPE_MAP = Object.freeze({ suburb: ['neighborhood', 'political'], neighbourhood: ['neighborhood', 'political'], neighborhood: ['neighborhood', 'political'], + borough: ['sublocality', 'sublocality_level_1', 'political'], quarter: ['neighborhood', 'political'], postcode: ['postal_code'], road: ['route'], @@ -39,6 +41,17 @@ const ADDRESS_TYPE_MAP = Object.freeze({ university: ['university'], stadium: ['stadium'], island: ['natural_feature'], + harbour: ['natural_feature'], + bay: ['natural_feature'], + river: ['natural_feature'], + lake: ['natural_feature'], + sea: ['natural_feature'], + ocean: ['natural_feature'], + volcano: ['natural_feature'], + hotel: ['lodging', 'point_of_interest', 'establishment'], + motel: ['lodging', 'point_of_interest', 'establishment'], + museum: ['museum', 'point_of_interest', 'establishment'], + hospital: ['hospital', 'health', 'point_of_interest', 'establishment'], }); /** @@ -80,6 +93,8 @@ export function typesFromNominatim(hit) { ADDRESS_TYPE_MAP[addresstype] || ADDRESS_TYPE_MAP[osmType] || (osmClass === 'highway' ? ['route'] : null) || + (osmClass === 'railway' ? ['transit_station'] : null) || + (osmClass === 'boundary' ? ['political'] : null) || (osmClass === 'leisure' ? ['park'] : null) || (osmClass === 'natural' ? ['natural_feature'] : null) || (osmClass === 'aeroway' ? ['airport'] : null) || @@ -147,9 +162,8 @@ export function nominatimToGeocodeResult(hit) { * Translate a `swLat,swLng|neLat,neLng` bias into Nominatim's * `viewbox=west,north,east,south`, or null when the string is unusable. * - * Both edges are range-checked. A viewbox naming a longitude of 999 is not a - * hint the upstream can use, and sending it is a malformed request rather than - * a soft one. + * Both edges are range-checked. Tolerates whitespace around delimiter commas + * and pipe symbols. * * @param {string} bounds * @returns {string|null} @@ -158,7 +172,7 @@ export function nominatimViewboxFromBounds(bounds) { const match = String(bounds || '') .trim() .match( - /^(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)\|(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)$/, + /^(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\|\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)$/, ); if (!match) return null; const swLat = nominatimLatitude(match[1]); diff --git a/src/nominatimGeocode.test.mjs b/src/nominatimGeocode.test.mjs index b139ad8..55c8212 100644 --- a/src/nominatimGeocode.test.mjs +++ b/src/nominatimGeocode.test.mjs @@ -136,6 +136,19 @@ test('types fall back to a place rather than guessing a category', () => { assert.deepEqual(typesFromNominatim({ class: 'highway', type: 'primary' }), [ 'route', ]); + assert.deepEqual(typesFromNominatim({ addresstype: 'borough' }), [ + 'sublocality', + 'sublocality_level_1', + 'political', + ]); + assert.deepEqual(typesFromNominatim({ addresstype: 'museum' }), [ + 'museum', + 'point_of_interest', + 'establishment', + ]); + assert.deepEqual(typesFromNominatim({ class: 'railway', type: 'station' }), [ + 'transit_station', + ]); assert.deepEqual(typesFromNominatim({}), [ 'point_of_interest', 'establishment', @@ -151,6 +164,10 @@ test('a viewport hint is sent only when every edge is a real coordinate', () => nominatimViewboxFromBounds('30.1,-97.95|30.52,-97.55'), '-97.95,30.52,-97.55,30.1', ); + assert.equal( + nominatimViewboxFromBounds(' 30.1 , -97.95 | 30.52 , -97.55 '), + '-97.95,30.52,-97.55,30.1', + ); for (const bounds of [ null,