/*
* Licensed to Prodevelop SL under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The Prodevelop SL licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* For more information, contact:
*
* Prodevelop, S.L.
* Pza. Don Juan de Villarrasa, 14 - 5
* 46001 Valencia
* Spain
*
* +34 963 510 612
* +34 963 510 968
* prode@prodevelop.es
* http://www.prodevelop.es
*
* @author Alberto Romeu Carrasco http://www.albertoromeu.com
*/
package es.alrocar.poiproxy.proxy;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Logger;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import es.alrocar.jpe.parser.JPEParser;
import es.alrocar.jpe.parser.JPEParserManager;
import es.alrocar.jpe.parser.csv.CSVParser;
import es.alrocar.jpe.parser.exceptions.NoParserRegisteredException;
import es.alrocar.jpe.parser.json.JSONJPEParser;
import es.alrocar.jpe.parser.kml.KMLParser;
import es.alrocar.jpe.parser.xml.XMLJPEParser;
import es.alrocar.jpe.writer.GeoJSONWriter;
import es.alrocar.poiproxy.configuration.DescribeService;
import es.alrocar.poiproxy.configuration.DescribeServices;
import es.alrocar.poiproxy.configuration.Param;
import es.alrocar.poiproxy.configuration.ServiceConfigurationManager;
import es.alrocar.poiproxy.configuration.ServiceParams;
import es.alrocar.poiproxy.exceptions.POIProxyException;
import es.alrocar.poiproxy.geotools.GeotoolsUtils;
import es.alrocar.poiproxy.proxy.utiles.Calculator;
import es.alrocar.utils.CompressionEnum;
import es.alrocar.utils.Downloader;
import es.alrocar.utils.PropertyLocator;
import es.alrocar.utils.Unzip;
import es.prodevelop.geodetic.utils.conversion.ConversionCoords;
import es.prodevelop.gvsig.mini.geom.Extent;
import es.prodevelop.gvsig.mini.geom.impl.jts.JTSFeature;
import es.prodevelop.gvsig.mini.projection.TileConversor;
import es.prodevelop.gvsig.mobile.fmap.proj.CRSFactory;
/**
* The main entry point of the library. This class has methods to make a request
* to a service, parse the response and return the GeoJSON result
*
* @author albertoromeu
*
*/
public class POIProxy {
private static final String MERCATOR_SRS = "EPSG:900913";
private static final String GEODETIC_SRS = "EPSG:4326";
private final static Logger logger = Logger.getLogger(POIProxy.class);
public static String CACHE_DIR = "/var/lib/sp/cache";
private static POIProxy proxy;
private ServiceConfigurationManager serviceManager;
public static POIProxy asSingleton() {
if (proxy == null) {
proxy = new POIProxy();
proxy.initialize();
}
return proxy;
}
/**
* Instantiates the {@link ServiceConfigurationManager} that loads the
* services available at
* {@link ServiceConfigurationManager#CONFIGURATION_DIR}
*
* Registers a {@link JSONJPEParser} and a {@link XMLJPEParser} to allow
* parsing either json or xml depending on the format response of the
* registered services
*
* And finally instantiates a {@link GeoJSONWriter} that will return the
* GeoJSON document
*/
public void initialize() {
// Registra todos los servicios disponibles
serviceManager = new ServiceConfigurationManager();
JPEParserManager.getInstance().registerJPEParser(new JSONJPEParser());
JPEParserManager.getInstance().registerJPEParser(new XMLJPEParser());
JPEParserManager.getInstance().registerJPEParser(new CSVParser());
JPEParserManager.getInstance().registerJPEParser(new KMLParser());
}
/**
* Returns a JSON containing the describe service document of each service
* registered into the library
*
* @return
* @throws IOException
* @throws JsonMappingException
* @throws JsonGenerationException
*/
public String getAvailableServices() throws JsonGenerationException,
JsonMappingException, IOException {
DescribeServices services = serviceManager.getAvailableServices();
return services.asJSON();
}
/**
* This method is used to get the pois from a service and return a GeoJSON
* document with the data retrieved given a Z/X/Y tile.
*
* @param id
* The id of the service
* @param z
* The zoom level
* @param x
* The x tile
* @param y
* The y tile
* @return The GeoJSON response from the original service response
*/
public String getPOIs(String id, int z, int x, int y,
List<Param> optionalParams) throws Exception {
DescribeService describeService = getDescribeServiceByID(id);
Extent e1 = TileConversor.tileOSMMercatorBounds(x, y, z);
double[] minXY = ConversionCoords.reproject(e1.getMinX(), e1.getMinY(),
CRSFactory.getCRS(MERCATOR_SRS),
CRSFactory.getCRS(GEODETIC_SRS));
double[] maxXY = ConversionCoords.reproject(e1.getMaxX(), e1.getMaxY(),
CRSFactory.getCRS(MERCATOR_SRS),
CRSFactory.getCRS(GEODETIC_SRS));
String geoJSON = getResponseAsGeoJSON(id, optionalParams,
describeService, minXY[0], minXY[1], maxXY[0], maxXY[1], 0, 0);
return geoJSON;
}
/**
* This method is used to get the pois from a service and return a list of
* {@link JTSFeature} document with the data retrieved given a Z/X/Y tile.
*
* @param id
* The id of the service
* @param z
* The zoom level
* @param x
* The x tile
* @param y
* The y tile
* @return a list of {@link JTSFeature}
*/
public ArrayList<JTSFeature> getFeatures(String id, int z, int x, int y,
List<Param> optionalParams) throws Exception {
DescribeService describeService = getDescribeServiceByID(id);
Extent e1 = TileConversor.tileOSMMercatorBounds(x, y, z);
double[] minXY = ConversionCoords.reproject(e1.getMinX(), e1.getMinY(),
CRSFactory.getCRS(MERCATOR_SRS),
CRSFactory.getCRS(GEODETIC_SRS));
double[] maxXY = ConversionCoords.reproject(e1.getMaxX(), e1.getMaxY(),
CRSFactory.getCRS(MERCATOR_SRS),
CRSFactory.getCRS(GEODETIC_SRS));
return getFeatures(id, optionalParams, describeService, minXY[0],
minXY[1], maxXY[0], maxXY[1], 0, 0);
}
/**
* This method is used to get the pois from a category and return a list of
* {@link JTSFeature} document with the data retrieved given a Z/X/Y tile.
*
* @param id
* The category to request
* @see #getAvailableCategories()
* @param z
* The zoom level
* @param x
* The x tile
* @param y
* The y tile
* @return a list of {@link JTSFeature}
*/
public ArrayList<JTSFeature> getFeaturesByCategory(String category, int z,
int x, int y, List<Param> optionalParams) throws Exception {
List<String> ids = serviceManager.getAvailableServices()
.getServicesIDByCategory(category);
ArrayList<JTSFeature> features = new ArrayList<JTSFeature>();
for (String id : ids) {
try {
features.addAll(this.getFeatures(id, z, x, y, optionalParams));
} catch (Exception e) {
logger.error("POIProxy", e);
}
}
return features;
}
/**
* Calls the
* {@link ServiceConfigurationManager#getServiceConfiguration(String)}
*
* @param id
* The id of the service
* @return The {@link DescribeService}
*/
public DescribeService getServiceFromID(String id) {
return serviceManager.getServiceConfiguration(id);
}
/**
* This method is used to get the pois from a service and return a GeoJSON
* document with the data retrieved given a longitude, latitude and a radius
* in meters.
*
* @param id
* The id of the service
* @param lon
* The longitude
* @param lat
* The latitude
* @param distanceInMeters
* The distance in meters from the lon, lat
* @return The GeoJSON response from the original service response
*/
public String getPOIs(String id, double lon, double lat,
double distanceInMeters, List<Param> optionalParams)
throws Exception {
DescribeService describeService = getDescribeServiceByID(id);
double[] bbox = Calculator.boundingCoordinates(lon, lat,
distanceInMeters);
String geoJSON = getResponseAsGeoJSON(id, optionalParams,
describeService, bbox[0], bbox[1], bbox[2], bbox[3], lon, lat);
return geoJSON;
}
/**
* This method is used to get the pois from a service and return a list of
* {@link JTSFeature} document with the data retrieved given a longitude,
* latitude and a radius in meters.
*
* @param id
* The id of the service
* @param lon
* The longitude
* @param lat
* The latitude
* @param distanceInMeters
* The distance in meters from the lon, lat
* @return A list of {@link JTSFeature}
*/
public ArrayList<JTSFeature> getFeatures(String id, double lon, double lat,
double distanceInMeters, List<Param> optionalParams)
throws Exception {
DescribeService describeService = getDescribeServiceByID(id);
double[] bbox = Calculator.boundingCoordinates(lon, lat,
distanceInMeters);
return getFeatures(id, optionalParams, describeService, bbox[0],
bbox[1], bbox[2], bbox[3], lon, lat);
}
/**
* This method is used to get the pois from a category and return a list of
* {@link JTSFeature} document with the data retrieved given a longitude,
* latitude and a radius in meters.
*
* @param id
* The id of the service
* @param lon
* The longitude
* @param lat
* The latitude
* @param distanceInMeters
* The distance in meters from the lon, lat
* @return A list of {@link JTSFeature}
*/
public ArrayList<JTSFeature> getFeaturesByCategory(String category,
double lon, double lat, double distanceInMeters,
List<Param> optionalParams) throws Exception {
List<String> ids = serviceManager.getAvailableServices()
.getServicesIDByCategory(category);
ArrayList<JTSFeature> features = new ArrayList<JTSFeature>();
for (String id : ids) {
try {
features.addAll(this.getFeatures(id, lon, lat,
distanceInMeters, optionalParams));
} catch (Exception e) {
logger.error("POIProxy", e);
}
}
return features;
}
/**
* This method is used to get the pois from a service and return a GeoJSON
* document with the data retrieved given a bounding box corners
*
* @param id
* The id of the service
* @param minX
*
* @param minY
*
* @param maxX
*
* @param maxY
* @return The GeoJSON response from the original service response
*/
public String getPOIs(String id, double minX, double minY, double maxX,
double maxY, List<Param> optionalParams) throws Exception {
DescribeService describeService = getDescribeServiceByID(id);
String geoJSON = getResponseAsGeoJSON(id, optionalParams,
describeService, minX, minY, maxX, maxY, 0, 0);
return geoJSON;
}
/**
* This method is used to get the pois from a service and return a list of
* {@link JTSFeature} document with the data retrieved given a bounding box
* corners
*
* @param id
* The id of the service
* @param minX
*
* @param minY
*
* @param maxX
*
* @param maxY
* @return A list of {@link JTSFeature}
*/
public ArrayList<JTSFeature> getFeatures(String id, double minX,
double minY, double maxX, double maxY, List<Param> optionalParams)
throws Exception {
DescribeService describeService = getDescribeServiceByID(id);
return getFeatures(id, optionalParams, describeService, minX, minY,
maxX, maxY, 0, 0);
}
/**
* This method is used to get the pois from a category and return a list of
* {@link JTSFeature} document with the data retrieved given a bounding box
* corners
*
* @param id
* The id of the service
* @param minX
*
* @param minY
*
* @param maxX
*
* @param maxY
* @return A list of {@link JTSFeature}
*/
public ArrayList<JTSFeature> getFeaturesByCategory(String category,
double minX, double minY, double maxX, double maxY,
List<Param> optionalParams) throws Exception {
List<String> ids = serviceManager.getAvailableServices()
.getServicesIDByCategory(category);
ArrayList<JTSFeature> features = new ArrayList<JTSFeature>();
for (String id : ids) {
try {
features.addAll(getFeatures(id, minX, minY, maxX, maxY,
optionalParams));
} catch (Exception e) {
logger.error("POIProxy", e);
}
}
return features;
}
/**
* Given a DescribeService and some mandatory parameters, builds a valid
* request for the POI service
*
* @param describeService
* The DescribeService
* @param minX
* @param minY
* @param maxX
* @param maxY
* @param optionalParams
* A list of {@link Param}
* @param lon
* @param lat
* @return The url string to request to
*/
public String buildRequest(DescribeService describeService, double minX,
double minY, double maxX, double maxY, List<Param> optionalParams,
double lon, double lat) {
ServiceParams params = new ServiceParams();
Extent e1 = new Extent(minX, minY, maxX, maxY);
double[] minXY = GeotoolsUtils.transform(GEODETIC_SRS,
describeService.getSRS(),
new double[] { e1.getMinX(), e1.getMinY() });
double[] maxXY = GeotoolsUtils.transform(GEODETIC_SRS,
describeService.getSRS(),
new double[] { e1.getMaxX(), e1.getMaxY() });
int distanceMeters = this.getDistanceMeters(e1);
e1.setMinX(minXY[0]);
e1.setMinY(minXY[1]);
e1.setMaxX(maxXY[0]);
e1.setMaxY(maxXY[1]);
params.putParam(ServiceParams.MINX, String.valueOf(minXY[0]));
params.putParam(ServiceParams.MINY, String.valueOf(minXY[1]));
params.putParam(ServiceParams.MAXX, String.valueOf(maxXY[0]));
params.putParam(ServiceParams.MAXY, String.valueOf(maxXY[1]));
double longitude = (lon != 0) ? lon : e1.getCenter().getX();
double latitude = (lat != 0) ? lat : e1.getCenter().getY();
params.putParam(ServiceParams.LON, String.valueOf(longitude));
params.putParam(ServiceParams.LAT, String.valueOf(latitude));
params.putParam(ServiceParams.FORMAT, "json");
params.putParam(ServiceParams.DIST,
String.valueOf((int) distanceMeters));
params.putParam(ServiceParams.DISTKM,
String.valueOf((int) distanceMeters / 1000));
params.putParam(ServiceParams.KEY, describeService.getApiKey());
String url = describeService.getRequestForParam(optionalParams);
if (optionalParams != null) {
String p;
for (Param optParam : optionalParams) {
p = params.getServiceParamFromURLParam(optParam.getType());
params.putParam(p, optParam.getValue());
}
}
Set<String> keys = params.getParams().keySet();
Iterator<String> it = keys.iterator();
String key;
while (it.hasNext()) {
key = it.next();
url = url.replaceAll(key, params.getValueForParam(key));
}
return url;
}
/**
* Calls
* {@link Downloader#downloadFromUrl(String, es.prodevelop.gvsig.mini.utiles.Cancellable)}
*
* @param url
* The url to request to
* @param service
* The {@link DescribeService} object
* @param id
* The ID of the {@link DescribeService}
* @return The data downloaded
* @throws Exception
*/
public String doRequest(String url, DescribeService service, String id)
throws Exception {
// TODO do it in backgound?
Downloader d = new Downloader();
System.out.println(url);
d.downloadFromUrl(url, id, PropertyLocator.getInstance().tempFolder
+ id + File.separator, null);
if (service.getCompression() != null
&& service.getCompression().equals(CompressionEnum.ZIP.format)) {
Unzip unzip = new Unzip();
unzip.unzip(PropertyLocator.getInstance().tempFolder + id
+ File.separator, PropertyLocator.getInstance().tempFolder
+ id + File.separator + id, true);
Downloader opener = new Downloader();
return opener.openFile(PropertyLocator.getInstance().tempFolder
+ id + File.separator + service.getContentFile());
}
return new String(d.getData());
}
/**
* Called after {@link #doRequest(String)} succesfully downlaods the data.
*
* This method gets the {@link JPEParser} implementation given the
* {@link DescribeService#getFormat()} and calls
* {@link JPEParser#parse(String, DescribeService)}
*
* Once the response is parsed succesfully returns the GeoJSON response
*
* @param json
* The json document retrieved from the source service
* @param service
* The {@link DescribeService} used to parse the json
* @return A GeoJSON
* @throws NoParserRegisteredException
*/
public String onResponseReceived(String json, DescribeService service,
LocalFilter localFilter) throws NoParserRegisteredException {
final JPEParser jpeParser = JPEParserManager.getInstance()
.getJPEParser(service.getFormat());
jpeParser.parse(json, service, localFilter);
String geoJSON = jpeParser.getGeoJSON();
// Escribir en cache el geoJSON
return geoJSON;
}
private ArrayList<JTSFeature> parseFeatures(String json,
DescribeService service, LocalFilter localFilter)
throws NoParserRegisteredException {
final JPEParser jpeParser = JPEParserManager.getInstance()
.getJPEParser(service.getFormat());
return jpeParser.parse(json, service, localFilter);
}
/**
* Utility method to get the distance from the bottom left corner of the
* bounding box to the upper right corner
*
* @param boundingBox
* The bounding box
* @return The distance in meters
*/
public int getDistanceMeters(Extent boundingBox) {
return new Double(Math.floor(Calculator.latLonDist(
boundingBox.getMinX(), boundingBox.getMinY(),
boundingBox.getMaxX(), boundingBox.getMaxY()))).intValue();
}
/**
* Frees this instance of {@link POIProxy}
*/
public void destroy() {
logger.debug("Goodbye POIProxy");
}
/**
* Iterates the {@link DescribeServices} and returns a list of categories
* configured
*
* @return
*/
public List<String> getAvailableCategories() {
DescribeServices services = serviceManager.getAvailableServices();
return services.getCategories();
}
private DescribeService getDescribeServiceByID(String id)
throws POIProxyException {
DescribeService describeService = this.getServiceFromID(id);
if (describeService == null) {
throw new POIProxyException(this.getErrorForUnknownService(id));
}
return describeService;
}
/**
* Exception text when a service requested is not registered into the
* library
*
* @param id
* The id of the service not found
* @return The Exception text to show to the user
*/
private String getErrorForUnknownService(String id) {
StringBuffer error = new StringBuffer();
error.append("Services path: "
+ ServiceConfigurationManager.CONFIGURATION_DIR);
error.append("The service with id: " + id + " is not registered");
error.append("\n Available services are: ");
Set<String> keys = serviceManager.getRegisteredConfigurations()
.keySet();
Iterator<String> it = keys.iterator();
while (it.hasNext()) {
error.append(it.next()).append(" ");
}
return error.toString();
}
private String getResponseAsGeoJSON(String id, List<Param> optionalParams,
DescribeService describeService, double minX, double minY,
double maxX, double maxY, double lon, double lat) throws Exception,
NoParserRegisteredException {
String url = buildRequest(describeService, minX, minY, maxX, maxY,
optionalParams, lon, lat);
String file = doRequest(url, describeService, id);
String geoJSON = this.onResponseReceived(file, describeService,
describeService.getLocalFilter(optionalParams));
return geoJSON;
}
private ArrayList<JTSFeature> getFeatures(String id,
List<Param> optionalParams, DescribeService describeService,
double minX, double minY, double maxX, double maxY, double lon,
double lat) throws Exception, NoParserRegisteredException {
String url = buildRequest(describeService, minX, minY, maxX, maxY,
optionalParams, lon, lat);
String file = doRequest(url, describeService, id);
ArrayList<JTSFeature> features = this
.parseFeatures(file, describeService,
LocalFilter.fromOptionalParams(optionalParams));
return features;
}
/**
* Interface for registering a new service in POIProxy
*
* @param service
* The {@link DescribeService}
* @param describeService
* The JSON representation of the DescribeService for hot
* registering
* @throws POIProxyException
*/
public void register(DescribeService service, String describeService)
throws POIProxyException {
serviceManager.registerServiceConfiguration(service.getId(),
describeService, service);
}
}