package sushi.traffic.importer;
import java.io.IOException;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.xml.sax.SAXException;
import sushi.event.SushiEvent;
import sushi.event.SushiEventType;
import sushi.event.attribute.SushiAttributeTree;
import sushi.event.attribute.SushiAttributeTypeEnum;
import sushi.event.collection.SushiMapTree;
import sushi.event.collection.SushiTree;
import sushi.weather.importer.DWDHelper;
import sushi.event.collection.SushiTree;
/**
* Adapter for traffic incidents from tomtom api
*/
public class TomTomTrafficimporter {
String TOMTOMAPIKEY = "INSERT YOUR API KEY";
SushiEventType trafficEventType = null;
public final static void main(String[] args) throws Exception {
TomTomTrafficimporter t = new TomTomTrafficimporter();
System.out.println("###############################################");
System.out.println(t.getTrafficSushiEventHamburgToBerlin());
System.out.println("###############################################");
}
/**
* returns list of SushiEvents which represent the trafficincidents
* expire time is set to 2min, but events will not be deleted
*/
public ArrayList<SushiEvent> getTrafficSushiEventsPotsdam() throws JSONException{
return getTrafficSushiEventsForArea("52.355264", "12.997513", "52.430688", "13.137932", "16", "Potsdam");
}
/**
* returns list of SushiEvents which represent the trafficincidents
* expire time is set to 2min, but events will not be deleted
*/
public ArrayList<SushiEvent> getTrafficSushiEventsBerlin() throws JSONException{
return getTrafficSushiEventsForArea("52.393201", "13.119049", "52.601379", "13.702698", "16", "Berlin");
}
/**
* returns a list of TrafficEvents from each area descriped in the DWDHelper
*/
public ArrayList<SushiEvent> getTrafficSushiEventHamburgToBerlin() throws JSONException, XPathExpressionException, ParserConfigurationException, SAXException, IOException{
ArrayList<SushiEvent> incidents = new ArrayList<>();
// get the areaDescriptions with their rectangle provided by deutscher wetterdienst, the same used for the dwdimporter
HashMap<String, HashMap<String, String>> dwdAreas = DWDHelper.getAreasAndCoordinatesFromXMLFile();
for (String areaDesc : dwdAreas.keySet()){
HashMap<String, String> minMaxLatLon = dwdAreas.get(areaDesc);
incidents.addAll(
getTrafficSushiEventsForArea(minMaxLatLon.get("minLat"),
minMaxLatLon.get("minLon"),
minMaxLatLon.get("maxLat"),
minMaxLatLon.get("maxLon"),
"16",
areaDesc));
}
// remove all trafficevents which do not affect the main route
return filterEvents(incidents);
}
/**
* returns the TrafficEvents which are in the given rectangle
*/
public ArrayList<SushiEvent> getTrafficSushiEventsForArea(String maxLat, String maxLon, String minLat, String minLon, String zoomlevel, String areaDesc) throws JSONException{
JSONObject mainRespondElement = getTrafficEventsJSON(maxLat, maxLon, minLat, minLon, zoomlevel);
ArrayList<JSONObject> trafficincidents = getFlattenTrafficIncidents(mainRespondElement);
return getEventFromJSON(trafficincidents, areaDesc);
}
/**
* return the mapping of incidents type to english description
*/
public HashMap<String, String> getNameOfIncidentsMap(){
HashMap<String, String> incidentsMap = new HashMap<String, String>();
incidentsMap.put("-", "-");
incidentsMap.put("0", "Unknown");
incidentsMap.put("1", "Accident");
incidentsMap.put("2", "Fog");
incidentsMap.put("3", "Dangerous Conditions");
incidentsMap.put("4", "Rain");
incidentsMap.put("5", "Ice");
incidentsMap.put("6", "Jam");
incidentsMap.put("7", "Lane Closed");
incidentsMap.put("8", "Road Closed");
incidentsMap.put("9", "Road Works");
incidentsMap.put("10", "Wind");
incidentsMap.put("11", "Flooding");
incidentsMap.put("12", "Detour");
incidentsMap.put("13", "Cluster – returned if a cluster contains incidents with different icon categories");
return incidentsMap;
}
/**
* for internal usage / debugging
*/
private JSONObject getTrafficEventsJSONPotsdam(){
return getTrafficEventsJSON("52.355264", "12.997513", "52.430688", "13.137932", "16");
}
/**
* for internal usage / debugging
*/
private JSONObject getTrafficEventsJSONBerlin(){
return getTrafficEventsJSON("52.393201", "13.119049", "52.601379", "13.702698", "16");
}
/**
* return the respond element ("tm") of the traffic request send to tomtom
*
* lat/lon = latidude/longitude from Google maps (lat is the first value)
* zoomfactor stands for the visible size of the map, use 10 or higher for cities and lower for rural area
* example for potsdam: getTraffic(52.355264, 12.997513, 52.430688, 13.137932, 11)
*/
private JSONObject getTrafficEventsJSON(String maxLat, String maxLon, String minLat, String minLon, String zoomlevel) {
HttpClient httpclient = new DefaultHttpClient();
JSONObject mainRespondElement = null;
String requerstString = "https://api.tomtom.com/lbs/services/trafficIcons/3/s2/" +
maxLat + "," +
maxLon+ "," +
minLat + "," +
minLon +"/" +
zoomlevel +
"/-1/json?language=de&projection=EPSG4326&expandCluster=true&key=" + TOMTOMAPIKEY;
String responseBody = "";
try {
HttpGet httpget = new HttpGet(requerstString);
System.out.println("executing request " + httpget.getURI());
// Create a response handler
ResponseHandler<String> responseHandler = new BasicResponseHandler();
responseBody = httpclient.execute(httpget, responseHandler);
JSONObject jsonRespond = new JSONObject(responseBody);
mainRespondElement = jsonRespond.getJSONObject("tm");
} catch (Exception e) {
System.err.println("ERROR: unexpected tomtom respond or no connection possible: " + responseBody);
e.printStackTrace();
} finally {
// When HttpClient instance is no longer needed,
// shut down the connection manager to ensure
// immediate deallocation of all system resources
httpclient.getConnectionManager().shutdown();
}
return mainRespondElement;
}
/**
* returns the eventtype for traffic incidents
*/
public SushiEventType getTrafficEventtype(){
// singelton
if (trafficEventType == null){
// try to find the event type in db
trafficEventType = SushiEventType.findByTypeName("TomTomTrafficEvent");
// create new if undefined
if (trafficEventType == null){
SushiAttributeTree valueTree = new SushiAttributeTree();
valueTree.addRoot("expires", SushiAttributeTypeEnum.DATE);
valueTree.addRoot("duration", SushiAttributeTypeEnum.INTEGER);
valueTree.addRoot("length", SushiAttributeTypeEnum.INTEGER);
valueTree.addRoot("startStreet", SushiAttributeTypeEnum.STRING);
valueTree.addRoot("endStreet", SushiAttributeTypeEnum.STRING);
valueTree.addRoot("onRoadnumbers", SushiAttributeTypeEnum.STRING);
valueTree.addRoot("type", SushiAttributeTypeEnum.INTEGER);
valueTree.addRoot("areaDesc", SushiAttributeTypeEnum.STRING);
valueTree.addRoot("onCountry", SushiAttributeTypeEnum.STRING);
valueTree.addRoot("xvalue", SushiAttributeTypeEnum.STRING);
valueTree.addRoot("yvalue", SushiAttributeTypeEnum.STRING);
trafficEventType = new SushiEventType("TomTomTrafficEvent", valueTree, "detectedAt");
}
}
return trafficEventType;
}
/**
* generates Events from TOMTOM JSON respond
* just germany
* location is used to name the area
*/
private ArrayList<SushiEvent> getEventFromJSON(ArrayList<JSONObject> trafficincidents, String areaDesc) throws JSONException {
ArrayList<SushiEvent> sushiEvents = new ArrayList<SushiEvent>();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
cal.add(Calendar.MINUTE, 2);
String newTime = df.format(cal.getTime());
for (JSONObject incident : trafficincidents){
SushiMapTree<String, Serializable> values = new SushiMapTree<>();
String from = incident.has("f") ? incident.getString("f") : "-";
String to = incident.has("t") ? incident.getString("t") : "-";
DecimalFormat f = new DecimalFormat("#0.00");
String duration = incident.has("dl") ? f.format((incident.getInt("dl")/60.0)) : "-";
String type = incident.has("ic") ? incident.getString("ic") : "-";
String length = incident.has("l") ? incident.getString("l") : "-";
String roadNumbersAffected = incident.has("r") ? incident.getString("r") : "-";
String xvalue = incident.has("p") ? incident.getJSONObject("p").getString("x") : "-";
String yvalue = incident.has("p") ? incident.getJSONObject("p").getString("y") : "-";
values.addRootElement("expires", newTime);
values.addRootElement("duration", duration);
values.addRootElement("length", length);
values.addRootElement("startStreet", from);
values.addRootElement("endStreet", to);
values.addRootElement("onRoadnumbers", roadNumbersAffected);
values.addRootElement("xvalue", xvalue);
values.addRootElement("yvalue", yvalue);
values.addRootElement("type", type);
values.addRootElement("areaDesc", areaDesc);
values.addRootElement("onCountry", "germany");
SushiEvent trafficEvent = new SushiEvent(getTrafficEventtype(), new Date(), values);
sushiEvents.add(trafficEvent);
}
return sushiEvents;
}
/**
* generates list of JSONObjects out of JSON respond
*/
public ArrayList<JSONObject> getFlattenTrafficIncidents(JSONObject mainRespondElement) throws JSONException{
ArrayList<JSONObject> trafficIncidents = new ArrayList<JSONObject>();
// if there is no poi element, then there are no traffic incidents on this zoomlevel or location
if (!mainRespondElement.has("poi")) return trafficIncidents;
// mainRespondElement has incidents or cluster of incidents, if it has just one Cluster/incident then poi has just 1 JsonObject, otherwise it has a JsonArray of Objects
Object poi = mainRespondElement.get("poi");
if (poi instanceof JSONArray) {
// there are more than one traffic incident cluster (more then 1 poi element)
JSONArray pois = (JSONArray) poi;
for (int i = 0; i < pois.length(); ++i){
trafficIncidents.addAll(getFlattenTrafficIncidentsFromPoi(pois.getJSONObject(i)));
}
}
else if (poi instanceof JSONObject) {
// just one cluster (one 1 element)
trafficIncidents.addAll(getFlattenTrafficIncidentsFromPoi((JSONObject)poi));
poi = (JSONObject) poi;
}
else {
// ERROR
// It's something else, like a string or number
}
return trafficIncidents;
}
/**
* filters the traffic incidents out of the poi elements of the tomtom json respond,
* breaks down the hierachical representation provided by tomtom,
* used by getFlattenTrafficIncidents
*/
private ArrayList<JSONObject> getFlattenTrafficIncidentsFromPoi(JSONObject poi) throws JSONException {
// poi elements can have nested incidents "cpois" or is itself an traffic incident, if it is nested, use just the nested incidents cause the clustering elemets has no information
ArrayList<JSONObject> trafficIncidents = new ArrayList<JSONObject>();
int clustersize = poi.getInt("cs"); //clustersize
if (clustersize == 0){
// single traffic incident, no cluster, clustersize = 0
trafficIncidents.add(poi);
} else if (clustersize == 1){
// just one incident in cluster, therefore cpoi element is single JSONObject
trafficIncidents.add(poi.getJSONObject("cpoi"));
} else {
// more than 1 incident in cluster, extract all, therefore cpoi element is JSONArray of JSONObjects
JSONArray incidentsCluster = poi.getJSONArray("cpoi");
for (int i = 0; i < incidentsCluster.length(); ++i){
trafficIncidents.add(incidentsCluster.getJSONObject(i));
}
}
return trafficIncidents;
}
/**
* removes trafficevents which do not affect example usecase/route
*/
public ArrayList<SushiEvent> filterEvents(ArrayList<SushiEvent> trafficEvents) throws IOException{
ArrayList<String> useOnlyTheseStreets = TomTomHelper.getStreetnumbersOfExampleUsecase();
ArrayList<SushiEvent> filteredEvents = new ArrayList<SushiEvent>();
for (SushiEvent trafficEvent : trafficEvents){
SushiMapTree<String, Serializable> map = trafficEvent.getValues();
if ( areStreetsInSet((String)map.getValueOfAttribute("startStreet"), useOnlyTheseStreets) ||
areStreetsInSet((String)map.getValueOfAttribute("endStreet"), useOnlyTheseStreets) ||
areStreetsInSet((String)map.getValueOfAttribute("onRoadnumbers"), useOnlyTheseStreets)){
filteredEvents.add(trafficEvent);
}
}
return filteredEvents;
}
/**
* checks if the given streets string (e.g. "Berliner Straße / Neue Straße") includes a street that is in the useOnlyTheseStreets
*/
private boolean areStreetsInSet(String streets, ArrayList<String> useOnlyTheseStreets){
if (streets.equals("-")) return false;
String[] splittedStreets = streets.split("/");
for (String street : splittedStreets){
for (String filterStreet : useOnlyTheseStreets){
if (street.contains(filterStreet)) return true;
}
}
return false;
}
/**
* prettyprints the trafficincidents
*/
private void prettyPrint(ArrayList<JSONObject> trafficIncidents) throws JSONException{
int maxLengthf = 0;
int maxLengtht = 0;
HashMap<String, String> incidentsMap = getNameOfIncidentsMap();
if (trafficIncidents.isEmpty()){
System.out.println("no traffic Incidents");
return;
}
for (JSONObject incident : trafficIncidents){
if (incident.has("f") && incident.getString("f").length() > maxLengthf) maxLengthf = incident.getString("f").length();
if (incident.has("t") && incident.getString("t").length() > maxLengtht) maxLengtht = incident.getString("t").length();
}
System.out.format("%-"+maxLengthf+"s%-"+maxLengtht+"s%-8s%-20s%-9s%-5s%n", "from", "to", "street#", "type", "duration", "length");
for (JSONObject incident : trafficIncidents){
String from = incident.has("f") ? incident.getString("f") : "-";
String to = incident.has("t") ? incident.getString("t") : "-";
DecimalFormat f = new DecimalFormat("#0.00");
String duration = incident.has("dl") ? f.format((incident.getInt("dl")/60.0)) : "-";
String type = incident.has("ic") ? incident.getString("ic") : "-";
type = type + " (" + incidentsMap.get(type) + ")";
String length = incident.has("l") ? incident.getString("l") : "-";
String roadNumbersAffected = incident.has("r") ? incident.getString("r") : "-";
System.out.format("%-"+maxLengthf+"s%-"+maxLengtht+"s%-8s%-20s%-9s%-5s%n", from, to, roadNumbersAffected, type, duration, length);
}
}
}