/** * ***************************************************************************** * Copyright (c) 2012 Johannes Mitlmeier. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110, USA * ***************************************************************************** */ package de.fub.agg2graph.ui.gui.jmv; import de.fub.agg2graph.agg.AggConnection; import de.fub.agg2graph.agg.AggContainer; import de.fub.agg2graph.agg.AggNode; import de.fub.agg2graph.agg.tiling.DefaultCachingStrategy; import de.fub.agg2graph.agg.tiling.Tile; import de.fub.agg2graph.roadgen.Intersection; import de.fub.agg2graph.roadgen.Road; import de.fub.agg2graph.roadgen.RoadNetwork; import de.fub.agg2graph.structs.GPSPoint; import de.fub.agg2graph.structs.GPSSegment; import de.fub.agg2graph.structs.GPSTrack; import de.fub.agg2graph.structs.Hideable; import de.fub.agg2graph.structs.ILocation; import de.fub.agg2graph.structs.XYPoint; import de.fub.agg2graph.ui.gui.IRenderingPanel; import de.fub.agg2graph.ui.gui.LayerManager; import de.fub.agg2graph.ui.gui.RenderingOptions; import de.fub.agg2graph.ui.gui.RenderingOptions.LabelRenderingType; import de.fub.agg2graph.ui.gui.RenderingOptions.RenderingType; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D.Double; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.JComponent; import org.jdesktop.swingx.mapviewer.GeoPosition; public class Layer implements Hideable { private RenderingOptions options; private final List<HashMap<Object, RenderingOptions>> objects = new ArrayList<HashMap<Object, RenderingOptions>>(); private String name; private String description; private final List<IRenderingPanel> panels = new ArrayList<IRenderingPanel>(); private LayerManager layerManager; private BufferedImage image; private int drawnPointsCounter = 0; private boolean visible = true; private final int MAX_VISIBLE_POINTS_INTELLIGENT_LABELS = 7; private final int MAX_VISIBLE_POINTS_INTELLIGENT_POINT = 10; private final double CLIPPING_AREA_BORDER = 5000; private List<DrawObject> drawObjects; private Double lastGpsArea; public Layer(String name, RenderingOptions ro) { this.name = name; this.options = ro; } public Layer(String name, String description, RenderingOptions ro) { this.name = name; this.description = description; this.options = ro; } public LayerManager getLayerManager() { return layerManager; } public BufferedImage getImage() { return image; } public void setLayerManager(LayerManager layerManager) { this.layerManager = layerManager; } public List<IRenderingPanel> getPanels() { return panels; } public List<HashMap<Object, RenderingOptions>> getObjects() { return objects; } public void addPanel(IRenderingPanel panel) { if (!panels.contains(panel)) { panels.add(panel); } } public void addObject(Object object) { addObject(object, null); } public void addObject(Object object, RenderingOptions options) { HashMap<Object, RenderingOptions> map = new HashMap<Object, RenderingOptions>(); map.put(object, options); objects.add(map); } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getName() { return name; } public void setName(String name) { this.name = name; } public RenderingOptions getOptions() { return options; } public void setOptions(RenderingOptions options) { this.options = options; } @Override public String toString() { return String.format("Layer \"%s\", #panels=%d", name, panels.size()); } private void drawLine(Graphics2D g2, ILocation location1, ILocation location2, RenderingOptions ro) { drawLine(g2, location1, location2, ro, 1); } private void drawLine(Graphics2D g2, ILocation location1, ILocation location2, RenderingOptions ro, float weightFactor) { drawLine(g2, location1, location2, ro, weightFactor, true); } private void drawLine(Graphics2D g2, ILocation location1, ILocation location2, RenderingOptions ro, float weightFactor, boolean directed) { if (location1 == null || location2 == null || ro.getRenderingType() == RenderingType.POINTS) { return; } // make sure we only render what's visible Point2D p1 = getLayerManager().getMainPanel().convertGeoPositionToPoint(new GeoPosition(location1.getLat(), location1.getLon())); Point2D p2 = getLayerManager().getMainPanel().convertGeoPositionToPoint(new GeoPosition(location2.getLat(), location2.getLon())); drawObjects.add(new Line(new XYPoint(location1.getID(), p1.getX(), p1.getY()), new XYPoint(location2.getID(), p2.getX(), p2.getY()), ro, weightFactor, directed)); } private void drawPoint(Graphics2D g2, ILocation location, RenderingOptions ro) { if (location == null || ro.getRenderingType() == RenderingType.LINES) { return; } // make sure we only render what's visible Point2D mapPosition = getLayerManager().getMainPanel().convertGeoPositionToPoint(new GeoPosition(location.getLat(), location.getLon())); if (mapPosition == null) { return; } drawObjects.add(new Point(new XYPoint(location.getID(), mapPosition.getX(), mapPosition.getY()), ro)); drawnPointsCounter++; } public boolean repaint() { return repaint(false); } @SuppressWarnings("unchecked") public boolean repaint(boolean force) { if (layerManager.getSize() == null) { return false; } drawObjects = new ArrayList<DrawObject>(); // GraphicsEnvironment env = GraphicsEnvironment // .getLocalGraphicsEnvironment(); // GraphicsDevice device = env.getDefaultScreenDevice(); // GraphicsConfiguration config = device.getDefaultConfiguration(); // image = config.createCompatibleImage(layerManager.getSize().width, // layerManager.getSize().height, Transparency.TRANSLUCENT); image = new BufferedImage(layerManager.getSize().width, layerManager.getSize().height, BufferedImage.TYPE_INT_ARGB_PRE); if (objects.isEmpty() || !isVisible()) { return true; } Graphics2D g2 = (Graphics2D) image.getGraphics(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // create font for labels Font labelFont = new Font(Font.SERIF, Font.PLAIN, 76); // queue draw operations drawnPointsCounter = 0; List<HashMap<Object, RenderingOptions>> loopCopyObjects = new ArrayList<HashMap<Object, RenderingOptions>>(); loopCopyObjects.addAll(objects); for (Object loopObject : loopCopyObjects) { HashMap<Object, RenderingOptions> myMap = (HashMap<Object, RenderingOptions>) loopObject; Object object = myMap.keySet().toArray()[0]; RenderingOptions ro = options; if (myMap.get(object) != null) { ro = myMap.get(object); } if (ro.getRenderingType() == RenderingType.NONE) { continue; } if (object instanceof AggContainer) { AggContainer container = (AggContainer) object; Rectangle2D.Double visibleArea = layerManager.getGpsArea(); if (visibleArea != null) { // draw background (tile sizes) List<Tile<AggNode>> tiles = ((DefaultCachingStrategy) container .getCachingStrategy()).getTm().clipTiles( visibleArea); if (tiles != null) { // draw them g2.setColor(Color.WHITE); g2.setStroke(ro.getStroke(16)); for (Tile<AggNode> tile : tiles) { Rectangle2D.Double size = new Rectangle2D.Double( tile.getSize().x, tile.getSize().y, tile.getSize().width, tile.getSize().height); Rectangle2D.Double projected; if (!tile.isRoot()) { // get projected corners projected = projectRect(size); // fill g2.setColor(tile.isLoaded ? new Color(1f, 0f, 0f, 0.05f) : new Color(0f, 0f, 1f, 0.05f)); g2.fillRect((int) projected.x, (int) projected.y, (int) projected.width, (int) projected.height); // draw rectangle g2.setColor(Color.BLACK); g2.drawRect((int) projected.x, (int) projected.y, (int) projected.width, (int) projected.height); // name g2.setColor(Color.BLACK); g2.drawString(tile.getID(), (int) projected.x + 40, (int) projected.y + 40); } } } /* * retrieve all points and connections in drawing area (and * few around to make sure we catch all connections) */ double newWidth = visibleArea.width + 2 * CLIPPING_AREA_BORDER; double newHeight = visibleArea.height + 2 * CLIPPING_AREA_BORDER; visibleArea.setRect(visibleArea.x - (newWidth - visibleArea.width) / 2, visibleArea.y - (newHeight - visibleArea.height) / 2, newWidth, newHeight); List<AggNode> nodes = container.getCachingStrategy() .clipRegion(visibleArea); if (nodes != null) { Set<AggConnection> conns = new HashSet<AggConnection>(); for (AggNode node : nodes) { if (!node.isShallow()) { conns.addAll(node.getIn()); conns.addAll(node.getOut()); } } RenderingOptions roVisible = ro; RenderingOptions roHidden = ro; if (layerManager.renderWeight) { roVisible = ro.getCopy(); roVisible.setColor(Color.BLACK); roHidden = ro.getCopy(); roHidden.setColor(new Color(95, 95, 95)); } // first paint all connections RenderingOptions lineRo = null; float lineWidth = 1; for (AggConnection conn : conns) { if (layerManager.renderWeight) { lineRo = conn.isVisible() ? roVisible : roHidden; lineWidth = conn.getWeight(); } else { // render avgDist lineRo = ro; lineWidth = Math .max(1, (int) conn.getAvgDist()); } drawLine(g2, conn.getFrom(), conn.getTo(), lineRo, lineWidth); } // then paint all nodes for (AggNode node : nodes) { drawPoint(g2, node, ro); } } } } if (object instanceof Set<?>) { // Set<Intersection> // render intersections RenderingOptions intersectionRo = ro.getCopy(); intersectionRo.setStrokeBaseWidthFactor(layerManager.getSize().width / 50); intersectionRo.setStrokeBaseWidthFactor(7 + 17 / 6 * (layerManager .getUi().getMainPanel().getZoom() - 12)); for (Intersection i : (Set<Intersection>) object) { if (!i.isVisible()) { continue; } drawPoint(g2, i, intersectionRo); } } if (object instanceof RoadNetwork) { RoadNetwork roadNetwork = (RoadNetwork) object; RenderingOptions roPrimary = ro.getCopy(); RenderingOptions roSecondary = ro.getCopy(); RenderingOptions roTertiary = ro.getCopy(); roPrimary.setColor(new Color(219, 37, 37)); // red roSecondary.setColor(new Color(253, 143, 0)); // orange roTertiary.setColor(new Color(221, 255, 68)); // yellow // render roads RenderingOptions roInternal = ro; for (Road r : roadNetwork.getRoads()) { if (!r.isVisible()) { continue; } switch (r.getType()) { case PRIMARY: roInternal = roPrimary; break; case SECONDARY: roInternal = roSecondary; break; case TERTIARY: roInternal = roTertiary; break; } List<? extends ILocation> nodes = r.getNodes(); for (int i = 1; i < nodes.size(); i++) { drawLine(g2, nodes.get(i - 1), nodes.get(i), roInternal, 1, (i == nodes.size() - 1 && r.isOneWay())); } } } else if (object instanceof GPSTrack) { GPSTrack track = (GPSTrack) object; for (GPSSegment segment : track) { GPSPoint lastPoint = null; for (GPSPoint point : segment) { if (lastPoint != null) { drawLine(g2, lastPoint, point, ro); } drawPoint(g2, point, ro); lastPoint = point; } } } else if (object instanceof GPSSegment) { GPSSegment segment = (GPSSegment) object; GPSPoint lastPoint = null; for (GPSPoint point : segment) { if (lastPoint != null) { drawLine(g2, lastPoint, point, ro); } drawPoint(g2, point, ro); lastPoint = point; } } else if (object instanceof GPSPoint) { GPSPoint point = (GPSPoint) object; drawPoint(g2, point, ro); } else if (object instanceof List<?>) { List<ILocation> points = (List<ILocation>) object; ILocation lastPoint = null; for (ILocation point : points) { if (lastPoint != null) { drawLine(g2, lastPoint, point, ro, 1, true); } drawPoint(g2, point, ro); lastPoint = point; } } } labelFont = labelFont.deriveFont((float) (32 - 3 * Math .log(drawnPointsCounter))); g2.setFont(labelFont); // actually draw Polygon arrowHead = new Polygon(); arrowHead.addPoint((int) (-4 * layerManager.thicknessFactor), (int) (-8 * layerManager.thicknessFactor)); arrowHead.addPoint(0, (int) (4 * layerManager.thicknessFactor)); arrowHead.addPoint((int) (4 * layerManager.thicknessFactor), (int) (-8 * layerManager.thicknessFactor)); List<DrawObject> internalLoopDrawObjects = new ArrayList<DrawObject>(); internalLoopDrawObjects.addAll(drawObjects); for (DrawObject drawObject : internalLoopDrawObjects) { // set color if (drawObject == null) { continue; } RenderingOptions ro = drawObject.getRenderingOptions(); Color lineColor = new Color((float) (ro.getColor().getRed() / 255.0), (float) (ro.getColor().getGreen() / 255.0), (float) (ro.getColor().getBlue() / 255.0), (float) ro.getOpacity()); g2.setColor(lineColor); if (drawObject instanceof Point) { Point p = (Point) drawObject; // draw point if (ro.getRenderingType() == RenderingType.POINTS) { float width = ro.getStrokeBaseWidthFactor() * RenderingOptions.getBasicStroke().getLineWidth(); g2.fillOval((int) (p.at.getX() - width / 2), (int) (p.at.getY() - width / 2), (int) width, (int) width); } // draw label // do not draw label if not fully opaque if (ro.getOpacity() < 1) { continue; } if (p.at.getID() == null || ro.getLabelRenderingType() == LabelRenderingType.NEVER) { continue; } if (ro.getLabelRenderingType() == LabelRenderingType.INTELLIGENT && drawnPointsCounter > MAX_VISIBLE_POINTS_INTELLIGENT_LABELS) { continue; } g2.drawString(p.at.getID(), (int) p.at.getX(), (int) (p.at.getY() + layerManager.getMainPanel() .getHeight() * 0.075)); } else if (drawObject instanceof Line) { // draw line if (ro.getRenderingType() == RenderingType.POINTS) { continue; } Line l = (Line) drawObject; float width = l.weightFactor * layerManager.thicknessFactor; g2.setStroke(ro.getStroke(width)); g2.drawLine((int) l.from.getX(), (int) l.from.getY(), (int) l.to.getX(), (int) l.to.getY()); // make a nice arrow :) (code from // http://stackoverflow.com/a/3094933) if (ro.getRenderingType() == RenderingType.LINES) { continue; } if (ro.getRenderingType() == RenderingType.INTELLIGENT_ALL && drawnPointsCounter > MAX_VISIBLE_POINTS_INTELLIGENT_POINT) { continue; } if (l.directed) { double angle = Math.atan2(l.to.getY() - l.from.getY(), l.to.getX() - l.from.getX()); AffineTransform oldTx = g2.getTransform(); AffineTransform tx = new AffineTransform(oldTx); tx.translate(l.to.getX(), l.to.getY()); tx.rotate((angle - Math.PI / 2d)); g2.setTransform(tx); g2.setStroke(ro.getStroke(layerManager.thicknessFactor)); g2.drawLine(0, 0, (int) (6 + width * 2), (int) (-8 - width * 2)); g2.drawLine(0, 0, (int) (-6 - width * 2), (int) (-8 - width * 2)); g2.setTransform(oldTx); } } } return true; } public Rectangle2D.Double projectRect(Rectangle2D.Double source) { ILocation ul = new GPSPoint(source.x, source.y); ILocation lr = new GPSPoint(source.getMaxX(), source.getMaxY()); source.x = ul.getX(); source.y = ul.getY(); source.width = lr.getX() - ul.getX(); source.height = lr.getY() - ul.getY(); return source; } public void clear() { objects.clear(); } public interface DrawObject { public RenderingOptions getRenderingOptions(); } public class Line implements DrawObject { public ILocation from; public ILocation to; public RenderingOptions renderingOptions; public float weightFactor = 1; public boolean directed = true; public Line(ILocation from, ILocation to, RenderingOptions ro, float weightFactor) { this.from = from; this.to = to; this.renderingOptions = ro; this.weightFactor = weightFactor; } public Line(ILocation from, ILocation to, RenderingOptions ro, float weightFactor, boolean directed) { this(from, to, ro, weightFactor); this.directed = directed; } @Override public RenderingOptions getRenderingOptions() { return renderingOptions; } } public class Point implements DrawObject { public ILocation at; public RenderingOptions renderingOptions; public Point(ILocation at, RenderingOptions ro) { this.at = at; this.renderingOptions = ro; } @Override public RenderingOptions getRenderingOptions() { return renderingOptions; } } public void paintToComponent(JComponent component, Graphics2D g2) { // something new, different? Rectangle2D.Double gpsArea = layerManager.getGpsArea(); if (lastGpsArea == null || !lastGpsArea.equals(gpsArea) || component.getSize().width > getImage().getWidth() || component.getSize().height > getImage().getHeight()) { repaint(true); lastGpsArea = gpsArea; } // scale the image to the if (image == null) { return; } g2.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null); } @Override public boolean isVisible() { return visible; } @Override public void setVisible(boolean visible) { this.visible = visible; } }