package edu.kit.pse.ws2013.routekit.precalculation; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; import edu.kit.pse.ws2013.routekit.map.EdgeBasedGraph; import edu.kit.pse.ws2013.routekit.map.EdgeProperties; import edu.kit.pse.ws2013.routekit.map.Graph; import edu.kit.pse.ws2013.routekit.map.NodeProperties; import edu.kit.pse.ws2013.routekit.map.Restriction; import edu.kit.pse.ws2013.routekit.map.StreetMap; import edu.kit.pse.ws2013.routekit.map.TurnType; import edu.kit.pse.ws2013.routekit.models.ProgressReporter; import edu.kit.pse.ws2013.routekit.util.Coordinates; /** * This class provides the functionality to parse an OpenStreetMap data file. */ public class OSMParser { private Graph graph; private EdgeBasedGraph edgeBasedGraph; /* * Maps OSM node IDs to our node indexes. */ private Map<Long, Integer> nodes = new HashMap<>(); /* * Contains the outgoing edges for each node. */ private List<List<MapEdge>> edges = new ArrayList<>(); /* * Contains for each node the turn restrictions via that node. */ private Map<Integer, List<TurnRestriction>> turnRestrictions = new HashMap<>(); private int numberOfEdges = 0; private int numberOfTurns = 0; /* * The arrays for creating the Graph. */ private int graphNodes[]; private int graphEdges[]; private EdgeProperties edgeProps[]; private float lat[]; private float lon[]; private Map<Integer, NodeProperties> nodeProps; /* * The arrays for creating the EdgedBasedGraph. */ private int ebgEdges[]; private int ebgTurns[]; private TurnType turnTypes[]; private Map<Integer, Restriction> restrictions; /** * Reads an OpenStreetMap data file and creates a {@link Graph} from it as * well as the corresponding (not yet partitioned) {@link EdgeBasedGraph} * and returns them as a {@link StreetMap} object. * * @param file * the OSM file to be read * @return the {@link StreetMap} created from the OSM file * @throws IllegalArgumentException * if {@code file} is {@code null} * @throws IOException * if any I/O error occurs * @throws SAXException * if an error occurs during parsing the OSM file, e.g. if the * file format is invalid */ public StreetMap parseOSM(File file, ProgressReporter reporter) throws SAXException, IOException { if (file == null) { throw new IllegalArgumentException(); } reporter.setSubTasks(new float[] { .35f, .4f, .05f, .15f, .025f, .025f }); SAXParser parser = null; try { parser = SAXParserFactory.newInstance().newSAXParser(); } catch (ParserConfigurationException e) { e.printStackTrace(); } reporter.pushTask("Lese Kanten"); parser.parse(file, new FirstRunHandler()); reporter.nextTask("Lese Koordinaten, Straßeneigenschaften, Beschränkungen"); parser.parse(file, new SecondRunHandler()); reporter.nextTask("Baue Adjazenzfelder auf"); createAdjacencyField(); reporter.nextTask("Baue Graph auf"); graph = new Graph(graphNodes, graphEdges, nodeProps, edgeProps, lat, lon); reporter.nextTask("Baue Adjazenzfelder für kantenbasierten Graph auf"); buildEdgeBasedGraph(); reporter.nextTask("Baue kantenbasierten Graph auf"); edgeBasedGraph = new EdgeBasedGraph(ebgEdges, ebgTurns, turnTypes, restrictions); reporter.popTask(); return new StreetMap(graph, edgeBasedGraph); } private void createAdjacencyField() { graphNodes = new int[nodes.size()]; graphEdges = new int[numberOfEdges]; edgeProps = new EdgeProperties[numberOfEdges]; int edgeCount = 0; for (int node = 0; node < nodes.size(); node++) { graphNodes[node] = edgeCount; for (MapEdge edge : edges.get(node)) { edge.setId(edgeCount); graphEdges[edgeCount] = edge.getTargetNode(); edgeProps[edgeCount] = edge.getWay().getEdgeProperties(); edgeCount++; } } } private void buildEdgeBasedGraph() { ebgEdges = new int[numberOfEdges]; List<Integer> turns = new ArrayList<>(2 * numberOfEdges); List<TurnType> types = new ArrayList<>(2 * numberOfEdges); restrictions = new HashMap<>(); int edgeCount = 0; for (int node = 0; node < edges.size(); node++) { for (MapEdge fromEdge : edges.get(node)) { ebgEdges[edgeCount] = numberOfTurns; TurnRestriction tr = findTurnRestriction(fromEdge); for (MapEdge toEdge : edges.get(fromEdge.getTargetNode())) { if (toEdge.getTargetNode() != node && (tr == null || tr.allowsTo(toEdge))) { turns.add(toEdge.getId()); types.add(determineTurnType(node, fromEdge, toEdge)); Restriction restr = toEdge.getWay().getRestriction(); if (restr != null) { restrictions.put(numberOfTurns, restr); } numberOfTurns++; } } edgeCount++; } } turnTypes = types.toArray(new TurnType[0]); ebgTurns = new int[numberOfTurns]; for (int i = 0; i < ebgTurns.length; i++) { ebgTurns[i] = turns.get(i); } } private TurnRestriction findTurnRestriction(MapEdge fromEdge) { int turnNode = fromEdge.getTargetNode(); if (turnRestrictions.containsKey(turnNode)) { for (TurnRestriction r : turnRestrictions.get(turnNode)) { if (r.getFrom() == fromEdge.getWay().getId()) { return r; } } } return null; } private TurnType determineTurnType(int startNode, MapEdge fromEdge, MapEdge toEdge) { int turnNode = fromEdge.getTargetNode(); OSMWay fromWay = fromEdge.getWay(); OSMWay toWay = toEdge.getWay(); if (!fromWay.isRoundabout() && toWay.isRoundabout()) { return TurnType.RoundaboutEntry; } // No real turn if there are no other outgoing edges except backwards Set<Integer> outgoingEdges = graph.getOutgoingEdges(turnNode); if (outgoingEdges.size() == 1 || (outgoingEdges.size() == 2 && !fromWay.isOneway() && !fromWay .isReversedOneway())) { return TurnType.NoTurn; } if (fromWay.isRoundabout()) { if (toWay.isRoundabout()) { return TurnType.RoundaboutNoExit; } return TurnType.RoundaboutExit; } NodeProperties nodeProps = graph.getNodeProperties(turnNode); if (nodeProps != null && nodeProps.isMotorwayJunction()) { if (toWay.isHighwayLink()) { return TurnType.MotorwayJunction; } return TurnType.StraightOn; } float angle = graph.getCoordinates(turnNode).angleBetween( graph.getCoordinates(startNode), graph.getCoordinates(toEdge.getTargetNode())); if (angle <= 130) { return TurnType.RightTurn; } if (angle <= 160) { return TurnType.HalfRightTurn; } if (angle <= 200) { return TurnType.StraightOn; } if (angle <= 240) { return TurnType.HalfLeftTurn; } return TurnType.LeftTurn; } /* * First run: Get edges from OSM ways */ private class FirstRunHandler extends DefaultHandler { private String enclosing; private Locator locator; private int wayId; private Map<String, String> wayTags; private List<Long> wayNodes; @Override public void setDocumentLocator(Locator locator) { this.locator = locator; } @Override public void startElement(String uri, String localName, String qName, Attributes attr) throws SAXException { if (enclosing == null && !qName.equals("osm")) { throw new SAXParseException("No OSM root element found", locator); } switch (qName) { case "way": try { wayId = Integer.parseInt(attr.getValue("id")); } catch (NumberFormatException e) { throw new SAXParseException("Way ID missing or invalid", locator, e); } wayTags = new HashMap<>(); wayNodes = new ArrayList<>(); // fall through case "osm": enclosing = qName; break; case "nd": if (enclosing.equals("way")) { try { wayNodes.add(Long.parseLong(attr.getValue("ref"))); } catch (NumberFormatException e) { throw new SAXParseException("Way " + wayId + ": Invalid or missing node ID reference", locator, e); } } break; case "tag": if (enclosing.equals("way")) { String key = attr.getValue("k"); String value = attr.getValue("v"); if (key == null || value == null) { throw new SAXParseException("Way " + wayId + ": Invalid tag (key or value missing)", locator); } wayTags.put(key, value); } } } @Override public void endElement(String uri, String localName, String qName) { if (qName.equals("way")) { OSMWay way = new OSMWay(wayTags); if (way.getHighwayType() != null) { way.setId(wayId); for (int i = 0; i < wayNodes.size() - 1; i++) { int from = resolveNodeID(wayNodes.get(i)); int to = resolveNodeID(wayNodes.get(i + 1)); if (!way.isReversedOneway()) { addEdge(from, to, way); } if (!way.isOneway()) { addEdge(to, from, way); } } } wayTags = null; wayNodes = null; } if (qName.equals(enclosing)) { enclosing = qName.equals("osm") ? null : "osm"; } } private void addEdge(int from, int to, OSMWay way) { edges.get(from).add(new MapEdge(to, way)); numberOfEdges++; } private int resolveNodeID(Long id) { if (!nodes.containsKey(id)) { nodes.put(id, nodes.size()); edges.add(new ArrayList<MapEdge>(1)); } return nodes.get(id); } } /* * Second run: Get coordinates and properties from nodes, add turn * restrictions */ private class SecondRunHandler extends DefaultHandler { private String enclosing; private Locator locator; private long nodeId; private Map<String, String> tags; private int relationFromWay; private long relationViaNode; private int relationToWay; @Override public void setDocumentLocator(Locator locator) { this.locator = locator; } @Override public void startDocument() { lat = new float[nodes.size()]; lon = new float[nodes.size()]; nodeProps = new HashMap<>(); } @Override public void startElement(String uri, String localName, String qName, Attributes attr) throws SAXException { switch (qName) { case "node": try { nodeId = Long.parseLong(attr.getValue("id")); } catch (NumberFormatException e) { throw new SAXParseException("Node ID missing or invalid", locator, e); } if (nodes.containsKey(nodeId)) { int node = nodes.get(nodeId); try { lat[node] = Coordinates.parseLatitude(attr .getValue("lat")); lon[node] = Coordinates.parseLongitude(attr .getValue("lon")); } catch (IllegalArgumentException e) { throw new SAXParseException("Node " + nodeId + ": Coordinates invalid or unspecified", locator, e); } tags = new HashMap<>(); } // fall through case "osm": enclosing = qName; break; case "relation": enclosing = "relation"; tags = new HashMap<>(); relationFromWay = Integer.MIN_VALUE; relationViaNode = Long.MIN_VALUE; relationToWay = Integer.MIN_VALUE; break; case "member": if (enclosing.equals("relation")) { parseRelationMember(attr.getValue("type"), attr.getValue("role"), attr.getValue("ref")); } break; case "tag": if (enclosing.equals("node") && nodes.containsKey(nodeId) || enclosing.equals("relation")) { String key = attr.getValue("k"); String value = attr.getValue("v"); if (key == null || value == null) { throw new SAXParseException( (enclosing.equals("node")) ? ("Node " + nodeId) : "Relation" + ": Invalid tag (key or value missing)", locator); } tags.put(key, value); } } } private void parseRelationMember(String type, String role, String ref) throws SAXParseException { switch (type) { case "way": int way; try { way = Integer.parseInt(ref); } catch (NumberFormatException e) { throw new SAXParseException( "Relation: Invalid or missing way ID member reference", locator, e); } switch (role) { case "from": relationFromWay = way; break; case "to": relationToWay = way; } break; case "node": if (role != null && role.equals("via")) { try { relationViaNode = Long.parseLong(ref); } catch (NumberFormatException e) { throw new SAXParseException( "Relation: Invalid or missing node ID member reference", locator, e); } } } } @Override public void endElement(String uri, String localName, String qName) { if (qName.equals("node") && nodes.containsKey(nodeId)) { boolean isJunction = tags.containsKey("highway") && tags.get("highway").equals("motorway_junction"); boolean isTrafficLights = tags.containsKey("highway") && tags.get("highway").equals("traffic_signals"); if (isJunction || isTrafficLights) { nodeProps.put( nodes.get(nodeId), new NodeProperties(tags.get("ref"), tags .get("name"), isJunction, isTrafficLights)); } tags = null; } if (qName.equals("relation")) { if (tags.containsKey("type") && tags.get("type").equals("restriction") && tags.containsKey("restriction") && relationFromWay != Integer.MIN_VALUE && relationToWay != Integer.MIN_VALUE && nodes.containsKey(relationViaNode)) { int node = nodes.get(relationViaNode); MapEdge toEdge = null; for (MapEdge edge : edges.get(node)) { if (edge.getWay().getId() == relationToWay) { toEdge = edge; break; } } if (toEdge != null) { boolean onlyTurn = tags.get("restriction").startsWith( "only_"); if (onlyTurn || tags.get("restriction").startsWith("no_")) { if (!turnRestrictions.containsKey(node)) { turnRestrictions.put(node, new ArrayList<TurnRestriction>(1)); } turnRestrictions.get(node).add( new TurnRestriction(relationFromWay, toEdge, onlyTurn)); } } } tags = null; } if (qName.equals(enclosing)) { enclosing = qName.equals("osm") ? null : "osm"; } } } }