package io.lumify.flightTrack;
import com.google.inject.Inject;
import io.lumify.core.exception.LumifyException;
import io.lumify.core.model.properties.LumifyProperties;
import io.lumify.core.model.workQueue.WorkQueueRepository;
import io.lumify.core.util.LumifyLogger;
import io.lumify.core.util.LumifyLoggerFactory;
import org.json.JSONArray;
import org.json.JSONObject;
import org.securegraph.*;
import org.securegraph.mutation.ExistingElementMutation;
import org.securegraph.type.GeoPoint;
import org.supercsv.io.CsvListReader;
import org.supercsv.prefs.CsvPreference;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.securegraph.util.IterableUtils.toList;
public class FlightRepository {
private static final LumifyLogger LOGGER = LumifyLoggerFactory.getLogger(FlightRepository.class);
public static final SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
private static final String MULTI_VALUE_PROPERTY_KEY = FlightRepository.class.getName();
private static final String SOURCE_NAME = "FlightAware.com";
private Graph graph;
private WorkQueueRepository workQueueRepository;
private Map<String, Airport> airportCodeMap = new HashMap<String, Airport>();
private Map<String, Airline> identPrefixMap = new HashMap<String, Airline>();
private Map<String, Vertex> identToVertex = new HashMap<String, Vertex>();
private Map<String, Vertex> airportCodeToVertex = new HashMap<String, Vertex>();
private Map<String, Vertex> airlinePrefixToVertex = new HashMap<String, Vertex>();
public FlightRepository() {
try {
loadAirlines();
loadAirports();
} catch (IOException ex) {
throw new LumifyException("Could not read dat files", ex);
}
}
private void loadAirports() throws IOException {
InputStreamReader reader = new InputStreamReader(FlightRepository.class.getResourceAsStream("airports.dat"));
CsvListReader listReader = new CsvListReader(reader, CsvPreference.STANDARD_PREFERENCE);
List<String> line;
while ((line = listReader.read()) != null) {
String title = line.get(1);
String airportCode = line.get(5);
double lat = Double.parseDouble(line.get(6));
double lon = Double.parseDouble(line.get(7));
if (airportCode == null) {
continue;
}
airportCodeMap.put(airportCode.toLowerCase(), new Airport(airportCode, title, new GeoPoint(lat, lon)));
}
}
private void loadAirlines() throws IOException {
InputStreamReader reader = new InputStreamReader(FlightRepository.class.getResourceAsStream("airlines.dat"));
CsvListReader listReader = new CsvListReader(reader, CsvPreference.STANDARD_PREFERENCE);
List<String> line;
while ((line = listReader.read()) != null) {
String title = line.get(1);
String identPrefix = line.get(4);
if (identPrefix == null) {
continue;
}
identPrefixMap.put(identPrefix.toLowerCase(), new Airline(identPrefix, title));
}
}
public void save(JSONObject json, Visibility visibility, Authorizations authorizations) {
JSONArray aircraft = json
.getJSONObject("SearchResult")
.getJSONArray("aircraft");
for (int i = 0; i < aircraft.length(); i++) {
JSONObject flight = aircraft.getJSONObject(i);
saveFlightData(flight, visibility, authorizations);
}
}
public void saveFlightData(JSONObject flightJson, Visibility visibility, Authorizations authorizations) {
double latitude = flightJson.getDouble("latitude");
double longitude = flightJson.getDouble("longitude");
double altitude = flightJson.getDouble("altitude");
double heading = flightJson.getDouble("heading");
String ident = flightJson.getString("ident");
String origin = flightJson.getString("origin");
String destination = flightJson.getString("destination");
Vertex airplaneVertex = saveAirplane(ident, visibility, authorizations);
Vertex originVertex = saveAirport(origin, visibility, authorizations);
Vertex destinationVertex = saveAirport(destination, visibility, authorizations);
if (updateOrigin(airplaneVertex, originVertex, visibility, authorizations)
|| updateDestination(airplaneVertex, destinationVertex, visibility, authorizations)) {
identToVertex.remove(ident); // edges are now invalid in the cache and we need to refresh
}
updateLocation(airplaneVertex, latitude, longitude, altitude, heading, visibility, authorizations);
}
public void updateLocation(Vertex airplaneVertex, double latitude, double longitude, double altitude, double heading, Visibility visibility, Authorizations authorizations) {
LOGGER.debug("updating location of airplane %s (lat: %f, lon: %f, alt: %f, head: %f)", airplaneVertex.getId(), latitude, longitude, altitude, heading);
ExistingElementMutation<Vertex> m = airplaneVertex.prepareMutation();
FlightTrackOntology.LOCATION.addPropertyValue(m, MULTI_VALUE_PROPERTY_KEY, new GeoPoint(latitude, longitude, altitude), visibility);
FlightTrackOntology.ALTITUDE.addPropertyValue(m, MULTI_VALUE_PROPERTY_KEY, altitude, visibility);
FlightTrackOntology.HEADING.addPropertyValue(m, MULTI_VALUE_PROPERTY_KEY, heading, visibility);
m.save(authorizations);
graph.flush();
workQueueRepository.pushGraphPropertyQueue(airplaneVertex, MULTI_VALUE_PROPERTY_KEY, FlightTrackOntology.LOCATION.getPropertyName());
workQueueRepository.pushGraphPropertyQueue(airplaneVertex, MULTI_VALUE_PROPERTY_KEY, FlightTrackOntology.ALTITUDE.getPropertyName());
workQueueRepository.pushGraphPropertyQueue(airplaneVertex, MULTI_VALUE_PROPERTY_KEY, FlightTrackOntology.HEADING.getPropertyName());
}
public boolean updateDestination(Vertex airplaneVertex, Vertex destinationVertex, Visibility visibility, Authorizations authorizations) {
List<String> currentDestinations = toList(airplaneVertex.getVertexIds(Direction.BOTH, FlightTrackOntology.EDGE_LABEL_HAS_DESTINATION, authorizations));
if (currentDestinations.size() == 0 || !currentDestinations.get(0).equals(destinationVertex.getId())) {
LOGGER.debug("airplane %s changed destinations to %s", airplaneVertex.getId(), destinationVertex.getId());
for (Object currentDestinationEdgeId : airplaneVertex.getEdgeIds(Direction.BOTH, FlightTrackOntology.EDGE_LABEL_HAS_DESTINATION, authorizations)) {
graph.removeEdge((String) currentDestinationEdgeId, authorizations);
}
Edge e = graph.addEdge(toDestinationEdgeId(airplaneVertex, destinationVertex), airplaneVertex, destinationVertex, FlightTrackOntology.EDGE_LABEL_HAS_DESTINATION, visibility, authorizations);
graph.flush();
workQueueRepository.pushElement(e);
return true;
}
return false;
}
public boolean updateOrigin(Vertex airplaneVertex, Vertex originVertex, Visibility visibility, Authorizations authorizations) {
List<String> currentOrigins = toList(airplaneVertex.getVertexIds(Direction.BOTH, FlightTrackOntology.EDGE_LABEL_HAS_ORIGIN, authorizations));
if (currentOrigins.size() == 0 || !currentOrigins.get(0).equals(originVertex.getId())) {
LOGGER.debug("airplane %s changed origin to %s", airplaneVertex.getId(), originVertex.getId());
for (Object currentOriginEdgeId : airplaneVertex.getEdgeIds(Direction.BOTH, FlightTrackOntology.EDGE_LABEL_HAS_ORIGIN, authorizations)) {
graph.removeEdge((String) currentOriginEdgeId, authorizations);
}
Edge e = graph.addEdge(toOriginEdgeId(airplaneVertex, originVertex), airplaneVertex, originVertex, FlightTrackOntology.EDGE_LABEL_HAS_ORIGIN, visibility, authorizations);
graph.flush();
workQueueRepository.pushElement(e);
return true;
}
return false;
}
private Vertex saveAirport(String airportCode, Visibility visibility, Authorizations authorizations) {
Vertex v = airportCodeToVertex.get(airportCode);
if (v != null) {
return v;
}
String airportId = toAirportId(airportCode);
v = graph.getVertex(airportId, authorizations);
if (v != null) {
airportCodeToVertex.put(airportCode, v);
return v;
}
LOGGER.info("new airport %s", airportCode);
Airport airport = airportCodeMap.get(airportCode.toLowerCase());
VertexBuilder vb = graph.prepareVertex(airportId, visibility);
LumifyProperties.CONCEPT_TYPE.setProperty(vb, FlightTrackOntology.CONCEPT_TYPE_AIRPORT, visibility);
FlightTrackOntology.AIRPORT_CODE.setProperty(vb, airportCode, visibility);
LumifyProperties.SOURCE.addPropertyValue(vb, MULTI_VALUE_PROPERTY_KEY, SOURCE_NAME, visibility);
if (airport != null) {
FlightTrackOntology.LOCATION.addPropertyValue(vb, MULTI_VALUE_PROPERTY_KEY, airport.getGeoPoint(), visibility);
LumifyProperties.TITLE.addPropertyValue(vb, MULTI_VALUE_PROPERTY_KEY, airport.getTitle(), visibility);
}
v = vb.save(authorizations);
airportCodeToVertex.put(airportCode, v);
graph.flush();
workQueueRepository.pushElement(v);
workQueueRepository.pushGraphPropertyQueue(v, MULTI_VALUE_PROPERTY_KEY, FlightTrackOntology.AIRPORT_CODE.getPropertyName());
return v;
}
private Vertex saveAirplane(String ident, Visibility visibility, Authorizations authorizations) {
Vertex airplaneVertex = identToVertex.get(ident);
if (airplaneVertex != null) {
return airplaneVertex;
}
String airplaneId = toAirplaneId(ident);
airplaneVertex = graph.getVertex(airplaneId, authorizations);
if (airplaneVertex != null) {
identToVertex.put(ident, airplaneVertex);
return airplaneVertex;
}
LOGGER.info("new airplane %s", ident);
VertexBuilder vb = graph.prepareVertex(airplaneId, visibility);
LumifyProperties.CONCEPT_TYPE.setProperty(vb, FlightTrackOntology.CONCEPT_TYPE_AIRPLANE, visibility);
FlightTrackOntology.IDENT.setProperty(vb, ident, visibility);
LumifyProperties.SOURCE.addPropertyValue(vb, MULTI_VALUE_PROPERTY_KEY, SOURCE_NAME, visibility);
airplaneVertex = vb.save(authorizations);
identToVertex.put(ident, airplaneVertex);
graph.flush();
workQueueRepository.pushElement(airplaneVertex);
workQueueRepository.pushGraphPropertyQueue(airplaneVertex, MULTI_VALUE_PROPERTY_KEY, FlightTrackOntology.IDENT.getPropertyName());
Vertex airlineVertex = findAirlineVertexFromIdent(ident, visibility, authorizations);
graph.flush();
if (airlineVertex != null) {
String airlineHasAirplaneId = toAirlineHasAirplaneId(airlineVertex, airplaneVertex);
if (graph.getEdge(airlineHasAirplaneId, authorizations) == null) {
Edge e = graph.addEdge(airlineHasAirplaneId, airlineVertex, airplaneVertex, FlightTrackOntology.EDGE_LABEL_HAS_AIRPLANE, visibility, authorizations);
graph.flush();
workQueueRepository.pushElement(e);
}
}
return airplaneVertex;
}
private Vertex findAirlineVertexFromIdent(String ident, Visibility visibility, Authorizations authorizations) {
Airline airline = findAirlineFromIdent(ident);
if (airline == null) {
return null;
}
Vertex v = airlinePrefixToVertex.get(airline.getIdentPrefix());
if (v != null) {
return v;
}
String airlineId = toAirlineId(airline.getIdentPrefix());
v = graph.getVertex(airlineId, authorizations);
if (v != null) {
airlinePrefixToVertex.put(airline.getIdentPrefix(), v);
return v;
}
LOGGER.info("new airline %s", airline.getTitle());
VertexBuilder vb = graph.prepareVertex(airlineId, visibility);
LumifyProperties.CONCEPT_TYPE.setProperty(vb, FlightTrackOntology.CONCEPT_TYPE_AIRLINE, visibility);
LumifyProperties.TITLE.addPropertyValue(vb, MULTI_VALUE_PROPERTY_KEY, airline.getTitle(), visibility);
FlightTrackOntology.AIRLINE_PREFIX.addPropertyValue(vb, MULTI_VALUE_PROPERTY_KEY, airline.getIdentPrefix(), visibility);
LumifyProperties.SOURCE.addPropertyValue(vb, MULTI_VALUE_PROPERTY_KEY, SOURCE_NAME, visibility);
v = vb.save(authorizations);
airlinePrefixToVertex.put(airline.getIdentPrefix(), v);
graph.flush();
workQueueRepository.pushElement(v);
workQueueRepository.pushGraphPropertyQueue(v, MULTI_VALUE_PROPERTY_KEY, LumifyProperties.TITLE.getPropertyName());
workQueueRepository.pushGraphPropertyQueue(v, MULTI_VALUE_PROPERTY_KEY, FlightTrackOntology.AIRLINE_PREFIX.getPropertyName());
return v;
}
private Airline findAirlineFromIdent(String ident) {
ident = ident.toLowerCase();
while (ident.length() > 1) {
Airline airline = identPrefixMap.get(ident);
if (airline != null) {
return airline;
}
ident = ident.substring(0, ident.length() - 1);
}
return null;
}
private String toAirplaneId(String ident) {
return "FlightTrack_Airplane_" + ident;
}
private String toAirportId(String airportCode) {
return "FlightTrack_Airport_" + airportCode;
}
private String toAirlineId(String identPrefix) {
return "FlightTrack_Airline_" + identPrefix;
}
private String toAirlineHasAirplaneId(Vertex airlineVertex, Vertex airplaneVertex) {
return airlineVertex.getId() + "_has_" + airplaneVertex;
}
private String toOriginEdgeId(Vertex airplaneVertex, Vertex destinationVertex) {
return airplaneVertex.getId() + "_has_org_" + destinationVertex.getId();
}
private String toDestinationEdgeId(Vertex airplaneVertex, Vertex destinationVertex) {
return airplaneVertex.getId() + "_has_dest_" + destinationVertex.getId();
}
@Inject
public void setGraph(Graph graph) {
this.graph = graph;
}
@Inject
public void setWorkQueueRepository(WorkQueueRepository workQueueRepository) {
this.workQueueRepository = workQueueRepository;
}
private static class Airport {
private final String airportCode;
private final String title;
private final GeoPoint geoPoint;
public Airport(String airportCode, String title, GeoPoint geoPoint) {
this.airportCode = airportCode;
this.title = title;
this.geoPoint = geoPoint;
}
public String getAirportCode() {
return airportCode;
}
public String getTitle() {
return title;
}
public GeoPoint getGeoPoint() {
return geoPoint;
}
}
private static class Airline {
private final String identPrefix;
private final String title;
public Airline(String identPrefix, String title) {
this.identPrefix = identPrefix;
this.title = title;
}
public String getIdentPrefix() {
return identPrefix;
}
public String getTitle() {
return title;
}
}
}