/** * Copyright (C) 2010-2017 Structr GmbH * * This file is part of Structr <http://structr.org>. * * Structr 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 3 of the * License, or (at your option) any later version. * * Structr 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 Structr. If not, see <http://www.gnu.org/licenses/>. */ package org.structr.common.geo; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.slf4j.LoggerFactory; public class OSMGeoCodingProvider extends AbstractGeoCodingProvider{ private static final org.slf4j.Logger logger = LoggerFactory.getLogger(OSMGeoCodingProvider.class.getName()); @Override public GeoCodingResult geocode(String street, String house, String postalCode, String city, String state, String country, String language) throws IOException { String address = (StringUtils.isNotBlank(house) ? house : "") + " " + (StringUtils.isNotBlank(street) ? street : "") + " " + (StringUtils.isNotBlank(postalCode) ? postalCode : " " + (StringUtils.isNotBlank(city) ? city : "") + " " + (StringUtils.isNotBlank(state) ? state : "") + " " + (StringUtils.isNotBlank(country) ? country : "") + " " ); String encodedAddress; try { encodedAddress = URLEncoder.encode(address, "UTF-8"); } catch (UnsupportedEncodingException ex) { logger.warn("Unsupported Encoding", ex); return null; } Document xmlDoc; try { String protocol = "xml"; URL mapsUrl = new URL("http://nominatim.openstreetmap.org/search?q=" + encodedAddress + "&format=" + protocol + "&accept-language=" + language + "&addressdetails=1&limit=1"); HttpURLConnection connection = (HttpURLConnection) mapsUrl.openConnection(); SAXReader reader = new SAXReader(); BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream())); xmlDoc = reader.read(rd); connection.disconnect(); rd.close(); if (xmlDoc != null) { Map<String, String> data = new LinkedHashMap<>(); Element root = xmlDoc.getRootElement(); try { data.put("lat", root.element("place").attributeValue("lat")); } catch (Throwable t) {} try { data.put("lon", root.element("place").attributeValue("lon")); } catch (Throwable t) {} try { data.put("postalCode", root.element("place").element("postcode").getTextTrim()); } catch (Throwable t) {} try { data.put("state", root.element("place").element("state").getTextTrim()); } catch (Throwable t) {} try { data.put("state_district", root.element("place").element("state_district").getTextTrim()); } catch (Throwable t) {} try { data.put("city", root.element("place").element("city").getTextTrim()); } catch (Throwable t) {} try { data.put("countryRegion", root.element("place").element("country").getTextTrim()); } catch (Throwable t) {} try { data.put("road", root.element("place").element("road").getTextTrim()); } catch (Throwable t) {} try { data.put("house_number", root.element("place").element("house_number").getTextTrim()); } catch (Throwable t) {} if (data.containsKey("lat") && data.containsKey("lon")) { return new OSMGeoCodingProvider.OSMGeoCodingResult(address, data); } else { logger.warn("Geocoding result did not contain location information:\n{}", xmlDoc.asXML()); } } } catch (IOException ioe) { logger.warn("Connection to geocoding service failed", ioe); return null; } catch (DocumentException de) { logger.warn("Could not read result document", de); return null; } return null; } private static class OSMGeoCodingResult implements GeoCodingResult { private List <AddressComponent> addressComponents = new LinkedList<>(); private String address = null; private double latitude; private double longitude; public OSMGeoCodingResult(String address, Map<String, String> data) { this.address = address; this.latitude = Double.parseDouble(data.get("lat")); this.longitude = Double.parseDouble(data.get("lon")); String curData = null; curData = data.get("postalCode"); if(curData != null){ this.addressComponents.add(new OSMAddressComponent(curData, Type.postal_code)); } curData = data.get("state"); if(curData != null){ this.addressComponents.add(new OSMAddressComponent(curData, Type.administrative_area_level_1)); } curData = data.get("state_district"); if(curData != null){ this.addressComponents.add(new OSMAddressComponent(curData, Type.administrative_area_level_3)); } curData = data.get("countryRegion"); if(curData != null){ this.addressComponents.add(new OSMAddressComponent(curData, Type.country)); } curData = data.get("city"); if(curData != null){ this.addressComponents.add(new OSMAddressComponent(curData, Type.locality)); } curData = data.get("road"); if(curData != null){ this.addressComponents.add(new OSMAddressComponent(curData, Type.route)); } curData = data.get("house_number"); if(curData != null){ this.addressComponents.add(new OSMAddressComponent(curData, Type.street_number)); } } public OSMGeoCodingResult(final double latitude, final double longitude) { this.latitude = latitude; this.longitude = longitude; } @Override public String getAddress() { return address; } @Override public AddressComponent getAddressComponent(Type type) { for(AddressComponent addressComponent : addressComponents) { if(addressComponent.getType() == type) { return addressComponent; } } return null; } @Override public List<AddressComponent> getAddressComponents() { return addressComponents; } @Override public double getLatitude() { return latitude; } @Override public double getLongitude() { return longitude; } @Override public void setAddress(String address) { this.address = address; } @Override public void setLatitude(double latitude) { this.latitude = latitude; } @Override public void setLongitude(double longitude) { this.longitude = longitude; } @Override public Double[] toArray() { return new Double[]{latitude, longitude}; } } private static class OSMAddressComponent implements AddressComponent { GeoCodingResult.Type type = null; String value = null; public OSMAddressComponent(String value, GeoCodingResult.Type type) { this.type = type; this.value = value; } @Override public String getValue() { return value; } @Override public GeoCodingResult.Type getType() { return type; } } }