diff --git a/src/org.gnome.Maps.src.gresource.xml b/src/org.gnome.Maps.src.gresource.xml
index 8a6dede53c27b967a2d3d7abd111701fd37dbdb2..d9eea458fd2d1d47b755d6928bd467691070a74b 100644
--- a/src/org.gnome.Maps.src.gresource.xml
+++ b/src/org.gnome.Maps.src.gresource.xml
@@ -104,6 +104,7 @@
userLocationBubble.js
userLocationMarker.js
utils.js
+ urls.js
wikipedia.js
xmldom/dom.js
xmldom/domparser.js
diff --git a/src/place.js b/src/place.js
index 073be2f5e5deca36dc24235f9ec30ec1ccb80c41..b0e50e9e91aa57aa4be39e522b52e532bb2452fa 100644
--- a/src/place.js
+++ b/src/place.js
@@ -27,6 +27,7 @@ const GObject = imports.gi.GObject;
const Location = imports.location;
const Overpass = imports.overpass;
+const URLS = imports.urls;
const Utils = imports.utils;
// Matches coordinates string in 'Decimal Degrees' format
@@ -41,14 +42,6 @@ const DMS_COORDINATES_REGEX = new RegExp(
"i"
);
-// Matches a URL pointing to an object in OpenStreetMap
-const OSM_OBJECT_URL_REGEX =
- new RegExp(/https?:\/\/(www\.)?openstreetmap\.org\/(node|way|relation)\/(\d+)\/?$/);
-
-// Matches a URL with a specifies coordinate in OpenStreetMap
-const OSM_COORD_URL_REGEX =
- new RegExp(/https?:\/\/(www\.)?openstreetmap\.org\/?\?(\&?mlat=(\d+(?:\.\d+)?))?(\&mlon=(\d+(?:\.\d+)?))?(\&zoom=(\d+))?(\#map=(\d+)\/(\d+(?:\.\d+)?)\/(\d+(?:\.\d+)?))?/);
-
var Place = GObject.registerClass(
class Place extends Geocode.Place {
@@ -401,71 +394,44 @@ let overpass = null;
*/
const Application = imports.application;
-function _parseOSMObjectURL(match, callback) {
- let [,, type, id] = match;
- let storedPlace = Application.placeStore.existsWithOsmTypeAndId(type, id);
-
- if (storedPlace) {
- callback(storedPlace, null);
- return;
- }
-
- if (overpass === null)
- overpass = new Overpass.Overpass();
-
- Application.application.mark_busy();
- overpass.fetchPlace(type, id, (place) => {
- Application.application.unmark_busy();
- if (place)
- callback(place, null);
- else
- callback(null, _("Place not found in OpenStreetMap"));
- });
-}
-
-function _parseOSMCoordURL(match, callback) {
- let [,,,mlat,,mlon,,zoom,,mapZoom,mapLat, mapLon] = match;
- let lat;
- let lon;
- let z;
-
- if (mapZoom && mapLat && mapLon) {
- lat = mapLat;
- lon = mapLon;
- z = mapZoom;
- } else if (mlat && mlon) {
- lat = mlat;
- lon = mlon;
- if (zoom)
- z = zoom;
- } else {
- callback(null, _("OpenStreetMap URL is not valid"));
- return;
- }
-
- if (!Place.validateCoordinates(lat, lon)) {
- callback(null, _("Coordinates in URL are not valid"));
- return;
- }
-
- let location = new Location.Location({ latitude: lat, longitude: lon });
- let place = z ? new Place({ location: location, initialZoom: z }) :
- new Place({ location: location });
+function parseHttpURL(text, callback) {
+ let [type, id] = URLS.parseAsObjectURL(text);
- callback(place, null);
-}
+ if (type && id) {
+ let storedPlace = Application.placeStore.existsWithOsmTypeAndId(type, id);
-function parseHttpURL(text, callback) {
- let match = text.match(OSM_OBJECT_URL_REGEX);
+ if (storedPlace) {
+ callback(storedPlace, null);
+ return;
+ }
- if (match) {
- _parseOSMObjectURL(match, callback);
+ if (overpass === null)
+ overpass = new Overpass.Overpass();
+
+ Application.application.mark_busy();
+ overpass.fetchPlace(type, id, (place) => {
+ Application.application.unmark_busy();
+ if (place)
+ callback(place, null);
+ else
+ callback(null, _("Place not found in OpenStreetMap"));
+ });
} else {
- match = text.match(OSM_COORD_URL_REGEX);
- if (match)
- _parseOSMCoordURL(match, callback);
- else
+ let [lat, lon, zoom] = URLS.parseAsCoordinateURL(text);
+
+ if (lat && lon) {
+ if (!Place.validateCoordinates(lat, lon)) {
+ callback(null, _("Coordinates in URL are not valid"));
+ } else {
+ let location = new Location.Location({ latitude: lat, longitude: lon });
+ let place = zoom ? new Place({ location: location, initialZoom: zoom }) :
+ new Place({ location: location });
+
+ callback(place, null);
+ }
+ } else {
callback(null, _("URL is not supported"));
+ }
}
}
diff --git a/src/placeEntry.js b/src/placeEntry.js
index 7be0aa046d2a2cbb868054c8f15012243bc9f1e6..732d3f0f9ed613fd1aa735fb1cd1851effba1a01 100644
--- a/src/placeEntry.js
+++ b/src/placeEntry.js
@@ -199,6 +199,15 @@ var PlaceEntry = GObject.registerClass({
return false;
}
+ /**
+ * Returns true if two locations are equal when rounded to displayes
+ * coordinate precision
+ */
+ _roundedLocEquals(locA, locB) {
+ return '%.5f, %.5f'.format(locA.latitude, locA.longitude) ===
+ '%.5f, %.5f'.format(locB.latitude, locB.longitude)
+ }
+
_parse() {
let parsed = false;
@@ -218,7 +227,14 @@ var PlaceEntry = GObject.registerClass({
let parsedLocation = Place.Place.parseCoordinates(this.text);
if (parsedLocation) {
- this.place = new Place.Place({ location: parsedLocation });
+ /* if the place was a parsed OSM coordinate URL, it will have
+ * gotten re-written as bare coordinates and trigger a search-changed,
+ * in this case don't re-set the place, as it will loose the zoom
+ * level from the URL if set
+ */
+ if (!this.place ||
+ !this._roundedLocEquals(parsedLocation, this.place.location))
+ this.place = new Place.Place({ location: parsedLocation });
parsed = true;
}
diff --git a/src/urls.js b/src/urls.js
new file mode 100644
index 0000000000000000000000000000000000000000..96b698f22d3c577107e760dc0eb7b7534f073b7b
--- /dev/null
+++ b/src/urls.js
@@ -0,0 +1,104 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * Copyright (c) 2020 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see .
+ *
+ * Author: Marcus Lundblad
+ */
+
+const _ = imports.gettext.gettext;
+
+const GLib = imports.gi.GLib;
+
+// Matches URLs for OpenStreetMap (for addressing objects or coordinates)
+const OSM_URL_REGEX = new RegExp(/https?:\/\/(www\.)?openstreetmap\.org./);
+
+/**
+ * For URLs identifiable as pointing to a coordinate
+ * e.g. an openstreetmap.org URL with lat and lon query parameters,
+ * returns [lat, lon, optional zoom], otherwise [].
+ */
+function parseAsCoordinateURL(url) {
+ if (url.match(OSM_URL_REGEX)) {
+ /* it seems #map= is not handle well by parse_params(), so just remove
+ * the # as a work-around
+ */
+ let uri = GLib.Uri.parse(url.replace('#map=', 'map='), GLib.UriFlags.NONE);
+ let params = GLib.Uri.parse_params(uri.get_query(), -1, '&',
+ GLib.UriParamsFlags.NONE);
+
+ let lat = params.lat;
+ let lon = params.lon;
+ let mlat = params.mlat;
+ let mlon = params.mlon;
+ let zoom;
+ let map = params.map;
+
+ if (map) {
+ let parts = map.split('/');
+
+ if (parts.length !== 3) {
+ return [];
+ } else {
+ zoom = parseInt(parts[0]);
+ lat = parseFloat(parts[1]);
+ lon = parseFloat(parts[2]);
+ }
+ } else if (mlat && mlon) {
+ lat = parseFloat(mlat);
+ lon = parseFloat(mlon);
+
+ if (params.zoom)
+ zoom = parseInt(params.zoom);
+ } else if (lat && lon) {
+ lat = parseFloat(lat);
+ lon = parseFloat(lon);
+
+ if (params.zoom)
+ zoom = parseInt(params.zoom);
+ } else {
+ return [];
+ }
+
+ return [lat, lon, zoom];
+ } else {
+ return [];
+ }
+}
+
+/**
+ * For URLs addressing a specific OSM object (node, way, or relation),
+ * returns [type,id], otherwise [].
+ */
+function parseAsObjectURL(url) {
+ if (url.match(OSM_URL_REGEX)) {
+ let uri = GLib.Uri.parse(url, GLib.UriFlags.NONE);
+ let path = uri.get_path();
+ let parts = path.split('/');
+
+ if (parts.length === 3 && parts[0] === '' &&
+ (parts[1] === 'node' ||
+ parts[1] === 'way' ||
+ parts[1] === 'relation')) {
+ let id = parseInt(parts[2]);
+
+ if (id > 0)
+ return [parts[1], id];
+ }
+ }
+
+ return [];
+}
diff --git a/tests/meson.build b/tests/meson.build
index 86475d0989d9f5726ecec8095f68052a4acde729..551ade73ef34b901a160dc1ac96bbc0a919ee689 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,4 +1,4 @@
-tests = ['addressTest', 'colorTest', 'osmNamesTest', 'utilsTest']
+tests = ['addressTest', 'colorTest', 'osmNamesTest', 'utilsTest', 'urlsTest']
foreach test : tests
script_conf = configuration_data()
diff --git a/tests/urlsTest.js b/tests/urlsTest.js
new file mode 100644
index 0000000000000000000000000000000000000000..b276df98c8696eb94af078174ba5ec0108a8045e
--- /dev/null
+++ b/tests/urlsTest.js
@@ -0,0 +1,68 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * Copyright (c) 2020 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see .
+ *
+ * Author: Marcus Lundblad
+ */
+
+const JsUnit = imports.jsUnit;
+
+const URLS = imports.urls;
+
+const OSM_COORD_URL1 =
+ 'https://www.openstreetmap.org/?lat=39.9882&lon=-78.2409&zoom=14&layers=B000FTF';
+
+function main() {
+ parseAsObjectURLTest();
+ parseAsCoordinateURLTest();
+}
+
+function parseAsObjectURLTest() {
+ _assertArrayEquals([], URLS.parseAsObjectURL('https://www.example.com'));
+ _assertArrayEquals([], URLS.parseAsObjectURL('https://www.openstreet.org/'));
+ _assertArrayEquals(['node', 1],
+ URLS.parseAsObjectURL('https://www.openstreetmap.org/node/1'));
+ _assertArrayEquals(['way', 2],
+ URLS.parseAsObjectURL('https://www.openstreetmap.org/way/2'));
+ _assertArrayEquals(['relation', 3],
+ URLS.parseAsObjectURL('https://www.openstreetmap.org/relation/3'));
+ _assertArrayEquals([],
+ URLS.parseAsObjectURL('https://www.openstreetmap.org/foo/1'));
+ _assertArrayEquals(['node', 4],
+ URLS.parseAsObjectURL('https://openstreetmap.org/node/4'));
+ _assertArrayEquals(['node', 5],
+ URLS.parseAsObjectURL('http://www.openstreetmap.org/node/5'));
+}
+
+function parseAsCoordinateURLTest() {
+ _assertArrayEquals([],
+ URLS.parseAsCoordinateURL('https://www.example.com'));
+ _assertArrayEquals([],
+ URLS.parseAsCoordinateURL('https://www.openstreet.org/'));
+ _assertArrayEquals([39.9882, -78.2409, 14],
+ URLS.parseAsCoordinateURL('https://www.openstreetmap.org/?lat=39.9882&lon=-78.2409&zoom=14&layers=B000FTF'));
+ _assertArrayEquals([59.40538, 17.34894, 12],
+ URLS.parseAsCoordinateURL('https://www.openstreetmap.org/?#map=12/59.40538/17.34894'));
+}
+
+function _assertArrayEquals(arr1, arr2) {
+ JsUnit.assertEquals(arr1.length, arr2.length);
+ for (let i = 0; i < arr1.length; i++) {
+ JsUnit.assertEquals(arr1[i], arr2[i]);
+ }
+}
+