From 607f9534656126ae02976c84c9bc8771fbb7bb4c Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Tue, 11 Mar 2025 21:20:21 +0100 Subject: [PATCH] add alternative formats for coordinate search: (#10805) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * zoom/x/y – copy/paste from an osm.org URL or web map with map-hash param (this also sets map zoom to the respective value) * x/y – like the above, but does not set zoom level * x y – where x and y are numbers in the user's locale's number format --- CHANGELOG.md | 2 ++ modules/ui/feature_list.js | 12 +++++++----- modules/util/units.js | 35 ++++++++++++++++++++++++++++------- test/spec/util/units.js | 22 ++++++++++++++++++++++ 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 936edd877..709981776 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ _Breaking developer changes, which may affect downstream projects or sites that # unreleased (v2.33.0-dev) #### :sparkles: Usability & Accessibility +* Allow searching for coordinates in localized number format in search box ([#10805]) #### :scissors: Operations #### :camera: Street-Level #### :white_check_mark: Validation @@ -49,6 +50,7 @@ _Breaking developer changes, which may affect downstream projects or sites that #### :mortar_board: Walkthrough / Help #### :hammer: Development +[#10805]: https://github.com/openstreetmap/iD/pull/10805 [#10299]: https://github.com/openstreetmap/iD/issues/10299 [#10843]: https://github.com/openstreetmap/iD/pull/10843 diff --git a/modules/ui/feature_list.js b/modules/ui/feature_list.js index 4077fc4c9..0571ae5ef 100644 --- a/modules/ui/feature_list.js +++ b/modules/ui/feature_list.js @@ -120,7 +120,7 @@ export function uiFeatureList(context) { var result = []; var graph = context.graph(); var visibleCenter = context.map().extent().center(); - var q = search.property('value').toLowerCase(); + var q = search.property('value').toLowerCase().trim(); if (!q) return result; @@ -132,8 +132,9 @@ export function uiFeatureList(context) { const isLatLonValid = latLon[0] >= -90 && latLon[0] <= 90 && latLon[1] >= -180 && latLon[1] <= 180; let isLonLatValid = lonLat[0] >= -90 && lonLat[0] <= 90 && lonLat[1] >= -180 && lonLat[1] <= 180; - isLonLatValid &&= !q.match(/[NSEW]/i); - isLonLatValid &&= lonLat[0] !== lonLat[1]; + isLonLatValid &&= !q.match(/[NSEW]/i); // don't flip coords with explicit cardinal directions + isLonLatValid &&= !locationMatch[2]; // don't flip zoom/x/y coords + isLonLatValid &&= lonLat[0] !== lonLat[1]; // don't flip when lat=lon if (isLatLonValid) { result.push({ @@ -141,7 +142,8 @@ export function uiFeatureList(context) { geometry: 'point', type: t('inspector.location'), name: dmsCoordinatePair([latLon[1], latLon[0]]), - location: latLon + location: latLon, + zoom: locationMatch[2] }); } if (isLonLatValid) { @@ -369,7 +371,7 @@ export function uiFeatureList(context) { d3_event.preventDefault(); if (d.location) { - context.map().centerZoomEase([d.location[1], d.location[0]], 19); + context.map().centerZoomEase([d.location[1], d.location[0]], d.zoom || 19); } else if (d.entity) { utilHighlightEntities([d.id], false, context); diff --git a/modules/util/units.js b/modules/util/units.js index 46ee437ab..7b1f21599 100644 --- a/modules/util/units.js +++ b/modules/util/units.js @@ -188,7 +188,7 @@ export function decimalCoordinatePair(coord) { // Return the parsed value that @mapbox/sexagesimal can't parse // return value format : [D, D] ex:[ 35.1861, 136.83161 ] -export function dmsMatcher(q) { +export function dmsMatcher(q, _localeCode = undefined) { const matchers = [ // D M SS , D M SS ex: 35 11 10.1 , 136 49 53.8 { @@ -199,9 +199,7 @@ export function dmsMatcher(q) { const lng = (+match[6]) + (+match[7]) / 60 + (+match[8]) / 3600; const isNegLat = match[1] === '-' ? -lat : lat; const isNegLng = match[5] === '-' ? -lng : lng; - const d = [isNegLat, isNegLng]; - - return d; + return [isNegLat, isNegLng]; } }, // D MM , D MM ex: 35 11.1683 , 136 49.8966 @@ -213,12 +211,35 @@ export function dmsMatcher(q) { const lng = +match[5] + (+match[6]) / 60; const isNegLat = match[1] === '-' ? -lat : lat; const isNegLng = match[4] === '-' ? -lng : lng; - const d = [isNegLat, isNegLng]; - - return d; + return [isNegLat, isNegLng]; } + }, + // zoom/x/y ex: 2/1.23/34.44 + { + condition: /^\s*(\d+\.?\d*)\s*\/\s*(-?\d+\.?\d*)\s*\/\s*(-?\d+\.?\d*)\s*$/, + parser: function(q) { + const match = this.condition.exec(q); + const lat = +match[2]; + const lng = +match[3]; + const zoom = +match[1]; + return [lat, lng, zoom]; + } + }, + // x/y , x, y , x y where x and y are localized floats, e.g. in German locale: 49,4109399, 8,7147086 + { + condition: { test: q => !!localizedNumberCoordsParser(q) }, + parser: localizedNumberCoordsParser } ]; + function localizedNumberCoordsParser(q, ) { + const parseLocaleFloat = localizer.floatParser(_localeCode || localizer.localeCode()); + let parts = q.split(/,?\s+|\s*[\/\\]\s*/); + if (parts.length !== 2) return false; + const lat = parseLocaleFloat(parts[0]); + const lng = parseLocaleFloat(parts[1]); + if (isNaN(lat) || isNaN(lng)) return false; + return [lat, lng]; + } for (const matcher of matchers) { if (matcher.condition.test(q)){ return matcher.parser(q); diff --git a/test/spec/util/units.js b/test/spec/util/units.js index 877635838..a2feaea7b 100644 --- a/test/spec/util/units.js +++ b/test/spec/util/units.js @@ -21,6 +21,28 @@ describe('iD.units', function() { expect(result[0]).to.be.closeTo( -35.18614, 0.00001); expect(result[1]).to.be.closeTo(-136.83161, 0.00001); }); + it('parses z/x/y coordinate', () => { + var result = iD.dmsMatcher('2/-1.23/34.44'); + expect(result[0]).to.be.closeTo(-1.23, 0.00001); + expect(result[1]).to.be.closeTo(34.44, 0.00001); + expect(result[2]).to.eql(2); + }); + it('parses x/y coordinate', () => { + var result = iD.dmsMatcher('-1.23/34.44'); + expect(result[0]).to.be.closeTo(-1.23, 0.00001); + expect(result[1]).to.be.closeTo(34.44, 0.00001); + }); + it('parses z/x/y coordinate', () => { + var result = iD.dmsMatcher('2/-1.23/34.44'); + expect(result[0]).to.be.closeTo(-1.23, 0.00001); + expect(result[1]).to.be.closeTo(34.44, 0.00001); + expect(result[2]).to.eql(2); + }); + it('parses coordinate with localized numbers', () => { + var result = iD.dmsMatcher('49,4109399, 8,7147086', 'de'); + expect(result[0]).to.be.closeTo(49.4109399, 0.00001); + expect(result[1]).to.be.closeTo( 8.7147086, 0.00001); + }); it('handles invalid input', function() { var result = iD.dmsMatcher('!@#$');