package maps.gml.formats; import maps.gml.GMLMap; import maps.gml.GMLCoordinates; import maps.gml.GMLShape; import maps.gml.GMLBuilding; import maps.gml.GMLRoad; import maps.gml.GMLNode; import maps.gml.GMLEdge; import maps.gml.GMLDirectedEdge; import maps.gml.GMLTools; import maps.gml.GMLMapFormat; import maps.ConstantConversion; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.Attribute; import org.dom4j.QName; import org.dom4j.Namespace; import org.dom4j.XPath; import org.dom4j.DocumentHelper; import org.jaxen.SimpleVariableContext; import java.util.List; import java.util.ArrayList; import java.util.Map; import java.util.HashMap; import java.util.Collection; import java.util.Iterator; import java.util.Collections; import rescuecore2.misc.Pair; import rescuecore2.log.Logger; /** A MapFormat that can handle GML maps from Meijo University. */ public final class MeijoFormat extends GMLMapFormat { /** Singleton instance. */ public static final MeijoFormat INSTANCE = new MeijoFormat(); private static final String MEIJO_NAMESPACE_URI = "http://sakura.meijo-u.ac.jp/rcrs"; private static final String GML_APP_NAMESPACE_URI = "http://www.opengis.net/app"; private static final Namespace RCRS_NAMESPACE = DocumentHelper.createNamespace("rcrs", MEIJO_NAMESPACE_URI); private static final Namespace GML_APP_NAMESPACE = DocumentHelper.createNamespace("app", GML_APP_NAMESPACE_URI); private static final QName ROOT_QNAME = DocumentHelper.createQName("Topology", GML_APP_NAMESPACE); private static final QName VERSION_QNAME = DocumentHelper.createQName("Version", RCRS_NAMESPACE); private static final QName DESCRIPTION_QNAME = DocumentHelper.createQName("Description", RCRS_NAMESPACE); private static final QName AREA_QNAME = DocumentHelper.createQName("Area", RCRS_NAMESPACE); private static final QName NODE_LIST_QNAME = DocumentHelper.createQName("NodeList", RCRS_NAMESPACE); private static final QName EDGE_LIST_QNAME = DocumentHelper.createQName("EdgeList", RCRS_NAMESPACE); private static final QName FACE_LIST_QNAME = DocumentHelper.createQName("FaceList", RCRS_NAMESPACE); private static final QName FACE_QNAME = DocumentHelper.createQName("Face", RCRS_NAMESPACE); private static final QName BUILDING_PROPERTY_QNAME = DocumentHelper.createQName("BuildingProperty", RCRS_NAMESPACE); // Map from uri prefix to uri for writing XML documents private static final Map<String, String> URIS = new HashMap<String, String>(); private static final XPath NODE_XPATH = DocumentHelper.createXPath("//app:Topology/rcrs:Area/rcrs:NodeList/gml:Node"); private static final XPath EDGE_XPATH = DocumentHelper.createXPath("//app:Topology/rcrs:Area/rcrs:EdgeList/gml:Edge"); private static final XPath FACE_XPATH = DocumentHelper.createXPath("//app:Topology/rcrs:Area/rcrs:FaceList/rcrs:Face"); private static final XPath NODE_COORDINATES_XPATH = DocumentHelper.createXPath("gml:pointProperty/gml:Point/gml:coordinates"); private static final XPath EDGE_COORDINATES_XPATH = DocumentHelper.createXPath("gml:centerLineOf/gml:LineString/gml:coordinates"); private static final XPath FACE_COORDINATES_XPATH = DocumentHelper.createXPath("gml:polygon/gml:LinearRing/gml:coordinates"); private static final XPath EDGE_START_XPATH = DocumentHelper.createXPath("gml:directedNode[1]//@xlink:href"); private static final XPath EDGE_END_XPATH = DocumentHelper.createXPath("gml:directedNode[2]/@xlink:href"); private static final SimpleVariableContext FACE_NEIGHBOUR_XPATH_CONTEXT = new SimpleVariableContext(); private static final String FACE_NEIGHBOUR_XPATH_STRING = "//rcrs:EdgeList/gml:Edge[@gml:id=\"$edgeid\"]/gml:directedFace[@xlink:href!=\"#$faceid\"]/@xlink:href"; // private static final XPath FACE_NEIGHBOUR_XPATH = DocumentHelper.createXPath("//rcrs:EdgeList/gml:Edge[@gml:id=\"$edgeid\"]/gml:directedFace[@xlink:href!=\"#$faceid\"]/@xlink:href"); // private static final XPath FACE_NEIGHBOUR_XPATH = DocumentHelper.createXPath("//rcrs:EdgeList/gml:Edge[@gml:id=\"$edgeid\"]/gml:directedFace", FACE_NEIGHBOUR_XPATH_CONTEXT); private static final double THRESHOLD = 0.0001; static { URIS.put("gml", Common.GML_NAMESPACE_URI); URIS.put("app", GML_APP_NAMESPACE_URI); URIS.put("xlink", Common.XLINK_NAMESPACE_URI); URIS.put("rcrs", MEIJO_NAMESPACE_URI); NODE_XPATH.setNamespaceURIs(URIS); EDGE_XPATH.setNamespaceURIs(URIS); FACE_XPATH.setNamespaceURIs(URIS); NODE_COORDINATES_XPATH.setNamespaceURIs(URIS); EDGE_COORDINATES_XPATH.setNamespaceURIs(URIS); FACE_COORDINATES_XPATH.setNamespaceURIs(URIS); EDGE_START_XPATH.setNamespaceURIs(URIS); EDGE_END_XPATH.setNamespaceURIs(URIS); // FACE_NEIGHBOUR_XPATH.setNamespaceURIs(URIS); } private MeijoFormat() { } @Override public Map<String, String> getNamespaces() { return Collections.unmodifiableMap(URIS); } @Override public String toString() { return "Meijo"; } @Override public boolean isCorrectRootElement(String uri, String localName) { return MEIJO_NAMESPACE_URI.equals(uri) && "Topology".equals(localName); } @Override public GMLMap read(Document doc) { GMLMap result = new GMLMap(); readNodes(doc, result); // This format has coordinates in mm, so divide by 1000 to convert to m. // CHECKSTYLE:OFF:MagicNumber result.convertCoordinates(new ConstantConversion(0.001)); // CHECKSTYLE:ON:MagicNumber readEdges(doc, result); readFaces(doc, result); splitMultipleEdges(result); // checkEdgeOrderAndDirection(result); return result; } @Override public Document write(GMLMap map) { Element root = DocumentHelper.createElement(ROOT_QNAME); Document result = DocumentHelper.createDocument(root); writeNodes(map, root.addElement(NODE_LIST_QNAME)); writeEdges(map, root.addElement(EDGE_LIST_QNAME)); writeFaces(map, root.addElement(FACE_LIST_QNAME)); return result; } private void writeNodes(GMLMap map, Element parent) { for (GMLNode next : map.getNodes()) { Element e = parent.addElement(Common.GML_NODE_QNAME); e.addAttribute(Common.GML_ID_QNAME, String.valueOf(next.getID())); e.addElement(Common.GML_POINT_PROPERTY_QNAME).addElement(Common.GML_POINT_QNAME).addElement(Common.GML_COORDINATES_QNAME).setText(next.getCoordinates().toString()); } } private void writeEdges(GMLMap map, Element parent) { for (GMLEdge next : map.getEdges()) { Element e = parent.addElement(Common.GML_EDGE_QNAME); e.addAttribute(Common.GML_ID_QNAME, String.valueOf(next.getID())); e.addElement(Common.GML_DIRECTED_NODE_QNAME).addAttribute(Common.GML_ORIENTATION_QNAME, "+").addAttribute(Common.XLINK_HREF_QNAME, "#" + next.getStart().getID()); e.addElement(Common.GML_DIRECTED_NODE_QNAME).addAttribute(Common.GML_ORIENTATION_QNAME, "+").addAttribute(Common.XLINK_HREF_QNAME, "#" + next.getEnd().getID()); // Add directed faces // This will be real slow for (GMLShape shape : map.getAllShapes()) { for (GMLDirectedEdge edge : shape.getEdges()) { if (edge.getEdge() == next) { e.addElement(Common.GML_DIRECTED_FACE_QNAME).addAttribute(Common.GML_ORIENTATION_QNAME, "+").addAttribute(Common.XLINK_HREF_QNAME, "#" + shape.getID()); } } } } } private void writeFaces(GMLMap map, Element parent) { for (GMLShape next : map.getAllShapes()) { Element e = parent.addElement(FACE_QNAME); if (next instanceof GMLBuilding) { parent.addElement(BUILDING_PROPERTY_QNAME); } e = e.addElement(Common.GML_FACE_QNAME); e.addAttribute(Common.GML_ID_QNAME, String.valueOf(next.getID())); for (GMLDirectedEdge edge : next.getEdges()) { String orientation = "-"; if (edge.getEdge().isPassable()) { orientation = "+"; } e.addElement(Common.GML_DIRECTED_EDGE_QNAME).addAttribute(Common.GML_ORIENTATION_QNAME, orientation).addAttribute(Common.XLINK_HREF_QNAME, "#" + edge.getEdge().getID()); } e.addElement(Common.GML_POLYGON_QNAME).addElement(Common.GML_LINEAR_RING_QNAME).addElement(Common.GML_COORDINATES_QNAME).setText(GMLTools.getCoordinatesString(next.getCoordinates())); } } private void readNodes(Document doc, GMLMap result) { for (Object next : NODE_XPATH.selectNodes(doc)) { Element e = (Element)next; int id = readID(e); String coordinates = ((Element)NODE_COORDINATES_XPATH.evaluate(e)).getText(); GMLCoordinates c = new GMLCoordinates(coordinates); GMLNode node = new GMLNode(id, c); result.addNode(node); Logger.debug("Read node " + node); } } private void readEdges(Document doc, GMLMap result) { for (Object next : EDGE_XPATH.selectNodes(doc)) { Element e = (Element)next; int id = readID(e); // Logger.debug("Children: " + e.elements()); // Logger.debug("Start: " + EDGE_START_XPATH.evaluate(e)); int startID = Integer.parseInt(((Attribute)EDGE_START_XPATH.evaluate(e)).getValue().substring(1)); int endID = Integer.parseInt(((Attribute)EDGE_END_XPATH.evaluate(e)).getValue().substring(1)); GMLEdge edge = new GMLEdge(id, result.getNode(startID), result.getNode(endID), false); // Read the edge coordinates edge.setPoints(GMLTools.getCoordinatesList(((Element)EDGE_COORDINATES_XPATH.evaluate(e)).getText())); result.addEdge(edge); Logger.debug("Read edge " + edge); } } private void readFaces(Document doc, GMLMap result) { // Logger.debug("Reading buildings"); for (Object next : FACE_XPATH.selectNodes(doc)) { // Logger.debug("Reading " + next); Element e = (Element)next; String type = e.attributeValue("type"); Element gmlFace = e.element(Common.GML_FACE_QNAME); int id = readID(gmlFace); // Logger.debug("ID = " + id); // Logger.debug("Type = " + type); Pair<List<GMLDirectedEdge>, List<Integer>> edges = readEdges(gmlFace, result, id); // Logger.debug("Edges: " + edges); GMLShape shape = null; if ("building".equals(type)) { shape = new GMLBuilding(id, edges.first(), edges.second()); } else { shape = new GMLRoad(id, edges.first(), edges.second()); } // Logger.debug("Computing coordinates"); String coordsString = ((Element)FACE_COORDINATES_XPATH.evaluate(gmlFace)).getText(); // Logger.debug("coordsString = " + coordsString); List<GMLCoordinates> coords = GMLTools.getCoordinatesList(coordsString); // Logger.debug("coords = " + coords); shape.setCoordinates(coords); result.add(shape); Logger.debug("Read shape: " + shape); } } private Pair<List<GMLDirectedEdge>, List<Integer>> readEdges(Element e, GMLMap map, int faceID) { List<GMLDirectedEdge> edges = new ArrayList<GMLDirectedEdge>(); List<Integer> neighbours = new ArrayList<Integer>(); // Logger.debug("Reading edges for face " + faceID); for (Object next : e.elements(Common.GML_DIRECTED_EDGE_QNAME)) { Element dEdge = (Element)next; boolean passable = "+".equals(dEdge.attributeValue(Common.GML_ORIENTATION_QNAME)); int edgeID = Integer.parseInt(dEdge.attributeValue(Common.XLINK_HREF_QNAME).substring(1)); // Logger.debug("Edge ID: " + edgeID); // Logger.debug("Passable? " + passable); edges.add(new GMLDirectedEdge(map.getEdge(edgeID), true)); XPath xpath = makeFaceNeighbourXPath(edgeID, faceID); // FACE_NEIGHBOUR_XPATH_CONTEXT.setVariableValue("edgeid", String.valueOf(edgeID)); // FACE_NEIGHBOUR_XPATH_CONTEXT.setVariableValue("faceid", String.valueOf(faceID)); Object o = xpath.evaluate(e); // Logger.debug("Neighbours: " + o); if (o == null) { neighbours.add(null); } else if (o instanceof Collection && ((Collection)o).isEmpty()) { neighbours.add(null); } else { int neighbourID = Integer.parseInt(((Attribute)o).getValue().substring(1)); neighbours.add(neighbourID); } // Logger.debug("Edge list : " + edges); // Logger.debug("Neighbour list: " + neighbours); } // Logger.debug("Finished reading edges for face " + faceID); return new Pair<List<GMLDirectedEdge>, List<Integer>>(edges, neighbours); } private int readID(Element e) { return Integer.parseInt(e.attributeValue(Common.GML_ID_QNAME)); } private XPath makeFaceNeighbourXPath(int edgeID, int faceID) { String path = FACE_NEIGHBOUR_XPATH_STRING.replace("$edgeid", String.valueOf(edgeID)).replace("$faceid", String.valueOf(faceID)); // Logger.debug("Neighbour XPath: " + path); XPath result = DocumentHelper.createXPath(path); result.setNamespaceURIs(URIS); return result; } private void splitMultipleEdges(GMLMap map) { // Look for edges that have more then 2 GMLCoordinates and split them into multiple edges for (GMLEdge edge : map.getEdges()) { if (edge.getPoints().size() != 2) { // Split this edge Iterator<GMLCoordinates> it = edge.getPoints().iterator(); GMLCoordinates first = it.next(); List<GMLEdge> newEdges = new ArrayList<GMLEdge>(); while (it.hasNext()) { GMLCoordinates second = it.next(); GMLNode n1 = map.createNode(first); GMLNode n2 = map.createNode(second); GMLEdge newEdge = map.createEdge(n1, n2); newEdges.add(newEdge); first = second; } // Update any shapes that reference the old edge for (GMLShape shape : map.getAllShapes()) { replaceEdge(shape, edge, newEdges); } map.removeEdge(edge); // Logger.debug("Split " + edge); // Logger.debug("New edges: " + newEdges); } } } private void replaceEdge(GMLShape shape, GMLEdge oldEdge, List<GMLEdge> newEdges) { List<GMLDirectedEdge> newShapeEdges = new ArrayList<GMLDirectedEdge>(); List<Integer> newShapeNeighbours = new ArrayList<Integer>(); boolean found = false; for (GMLDirectedEdge e : shape.getEdges()) { if (e.getEdge().equals(oldEdge)) { found = true; GMLNode start = e.getStartNode(); Integer neighbour = shape.getNeighbour(e); for (GMLEdge next : newEdges) { GMLDirectedEdge newDEdge = new GMLDirectedEdge(next, start); newShapeEdges.add(newDEdge); newShapeNeighbours.add(neighbour); start = newDEdge.getEndNode(); } } else { newShapeEdges.add(e); newShapeNeighbours.add(shape.getNeighbour(e)); } } if (found) { shape.setEdges(newShapeEdges); Iterator<GMLDirectedEdge> it = newShapeEdges.iterator(); Iterator<Integer> ix = newShapeNeighbours.iterator(); while (it.hasNext() && ix.hasNext()) { shape.setNeighbour(it.next(), ix.next()); } } } /* private void checkEdgeOrderAndDirection(GMLMap map) { Set<GMLDirectedEdge> remaining = new HashSet<GMLDirectedEdge>(); List<GMLDirectedEdge> reordered = new ArrayList<GMLDirectedEdge>(); for (GMLShape shape : map.getAllShapes()) { remaining.clear(); reordered.clear(); remaining.addAll(shape.getEdges()); // Iterator<GMLDirectedEdge> it = shape.getEdges().iterator(); GMLDirectedEdge edge = shape.getEdges().get(0); GMLNode start = edge.getEndNode(); // Logger.debug("Reordering " + remaining.size() + " edges for " + shape); // Logger.debug("Original order"); // for (GMLDirectedEdge e : shape.getEdges()) { // logEdge(e); // } // Logger.debug("First edge"); // logEdge(edge); remaining.remove(edge); reordered.add(edge); while (!remaining.isEmpty()) { edge = null; // Find the next edge for (GMLDirectedEdge next : remaining) { if (closeEnough(next.getStartNode(), start)) { edge = next; break; } if (closeEnough(next.getEndNode(), start)) { edge = next; edge.reverse(); break; } } if (edge == null) { throw new RuntimeException("Failed to reorder edges: found discontinuity in shape outline"); } // Logger.debug("Next edge"); // logEdge(edge); remaining.remove(edge); reordered.add(edge); start = edge.getEndNode(); } // Logger.debug("Reordered"); // for (GMLDirectedEdge e : reordered) { // logEdge(e); // } shape.reorderEdges(reordered); } } */ // private void logEdge(GMLDirectedEdge e) { // Logger.debug(e.getEdge().getID() + ": " + e.getStartNode().getID() + " -> " + e.getEndNode().getID()); // } private boolean closeEnough(GMLNode n1, GMLNode n2) { if (n1 == n2) { return true; } double dx = n1.getX() - n2.getX(); double dy = n1.getY() - n2.getY(); return (dx > -THRESHOLD && dx < THRESHOLD && dy > -THRESHOLD && dy < THRESHOLD); } }