// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.opendata.core.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;
import org.openstreetmap.josm.io.AbstractReader;
import org.openstreetmap.josm.io.IllegalDataException;
import org.openstreetmap.josm.plugins.opendata.core.datasets.AbstractDataSetHandler;
import org.openstreetmap.josm.plugins.opendata.core.datasets.fr.FrenchConstants;
import org.xml.sax.SAXException;
import neptune.ChouetteAreaType;
import neptune.ChouettePTNetworkType;
import neptune.ChouettePTNetworkType.ChouetteArea.AreaCentroid;
import neptune.ChouettePTNetworkType.ChouetteArea.StopArea;
import neptune.ChouettePTNetworkType.ChouetteLineDescription.ChouetteRoute;
import neptune.ChouettePTNetworkType.ChouetteLineDescription.StopPoint;
import neptune.LineType;
import neptune.LongLatTypeType;
import neptune.PTLinkType;
import neptune.PTNetworkType;
import neptune.PointType;
import neptune.StopAreaType;
import neptune.StopPointType;
import neptune.TridentObjectType;
/**
* NEPTUNE -> OSM converter
* See http://www.chouette.mobi/IMG/pdf/NF__F_-Neptune-maj.pdf
*/
public class NeptuneReader extends AbstractReader implements FrenchConstants {
public static final String OSM_PUBLIC_TRANSPORT = "public_transport";
public static final String OSM_STOP = "stop";
public static final String OSM_STOP_AREA = "stop_area";
public static final String OSM_STOP_POSITION = "stop_position";
public static final String OSM_PLATFORM = "platform";
public static final String OSM_STATION = "station";
public static final String OSM_NETWORK = "network";
public static final String OSM_ROUTE = "route";
public static final String OSM_ROUTE_MASTER = "route_master";
public static final String OSM_TRAIN = "train";
public static final String OSM_SUBWAY = "subway";
public static final String OSM_MONORAIL = "monorail";
public static final String OSM_TRAM = "tram";
public static final String OSM_BUS = "bus";
public static final String OSM_TROLLEYBUS = "trolleybus";
public static final String OSM_AERIALWAY = "aerialway";
public static final String OSM_FERRY = "ferry";
private static final List<URL> schemas = new ArrayList<>();
static {
schemas.add(NeptuneReader.class.getResource(NEPTUNE_XSD));
}
private ChouettePTNetworkType root;
private final Map<String, OsmPrimitive> tridentObjects = new HashMap<>();
public static final boolean acceptsXmlNeptuneFile(File file) {
return acceptsXmlNeptuneFile(file, null);
}
public static final boolean acceptsXmlNeptuneFile(File file, URL schemaURL) {
if (schemaURL == null) {
schemaURL = schemas.get(0);
}
try (FileInputStream in = new FileInputStream(file)) {
Source xmlFile = new StreamSource(in);
try {
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema(schemaURL);
Validator validator = schema.newValidator();
validator.validate(xmlFile);
Main.info(xmlFile.getSystemId() + " is valid");
return true;
} catch (SAXException e) {
Main.error(xmlFile.getSystemId() + " is NOT valid");
Main.error("Reason: " + e.getLocalizedMessage());
} catch (IOException e) {
Main.error(xmlFile.getSystemId() + " is NOT valid");
Main.error("Reason: " + e.getLocalizedMessage());
}
} catch (IOException e) {
Main.error(e);
}
return false;
}
public static DataSet parseDataSet(InputStream in, AbstractDataSetHandler handler, ProgressMonitor instance) throws IllegalDataException {
return new NeptuneReader().doParseDataSet(in, instance);
}
protected static final <T> T unmarshal(Class<T> docClass, InputStream inputStream) throws JAXBException {
String packageName = docClass.getPackage().getName();
JAXBContext jc = JAXBContext.newInstance(packageName, NeptuneReader.class.getClassLoader());
Unmarshaller u = jc.createUnmarshaller();
@SuppressWarnings("unchecked")
JAXBElement<T> doc = (JAXBElement<T>) u.unmarshal(inputStream);
return doc.getValue();
}
private void linkTridentObjectToOsmPrimitive(TridentObjectType object, OsmPrimitive p) {
p.put("ref:neptune", object.getObjectId());
if (tridentObjects.put(object.getObjectId(), p) != null) {
Main.error("Trident object duplicated !!! : "+object.getObjectId());
}
}
protected Node createNode(LatLon latlon) {
Node n = new Node(latlon);
ds.addPrimitive(n);
return n;
}
private Node createPlatform(StopPointType stop) {
Node n = createNode(createLatLon(stop));
n.put(OSM_PUBLIC_TRANSPORT, OSM_PLATFORM);
linkTridentObjectToOsmPrimitive(stop, n);
n.put("name", stop.getName());
return n;
}
protected Relation createRelation(String type) {
Relation r = new Relation();
r.put("type", type);
ds.addPrimitive(r);
return r;
}
protected Relation createPtRelation(String pt, TridentObjectType object) {
Relation r = createRelation(OSM_PUBLIC_TRANSPORT);
r.put(OSM_PUBLIC_TRANSPORT, pt);
linkTridentObjectToOsmPrimitive(object, r);
return r;
}
protected Relation createNetwork(PTNetworkType network) {
Relation r = createRelation(OSM_NETWORK);
linkTridentObjectToOsmPrimitive(network, r);
r.put("name", network.getName());
return r;
}
protected Relation createRouteMaster(LineType line) {
Relation r = createPtRelation(OSM_ROUTE_MASTER, line);
switch (line.getTransportModeName()) {
case BUS:
r.put(OSM_ROUTE_MASTER, OSM_BUS); break;
case AIR:
r.put(OSM_ROUTE_MASTER, OSM_AERIALWAY); break;
case FERRY:
r.put(OSM_ROUTE_MASTER, OSM_FERRY); break;
case METRO:
r.put(OSM_ROUTE_MASTER, OSM_SUBWAY); break;
case TRAIN:
r.put(OSM_ROUTE_MASTER, OSM_TRAIN); break;
case TRAMWAY:
r.put(OSM_ROUTE_MASTER, OSM_TRAM); break;
case TROLLEYBUS:
r.put(OSM_ROUTE_MASTER, OSM_TROLLEYBUS); break;
default:
Main.warn("Unsupported transport mode: "+line.getTransportModeName());
}
r.put("ref", line.getNumber());
r.put("name", line.getTransportModeName().value()+" "+line.getNumber()+": "+line.getName());
return r;
}
private Relation createRoute(ChouetteRoute route) {
Relation r = createPtRelation(OSM_ROUTE, route);
r.put("name", route.getName());
return r;
}
protected Relation createStopArea(StopAreaType sa) {
Relation r = createPtRelation(OSM_STOP_AREA, sa);
r.put("name", sa.getName());
return r;
}
protected LatLon createLatLon(PointType point) {
return new LatLon(point.getLatitude().doubleValue(), point.getLongitude().doubleValue());
}
protected final <T extends TridentObjectType> T findTridentObject(List<T> list, String id) {
for (T object : list) {
if (object.getObjectId().equals(id)) {
return object;
}
}
return null;
}
protected StopPoint findStopPoint(String id) {
return findTridentObject(root.getChouetteLineDescription().getStopPoint(), id);
}
protected StopArea findStopArea(String id) {
return findTridentObject(root.getChouetteArea().getStopArea(), id);
}
protected AreaCentroid findAreaCentroid(String id) {
return findTridentObject(root.getChouetteArea().getAreaCentroid(), id);
}
protected PTLinkType findPtLink(String id) {
return findTridentObject(root.getChouetteLineDescription().getPtLink(), id);
}
protected static final boolean isNullLatLon(LatLon ll) {
return ll.lat() == 0.0 && ll.lon() == 0.0;
}
@Override
protected DataSet doParseDataSet(InputStream in, ProgressMonitor instance) throws IllegalDataException {
try {
root = unmarshal(ChouettePTNetworkType.class, in);
} catch (JAXBException e) {
throw new IllegalDataException(e);
}
Relation network = createNetwork(root.getPTNetwork());
// Parsing Stop areas
for (StopArea sa : root.getChouetteArea().getStopArea()) {
if (sa.getStopAreaExtension().getAreaType().equals(ChouetteAreaType.COMMERCIAL_STOP_POINT)) {
Relation stopArea = createStopArea(sa);
stopArea.put("name", sa.getName());
for (String childId : sa.getContains()) {
if (childId.contains("StopArea")) {
StopArea child = findStopArea(childId);
if (child == null) {
Main.warn("Cannot find StopArea: "+childId);
} else {
if (child.getStopAreaExtension().getAreaType().equals(ChouetteAreaType.BOARDING_POSITION)) {
for (String grandchildId : child.getContains()) {
if (grandchildId.contains("StopPoint")) {
StopPoint grandchild = findStopPoint(grandchildId);
if (grandchild == null) {
Main.warn("Cannot find StopPoint: "+grandchildId);
} else {
if (grandchild.getLongLatType().equals(LongLatTypeType.WGS_84)) {
Node platform = createPlatform(grandchild);
stopArea.addMember(new RelationMember(OSM_PLATFORM, platform));
} else {
Main.warn("Unsupported long/lat type: "+grandchild.getLongLatType());
}
}
} else {
Main.warn("Unsupported grandchild: "+grandchildId);
}
}
String centroidId = child.getCentroidOfArea();
AreaCentroid areaCentroid = findAreaCentroid(centroidId);
if (areaCentroid == null) {
Main.warn("Cannot find AreaCentroid: "+centroidId);
} else if (!areaCentroid.getLongLatType().equals(LongLatTypeType.WGS_84)) {
Main.warn("Unsupported long/lat type: "+areaCentroid.getLongLatType());
} else {
for (RelationMember member : stopArea.getMembers()) {
// Fix stop coordinates if needed
if (member.getRole().equals(OSM_PLATFORM) && isNullLatLon(member.getNode().getCoor())) {
member.getNode().setCoor(createLatLon(areaCentroid));
}
}
}
} else {
Main.warn("Unsupported child type: "+child.getStopAreaExtension().getAreaType());
}
}
} else if (childId.contains("StopPoint")) {
StopPoint child = findStopPoint(childId);
if (child == null) {
Main.warn("Cannot find StopPoint: "+childId);
} else {
// TODO
Main.info("TODO: handle StopPoint "+childId);
}
} else {
Main.warn("Unsupported child: "+childId);
}
}
} else if (sa.getStopAreaExtension().getAreaType().equals(ChouetteAreaType.BOARDING_POSITION)) {
//Main.info("skipping StopArea with type "+sa.getStopAreaExtension().getAreaType()+": "+sa.getObjectId());
} else {
Main.warn("Unsupported StopArea type: "+sa.getStopAreaExtension().getAreaType());
}
}
Relation routeMaster = createRouteMaster(root.getChouetteLineDescription().getLine());
network.addMember(new RelationMember(null, routeMaster));
for (ChouetteRoute cr : root.getChouetteLineDescription().getChouetteRoute()) {
Relation route = createRoute(cr);
routeMaster.addMember(new RelationMember(null, route));
for (String id : cr.getPtLinkId()) {
PTLinkType ptlink = findPtLink(id);
if (ptlink == null) {
Main.warn("Cannot find PTLinkType: "+id);
} else {
/*StopPoint start = findStopPoint(ptlink.getStartOfLink());
StopPoint end = findStopPoint(ptlink.getEndOfLink());*/
OsmPrimitive start = tridentObjects.get(ptlink.getStartOfLink());
OsmPrimitive end = tridentObjects.get(ptlink.getEndOfLink());
if (start == null) {
Main.warn("Cannot find start StopPoint: "+ptlink.getStartOfLink());
} else if (start.get(OSM_PUBLIC_TRANSPORT).equals(OSM_STOP) || start.get(OSM_PUBLIC_TRANSPORT).equals(OSM_PLATFORM)) {
addStopToRoute(route, start);
}
if (end == null) {
Main.warn("Cannot find end StopPoint: "+ptlink.getEndOfLink());
} else if (end.get(OSM_PUBLIC_TRANSPORT).equals(OSM_STOP) || end.get(OSM_PUBLIC_TRANSPORT).equals(OSM_PLATFORM)) {
addStopToRoute(route, end);
}
}
}
}
return ds;
}
private static boolean addStopToRoute(Relation route, OsmPrimitive stop) {
if (route.getMembersCount() == 0 || !route.getMember(route.getMembersCount()-1).getMember().equals(stop)) {
route.addMember(new RelationMember(stop.get(OSM_PUBLIC_TRANSPORT), stop));
return true;
} else {
return false;
}
}
public static List<URL> getSchemas() {
return schemas;
}
public static void registerSchema(URL resource) {
if (resource != null && !schemas.contains(resource)) {
schemas.add(resource);
}
}
}