diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 6bb905049..c469039b7 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -16,6 +16,6 @@ jobs: with: check_filenames: true skip: ./.git,./data/territory_languages.json,./data/imagery.json,./data/languages.json,./data/address_formats.json,./dist/locales,./docs/img,./dist/img - ignore_words_list: "auxilary,casette,cemetary,chancel,childs,extentions,falsy,files',froms,generat,guerilla,inflight,kindergarden,nd,specialties,tos,vias,visibles" + ignore_words_list: "auxilary,casette,cemetary,chancel,childs,extentions,falsy,files',fillL,froms,generat,guerilla,inflight,kindergarden,nd,ot,pavillion,specialties,tos,vias,visibles" exclude_file: .codespellignorelines only_warn: 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index bf2451eff..c27256faf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ _Breaking developer changes, which may affect downstream projects or sites that * Hide tag suggestions for tags like `name_1` in raw tag editor autocomplete ([#9422]) * Show `(empty)` as a tag value option in the raw tag editor when a multi selections contains at least one feature which does not have the particular tag ([#9876], thanks [@k-yle]) * Allow to search for OSM notes by id in search bar ([#10062], thanks [@NaVis0mple]) +* Add support for coordinates in ` [ ]` format to search bar ([#10066], thanks [@NaVis0mple]) #### :scissors: Operations #### :camera: Street-Level #### :white_check_mark: Validation @@ -66,6 +67,7 @@ _Breaking developer changes, which may affect downstream projects or sites that [#9983]: https://github.com/openstreetmap/iD/issues/9983 [#9992]: https://github.com/openstreetmap/iD/issues/9992 [#10062]: https://github.com/openstreetmap/iD/pull/10062 +[#10066]: https://github.com/openstreetmap/iD/pull/10066 [id-tagging-schema#1076]: https://github.com/openstreetmap/id-tagging-schema/pull/1076 [@ramith-kulal]: https://github.com/ramith-kulal [@mangerlahn]: https://github.com/mangerlahn diff --git a/modules/ui/feature_list.js b/modules/ui/feature_list.js index 31b7cdc04..b57747294 100644 --- a/modules/ui/feature_list.js +++ b/modules/ui/feature_list.js @@ -5,7 +5,7 @@ import * as sexagesimal from '@mapbox/sexagesimal'; import { presetManager } from '../presets'; import { t } from '../core/localizer'; -import { dmsCoordinatePair } from '../util/units'; +import { dmsCoordinatePair, dmsMatcher } from '../util/units'; import { coreGraph } from '../core/graph'; import { geoSphericalDistance } from '../geo/geo'; import { geoExtent } from '../geo'; @@ -125,7 +125,7 @@ export function uiFeatureList(context) { if (!q) return result; - var locationMatch = sexagesimal.pair(q.toUpperCase()) || q.match(/^(-?\d+\.?\d*)\s+(-?\d+\.?\d*)$/); + var locationMatch = sexagesimal.pair(q.toUpperCase()) || dmsMatcher(q); if (locationMatch) { var loc = [Number(locationMatch[0]), Number(locationMatch[1])]; diff --git a/modules/util/index.js b/modules/util/index.js index 1aebc3adb..cfa2f3c48 100644 --- a/modules/util/index.js +++ b/modules/util/index.js @@ -55,3 +55,6 @@ export { utilUnicodeCharsTruncated } from './util'; export { utilUniqueDomId } from './util'; export { utilWrap } from './util'; export { utilCleanOsmString } from './util'; + +export { dmsCoordinatePair } from './units'; +export { dmsMatcher } from './units'; diff --git a/modules/util/units.js b/modules/util/units.js index d49724d03..46ee437ab 100644 --- a/modules/util/units.js +++ b/modules/util/units.js @@ -106,33 +106,50 @@ function clamp(x, min, max) { return Math.max(min, Math.min(x, max)); } -function displayCoordinate(deg, pos, neg) { - var locale = localizer.localeCode(); - var min = (Math.abs(deg) - Math.floor(Math.abs(deg))) * 60; - var sec = (min - Math.floor(min)) * 60; - var displayDegrees = t('units.arcdegrees', { - quantity: Math.floor(Math.abs(deg)).toLocaleString(locale) - }); - var displayCoordinate; +function roundToDecimal (target, decimalPlace) { + target = Number(target); + decimalPlace = Number(decimalPlace); + const factor = Math.pow(10, decimalPlace); + return Math.round(target * factor) / factor; +} - if (Math.floor(sec) > 0) { - displayCoordinate = displayDegrees + - t('units.arcminutes', { - quantity: Math.floor(min).toLocaleString(locale) - }) + - t('units.arcseconds', { - quantity: Math.round(sec).toLocaleString(locale) - }); - } else if (Math.floor(min) > 0) { - displayCoordinate = displayDegrees + - t('units.arcminutes', { - quantity: Math.round(min).toLocaleString(locale) - }); - } else { - displayCoordinate = t('units.arcdegrees', { - quantity: Math.round(Math.abs(deg)).toLocaleString(locale) - }); +function displayCoordinate(deg, pos, neg) { + var displayCoordinate; + var locale = localizer.localeCode(); + + var degreesFloor = Math.floor(Math.abs(deg)); + var min = (Math.abs(deg) - degreesFloor) * 60; + var minFloor = Math.floor(min); + var sec = (min - minFloor) * 60; + + + // if you input 45°,90°0'0.5" , sec should be 0.5 instead 0.499999… + // in order to mitigate precision errors after calculating, round two time + // 0.499999… => 0.5 + var fix = roundToDecimal(sec, 8); + // 0.5 => 1 + var secRounded = roundToDecimal(fix, 0); + + if (secRounded === 60) { + secRounded = 0; + minFloor += 1; + if (minFloor === 60) { + minFloor = 0; + degreesFloor += 1; + } } + displayCoordinate = + t('units.arcdegrees', { + quantity: degreesFloor.toLocaleString(locale) + }) + + (minFloor !== 0 || secRounded !== 0 ? + t('units.arcminutes', { + quantity: minFloor.toLocaleString(locale) + }) : '') + + (secRounded !== 0 ? + t('units.arcseconds', { + quantity: secRounded.toLocaleString(locale) + }) : '' ); if (deg === 0) { return displayCoordinate; @@ -168,3 +185,44 @@ export function decimalCoordinatePair(coord) { longitude: wrap(coord[0], -180, 180).toFixed(OSM_PRECISION) }); } + +// Return the parsed value that @mapbox/sexagesimal can't parse +// return value format : [D, D] ex:[ 35.1861, 136.83161 ] +export function dmsMatcher(q) { + const matchers = [ + // D M SS , D M SS ex: 35 11 10.1 , 136 49 53.8 + { + condition: /^\s*(-?)\s*(\d+)\s+(\d+)\s+(\d+\.?\d*)\s*\,\s*(-?)\s*(\d+)\s+(\d+)\s+(\d+\.?\d*)\s*$/, + parser: function(q) { + const match = this.condition.exec(q); + const lat = (+match[2]) + (+match[3]) / 60 + (+match[4]) / 3600; + 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; + } + }, + // D MM , D MM ex: 35 11.1683 , 136 49.8966 + { + condition: /^\s*(-?)\s*(\d+)\s+(\d+\.?\d*)\s*\,\s*(-?)\s*(\d+)\s+(\d+\.?\d*)\s*$/, + parser: function(q) { + const match = this.condition.exec(q); + const lat = +match[2] + (+match[3]) / 60; + 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; + } + } + ]; + for (const matcher of matchers) { + if (matcher.condition.test(q)){ + return matcher.parser(q); + } + } + return null; +} diff --git a/test/spec/util/units.js b/test/spec/util/units.js new file mode 100644 index 000000000..877635838 --- /dev/null +++ b/test/spec/util/units.js @@ -0,0 +1,55 @@ +describe('iD.units', function() { + describe('dmsMatcher', function() { + it('parses D M SS format', function() { + var result = iD.dmsMatcher('35 11 10.1 , 136 49 53.8'); + expect(result[0]).to.be.closeTo( 35.18614, 0.00001); + expect(result[1]).to.be.closeTo(136.83161, 0.00001); + }); + it('parses D M SS format, with negative value', function() { + var result = iD.dmsMatcher('-35 11 10.1 , -136 49 53.8'); + expect(result[0]).to.be.closeTo( -35.18614, 0.00001); + expect(result[1]).to.be.closeTo(-136.83161, 0.00001); + }); + + it('parses D MM format', function() { + var result = iD.dmsMatcher('35 11.1683 , 136 49.8966'); + expect(result[0]).to.be.closeTo( 35.18614, 0.00001); + expect(result[1]).to.be.closeTo(136.83161, 0.00001); + }); + it('parses D MM format, with negative value', function() { + var result = iD.dmsMatcher('-35 11.1683 , -136 49.8966'); + expect(result[0]).to.be.closeTo( -35.18614, 0.00001); + expect(result[1]).to.be.closeTo(-136.83161, 0.00001); + }); + + it('handles invalid input', function() { + var result = iD.dmsMatcher('!@#$'); + expect(result).to.be.null; + }); + }); + + describe('dmsCoordinatePair', function() { + it('formats coordinate pair', function () { + var result = iD.dmsCoordinatePair([90 + 0.5/3600, 45]); + expect(result).to.be.eql('45°N, 90°0′1″E'); + }); + it('formats 0°', function () { + var result = iD.dmsCoordinatePair([0, 0]); + expect(result).to.be.eql('0°, 0°'); + }); + it('formats negative value', function () { + var result = iD.dmsCoordinatePair([-179, -90]); + expect(result).to.be.eql('90°S, 179°W'); + }); + it('formats 180° lng, should be E or W', function () { + // The longitude at this line can be given as either east or west. + var result = iD.dmsCoordinatePair([180, 0]); + expect(result).to.be.oneOf(['0°, 180°W', '0°, 180E°']); + }); + it('formats value over 90°lat or 180°lng', function () { + var result = iD.dmsCoordinatePair([181, 91]); + expect(result).to.be.oneOf(['90°N, 179°W']); + }); + }); +}); +