package maps.gml.view; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Color; import java.awt.Insets; import java.awt.Point; import java.awt.geom.Rectangle2D; import javax.swing.JComponent; import java.util.Map; import java.util.HashMap; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.ArrayList; import java.util.HashSet; import rescuecore2.misc.gui.ScreenTransform; import rescuecore2.misc.gui.PanZoomListener; import maps.gml.GMLMap; import maps.gml.GMLNode; import maps.gml.GMLEdge; import maps.gml.GMLBuilding; import maps.gml.GMLRoad; import maps.gml.GMLSpace; import maps.gml.GMLObject; import maps.gml.GMLCoordinates; import maps.gml.GMLTools; /** A component for viewing GML maps. */ public class GMLMapViewer extends JComponent { private static final Color BUILDING_COLOUR = new Color(0, 255, 0, 128); // Transparent lime private static final Color ROAD_COLOUR = new Color(192, 192, 192, 128); // Transparent light gray private static final Color SPACE_COLOUR = new Color(0, 128, 0, 128); // Transparent green private static final Color GRID_COLOUR = new Color(64, 64, 64, 64); // Transparent dark gray private static final Color NODE_COLOUR = Color.BLACK; private static final int NODE_SIZE = 3; private static final Color EDGE_COLOUR = Color.BLACK; private static final double MIN_ZOOM_BOUNDS_SIZE = 0.1; private GMLMap map; private ScreenTransform transform; private PanZoomListener panZoom; private transient NodeDecorator defaultNodeDecorator; private transient Map<GMLNode, NodeDecorator> nodeDecorators; private transient EdgeDecorator defaultEdgeDecorator; private transient Map<GMLEdge, EdgeDecorator> edgeDecorators; private transient BuildingDecorator defaultBuildingDecorator; private transient Map<GMLBuilding, BuildingDecorator> buildingDecorators; private transient RoadDecorator defaultRoadDecorator; private transient Map<GMLRoad, RoadDecorator> roadDecorators; private transient SpaceDecorator defaultSpaceDecorator; private transient Map<GMLSpace, SpaceDecorator> spaceDecorators; private transient List<Overlay> overlays; private boolean grid; private double gridResolution; /** Create a GMLMapViewer. */ public GMLMapViewer() { this(null); } /** Create a GMLMapViewer. @param map The map to view. */ public GMLMapViewer(GMLMap map) { panZoom = new PanZoomListener(this); defaultNodeDecorator = new CrossNodeDecorator(NODE_COLOUR, NODE_SIZE); defaultEdgeDecorator = new LineEdgeDecorator(EDGE_COLOUR); FilledShapeDecorator d = new FilledShapeDecorator(BUILDING_COLOUR, ROAD_COLOUR, SPACE_COLOUR); defaultBuildingDecorator = d; defaultRoadDecorator = d; defaultSpaceDecorator = d; nodeDecorators = new HashMap<GMLNode, NodeDecorator>(); edgeDecorators = new HashMap<GMLEdge, EdgeDecorator>(); buildingDecorators = new HashMap<GMLBuilding, BuildingDecorator>(); roadDecorators = new HashMap<GMLRoad, RoadDecorator>(); spaceDecorators = new HashMap<GMLSpace, SpaceDecorator>(); grid = false; gridResolution = 1; overlays = new ArrayList<Overlay>(); setMap(map); } /** Set the map. @param map The map to view. */ public void setMap(GMLMap map) { this.map = map; transform = null; if (map != null) { if (!map.hasSize()) { // CHECKSTYLE:OFF:MagicNumber transform = new ScreenTransform(0, 0, 100, 100); // CHECKSTYLE:ON:MagicNumber } else { transform = new ScreenTransform(map.getMinX(), map.getMinY(), map.getMaxX(), map.getMaxY()); } } panZoom.setScreenTransform(transform); } /** View a particular set of objects. @param objects The objects to view. */ public void view(GMLObject... objects) { view(Arrays.asList(objects)); } /** View a particular set of objects. @param objects The objects to view. */ public void view(List<? extends GMLObject> objects) { if (objects == null || objects.isEmpty()) { return; } Rectangle2D bounds = GMLTools.getObjectBounds(objects); if (bounds == null) { return; } if (bounds.getWidth() < MIN_ZOOM_BOUNDS_SIZE) { bounds = new Rectangle2D.Double(bounds.getX() - MIN_ZOOM_BOUNDS_SIZE / 2, bounds.getY(), MIN_ZOOM_BOUNDS_SIZE, bounds.getHeight()); } if (bounds.getHeight() < MIN_ZOOM_BOUNDS_SIZE) { bounds = new Rectangle2D.Double(bounds.getX(), bounds.getY() - MIN_ZOOM_BOUNDS_SIZE / 2, bounds.getWidth(), MIN_ZOOM_BOUNDS_SIZE); } transform.show(bounds); } /** View all objects. */ public void viewAll() { transform.resetZoom(); } /** Get the PanZoomListener for this component. @return The PanZoomListener. */ public PanZoomListener getPanZoomListener() { return panZoom; } /** Set the default node decorator. @param defaultDecorator The new default node decorator. */ public void setDefaultNodeDecorator(NodeDecorator defaultDecorator) { defaultNodeDecorator = defaultDecorator; } /** Get the default node decorator. @return The default node decorator. */ public NodeDecorator getDefaultNodeDecorator() { return defaultNodeDecorator; } /** Set the NodeDecorator for a set of GMLNodes. @param decorator The decorator to set. @param nodes The nodes to set the decorator for. */ public void setNodeDecorator(NodeDecorator decorator, GMLNode... nodes) { setNodeDecorator(decorator, Arrays.asList(nodes)); } /** Set the NodeDecorator for a set of GMLNodes. @param decorator The decorator to set. @param nodes The nodes to set the decorator for. */ public void setNodeDecorator(NodeDecorator decorator, Collection<? extends GMLNode> nodes) { for (GMLNode next : nodes) { nodeDecorators.put(next, decorator); } } /** Get the NodeDecorator for a GMLNodes. @param node The node to look up. @return The NodeDecorator for that node. This will be the default decorator if no custom decorator has been set for that node. */ public NodeDecorator getNodeDecorator(GMLNode node) { NodeDecorator result = nodeDecorators.get(node); if (result == null) { result = defaultNodeDecorator; } return result; } /** Remove any custom NodeDecorator for a set of GMLNodes. @param nodes The nodes to remove any custom decorator for. */ public void clearNodeDecorator(GMLNode... nodes) { clearNodeDecorator(Arrays.asList(nodes)); } /** Remove any custom NodeDecorator for a set of GMLNodes. @param nodes The nodes to remove any custom decorator for. */ public void clearNodeDecorator(Collection<? extends GMLNode> nodes) { for (GMLNode next : nodes) { nodeDecorators.remove(next); } } /** Remove any custom NodeDecorators. */ public void clearAllNodeDecorators() { nodeDecorators.clear(); } /** Set the default edge decorator. @param defaultDecorator The new default edge decorator. */ public void setDefaultEdgeDecorator(EdgeDecorator defaultDecorator) { defaultEdgeDecorator = defaultDecorator; } /** Get the default edge decorator. @return The default edge decorator. */ public EdgeDecorator getDefaultEdgeDecorator() { return defaultEdgeDecorator; } /** Set the EdgeDecorator for a set of GMLEdges. @param decorator The decorator to set. @param edges The edges to set the decorator for. */ public void setEdgeDecorator(EdgeDecorator decorator, GMLEdge... edges) { setEdgeDecorator(decorator, Arrays.asList(edges)); } /** Set the EdgeDecorator for a set of GMLEdges. @param decorator The decorator to set. @param edges The edges to set the decorator for. */ public void setEdgeDecorator(EdgeDecorator decorator, Collection<? extends GMLEdge> edges) { for (GMLEdge next : edges) { edgeDecorators.put(next, decorator); } } /** Get the EdgeDecorator for a GMLEdge. @param edge The edge to look up. @return The EdgeDecorator for that edge. This will be the default decorator if no custom decorator has been set for that edge. */ public EdgeDecorator getEdgeDecorator(GMLEdge edge) { EdgeDecorator result = edgeDecorators.get(edge); if (result == null) { result = defaultEdgeDecorator; } return result; } /** Remove any custom EdgeDecorator for a set of GMLEdges. @param edges The edges to remove any custom decorator for. */ public void clearEdgeDecorator(GMLEdge... edges) { clearEdgeDecorator(Arrays.asList(edges)); } /** Remove any custom EdgeDecorator for a set of GMLEdges. @param edges The edges to remove any custom decorator for. */ public void clearEdgeDecorator(Collection<? extends GMLEdge> edges) { for (GMLEdge next : edges) { edgeDecorators.remove(next); } } /** Remove any custom EdgeDecorators. */ public void clearAllEdgeDecorators() { edgeDecorators.clear(); } /** Set the default building decorator. @param defaultDecorator The new default building decorator. */ public void setDefaultBuildingDecorator(BuildingDecorator defaultDecorator) { defaultBuildingDecorator = defaultDecorator; } /** Get the default building decorator. @return The default building decorator. */ public BuildingDecorator getDefaultBuildingDecorator() { return defaultBuildingDecorator; } /** Set the BuildingDecorator for a set of GMLBuildings. @param decorator The decorator to set. @param buildings The buildings to set the decorator for. */ public void setBuildingDecorator(BuildingDecorator decorator, GMLBuilding... buildings) { setBuildingDecorator(decorator, Arrays.asList(buildings)); } /** Set the BuildingDecorator for a set of GMLBuildings. @param decorator The decorator to set. @param buildings The buildings to set the decorator for. */ public void setBuildingDecorator(BuildingDecorator decorator, Collection<? extends GMLBuilding> buildings) { for (GMLBuilding next : buildings) { buildingDecorators.put(next, decorator); } } /** Get the BuildingDecorator for a GMLBuildings. @param building The building to look up. @return The BuildingDecorator for that building. This will be the default decorator if no custom decorator has been set for that building. */ public BuildingDecorator getBuildingDecorator(GMLBuilding building) { BuildingDecorator result = buildingDecorators.get(building); if (result == null) { result = defaultBuildingDecorator; } return result; } /** Remove any custom BuildingDecorator for a set of GMLBuildings. @param buildings The buildings to remove any custom decorator for. */ public void clearBuildingDecorator(GMLBuilding... buildings) { clearBuildingDecorator(Arrays.asList(buildings)); } /** Remove any custom BuildingDecorator for a set of GMLBuildings. @param buildings The buildings to remove any custom decorator for. */ public void clearBuildingDecorator(Collection<? extends GMLBuilding> buildings) { for (GMLBuilding next : buildings) { buildingDecorators.remove(next); } } /** Remove any custom BuildingDecorators. */ public void clearAllBuildingDecorators() { buildingDecorators.clear(); } /** Set the default road decorator. @param defaultDecorator The new default road decorator. */ public void setDefaultRoadDecorator(RoadDecorator defaultDecorator) { defaultRoadDecorator = defaultDecorator; } /** Get the default road decorator. @return The default road decorator. */ public RoadDecorator getDefaultRoadDecorator() { return defaultRoadDecorator; } /** Set the RoadDecorator for a set of GMLRoads. @param decorator The decorator to set. @param roads The roads to set the decorator for. */ public void setRoadDecorator(RoadDecorator decorator, GMLRoad... roads) { setRoadDecorator(decorator, Arrays.asList(roads)); } /** Set the RoadDecorator for a set of GMLRoads. @param decorator The decorator to set. @param roads The roads to set the decorator for. */ public void setRoadDecorator(RoadDecorator decorator, Collection<? extends GMLRoad> roads) { for (GMLRoad next : roads) { roadDecorators.put(next, decorator); } } /** Get the RoadDecorator for a GMLRoads. @param road The road to look up. @return The RoadDecorator for that road. This will be the default decorator if no custom decorator has been set for that road. */ public RoadDecorator getRoadDecorator(GMLRoad road) { RoadDecorator result = roadDecorators.get(road); if (result == null) { result = defaultRoadDecorator; } return result; } /** Remove any custom RoadDecorator for a set of GMLRoads. @param roads The roads to remove any custom decorator for. */ public void clearRoadDecorator(GMLRoad... roads) { clearRoadDecorator(Arrays.asList(roads)); } /** Remove any custom RoadDecorator for a set of GMLRoads. @param roads The roads to remove any custom decorator for. */ public void clearRoadDecorator(Collection<? extends GMLRoad> roads) { for (GMLRoad next : roads) { roadDecorators.remove(next); } } /** Remove any custom RoadDecorators. */ public void clearAllRoadDecorators() { roadDecorators.clear(); } /** Set the default space decorator. @param defaultDecorator The new default space decorator. */ public void setDefaultSpaceDecorator(SpaceDecorator defaultDecorator) { defaultSpaceDecorator = defaultDecorator; } /** Get the default space decorator. @return The default space decorator. */ public SpaceDecorator getDefaultSpaceDecorator() { return defaultSpaceDecorator; } /** Set the SpaceDecorator for a set of GMLSpaces. @param decorator The decorator to set. @param spaces The spaces to set the decorator for. */ public void setSpaceDecorator(SpaceDecorator decorator, GMLSpace... spaces) { setSpaceDecorator(decorator, Arrays.asList(spaces)); } /** Set the SpaceDecorator for a set of GMLSpaces. @param decorator The decorator to set. @param spaces The spaces to set the decorator for. */ public void setSpaceDecorator(SpaceDecorator decorator, Collection<? extends GMLSpace> spaces) { for (GMLSpace next : spaces) { spaceDecorators.put(next, decorator); } } /** Get the SpaceDecorator for a GMLSpaces. @param space The space to look up. @return The SpaceDecorator for that space. This will be the default decorator if no custom decorator has been set for that space. */ public SpaceDecorator getSpaceDecorator(GMLSpace space) { SpaceDecorator result = spaceDecorators.get(space); if (result == null) { result = defaultSpaceDecorator; } return result; } /** Remove any custom SpaceDecorator for a set of GMLSpaces. @param spaces The spaces to remove any custom decorator for. */ public void clearSpaceDecorator(GMLSpace... spaces) { clearSpaceDecorator(Arrays.asList(spaces)); } /** Remove any custom SpaceDecorator for a set of GMLSpaces. @param spaces The spaces to remove any custom decorator for. */ public void clearSpaceDecorator(Collection<? extends GMLSpace> spaces) { for (GMLSpace next : spaces) { spaceDecorators.remove(next); } } /** Remove any custom SpaceDecorators. */ public void clearAllSpaceDecorators() { spaceDecorators.clear(); } /** Set whether to draw the grid or not. @param b True to draw the grid. */ public void setGridEnabled(boolean b) { grid = b; } /** Set the grid resolution. @param resolution The new grid resolution. */ public void setGridResolution(double resolution) { gridResolution = resolution; } /** Add an overlay to the view. @param overlay The overlay to add. */ public void addOverlay(Overlay overlay) { overlays.add(overlay); } /** Remove an overlay from the view. @param overlay The overlay to remove. */ public void removeOverlay(Overlay overlay) { overlays.remove(overlay); } @Override public void paintComponent(Graphics graphics) { super.paintComponent(graphics); Graphics copy = graphics.create(); copy.setColor(getBackground()); copy.fillRect(0, 0, getWidth(), getHeight()); if (map == null) { return; } Insets insets = getInsets(); int width = getWidth() - insets.left - insets.right; int height = getHeight() - insets.top - insets.bottom; Graphics2D g = (Graphics2D)graphics.create(insets.left, insets.top, width + 1 , height + 1); transform.rescale(width, height); Collection<GMLRoad> roads; Collection<GMLBuilding> buildings; Collection<GMLSpace> spaces; Collection<GMLEdge> edges; Collection<GMLNode> nodes; synchronized (map) { roads = new HashSet<GMLRoad>(map.getRoads()); buildings = new HashSet<GMLBuilding>(map.getBuildings()); spaces = new HashSet<GMLSpace>(map.getSpaces()); edges = new HashSet<GMLEdge>(map.getEdges()); nodes = new HashSet<GMLNode>(map.getNodes()); } for (GMLRoad next : roads) { RoadDecorator d = getRoadDecorator(next); if (d != null) { d.decorate(next, (Graphics2D)g.create(), transform); } } for (GMLBuilding next : buildings) { BuildingDecorator d = getBuildingDecorator(next); if (d != null) { d.decorate(next, (Graphics2D)g.create(), transform); } } for (GMLSpace next : spaces) { SpaceDecorator d = getSpaceDecorator(next); if (d != null) { d.decorate(next, (Graphics2D)g.create(), transform); } } for (GMLEdge next : edges) { EdgeDecorator e = getEdgeDecorator(next); if (e != null) { e.decorate(next, (Graphics2D)g.create(), transform); } } for (GMLNode next : nodes) { NodeDecorator n = getNodeDecorator(next); if (n != null) { n.decorate(next, (Graphics2D)g.create(), transform); } } for (Overlay next : overlays) { next.render((Graphics2D)g.create(), transform); } if (grid) { double xMin = roundDownToGrid(transform.screenToX(0)); double xMax = roundUpToGrid(transform.screenToX(width)); double yMin = roundDownToGrid(transform.screenToY(height)); double yMax = roundUpToGrid(transform.screenToY(0)); g.setColor(GRID_COLOUR); for (double worldX = xMin; worldX <= xMax; worldX += gridResolution) { int x = transform.xToScreen(worldX); g.drawLine(x, 0, x, height); } for (double worldY = yMin; worldY <= yMax; worldY += gridResolution) { int y = transform.yToScreen(worldY); g.drawLine(0, y, width, y); } } } @Override public boolean isOpaque() { return true; } /** Enable or disable the pan/zoom feature. @param enabled Whether pan/zoom should be enabled or not. */ public void setPanZoomEnabled(boolean enabled) { panZoom.setEnabled(enabled); } /** Get the coordinates of a point on screen. @param x The screen x coordinate. @param y The screen y coordinate. @return The coordinates in the GML map under the screen point. */ public GMLCoordinates getCoordinatesAtPoint(int x, int y) { double cx = transform.screenToX(x); double cy = transform.screenToY(y); return new GMLCoordinates(cx, cy); } /** Get the on-screen coordinates for a point. @param c The GML coordinates to look up. @return The on-screen coordinates of the point. */ public Point getScreenCoordinates(GMLCoordinates c) { int x = transform.xToScreen(c.getX()); int y = transform.yToScreen(c.getY()); return new Point(x, y); } private double roundDownToGrid(double d) { return Math.floor(d / gridResolution) * gridResolution; } private double roundUpToGrid(double d) { return Math.ceil(d / gridResolution) * gridResolution; } }