/** * 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.Iterator; import java.util.LinkedList; import java.util.List; 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.Logger; import org.slf4j.LoggerFactory; import org.structr.common.geo.GeoCodingResult.Type; /** * * */ public class GoogleGeoCodingProvider extends AbstractGeoCodingProvider { private static final Logger logger = LoggerFactory.getLogger(GoogleGeoCodingProvider.class.getName()); @Override public GeoCodingResult geocode(final String street, final String house, String postalCode, final String city, final String state, final String country, final String language) throws IOException { String address = (StringUtils.isNotBlank(street) ? street : "") + " " + (StringUtils.isNotBlank(house) ? house : "") + " " + (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 { StringBuilder urlBuffer = new StringBuilder("https://maps.google.com/maps/api/geocode/"); // output format XML urlBuffer.append("xml"); // set the address urlBuffer.append("?address=").append(encodedAddress); // set the ouput language urlBuffer.append("&language=").append(language); // set the api key if there is any if(apiKey != null && !apiKey.isEmpty()) { urlBuffer.append("&key=").append(apiKey); } URL mapsUrl = new URL(urlBuffer.toString()); HttpURLConnection connection = (HttpURLConnection) mapsUrl.openConnection(); connection.connect(); SAXReader reader = new SAXReader(); BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream())); xmlDoc = reader.read(rd); connection.disconnect(); rd.close(); } 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; } Element root = xmlDoc.getRootElement(); // List<Element> rootChildren = root.elements(); String status = root.element("status").getTextTrim(); if ("OK".equals(status)) { try { return new GoogleGeoCodingResult(address, root); } catch(Throwable t) { logger.warn("Unable to find geocoding for address {}: {}", new Object[] { address, t.getMessage() }); } } else { logger.warn("Status not OK for address {}: {}", new Object[] { address, status }); } return null; } //~--- inner classes -------------------------------------------------- public static class GoogleGeoCodingResult implements GeoCodingResult { private List<AddressComponent> addressComponents = new LinkedList<>(); private String address = null; private double latitude; private double longitude; //~--- constructors ------------------------------------------- public GoogleGeoCodingResult(String address, Element root) { this.address = address; String latString = root.element("result").element("geometry").element("location").element("lat").getTextTrim(); String lonString = root.element("result").element("geometry").element("location").element("lng").getTextTrim(); Iterator<Element> addressComponentsElement = root.element("result").elementIterator("address_component"); for(;addressComponentsElement.hasNext();) { addressComponents.add(new GoogleAddressComponent(addressComponentsElement.next())); } this.latitude = Double.parseDouble(latString); this.longitude = Double.parseDouble(lonString); } public GoogleGeoCodingResult(final double latitude, final double longitude) { this.latitude = latitude; this.longitude = longitude; } //~--- get methods -------------------------------------------- /** * @return the latitude */ @Override public double getLatitude() { return latitude; } /** * @return the longitude */ @Override public double getLongitude() { return longitude; } //~--- set methods -------------------------------------------- /** * @param latitude the latitude to set */ @Override public void setLatitude(double latitude) { this.latitude = latitude; } /** * @param longitude the longitude to set */ @Override public void setLongitude(double longitude) { this.longitude = longitude; } @Override public Double[] toArray() { return new Double[]{ latitude, longitude }; } @Override public String getAddress() { return address; } @Override public void setAddress(String address) { this.address = 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; } } public static class GoogleAddressComponent implements AddressComponent { private Type type = null; private String value = null; public GoogleAddressComponent(Element addressComponent) { this.value = addressComponent.element("long_name").getTextTrim(); Iterator<Element> typesElement = addressComponent.elementIterator("type"); for(;typesElement.hasNext();) { Element typeElement = typesElement.next(); String typeName = typeElement.getTextTrim(); try { this.type = Type.valueOf(typeName); break; } catch(Throwable t) { logger.warn("Encountered unknown address component type {} while parsing.", typeName); } } } @Override public Type getType() { return type; } @Override public String getValue() { return value; } } }