All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geomajas.plugin.geocoder.service.YahooPlaceFinderGeocoderService Maven / Gradle / Ivy

There is a newer version: 1.18.5
Show newest version
/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2015 Geosparc nv, http://www.geosparc.com/, Belgium.
 *
 * The program is available in open source according to the GNU Affero
 * General Public License. All contributions in this program are covered
 * by the Geomajas Contributors License Agreement. For full licensing
 * details, see LICENSE.txt in the project root.
 */

package org.geomajas.plugin.geocoder.service;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.geomajas.annotation.Api;
import org.geomajas.geometry.Bbox;
import org.geomajas.geometry.Crs;
import org.geomajas.plugin.geocoder.api.GeocoderService;
import org.geomajas.plugin.geocoder.api.GetLocationResult;
import org.geomajas.service.DtoConverterService;
import org.geomajas.service.GeoService;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.vividsolutions.jts.geom.Coordinate;

/**
 * Geocoder service using the Yahoo! PlaceFinder. See http://developer.yahoo.com/geo/placefinder/
 * 
 * @author Joachim Van der Auwera
 * @author Jan Venstermans
 * @since 1.4.0
 */
@Api
public class YahooPlaceFinderGeocoderService implements GeocoderService {

	private final Logger log = LoggerFactory
			.getLogger(YahooPlaceFinderGeocoderService.class);

	private static final double DELTA = 1e-20; // max coordinate variation for
												// equal locations
	private static final String USER_AGENT = "Geomajas Yahoo! PlaceFinder geocoder service";
	private static final int READ_TIMEOUT = 120000;
	private static final int CONNECT_TIMEOUT = 10000;
	private static final String URL_BASE = "http://query.yahooapis.com/v1/public/yql?";

	@Autowired
	private GeoService geoService;

	@Autowired
	private DtoConverterService dtoConverterService;

	@Autowired
	private SplitCommaReverseService splitCommaReverseService;

	private Crs crs;

	private String name = "YahooPlaceFinder";

	private String appId;
	private String appIdProperty;
	private boolean skipAppIdCheck;

	@PostConstruct
	protected void initCrs() throws Exception {
		crs = geoService.getCrs2("EPSG:4326"); // WGS-84 latlong

		if (null != appIdProperty) {
			appId = System.getProperty(appIdProperty);
		}
		if (null == appId && !skipAppIdCheck) {
			throw new RuntimeException(
					"Cannot instantiate YahooPlaceFinderGeocoderService, appId not known, "
							+ "you need to set appId or appIdProperty.");
		}
	}

	@Override
	public String getName() {
		return name;
	}

	/**
	 * Set the name for this geocoder service.
	 * 
	 * @param name
	 *            name
	 * @since 1.0.0
	 */
	@Api
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Set the appId which is required for accessing the service.
	 * 
	 * @param appId
	 *            Yahoo! appId
	 * @since 1.0.0
	 */
	@Api
	public void setAppId(String appId) {
		this.appId = appId;
	}

	/**
	 * Set a property which contains the appId. This allows setting the appId
	 * from the command line instead of the configuration files.
	 * 
	 * @param appIdProperty
	 *            property which contains the appId
	 * @since 1.0.0
	 */
	@Api
	public void setAppIdProperty(String appIdProperty) {
		this.appIdProperty = appIdProperty;
	}

	/**
	 * When setting this to true, you can avoid having an exception throws when
	 * the appId is not known. It will just find nothing (it won't even try).
	 * This can be useful in combination with the setAappIdProperty() to make it
	 * run then the property is not set.
	 * 
	 * @param skipAppIdCheck
	 *            true to disable throwing the error when no appId is specified
	 * @since 1.0.0
	 */
	@Api
	public void setSkipAppIdCheck(boolean skipAppIdCheck) {
		this.skipAppIdCheck = skipAppIdCheck;
	}

	@Override
	public CoordinateReferenceSystem getCrs() {
		return crs;
	}

	@Override
	public GetLocationResult[] getLocation(List location,
			int maxAlternatives, Locale locale) {
		// if (null == appId) {
		// return null;
		// }
		try {
			String query = splitCommaReverseService.combine(location);
			List locations = search(query, maxAlternatives,
					locale);
			return locations.toArray(new GetLocationResult[locations.size()]);
		} catch (Exception ex) { // NOSONAR
			log.error("Search failed", ex);
			return null;
		}
	}

