From bbda876b06cbb3b120438a5e94dc876e11ddbf2f Mon Sep 17 00:00:00 2001 From: "J. W. Kaltz" Date: Wed, 27 May 2026 10:47:26 +0200 Subject: [PATCH 1/5] make Nominatim host and protocol configurable via search service options --- web/client/api/Nominatim.js | 21 +-- web/client/api/__tests__/Nominatim-test.js | 167 +++++++++++++++++++++ 2 files changed, 178 insertions(+), 10 deletions(-) create mode 100644 web/client/api/__tests__/Nominatim-test.js diff --git a/web/client/api/Nominatim.js b/web/client/api/Nominatim.js index 8c48c89a5f7..684797403a9 100644 --- a/web/client/api/Nominatim.js +++ b/web/client/api/Nominatim.js @@ -1,15 +1,15 @@ /** - * Copyright 2015, GeoSolutions Sas. + * Copyright 2015-2026, GeoSolutions Sas. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ import axios from '../libs/ajax'; - import urlUtil from 'url'; const DEFAULT_URL = 'nominatim.openstreetmap.org'; const DEFAULT_REVERSE_URL = 'nominatim.openstreetmap.org/reverse'; +const DEFAULT_PROTOCOL = 'https'; const defaultOptions = { format: 'json', bounded: 0, @@ -21,23 +21,24 @@ const defaultOptions = { */ const Api = { geocode: function(text, options) { - var params = Object.assign({q: text}, defaultOptions, options || {}); + const {host, protocol, ...queryOptions} = options || {}; + var params = Object.assign({q: text}, defaultOptions, queryOptions); var url = urlUtil.format({ - protocol: "https", - host: DEFAULT_URL, + protocol: protocol || DEFAULT_PROTOCOL, + host: host || DEFAULT_URL, query: params }); - return axios.get(url); // TODO the jsonp method returns .promise and .cancel method,the last can be called when user cancel the query + return axios.get(url); }, reverseGeocode: function(coords, options) { - const params = Object.assign({lat: coords.lat, lon: coords.lng}, options || {}, defaultOptions); + const {host, protocol, ...queryOptions} = options || {}; + const params = Object.assign({lat: coords.lat, lon: coords.lng}, queryOptions, defaultOptions); const url = urlUtil.format({ - protocol: "https", - host: DEFAULT_REVERSE_URL, + protocol: protocol || DEFAULT_PROTOCOL, + host: host ? host + '/reverse' : DEFAULT_REVERSE_URL, query: params }); return axios.get(url); } }; - export default Api; diff --git a/web/client/api/__tests__/Nominatim-test.js b/web/client/api/__tests__/Nominatim-test.js new file mode 100644 index 00000000000..ecc3bac3ebe --- /dev/null +++ b/web/client/api/__tests__/Nominatim-test.js @@ -0,0 +1,167 @@ +/* + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +import Nominatim from '../Nominatim'; +import expect from 'expect'; +import axios from '../../libs/ajax'; +import MockAdapter from 'axios-mock-adapter'; + +let mockAxios; + +describe('Test Nominatim API', () => { + beforeEach(done => { + mockAxios = new MockAdapter(axios); + setTimeout(done); + }); + afterEach(done => { + mockAxios.restore(); + setTimeout(done); + }); + + describe('geocode', () => { + it('should use default host and protocol when no options provided', (done) => { + mockAxios.onGet().reply(config => { + try { + expect(config.url).toContain('nominatim.openstreetmap.org'); + expect(config.url).toContain('https'); + } catch (e) { + done(e); + } + return [200, []]; + }); + Nominatim.geocode('Bern').then(() => done()).catch(done); + }); + + it('should use custom host from options', (done) => { + mockAxios.onGet().reply(config => { + try { + expect(config.url).toContain('my-nominatim.example.com'); + expect(config.url).toNotContain('nominatim.openstreetmap.org'); + } catch (e) { + done(e); + } + return [200, []]; + }); + Nominatim.geocode('Bern', {host: 'my-nominatim.example.com'}).then(() => done()).catch(done); + }); + + it('should use custom protocol from options', (done) => { + mockAxios.onGet().reply(config => { + try { + expect(config.url).toContain('http://'); + expect(config.url).toNotContain('https://'); + } catch (e) { + done(e); + } + return [200, []]; + }); + Nominatim.geocode('Bern', {protocol: 'http'}).then(() => done()).catch(done); + }); + + it('should use custom host and protocol from options', (done) => { + mockAxios.onGet().reply(config => { + try { + expect(config.url).toContain('http://'); + expect(config.url).toContain('my-nominatim.example.com'); + } catch (e) { + done(e); + } + return [200, []]; + }); + Nominatim.geocode('Bern', { + host: 'my-nominatim.example.com', + protocol: 'http' + }).then(() => done()).catch(done); + }); + + it('should not pass host and protocol as query parameters', (done) => { + mockAxios.onGet().reply(config => { + try { + expect(config.url).toNotContain('host='); + expect(config.url).toNotContain('protocol='); + } catch (e) { + done(e); + } + return [200, []]; + }); + Nominatim.geocode('Bern', { + host: 'my-nominatim.example.com', + protocol: 'http' + }).then(() => done()).catch(done); + }); + + it('should pass other options as query parameters', (done) => { + mockAxios.onGet().reply(config => { + try { + expect(config.url).toContain('limit=2'); + expect(config.url).toContain('polygon_geojson=1'); + } catch (e) { + done(e); + } + return [200, []]; + }); + Nominatim.geocode('Bern', {limit: 2, polygon_geojson: 1}).then(() => done()).catch(done); + }); + }); + + describe('reverseGeocode', () => { + it('should use default host and protocol when no options provided', (done) => { + mockAxios.onGet().reply(config => { + try { + expect(config.url).toContain('nominatim.openstreetmap.org/reverse'); + expect(config.url).toContain('https'); + } catch (e) { + done(e); + } + return [200, {}]; + }); + Nominatim.reverseGeocode({lat: 46.7, lng: 7.6}).then(() => done()).catch(done); + }); + + it('should use custom host for reverse geocode', (done) => { + mockAxios.onGet().reply(config => { + try { + expect(config.url).toContain('my-nominatim.example.com/reverse'); + expect(config.url).toNotContain('nominatim.openstreetmap.org'); + } catch (e) { + done(e); + } + return [200, {}]; + }); + Nominatim.reverseGeocode({lat: 46.7, lng: 7.6}, { + host: 'my-nominatim.example.com' + }).then(() => done()).catch(done); + }); + + it('should use custom protocol for reverse geocode', (done) => { + mockAxios.onGet().reply(config => { + try { + expect(config.url).toContain('http://'); + } catch (e) { + done(e); + } + return [200, {}]; + }); + Nominatim.reverseGeocode({lat: 46.7, lng: 7.6}, { + protocol: 'http' + }).then(() => done()).catch(done); + }); + + it('should not pass host and protocol as query parameters in reverse geocode', (done) => { + mockAxios.onGet().reply(config => { + try { + expect(config.url).toNotContain('host='); + expect(config.url).toNotContain('protocol='); + } catch (e) { + done(e); + } + return [200, {}]; + }); + Nominatim.reverseGeocode({lat: 46.7, lng: 7.6}, { + host: 'my-nominatim.example.com', + protocol: 'http' + }).then(() => done()).catch(done); + }); + }); +}); From f7856413cc74ee362217954825bbbeb0c3515236 Mon Sep 17 00:00:00 2001 From: "J. W. Kaltz" Date: Mon, 1 Jun 2026 15:23:40 +0200 Subject: [PATCH 2/5] Use const instead of var (from review comment) --- web/client/api/Nominatim.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/client/api/Nominatim.js b/web/client/api/Nominatim.js index 684797403a9..aa536f9a348 100644 --- a/web/client/api/Nominatim.js +++ b/web/client/api/Nominatim.js @@ -22,8 +22,8 @@ const defaultOptions = { const Api = { geocode: function(text, options) { const {host, protocol, ...queryOptions} = options || {}; - var params = Object.assign({q: text}, defaultOptions, queryOptions); - var url = urlUtil.format({ + const params = Object.assign({q: text}, defaultOptions, queryOptions); + const url = urlUtil.format({ protocol: protocol || DEFAULT_PROTOCOL, host: host || DEFAULT_URL, query: params From 71fe9fc17e624680dcfc0861b6b04b67991536b5 Mon Sep 17 00:00:00 2001 From: "J. W. Kaltz" Date: Mon, 1 Jun 2026 15:56:35 +0200 Subject: [PATCH 3/5] Add test to cover correct usage of queryOptions --- web/client/api/__tests__/Nominatim-test.js | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/web/client/api/__tests__/Nominatim-test.js b/web/client/api/__tests__/Nominatim-test.js index ecc3bac3ebe..c10cf0b2c45 100644 --- a/web/client/api/__tests__/Nominatim-test.js +++ b/web/client/api/__tests__/Nominatim-test.js @@ -6,6 +6,7 @@ import Nominatim from '../Nominatim'; import expect from 'expect'; import axios from '../../libs/ajax'; import MockAdapter from 'axios-mock-adapter'; +import urlUtil from 'url'; let mockAxios; @@ -103,6 +104,22 @@ describe('Test Nominatim API', () => { }); Nominatim.geocode('Bern', {limit: 2, polygon_geojson: 1}).then(() => done()).catch(done); }); + + it('should allow overriding default options in geocode', (done) => { + mockAxios.onGet().reply(config => { + try { + const parsed = urlUtil.parse(config.url, true); + expect(parsed.query.priority).toBe('99'); + } catch (e) { + done(e); + } + return [200, []]; + }); + Nominatim.geocode('Bern', { + priority: 99 + }).then(() => done()).catch(done); + }); + }); describe('reverseGeocode', () => { @@ -163,5 +180,21 @@ describe('Test Nominatim API', () => { protocol: 'http' }).then(() => done()).catch(done); }); + + it('should allow overriding default options in reverse geocode', (done) => { + mockAxios.onGet().reply(config => { + try { + const parsed = urlUtil.parse(config.url, true); + expect(parsed.query.priority).toBe('99'); + } catch (e) { + done(e); + } + return [200, {}]; + }); + Nominatim.reverseGeocode({lat: 46.7, lng: 7.6}, { + priority: 99 + }).then(() => done()).catch(done); + }); + }); }); From 3967adaae2a36e79ff967d63c34e1894133be848 Mon Sep 17 00:00:00 2001 From: "J. W. Kaltz" Date: Mon, 1 Jun 2026 15:58:00 +0200 Subject: [PATCH 4/5] Correct the order of the parameters (as per review comment) --- web/client/api/Nominatim.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/client/api/Nominatim.js b/web/client/api/Nominatim.js index aa536f9a348..da4fcaf873f 100644 --- a/web/client/api/Nominatim.js +++ b/web/client/api/Nominatim.js @@ -32,7 +32,7 @@ const Api = { }, reverseGeocode: function(coords, options) { const {host, protocol, ...queryOptions} = options || {}; - const params = Object.assign({lat: coords.lat, lon: coords.lng}, queryOptions, defaultOptions); + const params = Object.assign({lat: coords.lat, lon: coords.lng}, defaultOptions, queryOptions); const url = urlUtil.format({ protocol: protocol || DEFAULT_PROTOCOL, host: host ? host + '/reverse' : DEFAULT_REVERSE_URL, From 89a35f781922da3023c35fbcce840fc2b53c33f3 Mon Sep 17 00:00:00 2001 From: "J. W. Kaltz" Date: Tue, 2 Jun 2026 07:14:36 +0200 Subject: [PATCH 5/5] Add documentation for new options --- docs/developer-guide/local-config.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/developer-guide/local-config.md b/docs/developer-guide/local-config.md index c439ce8b8fe..0f0b775b0b3 100644 --- a/docs/developer-guide/local-config.md +++ b/docs/developer-guide/local-config.md @@ -486,8 +486,11 @@ Nominatim configuration: "type": "nominatim", "searchTextTemplate": "${properties.display_name}", // text to use as searchText when an item is selected. Gets the result properties. "options": { - "polygon_geojson": 1, - "limit": 3 + "polygon_geojson": 1, // example parameter to be passed on to the Nominatim API, consult Nominatim documentation for further information + "limit": 3, // further example parameter to be passed on to the Nominatim API + "host": "my-nominatim.example.com", // optional custom Nominatim hostname + "protocol": "https" // optional protocol to use for the call to Nominatim API ('https' or 'http') + } } ```