diff --git a/frontend/app/map/hash/hash.js b/frontend/app/map/hash/hash.js index 957ea61e..70bf217b 100644 --- a/frontend/app/map/hash/hash.js +++ b/frontend/app/map/hash/hash.js @@ -1,6 +1,6 @@ (function(fm, $, ng, undefined) { - fm.app.factory("fmMapHash", function() { + fm.app.factory("fmMapHash", function($rootScope, fmUtils) { return function(map) { var hashControl = new L.Hash(map.map); @@ -16,21 +16,33 @@ args = hash.split("/"); var ret = L.Hash.parseHash(args.slice(0, 3).join("/")); - if(ret) { - // This gets called just in L.Hash.update(), so we can already add/remove the layers here - var l = args[3] && args[3].split("-"); - if(l && l.length > 0) { - for(var i in map.layers) { - if(l.indexOf(i) == -1) { - if(map.map.hasLayer(map.layers[i])) - map.map.removeLayer(map.layers[i]); - } else if(!map.map.hasLayer(map.layers[i])) - map.map.addLayer(map.layers[i]); - } + + // This gets called just in L.Hash.update(), so we can already add/remove the layers here + + var l = args[3] && args[3].split("-"); + if(l && l.length > 0) { + for(var i in map.layers) { + if(l.indexOf(i) == -1) { + if(map.map.hasLayer(map.layers[i])) + map.map.removeLayer(map.layers[i]); + } else if(!map.map.hasLayer(map.layers[i])) + map.map.addLayer(map.layers[i]); } } + + var routeParams = args.slice(4); + if(routeParams.length >= 2) { + var mode = null; + if(routeParams.length > 2 && [ "car", "bicycle", "pedestrian" ].indexOf(routeParams[routeParams.length-1]) != -1) + mode = routeParams.pop(); + + map.searchUi.route(routeParams, mode, !!ret); + } else if(routeParams.length == 1) { + map.searchUi.search(routeParams[0], !!ret, !fmUtils.isSearchId(routeParams[0])); + } + return ret; - }; + }.fmWrapApply($rootScope); hashControl.formatHash = function(mapObj) { var ret = L.Hash.formatHash(mapObj); @@ -43,6 +55,10 @@ ret += "/" + l.join("-"); + var searchHash = map.searchUi.getCurrentSearchForHash(); + if(searchHash) + ret += "/" + searchHash.join("/"); + return ret; }; @@ -53,6 +69,7 @@ map.map.on("layeradd", hashControl.onMapMove, hashControl); map.map.on("layerremove", hashControl.onMapMove, hashControl); + map.mapEvents.$on("searchchange", hashControl.onMapMove.bind(hashControl)); function decodeQueryString(str) { var obj = { }; @@ -124,7 +141,7 @@ }); if(obj.c && obj.c.s && obj.c.s.medium) - ret.push(obj.c.s.medium); + ret.push(obj.c.s.medium != "foot" ? obj.c.s.medium : "pedestrian"); } return ret; diff --git a/frontend/app/map/search/search-route.js b/frontend/app/map/search/search-route.js index e1ec68ec..b24a0c40 100644 --- a/frontend/app/map/search/search-route.js +++ b/frontend/app/map/search/search-route.js @@ -45,9 +45,6 @@ if(destination.suggestionQuery == destination.query) return resolve(); - destination.suggestions = [ ]; - destination.suggestionQuery = null; - destination.selectedSuggestionIdx = null; if(destination.query.trim() != "") { var query = destination.query; @@ -60,6 +57,9 @@ return reject(err); } + if(fmUtils.isSearchId(query) && results.length > 0 && results[0].display_name) + destination.query = query = results[0].display_name; + destination.suggestions = results; destination.suggestionQuery = query; destination.selectedSuggestionIdx = 0; @@ -70,7 +70,7 @@ }); }; - scope.route = function(dragging) { + scope.route = function(dragging, noZoom) { if(!dragging) scope.reset(); @@ -79,15 +79,14 @@ return $q.all(scope.destinations.map(scope.loadSuggestions)).then(function() { points = scope.destinations.map(function(destination) { - if(destination.suggestions.length == null) + if(destination.suggestions.length == 0) throw "No place has been found for search term “" + destination.query + "”."; - var sug = destination.suggestions[destination.selectedSuggestionIdx] || destination.suggestions[0]; - return { lat: sug.lat, lon: sug.lon }; + return destination.suggestions[destination.selectedSuggestionIdx] || destination.suggestions[0]; }); return $q(function(resolve, reject) { - map.socket.emit("getRoute", { destinations: points, mode: mode }, function(err, res) { + map.socket.emit("getRoute", { destinations: points.map(function(point) { return { lat: point.lat, lon: point.lon }; }), mode: mode }, function(err, res) { err ? reject(err) : resolve(res); }); }); @@ -96,7 +95,10 @@ route.routeMode = mode; scope.routeObj = route; - renderRoute(dragging); + scope.routeError = null; + renderRoute(dragging, noZoom); + + map.mapEvents.$emit("searchchange"); }).catch(function(err) { scope.routeError = err; }); @@ -132,7 +134,7 @@ } } - map.linesUi.createLine(type, scope.routeObj.routePoints, { mode: scope.routeObj.routeMode }); + map.linesUi.createLine(type, scope.routeObj.routePoints.map(function(point) { return { lat: point.lat, lon: point.lon }; }), { mode: scope.routeObj.routeMode }); scope.clear(); }; @@ -146,7 +148,7 @@ var markers = [ ]; var recalcRoute = fmUtils.minInterval(dragTimeout, false); - function renderRoute(dragging) { + function renderRoute(dragging, noZoom) { clearRoute(dragging); routeLayer = L.polyline(scope.routeObj.trackPoints.map(function(it) { return [ it.lat, it.lon ] }), lineStyle).addTo(map.map); @@ -167,7 +169,8 @@ }.fmWrapApply(scope)); if(!dragging) { - map.map.flyToBounds(routeLayer.getBounds()); + if(!noZoom) + map.map.flyToBounds(routeLayer.getBounds()); // Render markers @@ -240,7 +243,8 @@ lon: latlng.lng, display_name: disp, short_name: disp, - type: "coordinates" + type: "coordinates", + id: disp } ] }; } @@ -277,7 +281,6 @@ }, setFrom: function(from, suggestions, selectedSuggestion) { - console.log("setFrom", from); _setDestination(scope.destinations[0], from, suggestions, selectedSuggestion); }, @@ -292,8 +295,26 @@ _setDestination(scope.destinations[scope.destinations.length-1], to, suggestions, selectedSuggestion); }, - submit: function() { - scope.route(); + setMode: function(mode) { + scope.routeMode = mode; + }, + + getQueries: function() { + if(scope.routeObj) { + return scope.routeObj.routePoints.map(function(point) { + return point.id; + }); + } + }, + + getMode: function(mode) { + if(scope.routeObj) { + return scope.routeObj.routeMode; + } + }, + + submit: function(noZoom) { + scope.route(noZoom); } }; routeUi.hide(); diff --git a/frontend/app/map/search/search.js b/frontend/app/map/search/search.js index e9c08924..efd1d4dd 100644 --- a/frontend/app/map/search/search.js +++ b/frontend/app/map/search/search.js @@ -11,7 +11,7 @@ scope.showAll = false; scope.activeResult = null; - scope.search = function() { + scope.search = function(noZoom) { scope.searchResults = null; scope.loadedSearchString = ""; clearRenders(); @@ -34,41 +34,54 @@ if(err) return map.messages.showMessage("danger", err); + if(fmUtils.isSearchId(q) && results.length > 0 && results[0].display_name) + scope.searchString = q = results[0].display_name; + scope.loadedSearchString = q; + map.mapEvents.$emit("searchchange"); + if(typeof results == "string") - loadSearchResults(parseFiles([ results ])); + loadSearchResults(parseFiles([ results ]), noZoom); else - loadSearchResults(results); + loadSearchResults(results, noZoom); }); } }; - scope.showResult = function(result) { + scope.showResult = function(result, noZoom) { if(scope.showAll) { - _flyToBounds(layerGroup.getBounds()); + if(!noZoom) + _flyToBounds(layerGroup.getBounds()); result.marker ? result.marker.openPopup() : result.layer.openPopup(); } else { clearRenders(); renderResult(scope.loadedSearchString, scope.searchResults, result, true, layerGroup); - if(result.lat && result.lon && result.zoom) - map.map.flyTo([ result.lat, result.lon ], result.zoom); - else if(result.boundingbox) - _flyToBounds(L.latLngBounds([ [ result.boundingbox[0], result.boundingbox[3 ] ], [ result.boundingbox[1], result.boundingbox[2] ] ])); - else if(result.layer) - _flyToBounds(result.layer.getBounds()); + if(!noZoom) { + if(result.lat && result.lon && result.zoom) + map.map.flyTo([ result.lat, result.lon ], result.zoom); + else if(result.boundingbox) + _flyToBounds(L.latLngBounds([ [ result.boundingbox[0], result.boundingbox[3 ] ], [ result.boundingbox[1], result.boundingbox[2] ] ])); + else if(result.layer) + _flyToBounds(result.layer.getBounds()); + } } + + map.mapEvents.$emit("searchchange"); }; - scope.showAllResults = function() { + scope.showAllResults = function(noZoom) { clearRenders(); for(var i=0; i 0) - scope.showAll ? scope.showAllResults() : scope.showResult(scope.searchResults[0]); + scope.showAll ? scope.showAllResults(noZoom) : scope.showResult(scope.searchResults[0], noZoom); } function parseFiles(files) { @@ -355,15 +368,42 @@ el.hide(); }, - search: function(query) { + search: function(query, noZoom, showAll) { if(query != null) scope.searchString = query; - scope.search(); + if(showAll != null) + scope.showAll = showAll; + + scope.search(noZoom); }, showFiles: function(files) { loadSearchResults(parseFiles(files)); + }, + + route: function(destinations, mode, noZoom) { + searchUi.hide(); + routeUi.show(); + + routeUi.setQueries(destinations); + if(mode) + routeUi.setMode(mode); + + routeUi.submit(noZoom); + }, + + getCurrentSearchForHash: function() { + if(el.is(":visible")) { + if(!scope.showAll && scope.activeResult && scope.activeResult.id) + return [ scope.activeResult.id ]; + else if(scope.loadedSearchString) + return [ scope.loadedSearchString ]; + } else { + var queries = routeUi.getQueries(); + if(queries) + return queries.concat([ routeUi.getMode() ]); + } } }; diff --git a/frontend/app/utils/utils.js b/frontend/app/utils/utils.js index fc171339..26c0943e 100644 --- a/frontend/app/utils/utils.js +++ b/frontend/app/utils/utils.js @@ -435,6 +435,10 @@ }); }; + fmUtils.isSearchId = function(string) { + return string && string.match(/^[nwr]\d+$/i); + }; + return fmUtils; }); diff --git a/server/search.js b/server/search.js index 2f2fa591..fea4982d 100644 --- a/server/search.js +++ b/server/search.js @@ -70,42 +70,61 @@ function find(query, loadUrls) { return loadUrl(query); } - var lonlat_match = query.match(/^(geo\s*:\s*)?(-?\s*\d+([.,]\d+)?)\s*[,;]\s*(-?\s*\d+([.,]\d+)?)(\s*\?z\s*=\s*(\d+))?$/) - if(lonlat_match) - { // Coordinates - var lonlat = { - lat: 1*lonlat_match[2].replace(",", ".").replace(/\s+/, ""), - lon : 1*lonlat_match[4].replace(",", ".").replace(/\s+/, ""), - zoom : lonlat_match[7] != null ? 1*lonlat_match[7] : null - }; + var lonlat_match = query.match(/^(geo\s*:\s*)?(-?\s*\d+([.,]\d+)?)\s*[,;]\s*(-?\s*\d+([.,]\d+)?)(\s*\?z\s*=\s*(\d+))?$/); + var osm_match = query.match(/^([nwr])(\d+)$/i); + if(lonlat_match || osm_match) + { // Reverse search + var url = nameFinderUrl + "/reverse?format=json&addressdetails=1&polygon_geojson=1&extratags=1&namedetails=1"; + var lonlat; + + if(lonlat_match) { + lonlat = { + lat: 1*lonlat_match[2].replace(",", ".").replace(/\s+/, ""), + lon : 1*lonlat_match[4].replace(",", ".").replace(/\s+/, ""), + zoom : lonlat_match[7] != null ? 1*lonlat_match[7] : null + }; + url += "&lat=" + lonlat.lat + + "&lon=" + lonlat.lon + + "&zoom=" + (lonlat.zoom != null ? (lonlat.zoom >= 12 ? lonlat.zoom+2 : lonlat.zoom) : 17); + } else { + url += "&osm_type=" + osm_match[1].toUpperCase() + + "&osm_id=" + osm_match[2] + } return request({ - url: nameFinderUrl + "/reverse?format=json&addressdetails=1&polygon_geojson=1&extratags=1&namedetails=1" - + "&lat=" + lonlat.lat - + "&lon=" + lonlat.lon - + "&zoom=" + (lonlat.zoom != null ? (lonlat.zoom >= 12 ? lonlat.zoom+2 : lonlat.zoom) : 17), + url: url, json: true }).then(function(body) { - if(!body || body) { - var name = utils.round(lonlat.lat, 5) + ", " + utils.round(lonlat.lon, 5); - return [ { - lat: lonlat.lat, - lon : lonlat.lon, - type : "coordinates", - short_name: name, - display_name : name, - zoom: lonlat.zoom != null ? lonlat.zoom : 15, - icon: "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png" - } ]; + if(!body || body.error) { + if(lonlat) { + var name = utils.round(lonlat.lat, 5) + ", " + utils.round(lonlat.lon, 5); + return [ { + lat: lonlat.lat, + lon : lonlat.lon, + type : "coordinates", + short_name: name, + display_name : name, + zoom: lonlat.zoom != null ? lonlat.zoom : 15, + icon: "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png" + } ]; + } else + throw body ? body.error : "Invalid response from name finder"; } - body.lat = lonlat.lat; - body.lon = lonlat.lon; + if(lonlat) { + body.lat = lonlat.lat; + body.lon = lonlat.lon; - if(lonlat.zoom != null) - body.zoom = lonlat.zoom; + if(lonlat.zoom != null) + body.zoom = lonlat.zoom; + } - return [ prepareSearchResult(body) ]; + var res = prepareSearchResult(body); + + if(lonlat) + res.id = query; + + return [ res ]; }); } @@ -137,8 +156,7 @@ function prepareSearchResult(result) { geojson: result.geojson, icon: result.icon || "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png", type: result.type == "yes" ? result.category : result.type, - osm_id: result.osm_id, - osm_type: result.osm_type + id: result.osm_type.charAt(0) + result.osm_id }; }