nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

commit 81a5954c7f669ae4ea6bfd85cb0b2cd95f372e33
parent 56467579e3365b61ff7c8480d197044d7f93662e
Author: Sergey Orlov <wladimirych@gmail.com>
Date:   Sun,  2 Aug 2020 11:16:35 +0200

search: extract view from short links. Fixes #486

Diffstat:
Msrc/lib/leaflet.control.search/providers/links.js | 100++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mtest/test_search_links.js | 48+++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 113 insertions(+), 35 deletions(-)

diff --git a/src/lib/leaflet.control.search/providers/links.js b/src/lib/leaflet.control.search/providers/links.js @@ -1,7 +1,11 @@ import L from 'leaflet'; +import {fetch} from '~/lib/xhr-promise'; +import urlViaCorsProxy from '~/lib/CORSProxy'; + const MAX_ZOOM = 18; const MESSAGE_LINK_MALFORMED = 'Invalid coordinates in {name} link'; +const MESSAGE_SHORT_LINK_MALFORMED = 'Broken {name} short link'; function makeSearchResults(lat, lon, zoom, title) { if ( @@ -37,18 +41,32 @@ function makeSearchResults(lat, lon, zoom, title) { const YandexMapsUrl = { isOurUrl: function(url) { - return Boolean(url.hostname.match(/\byandex\.[^.]+$/u) && url.pathname.match(/^\/maps\//u)); + return ( + (url.hostname.match(/\byandex\./u) && url.pathname.match(/^\/maps\//u)) || + url.hostname.match(/static-maps\.yandex\./u) + ); }, - getResults: function(url) { - const paramLl = url.searchParams.get('ll'); - const paramZ = url.searchParams.get('z'); + getResults: async function(url) { + let isShort = false; try { + if (url.pathname.match(/^\/maps\/-\//u)) { + isShort = true; + const xhr = await fetch(urlViaCorsProxy(url.toString())); + const dom = new DOMParser().parseFromString(xhr.response, 'text/html'); + url = new URL(dom.querySelector('meta[property="og:image:secure_url"]').content); + } + const paramLl = url.searchParams.get('ll'); + const paramZ = url.searchParams.get('z'); const [lon, lat] = paramLl.split(',').map(parseFloat); const zoom = Math.round(parseFloat(paramZ)); return makeSearchResults(lat, lon, zoom, 'Yandex map view'); } catch (_) { - return {error: L.Util.template(MESSAGE_LINK_MALFORMED, {name: 'Yandex'})}; + return { + error: L.Util.template(isShort ? MESSAGE_SHORT_LINK_MALFORMED : MESSAGE_LINK_MALFORMED, { + name: 'Yandex', + }), + }; } }, }; @@ -70,19 +88,15 @@ const GoogleMapsSimpleMapUrl = { } else { title = 'Google map view'; } - try { - const lat = parseFloat(viewMatch[1]); - const lon = parseFloat(viewMatch[2]); - let zoom = parseFloat(viewMatch[3]); - // zoom for satellite images is expressed in meters - if (viewMatch[4] === 'm') { - zoom = Math.log2(149175296 / zoom * Math.cos(lat / 180 * Math.PI)); - } - zoom = Math.round(zoom); - return makeSearchResults(lat, lon, zoom, title); - } catch (_) { - return {error: L.Util.template(MESSAGE_LINK_MALFORMED, {name: 'Google'})}; + const lat = parseFloat(viewMatch[1]); + const lon = parseFloat(viewMatch[2]); + let zoom = parseFloat(viewMatch[3]); + // zoom for satellite images is expressed in meters + if (viewMatch[4] === 'm') { + zoom = Math.log2((149175296 / zoom) * Math.cos((lat / 180) * Math.PI)); } + zoom = Math.round(zoom); + return makeSearchResults(lat, lon, zoom, title); }, }; @@ -97,30 +111,42 @@ const GoogleMapsQueryUrl = { getResults: function(url) { const data = url.searchParams.get('q'); const m = data.match(/^(?:loc:)?([-\d.]+),([-\d.]+)$/u); - try { - const lat = parseFloat(m[1]); - const lon = parseFloat(m[2]); - return makeSearchResults(lat, lon, this.zoom, this.title); - } catch (_) { - return {error: L.Util.template(MESSAGE_LINK_MALFORMED, {name: 'Google'})}; - } - } + const lat = parseFloat(m[1]); + const lon = parseFloat(m[2]); + return makeSearchResults(lat, lon, this.zoom, this.title); + }, }; const GoogleMapsUrl = { subprocessors: [GoogleMapsSimpleMapUrl, GoogleMapsQueryUrl], isOurUrl: function(url) { - return Boolean(url.hostname.match(/\bgoogle\..+$/u) && url.pathname.match(/^\/maps(\/|$)/u)); + return (url.hostname.match(/\bgoogle\./u) || url.hostname === 'goo.gl') && url.pathname.match(/^\/maps(\/|$)/u); }, - getResults: function(url) { + getResults: async function(url) { + let isShort = false; + try { + if (url.hostname === 'goo.gl') { + isShort = true; + const xhr = await fetch(urlViaCorsProxy(url.toString()), {method: 'HEAD'}); + url = new URL(xhr.responseURL); + } + } catch (e) { + // pass + } for (let subprocessor of this.subprocessors) { - if (subprocessor.isOurUrl(url)) { - return subprocessor.getResults(url); + try { + if (subprocessor.isOurUrl(url)) { + return subprocessor.getResults(url); + } + } catch (e) { + // pass } } - return {error: L.Util.template(MESSAGE_LINK_MALFORMED, {name: 'Google'})}; + return { + error: L.Util.template(isShort ? MESSAGE_SHORT_LINK_MALFORMED : MESSAGE_LINK_MALFORMED, {name: 'Google'}), + }; }, }; @@ -129,14 +155,24 @@ const MapyCzUrl = { return Boolean(url.hostname.match(/\bmapy\.cz$/u)); }, - getResults: function(url) { + getResults: async function(url) { + let isShort = false; try { + if (url.pathname.match(/^\/s\//u)) { + isShort = true; + const xhr = await fetch(urlViaCorsProxy(url.toString()), {method: 'HEAD'}); + url = new URL(xhr.responseURL); + } const lon = parseFloat(url.searchParams.get('x')); const lat = parseFloat(url.searchParams.get('y')); const zoom = Math.round(parseFloat(url.searchParams.get('z'))); return makeSearchResults(lat, lon, zoom, 'Mapy.cz view'); } catch (_) { - return {error: L.Util.template(MESSAGE_LINK_MALFORMED, {name: 'Mapy.cz'})}; + return { + error: L.Util.template(isShort ? MESSAGE_SHORT_LINK_MALFORMED : MESSAGE_LINK_MALFORMED, { + name: 'Mapy.cz', + }), + }; } }, }; diff --git a/test/test_search_links.js b/test/test_search_links.js @@ -23,6 +23,28 @@ suite('LinksProvider - parsing valid links'); 14, ], ['https://yandex.ru/maps/?ll=16.548629%2C49.219896&z=14', 'Yandex map view', {lat: 49.219896, lng: 16.548629}, 14], + [ + 'https://yandex.ru/maps/?l=sat&ll=16.843527%2C49.363860&z=13', + 'Yandex map view', + {lat: 49.36386, lng: 16.843527}, + 13, + ], + [ + 'https://yandex.ru/maps/?l=sat%2Cskl&ll=16.843527%2C49.363860&z=13', + 'Yandex map view', + {lat: 49.36386, lng: 16.843527}, + 13, + ], + [ + 'https://static-maps.yandex.ru/1.x/?lang=ru_RU&size=520%2C440&l=sat%2Cskl&z=14&ll=16.548629%2C49.219896', + 'Yandex map view', + {lat: 49.219896, lng: 16.548629}, + 14, + ], + ['https://yandex.ru/maps/-/CCQpqZXJCB', 'Yandex map view', {lat: 49.219896, lng: 16.548629}, 14], + ['https://yandex.ru/maps/-/CCQpqZdgpA', 'Yandex map view', {lat: 49.219896, lng: 16.548629}, 14], + ['https://yandex.ru/maps/-/CCQpqZhrsB', 'Yandex map view', {lat: 49.219896, lng: 16.548629}, 14], + ['https://www.openstreetmap.org/#map=14/49.2199/16.5486', 'OpenStreetMap view', {lat: 49.2199, lng: 16.5486}, 14], [ 'https://en.mapy.cz/turisticka?x=16.5651083&y=49.2222502&z=14', @@ -94,6 +116,22 @@ suite('LinksProvider - parsing valid links'); ['https://nakarte.me/#m=11/49.44893/16.59897&l=O', 'Nakarte view', {lat: 49.44893, lng: 16.59897}, 11], ['https://nakarte.me/#l=O&m=11/49.44893/16.59897', 'Nakarte view', {lat: 49.44893, lng: 16.59897}, 11], ['https://example.com/#l=O&m=11/49.44893/16.59897', 'Nakarte view', {lat: 49.44893, lng: 16.59897}, 11], + ['https://en.mapy.cz/s/favepemeko', 'Mapy.cz view', {lat: 49.4113109, lng: 16.8975623}, 11], + ['https://en.mapy.cz/s/lucacunomo', 'Mapy.cz view', {lat: 49.4113109, lng: 16.8975623}, 11], + ['https://en.mapy.cz/s/mepevemazo', 'Mapy.cz view', {lat: 50.1592323, lng: 16.8245081}, 12], + ['https://goo.gl/maps/cJ8wwQi9oMYM9yiy6', 'Google map view', {lat: 49.0030846, lng: 15.2993434}, 14], + [ + 'https://goo.gl/maps/ZvjVBY78HUP8HjQi6', + 'Google map - 561 69 Dolní Morava', + {lat: 50.1568257, lng: 16.754047}, + 12, + ], + [ + 'https://goo.gl/maps/iMv4esLL1nwF9yns7', + 'Google map - 561 69 Dolní Morava', + {lat: 50.1568257, lng: 16.754047}, + 12, + ], ].forEach(function([query, expectedTitle, expectedCoordinates, expectedZoom]) { test(`Parse link ${query}`, async function() { assert.isTrue(links.isOurQuery(query)); @@ -117,13 +155,14 @@ suite('LinksProvider - parse invalid links'); ['https://', 'Invalid link'], ['http://', 'Invalid link'], ['https://example.com', 'Unsupported link'], - ['https://yandex.ru/maps/-/CCQlZLeFHA', 'Invalid coordinates in Yandex link'], ['https://yandex.ru/maps/', 'Invalid coordinates in Yandex link'], ['https://yandex.ru/maps/10509/brno/?ll=16.548629%2C149.219896&z=14', 'Invalid coordinates in Yandex link'], - ['https://en.mapy.cz/s/kofosuhuda', 'Invalid coordinates in Mapy.cz link'], + [ + 'https://static-maps.yandex.ru/1.x/?lang=ru_RU&size=520%2C440&l=sat%2Cskl&ll=16.548629%2C49.219896', + 'Invalid coordinates in Yandex link', + ], ['https://en.mapy.cz/turisticka?x=16.5651083&y=49.2222502&z=', 'Invalid coordinates in Mapy.cz link'], ['https://www.google.com/maps', 'Invalid coordinates in Google link'], - ['https://goo.gl/maps/igLWhY3jFpifZhTk6', 'Unsupported link'], ['https://www.google.com/maps/@99.1906435,16.5429962,14z', 'Invalid coordinates in Google link'], ['https://www.google.com/maps/@49.1906435,190.5429962,14z', 'Invalid coordinates in Google link'], ['https://www.google.com/maps/@49.1906435,19.5429962,45z', 'Invalid coordinates in Google link'], @@ -140,6 +179,9 @@ suite('LinksProvider - parse invalid links'); ['https://nakarte.me/#l=O', 'Invalid coordinates in Nakarte link'], ['https://example.com/#l=O&m=11/49.44893/', 'Unsupported link'], ['https://example.com/#l=O&m=99/49.44893/52.5547', 'Unsupported link'], + ['https://en.mapy.cz/s/lucacunom', 'Broken Mapy.cz short link'], + ['https://goo.gl/maps/ZvjVBY78HUP8HjQi', 'Broken Google short link'], + // ['https://yandex.ru/maps/-/CCQpqZXJ', 'Broken Yandex short link'], // Yandex returns good result for broken link ].forEach(function([query, expectedError]) { test(`Invalid link ${query}`, async function() { assert.isTrue(links.isOurQuery(query));