// License: GPL. See LICENSE file for details. Copyright 2012 by Josh Doe and others. package org.openstreetmap.josm.plugins.conflation; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Point; import java.awt.geom.GeneralPath; import java.util.Iterator; import javax.swing.Action; import javax.swing.Icon; import org.openstreetmap.josm.actions.RenameLayerAction; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.gui.MapView.LayerChangeListener; import org.openstreetmap.josm.gui.dialogs.LayerListDialog; import org.openstreetmap.josm.gui.dialogs.LayerListPopup; import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.gui.layer.Layer.SeparatorLayerAction; import static org.openstreetmap.josm.tools.I18n.tr; import org.openstreetmap.josm.tools.ImageProvider; /** * A layer to show arrows and other symbols to indicate what primitives have been matched. * TODO: warn on closing layer? * @author joshdoe */ public class ConflationLayer extends Layer implements LayerChangeListener { protected SimpleMatchList matches; public ConflationLayer(SimpleMatchList matches) { super(tr("Conflation")); MapView.addLayerChangeListener(this); this.matches = matches; } public ConflationLayer() { this(null); } /** * Draw symbols connecting matched primitives. */ @SuppressWarnings("unchecked") @Override public void paint(final Graphics2D g, final MapView mv, Bounds bounds) { Graphics2D g2 = g; BasicStroke line = new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); g2.setStroke(line); final double PHI = Math.toRadians(20); final double cosPHI = Math.cos(PHI); final double sinPHI = Math.sin(PHI); for (Iterator<SimpleMatch> it = this.matches.iterator(); it.hasNext();) { SimpleMatch match = it.next(); if (matches.getSelected().contains(match)) { g2.setColor(Color.blue); } else { g2.setColor(Color.cyan); } OsmPrimitive reference = match.getReferenceObject(); OsmPrimitive subject = match.getSubjectObject(); if (reference != null && subject != null) { GeneralPath path = new GeneralPath(); // we have a pair, so draw line between them Point p1 = mv.getPoint(ConflationUtils.getCenter(reference)); Point p2 = mv.getPoint(ConflationUtils.getCenter(subject)); path.moveTo(p1.x, p1.y); path.lineTo(p2.x, p2.y); //logger.info(String.format("Line %d,%d to %d,%d", p1.x, p1.y, p2.x, p2.y)); // draw arrow head if (true) { final double segmentLength = p1.distance(p2); if (segmentLength != 0.0) { final double l = (10. + line.getLineWidth()) / segmentLength; final double sx = l * (p1.x - p2.x); final double sy = l * (p1.y - p2.y); path.moveTo(p2.x + cosPHI * sx - sinPHI * sy, p2.y + sinPHI * sx + cosPHI * sy); path.lineTo(p2.x, p2.y); path.lineTo(p2.x + cosPHI * sx + sinPHI * sy, p2.y - sinPHI * sx + cosPHI * sy); } } g2.draw(path); } } } @Override public Icon getIcon() { // TODO: change icon return ImageProvider.get("dialogs", "conflation"); } @Override public String getToolTipText() { return "Conflation tool tip text goes here"; } @Override public void mergeFrom(Layer layer) { // we can't merge, so do nothing } @Override public boolean isMergable(Layer layer) { return false; } @Override public void visitBoundingBox(BoundingXYVisitor v) { for (Iterator<SimpleMatch> it = this.matches.iterator(); it.hasNext();) { SimpleMatch match = it.next(); OsmPrimitive reference = match.getReferenceObject(); OsmPrimitive subject = match.getSubjectObject(); if (reference != null && reference instanceof Node) v.visit((Node)reference); if (subject != null && subject instanceof Node) v.visit((Node)subject); } } @Override public Object getInfoComponent() { return getToolTipText(); } @Override public Action[] getMenuEntries() { return new Action[]{ LayerListDialog.getInstance().createShowHideLayerAction(), LayerListDialog.getInstance().createDeleteLayerAction(), SeparatorLayerAction.INSTANCE, new RenameLayerAction(this.getAssociatedFile(), this), SeparatorLayerAction.INSTANCE, new LayerListPopup.InfoAction(this)}; } @Override public void activeLayerChange(Layer layer, Layer layer1) { //TODO: possibly change arrow styling depending on active layer? } @Override public void layerAdded(Layer layer) { // shouldn't have to do anything here } @Override public void layerRemoved(Layer layer) { //TODO: if ref or non-ref layer removed, remove arrows } public void setMatches(SimpleMatchList matches) { this.matches = matches; // TODO: does repaint automatically occur? } }