	/**
	 * Call the Yahoo! PlaceFinder service for a result.
	 * 
	 * @param q
	 *            search string
	 * @param maxRows
	 *            max number of rows in result, or 0 for all
	 * @param locale
	 *            locale for strings
	 * @return list of found results
	 * @throws Exception
	 *             oops
	 * @see Yahoo!
	 *      Boss Geo PlaceFinder
	 */
	public List search(String q, int maxRows, Locale locale)
			throws Exception {
		List searchResult = new ArrayList();

		String url = URLEncoder.encode(q, "UTF8");
		url = "q=select%20*%20from%20geo.placefinder%20where%20text%3D%22"
				+ url + "%22";
		if (maxRows > 0) {
			url = url + "&count=" + maxRows;
		}
		url = url + "&flags=GX";
		if (null != locale) {
			url = url + "&locale=" + locale;
		}
		if (appId != null) {
			url = url + "&appid=" + appId;
		}

		InputStream inputStream = connect(url);
		if (null != inputStream) {
			SAXBuilder parser = new SAXBuilder();
			Document doc = parser.build(inputStream);

			Element root = doc.getRootElement();

			// check code for exception
			String message = root.getChildText("Error");
			// Integer errorCode = Integer.parseInt(message);
			if (message != null && Integer.parseInt(message) != 0) {
				throw new Exception(root.getChildText("ErrorMessage"));
			}
			Element results = root.getChild("results");
			for (Object obj : results.getChildren("Result")) {
				Element toponymElement = (Element) obj;
				GetLocationResult location = getLocationFromElement(toponymElement);
				searchResult.add(location);
			}
		}
		return searchResult;
	}

	/**
	 * Open the connection to the server.
	 * 
	 * @param url
	 *            the url to connect to
	 * @return returns the input stream for the connection
	 * @throws java.io.IOException
	 *             cannot get result
	 */
	private InputStream connect(String url) throws IOException {
		log.debug(URL_BASE + url);
		URLConnection conn = new URL(URL_BASE + url).openConnection();
		conn.setConnectTimeout(CONNECT_TIMEOUT);
		conn.setReadTimeout(READ_TIMEOUT);
		conn.setRequestProperty("User-Agent", USER_AGENT);
		return conn.getInputStream();
	}

	private GetLocationResult getLocationFromElement(Element locationElement) {
		GetLocationResult location = new GetLocationResult();

		List canonical = new ArrayList();
		canonicalAdd(canonical, locationElement, "line4");
		canonicalAdd(canonical, locationElement, "line3");
		canonicalAdd(canonical, locationElement, "line2");
		canonicalAdd(canonical, locationElement, "line1");
		if (0 == canonical.size()) {
			canonicalAdd(canonical, locationElement, "level5");
			canonicalAdd(canonical, locationElement, "level4");
			canonicalAdd(canonical, locationElement, "level3");
			canonicalAdd(canonical, locationElement, "level2");
			canonicalAdd(canonical, locationElement, "level1");
			canonicalAdd(canonical, locationElement, "level0");
		}
		location.setCanonicalStrings(canonical);

		String woeid = locationElement.getChildText("woeid");

		try {
			getBoudingBox(woeid, location);
		} catch (Exception e) {
			// don't do anything: no bounding box, location is point
		}

		location.setCoordinate(new Coordinate(Double
				.parseDouble(locationElement.getChildText("longitude")), Double
				.parseDouble(locationElement.getChildText("latitude"))));

		return location;
	}

	private void canonicalAdd(List canonical, Element locationElement,
			String tag) {
		String value = locationElement.getChildText(tag);
		if (null != value && value.length() > 0) {
			canonical.add(value);
		}
	}

	private void getBoudingBox(String woeid, GetLocationResult location)
			throws Exception {
		String url = "q=select%20boundingBox%20from%20geo.places%20where%20woeid%3D%22"
				+ woeid + "%22&format=json";
		InputStream inputStream = connect(url);
		if (null != inputStream) {
			ObjectMapper mapper = new ObjectMapper();
			Map data = mapper.readValue(inputStream, Map.class);
			Map query = (Map) data.get("query");
			Map results = (Map) query
					.get("results");
			Map place = (Map) results
					.get("place");
			Map boundingBox = (Map) place
					.get("boundingBox");

			Map southWest = (Map) boundingBox
					.get("southWest");
			double south = Double.parseDouble((String) southWest
					.get("latitude"));
			double west = Double.parseDouble((String) southWest
					.get("longitude"));
			Map northEast = (Map) boundingBox
					.get("northEast");
			double north = Double.parseDouble((String) northEast
					.get("latitude"));
			double east = Double.parseDouble((String) northEast
					.get("longitude"));
			log.debug(south + " " + west + " " + north + " " + east);

			Bbox box = null;
			if (Math.abs(north - south) > DELTA
					|| Math.abs(east - west) > DELTA) {
				// return point when bbox is a point
				box = new Bbox(west, south, east - west, north - south);
			}
			if (box != null) {
				location.setEnvelope(dtoConverterService.toInternal(box));
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy