From e4c0e06a96ca8c76054c7417a10c895df4d28be4 Mon Sep 17 00:00:00 2001 From: gaellafond Date: Thu, 22 Oct 2020 12:28:36 +0800 Subject: [PATCH] [EAT-101] Google Location search uses map bbox for more accurate results v2.1.4 - Fixed "Premature end of chunk" exception when URLCache request a URL - Better logging when search API key is missing - Added "bounds" parameter to Google location search --- .../modules/MapPanel/Layer/SearchResults.js | 20 +++- pom.xml | 2 +- .../aims/atlasmapperserver/ClientConfig.java | 51 +++++++--- .../atlasmapperserver/LocationSearch.java | 92 +++++++++++++++++-- .../atlasmapperserver/cache/URLCache.java | 12 +-- src/main/webapp/public/search.jsp | 17 +++- 6 files changed, 163 insertions(+), 31 deletions(-) diff --git a/clientResources/amc/modules/MapPanel/Layer/SearchResults.js b/clientResources/amc/modules/MapPanel/Layer/SearchResults.js index 739c93d8..5f490609 100644 --- a/clientResources/amc/modules/MapPanel/Layer/SearchResults.js +++ b/clientResources/amc/modules/MapPanel/Layer/SearchResults.js @@ -66,6 +66,10 @@ Atlas.Layer.SearchResults = OpenLayers.Class(Atlas.Layer.AbstractLayer, { return; } + this.mapPanel = mapPanel; + this.json = jsonLayer; + this.parent = parent; + this.searchCount = Atlas.Layer.SearchResults.count; Atlas.Layer.SearchResults.count++; @@ -161,12 +165,26 @@ Atlas.Layer.SearchResults = OpenLayers.Class(Atlas.Layer.AbstractLayer, { var params = { client: Atlas.conf['clientId'], query: this.query, - bounds: null, offset: this.page * this.NB_RESULTS_PER_PAGE, qty: this.NB_RESULTS_PER_PAGE, noCache: (new Date()).getTime() // Anti-caching }; + // Add map bounds to the request + if (this.mapPanel && this.mapPanel.map) { + var mapBounds = this.mapPanel.map.calculateBounds(); + if (mapBounds) { + var reprojectedBounds = mapBounds.transform(this.mapPanel.map.getProjectionObject(), this.mapPanel.defaultLonLatProjection); + + if (reprojectedBounds) { + params["north"] = reprojectedBounds.top; + params["east"] = reprojectedBounds.right; + params["south"] = reprojectedBounds.bottom; + params["west"] = reprojectedBounds.left; + } + } + } + OpenLayers.Request.GET({ url: this.searchServiceUrl, params: params, diff --git a/pom.xml b/pom.xml index 80fe4f9f..dd6b1570 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ au.gov.aims atlasmapper war - 2.1.3 + 2.1.4 AtlasMapper server and clients This application compiled as a single War, that can be deployed in Tomcat, without any other dependency.\n\ It contains:\n\ diff --git a/src/main/java/au/gov/aims/atlasmapperserver/ClientConfig.java b/src/main/java/au/gov/aims/atlasmapperserver/ClientConfig.java index 84f078fa..d0ad0131 100644 --- a/src/main/java/au/gov/aims/atlasmapperserver/ClientConfig.java +++ b/src/main/java/au/gov/aims/atlasmapperserver/ClientConfig.java @@ -1260,8 +1260,13 @@ private String getHTMLListAndHideChildrenLayers(JSONObject layers, JSONArray chi } - public JSONObject locationSearch(URLCache urlCache, String query, String referer, String mapBounds, int offset, int qty) - throws JSONException, IOException, RevivableThreadInterruptedException { + public JSONObject locationSearch( + URLCache urlCache, + String query, + String referer, + LocationSearch.LocationSearchBoundingBox mapBounds, + int offset, int qty + ) throws JSONException, IOException, RevivableThreadInterruptedException { if (Utils.isBlank(query) || qty <= 0) { return null; @@ -1293,10 +1298,16 @@ public int compare(JSONObject o1, JSONObject o2) { try { String googleSearchAPIKey = this.getGoogleSearchAPIKey(); - if (this.isShowGoogleResults() && Utils.isNotBlank(googleSearchAPIKey)) { - List googleResults = LocationSearch.googleSearch(urlCache, googleSearchAPIKey, referer, encodedQuery, mapBounds); - if (googleResults != null && !googleResults.isEmpty()) { - resultsSet.addAll(googleResults); + if (this.isShowGoogleResults()) { + if (Utils.isBlank(googleSearchAPIKey)) { + LOGGER.warning(String.format("Client %s can't use Google Search. The Google search API key is blank.", + this.getClientId())); + } else { + List googleResults = LocationSearch.googleSearch( + urlCache, googleSearchAPIKey, referer, encodedQuery, mapBounds); + if (googleResults != null && !googleResults.isEmpty()) { + resultsSet.addAll(googleResults); + } } } } catch(Exception ex) { @@ -1306,10 +1317,16 @@ public int compare(JSONObject o1, JSONObject o2) { try { String osmSearchAPIKey = this.getOsmSearchAPIKey(); - if (this.isShowOSMResults() && Utils.isNotBlank(osmSearchAPIKey)) { - List osmNominatimResults = LocationSearch.osmNominatimSearch(urlCache, osmSearchAPIKey, referer, encodedQuery, mapBounds); - if (osmNominatimResults != null && !osmNominatimResults.isEmpty()) { - resultsSet.addAll(osmNominatimResults); + if (this.isShowOSMResults()) { + if (Utils.isBlank(osmSearchAPIKey)) { + LOGGER.warning(String.format("Client %s can't use OSM Search. The OSM search API key is blank.", + this.getClientId())); + } else { + List osmNominatimResults = LocationSearch.osmNominatimSearch( + urlCache, osmSearchAPIKey, referer, encodedQuery, mapBounds); + if (osmNominatimResults != null && !osmNominatimResults.isEmpty()) { + resultsSet.addAll(osmNominatimResults); + } } } } catch(Exception ex) { @@ -1319,10 +1336,16 @@ public int compare(JSONObject o1, JSONObject o2) { try { String arcGISSearchUrl = this.getArcGISSearchUrl(); - if (this.isShowArcGISResults() && Utils.isNotBlank(arcGISSearchUrl)) { - List arcGISResults = LocationSearch.arcGISSearch(urlCache, referer, arcGISSearchUrl, encodedQuery, mapBounds); - if (arcGISResults != null && !arcGISResults.isEmpty()) { - resultsSet.addAll(arcGISResults); + if (this.isShowArcGISResults()) { + if (Utils.isBlank(arcGISSearchUrl)) { + LOGGER.warning(String.format("Client %s can't use ArcGIS Search. The ArcGIS search URL is blank.", + this.getClientId())); + } else { + List arcGISResults = LocationSearch.arcGISSearch( + urlCache, referer, arcGISSearchUrl, encodedQuery, mapBounds); + if (arcGISResults != null && !arcGISResults.isEmpty()) { + resultsSet.addAll(arcGISResults); + } } } } catch(Exception ex) { diff --git a/src/main/java/au/gov/aims/atlasmapperserver/LocationSearch.java b/src/main/java/au/gov/aims/atlasmapperserver/LocationSearch.java index 09289fb6..0e303e34 100644 --- a/src/main/java/au/gov/aims/atlasmapperserver/LocationSearch.java +++ b/src/main/java/au/gov/aims/atlasmapperserver/LocationSearch.java @@ -62,16 +62,37 @@ private static void incSearchCount() { // Google // API: https://developers.google.com/maps/documentation/geocoding/ // URL: http://maps.googleapis.com/maps/api/geocode/json?address={QUERY}&sensor=false - public static List googleSearch(URLCache urlCache, String googleSearchAPIKey, String referer, String encodedQuery, String mapBounds) - throws JSONException, IOException, URISyntaxException, SQLException, RevivableThreadInterruptedException, ClassNotFoundException { - - String googleSearchUrl = "https://maps.googleapis.com/maps/api/geocode/json?address={QUERY}&sensor=false&key={APIKEY}"; + public static List googleSearch( + URLCache urlCache, + String googleSearchAPIKey, + String referer, + String encodedQuery, + LocationSearch.LocationSearchBoundingBox mapBounds + ) throws JSONException, IOException, URISyntaxException, SQLException, RevivableThreadInterruptedException, ClassNotFoundException { + + // The bounds parameter defines the latitude/longitude coordinates of + // the southwest and northeast corners of this bounding box + // using a pipe (|) character to separate the coordinates. + // NOTE: The pipe needs to be URL encoded: | = %7C + String googleSearchUrl = "https://maps.googleapis.com/maps/api/geocode/json?" + + "address={QUERY}" + + "&sensor=false" + + "&key={APIKEY}" + + "&bounds={SOUTH},{WEST}%7C{NORTH},{EAST}"; String encodedGoogleSearchAPIKey = URLEncoder.encode(googleSearchAPIKey.trim(), "UTF-8"); String queryURLStr = googleSearchUrl .replace("{QUERY}", encodedQuery) .replace("{APIKEY}", encodedGoogleSearchAPIKey); + if (mapBounds != null) { + queryURLStr = queryURLStr + .replace("{SOUTH}", "" + mapBounds.getSouth()) + .replace("{WEST}", "" + mapBounds.getWest()) + .replace("{NORTH}", "" + mapBounds.getNorth()) + .replace("{EAST}", "" + mapBounds.getEast()); + } + JSONObject json = LocationSearch.getSearchJSONObjectResponse(urlCache, queryURLStr, referer); if (json == null) { return null; @@ -256,8 +277,13 @@ public static List osmSearch(URLCache urlCache, String osmSearchAPIK // OSM Nominatim // API: https://developer.mapquest.com/documentation/open/nominatim-search/ // URL: http://open.mapquestapi.com/nominatim/v1/search?format=json&q={QUERY} - public static List osmNominatimSearch(URLCache urlCache, String osmSearchAPIKey, String referer, String encodedQuery, String mapBounds) - throws JSONException, IOException, URISyntaxException, SQLException, RevivableThreadInterruptedException, ClassNotFoundException { + public static List osmNominatimSearch( + URLCache urlCache, + String osmSearchAPIKey, + String referer, + String encodedQuery, + LocationSearch.LocationSearchBoundingBox mapBounds + ) throws JSONException, IOException, URISyntaxException, SQLException, RevivableThreadInterruptedException, ClassNotFoundException { String osmSearchUrl = "http://open.mapquestapi.com/nominatim/v1/search?format=json&q={QUERY}&key={APIKEY}"; @@ -324,8 +350,13 @@ public static List osmNominatimSearch(URLCache urlCache, String osmS // ArcGIS // API: http://resources.arcgis.com/en/help/rest/apiref/index.html?find.html // URL example: http://www.gbrmpa.gov.au/spatial_services/gbrmpaBounds/MapServer/find?f=json&contains=true&returnGeometry=true&layers=6%2C0&searchFields=LOC_NAME_L%2CNAME&searchText={QUERY} - public static List arcGISSearch(URLCache urlCache, String referer, String arcGISSearchUrl, String encodedQuery, String mapBounds) - throws JSONException, IOException, TransformException, FactoryException, URISyntaxException, SQLException, RevivableThreadInterruptedException, ClassNotFoundException { + public static List arcGISSearch( + URLCache urlCache, + String referer, + String arcGISSearchUrl, + String encodedQuery, + LocationSearch.LocationSearchBoundingBox mapBounds + ) throws JSONException, IOException, TransformException, FactoryException, URISyntaxException, SQLException, RevivableThreadInterruptedException, ClassNotFoundException { if (Utils.isBlank(arcGISSearchUrl)) { return null; @@ -474,4 +505,49 @@ public static JSONObject _createSearchResult(String title, String id, double[] c return result; } + + public static class LocationSearchBoundingBox { + private final float north; + private final float east; + private final float south; + private final float west; + + public LocationSearchBoundingBox(String northStr, String eastStr, String southStr, String westStr) { + this( + Float.parseFloat(northStr), + Float.parseFloat(eastStr), + Float.parseFloat(southStr), + Float.parseFloat(westStr) + ); + } + + public LocationSearchBoundingBox(float north, float east, float south, float west) { + this.north = north; + this.east = east; + this.south = south; + this.west = west; + } + + public float getNorth() { + return this.north; + } + + public float getEast() { + return this.east; + } + + public float getSouth() { + return this.south; + } + + public float getWest() { + return this.west; + } + + @Override + public String toString() { + return String.format("[N:%.3f, E:%.3f, S:%.3f, W:%.3f]", + this.north, this.east, this.south, this.west); + } + } } diff --git a/src/main/java/au/gov/aims/atlasmapperserver/cache/URLCache.java b/src/main/java/au/gov/aims/atlasmapperserver/cache/URLCache.java index cf73f288..5341d574 100644 --- a/src/main/java/au/gov/aims/atlasmapperserver/cache/URLCache.java +++ b/src/main/java/au/gov/aims/atlasmapperserver/cache/URLCache.java @@ -355,12 +355,6 @@ private void requestHttpDocument(CacheEntry cacheEntry) throws URISyntaxExceptio cacheEntry.setDocumentFile(tmpFile); } } finally { - if (httpGet != null) { - // Cancel the connection, if it's still alive - httpGet.abort(); - // Close connections - httpGet.reset(); - } if (in != null) { try { in.close(); } catch (Exception e) { LOGGER.log(Level.WARNING, String.format("Error occur while closing the URL %s: %s", url, Utils.getExceptionMessage(e)), e); @@ -376,6 +370,12 @@ private void requestHttpDocument(CacheEntry cacheEntry) throws URISyntaxExceptio LOGGER.log(Level.WARNING, String.format("Error occur while closing the HttpResponse: %s", Utils.getExceptionMessage(e)), e); } } + if (httpGet != null) { + // Cancel the connection, if it's still alive + httpGet.abort(); + // Close connections + httpGet.reset(); + } if (httpClient != null) { try { httpClient.close(); } catch (Exception e) { LOGGER.log(Level.WARNING, "Error occur while closing the HttpClient: " + Utils.getExceptionMessage(e), e); diff --git a/src/main/webapp/public/search.jsp b/src/main/webapp/public/search.jsp index 488957fb..1dca89a2 100644 --- a/src/main/webapp/public/search.jsp +++ b/src/main/webapp/public/search.jsp @@ -45,6 +45,7 @@ <%@page import="org.json.JSONArray"%> <%@page import="org.json.JSONObject"%> <%@page import="au.gov.aims.atlasmapperserver.cache.URLCache" %> +<%@page import="au.gov.aims.atlasmapperserver.LocationSearch" %> <% // Needed by StripTagProxy: // http://docs.sencha.com/extjs/3.4.0/#!/api/Ext.data.ScriptTagProxy @@ -66,8 +67,22 @@ // The query string, as entered by the user in the search field. String query = request.getParameter("query"); + // Map bounds, to help the server to order the results. - String bounds = request.getParameter("bounds"); + LocationSearch.LocationSearchBoundingBox bounds = null; + String northStr = request.getParameter("north"); + String eastStr = request.getParameter("east"); + String southStr = request.getParameter("south"); + String westStr = request.getParameter("west"); + if ( + northStr != null && !northStr.isEmpty() && + eastStr != null && !eastStr.isEmpty() && + southStr != null && !southStr.isEmpty() && + westStr != null && !westStr.isEmpty() + ) { + bounds = new LocationSearch.LocationSearchBoundingBox( + northStr, eastStr, southStr, westStr); + } // Start from result (default: 0 => Start from the first result). int offset = (request.getParameter("offset") != null ? Integer.parseInt(request.getParameter("offset")) :