/*
* Copyright (c) 2008 Boulder Community Foundation - iVolunteer
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package etl;
import com.sun.org.apache.xpath.internal.NodeSet;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
*
* @author Mark Chance
*
* For reference on the Google Maps API for geocoding, see:
* http://code.google.com/apis/maps/documentation/geocoding
*
*/
@Stateless
public class geocodeSessionBean implements geocodeSessionLocal {
private static final String GOOGLE_URL = "http://maps.google.com/maps/geo?";
private static final String GOOGLE_MAPS_API_KEY = "ABQIAAAA8HXiU_-E98nF20YvZ37zAxQ3KXTeRMsCydUtpdwIbkIA5o2l6BSDT71ZHbxEWhRhZfsByNDyDiEtKA";
private static Date LastGoogleRequest = new Date();
private static long timeBetweenRequests = 1000L;
/**
* Given what is know of an address, determine the lat/lon
* via Google Maps API
*
* @param loc
*/
public void encodeAddress(persistence.Location loc) {
// need something to go on.
String addrToEncode = "";
if ( !(isEmpty(loc.getStreet()) || loc.getStreet().equals("null null")))
addrToEncode += loc.getStreet();
if (!isEmpty(loc.getCity())) {
if (addrToEncode.length()>0) addrToEncode += " ";
addrToEncode += loc.getCity();
}
if (!isEmpty(loc.getState())) {
if (addrToEncode.length()>0) addrToEncode += " ";
addrToEncode += loc.getState();
}
if (!isEmpty(loc.getZip())) {
if (addrToEncode.length()>0) addrToEncode += " ";
addrToEncode += loc.getZip();
}
if (!isEmpty(loc.getCountry())) {
if (addrToEncode.length()>0) addrToEncode += " ";
addrToEncode += loc.getCountry();
}
if (addrToEncode.length() == 0) return;
// now go to Google
final String theXml = goToGoogle(addrToEncode, true);
if (!parseXMLforLatLon(theXml, loc)) {
// report error;
}
}
public void decodeAddress(persistence.Location loc) {
// need both lat and lon to decode
if (isEmpty(loc.getLatitude()) || isEmpty(loc.getLongitude())) return;
String latlon = loc.getLatitude() + "," + loc.getLongitude();
// now go to Google.
final String theXml = goToGoogle(latlon, false);
}
private boolean isEmpty(final String str) {
return str==null || str.length() == 0 || str.equals("null");
}
private String goToGoogle(final String input, boolean wantLatLon) {
StringBuilder sb = new StringBuilder();
try {
sb.append(GOOGLE_URL).append("output=xml&oe=utf8&sensor=false&key=").append(GOOGLE_MAPS_API_KEY);
sb.append("&q=");
if (wantLatLon) {
// then we have address input
sb.append(input.replace(" ", "+"));
} // else have lat,lon
Date now = new Date();
long dTime = now.getTime() - LastGoogleRequest.getTime();
if ( dTime < timeBetweenRequests) {
try {
Thread.sleep(timeBetweenRequests - dTime);
} catch (InterruptedException ex) {
Logger.getLogger(geocodeSessionBean.class.getName()).log(Level.SEVERE, null, ex);
}
}
LastGoogleRequest = now;
URL url = new URL(sb.toString());
URLConnection connection = url.openConnection();
int x;
sb = new StringBuilder();
while ((x = ((InputStream) connection.getContent()).read()) != -1) {
sb.append((char) x);
}
return sb.toString();
} catch (MalformedURLException ex) {
Logger.getLogger(geocodeSessionBean.class.getName()).log(Level.SEVERE, null, ex);
return null;
} catch (java.io.IOException ex) {
return null;
}
}
private boolean parseXMLforLatLon(final String xml, persistence.Location loc) {
try {
Document xmldoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
new InputSource(new StringReader(xml)));
NodeList nl = xmldoc.getElementsByTagName("coordinates");
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
NodeList nlSub = node.getChildNodes();
for (int j = 0; j < nlSub.getLength(); j++) {
Node subNode = nlSub.item(j);
if (subNode.getNodeType() == Node.TEXT_NODE) {
// lon/lat
String[] lonlat = subNode.getNodeValue().split(",");
loc.setLatitude(lonlat[1]);
loc.setLongitude(lonlat[0]);
return true;
}
}
}
} catch (SAXException se) {
} catch (IOException ioe) {
} catch (ParserConfigurationException pce) {
}
return false;
}
private boolean parseXMLforAddress(final String xml, persistence.Location loc) {
try {
Document xmldoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
new InputSource(new StringReader(xml)));
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
XPathExpression expr;
try {
expr = xpath.compile("/kml/Response/Placemark/AddressDetails/Country/AdministrativeArea");
NodeSet nodeSetResult = (NodeSet) expr.evaluate(xmldoc, XPathConstants.NODESET);
} catch (XPathExpressionException ex) {
Logger.getLogger(geocodeSessionBean.class.getName()).log(Level.SEVERE, null, ex);
}
// addresses returned best first.
NodeList nl = xmldoc.getElementsByTagName("Placemark");
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// ThoroughfareName == street
// PostalCodeNumber == zip
// LocalityName == city
// AdministrativeAreaName == state
NodeList nlSub = node.getChildNodes();
for (int j = 0; j < nlSub.getLength(); j++) {
Node subNode = nlSub.item(j);
if (subNode.getNodeType() == Node.TEXT_NODE) {
// lon/lat
String[] lonlat = subNode.getNodeValue().split(",");
loc.setLatitude(lonlat[0]);
loc.setLongitude(lonlat[1]);
return true;
}
}
}
} catch (SAXException se) {
} catch (IOException ioe) {
} catch (ParserConfigurationException pce) {
}
return false;
}
// TODO check for status <Status><Code>602</Code> -> unknown address
/*
* For future reference:
import java.lang.Math;
import java.lang.Double;
public int calcDistance(double latA, double longA, double latB, double longB)
{
double theDistance = (Math.sin(Math.toRadians(latA)) *
Math.sin(Math.toRadians(latB)) +
Math.cos(Math.toRadians(latA)) *
Math.cos(Math.toRadians(latB)) *
Math.cos(Math.toRadians(longA - longB)));
return = (Math.toDegrees(Math.acos(theDistance))) * 69.09;
}
*
* OR IN MYSql:
* (From http://dev.mysql.com/doc/refman/5.0/en/functions-that-test-spatial-relationships-between-geometries.html)
* Distance(g1,g2)
Returns as a double-precision number the shortest distance between any two points in the two geometries.
*
*/
// TODO: if we just have ZIP Code, could resolve city, state
}
/*
* <kml>
* <Response>
* <name>80305</name>
* <Status>
* <code>200</code>
* <request>geocode</request>
* </Status>
* <Placemark id="p1">
* <address>Boulder, CO 80305, USA</address>
* <AddressDetails Accuracy="5">
* <Country>
* <CountryNameCode>US</CountryNameCode>
* <CountryName>USA</CountryName>
* <AdministrativeArea>
* <AdministrativeAreaName>CO</AdministrativeAreaName>
* <Locality>
* <LocalityName>Boulder</LocalityName>
* <PostalCode>
* <PostalCodeNumber>80305</PostalCodeNumber>
* </PostalCode>
* </Locality>
* </AdministrativeArea>
* </Country>
* </AddressDetails>
* <ExtendedData>
* <LatLonBox north="40.0004470" south="39.9506840" east="-105.2211210" west="-105.2860380"/>
* </ExtendedData>
* <Point>
* <coordinates>-105.2487370,39.9799992,0</coordinates>
* </Point>
* </Placemark>
* </Response>
* </kml>
*/