/*
* Copyright (C) 2012, Emergya (http://www.emergya.com)
*
* @author <a href="mailto:marias@emergya.com">MarĂa Arias de Reyna</a>
*
* This file is part of GoFleetLS
*
* This software 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 2 of the License, or
* (at your option) any later version.
*
* This software 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 this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* As a special exception, if you link this library with other files to
* produce an executable, this library does not by itself cause the
* resulting executable to be covered by the GNU General Public License.
* This exception does not however invalidate any other reasons why the
* executable file might be covered by the GNU General Public License.
*/
package org.emergya.osrm;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;
import javax.xml.namespace.QName;
import net.opengis.gml.v_3_1_1.DirectPositionListType;
import net.opengis.gml.v_3_1_1.LineStringType;
import net.opengis.xls.v_1_2_0.AbstractResponseParametersType;
import net.opengis.xls.v_1_2_0.DetermineRouteRequestType;
import net.opengis.xls.v_1_2_0.DetermineRouteResponseType;
import net.opengis.xls.v_1_2_0.DistanceType;
import net.opengis.xls.v_1_2_0.DistanceUnitType;
import net.opengis.xls.v_1_2_0.RouteGeometryType;
import net.opengis.xls.v_1_2_0.RouteHandleType;
import net.opengis.xls.v_1_2_0.RouteInstructionType;
import net.opengis.xls.v_1_2_0.RouteInstructionsListType;
import net.opengis.xls.v_1_2_0.RouteSummaryType;
import net.opengis.xls.v_1_2_0.WayPointListType;
import net.opengis.xls.v_1_2_0.WayPointType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.gofleet.internacionalization.I18n;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
/**
* @author marias
*
*/
@SuppressWarnings("restriction")
@Repository
public class OSRM {
protected static final String EPSG_4326 = "EPSG:4326";
private static Log LOG = LogFactory.getLog(OSRM.class);
private GeometryFactory gf = new GeometryFactory();
/*
* Atribute to save tha last coordinate form a point in the line string
* request
*/
private Integer lastNode = null;
private DatatypeFactory dataTypeFactory = new com.sun.org.apache.xerces.internal.jaxp.datatype.DatatypeFactoryImpl();
@Autowired
private I18n i18n;
/**
* @param i18n
* the i18n to set
*/
public void setI18n(I18n i18n) {
this.i18n = i18n;
}
public OSRM() {
}
/**
* To use outside spring context
*/
public OSRM(I18n i18n) {
this();
setI18n(i18n);
}
/**
* Route plan using osrm server.
*
* @param param
* @param host_port
* @param http
* @return
* @throws IOException
* @throws ParseException
* @throws JAXBException
* @throws InterruptedException
*/
public AbstractResponseParametersType routePlan(
DetermineRouteRequestType param, String host_port, String http,
Locale locale) throws IOException, JAXBException, ParseException,
InterruptedException {
DetermineRouteResponseType res = new DetermineRouteResponseType();
// MANDATORY:
// Describes the overall characteristics of the route
RouteSummaryType routeSummary = new RouteSummaryType();
// OPTIONAL:
// Contains a reference to the route stored at the Route
// Determination Service server.
// Can be used in subsequent request to the Route Service to request
// additional information about the route, or to request an
// alternate route.
RouteHandleType routeHandle = new RouteHandleType();
// Constains a list of turn-by-turn route instructions and advisories,
// formatted for presentation.
// May contain the geometry and bounding box if specified in the
// request.
// May contain description. FOr example this can be used to connect the
// instruction with a map.
RouteInstructionsListType routeInstructionsList = new RouteInstructionsListType();
// RouteMapType: Contains a list of route maps.
// Can be used to specify which type of map to be generated whether an
// overview or maneuver.
// May contain description. For example this can be used to connect the
// instruction with a map.
try {
lastNode = null;
RouteGeometryType routeGeometry = new RouteGeometryType();
WayPointListType wayPointList = param.getRoutePlan()
.getWayPointList();
String url = "/viaroute";
CoordinateReferenceSystem sourceCRS = CRS.decode(EPSG_4326);
CoordinateReferenceSystem targetCRS = GeoUtil.getSRS(wayPointList
.getStartPoint());
com.vividsolutions.jts.geom.Point point = GeoUtil.getPoint(
wayPointList.getStartPoint(), sourceCRS);
url += "?loc=" + point.getY() + "," + point.getX();
for (WayPointType wayPoint : wayPointList.getViaPoint()) {
point = GeoUtil.getPoint(wayPoint, sourceCRS);
url += "&loc=" + point.getY() + "," + point.getX();
}
point = GeoUtil.getPoint(wayPointList.getEndPoint(), sourceCRS);
url += "&loc=" + point.getY() + "," + point.getX();
LOG.debug(url);
LineStringType lst = new LineStringType();
lst.setSrsName(targetCRS.getName().getCode());
routeGeometry.setLineString(lst);
res.setRouteGeometry(routeGeometry);
JsonFactory f = new JsonFactory();
JsonParser jp = f.createJsonParser(getOSRMStream(host_port, url));
jp.nextToken();
while (jp.nextToken() != JsonToken.END_OBJECT
&& jp.getCurrentToken() != null) {
String fieldname = jp.getCurrentName();
if (fieldname == null)
;
else if (jp.getCurrentName().equals("total_distance")) {
DistanceType duration = new DistanceType();
duration.setUom(DistanceUnitType.M);
duration.setValue(new BigDecimal(jp.getText()));
routeSummary.setTotalDistance(duration);
} else if (jp.getCurrentName().equals("total_time")) {
Duration duration = dataTypeFactory.newDuration(true, 0, 0,
0, 0, 0, jp.getIntValue());
routeSummary.setTotalTime(duration);
} else if (jp.getCurrentName().equals("route_geometry")) {
decodeRouteGeometry(lst.getPosOrPointPropertyOrPointRep(),
targetCRS, sourceCRS, jp);
} else if (jp.getCurrentName().equals("route_instructions")) {
processInstructions(locale, routeSummary,
routeInstructionsList, jp, lst);
}
jp.nextToken();
}
jp.close(); // ensure resources get cleaned up timely and properly
res.setRouteHandle(routeHandle);
if (param.getRouteInstructionsRequest() != null) {
res.setRouteInstructionsList(routeInstructionsList);
res.getRouteInstructionsList().setFormat("text/plain");
res.getRouteInstructionsList().setLang(locale.getLanguage());
}
res.setRouteSummary(routeSummary);
} catch (Throwable t) {
LOG.error("Error generating route response: " + t, t);
t.printStackTrace();
}
return res;
}
private BufferedReader getOSRMStream(String host_port, String url)
throws UnknownHostException, IOException {
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://" + host_port + url);
HttpResponse response = httpclient.execute(httpget);
BufferedReader in = new BufferedReader(new InputStreamReader(response
.getEntity().getContent()));
return in;
}
/**
* @param locale
* @param routeSummary
* @param routeInstructionsList
* @param jp
* @throws IOException
* @throws JsonParseException
*/
public void processInstructions(Locale locale,
RouteSummaryType routeSummary,
RouteInstructionsListType routeInstructionsList, JsonParser jp,
LineStringType lineString) throws IOException, JsonParseException {
while (jp.nextToken() == JsonToken.START_ARRAY
&& jp.getCurrentToken() != null) {
RouteInstructionType e = new RouteInstructionType();
jp.nextToken();
String instruction = jp.getText();
if (i18n != null)
instruction = i18n.getString(locale, jp.getText());
jp.nextToken();
if (jp.getText().length() > 0) {
if (i18n == null)
instruction += " on " + jp.getText();
else
instruction += " " + i18n.getString(locale, "on") + " "
+ jp.getText();
}
e.setInstruction(instruction);
jp.nextToken();
DistanceType distance = new DistanceType();
distance.setUom(DistanceUnitType.M);
distance.setValue(new BigDecimal(jp.getText()));
e.setDistance(distance);
// Get position
jp.nextToken();
Integer currentNode = null;
if (routeInstructionsList.getRouteInstruction().size() > 0) {
List<RouteInstructionType> lri = routeInstructionsList
.getRouteInstruction();
currentNode = jp.getIntValue();
List<JAXBElement<?>> pointList = lineString.getPosOrPointPropertyOrPointRep();
LineStringType lineToRoute = new LineStringType();
if(currentNode > lastNode){
List<JAXBElement<?>> pointListToRoute = new LinkedList<JAXBElement<?>>();
RouteGeometryType rgt = new RouteGeometryType();
for(int i=lastNode; i<=currentNode; i++){
pointListToRoute.add(pointList.get(i));
}
lineToRoute.setPosOrPointPropertyOrPointRep(pointListToRoute);
rgt.setLineString(lineToRoute);
lri.get(lri.size()-1).setRouteInstructionGeometry(rgt);
}
}
lastNode = jp.getIntValue();
jp.nextToken();
Duration duration = dataTypeFactory.newDuration(true, 0, 0, 0, 0,
0, jp.getIntValue());
e.setDuration(duration);
while (jp.nextToken() != JsonToken.END_ARRAY
&& jp.getCurrentToken() != null)
;
routeInstructionsList.getRouteInstruction().add(e);
}
}
private List<JAXBElement<?>> decodeRouteGeometry(List<JAXBElement<?>> list,
CoordinateReferenceSystem targetCRS,
CoordinateReferenceSystem sourceCRS, JsonParser jp)
throws NoSuchAuthorityCodeException, FactoryException,
MismatchedDimensionException, TransformException,
JsonParseException, IOException {
MathTransform transform = null;
LOG.trace(targetCRS.toWKT());
LOG.trace(sourceCRS.toWKT());
jp.nextToken();
while (jp.nextToken() == JsonToken.START_ARRAY
&& jp.getCurrentToken() != null) {
jp.nextToken();
Double lat = jp.getDoubleValue();
jp.nextToken();
Double lon = jp.getDoubleValue();
jp.nextToken();
Coordinate coord = new Coordinate(lat, lon);
Point sourceGeometry = gf.createPoint(coord);
LOG.trace(sourceGeometry);
if (sourceCRS != targetCRS) {
if (transform == null)
transform = CRS.findMathTransform(sourceCRS, targetCRS);
sourceGeometry = JTS.transform(sourceGeometry, transform)
.getCentroid();
LOG.trace(sourceGeometry);
}
DirectPositionListType e = new DirectPositionListType();
e.getValue().add(sourceGeometry.getY());
e.getValue().add(sourceGeometry.getX());
JAXBElement<DirectPositionListType> elem = new JAXBElement<DirectPositionListType>(
new QName("http://www.opengis.net/gml", "pos", "gml"),
DirectPositionListType.class, e);
list.add(elem);
}
return list;
}
}