package org.openstreetmap.josm.plugins.contourmerge; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Point; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.util.Optional; import javax.validation.constraints.NotNull; import org.apache.commons.lang3.Validate; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.gui.layer.MapViewPaintable; /** * <p><strong>ContourMergeView</strong> renders the {@link ContourMergeModel} * for the * currently active data layer.</p> * */ public class ContourMergeView implements MapViewPaintable{ // static private final Logger logger = // Logger.getLogger(ContourMergeView.class.getName()); static private ContourMergeView instance; public static ContourMergeView getInstance() { return instance == null ? instance = new ContourMergeView() : instance; } public void wireToJOSM() { if (Main.map == null) return; if (Main.map.mapView == null) return; Main.map.mapView.addTemporaryLayer(this); } public void unwireFromJOSM() { if (Main.map == null) return; if (Main.map.mapView == null) return; Main.map.mapView.removeTemporaryLayer(this); } protected Optional<ContourMergeModel> getActiveModel() { return ContourMergePlugin.getModelManager().getActiveModel(); } protected void decorateFeedbackNode(Graphics2D g, MapView mv, Bounds bbox){ /* currently no decoration - mouse pointer is changing if mouse over a * node */ } protected void decorateSelectedNode(Graphics2D g, MapView mv, Bounds bbox, Node node){ if (!bbox.contains(node.getCoor())) return; Point p = mv.getPoint(node.getCoor()); g.translate(p.x,p.y); g.setColor(Color.ORANGE); g.setStroke(new BasicStroke(3,BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); g.drawLine(-5, 5, 5,-5); g.drawLine(-5, -5, 5, 5); g.setColor(Color.ORANGE.brighter()); g.setStroke(new BasicStroke(1,BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); g.drawLine(-5, 5, 5,-5); g.drawLine(-5, -5, 5, 5); g.translate(-p.x, -p.y); } protected void decorateSelectedNodes(Graphics2D g, MapView mv, Bounds bbox){ getActiveModel().ifPresent(model -> { model.getSelectedNodes().stream() .forEach(n -> decorateSelectedNode(g, mv, bbox, n)); }); } /** * <p>Highlights a way slice, i.e. the current drag source or a potential * drop target.</p> * * @param g graphics context * @param mv map view * @param bbox map bbox * @param slice the way slice. Must not be null. */ protected void highlightWaySlice(Graphics2D g, MapView mv, Bounds bbox, WaySlice slice){ Path2D polyline = project(mv, slice); g.setColor(Color.RED); g.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); g.draw(polyline); } /** * <p>Projects this way slice onto the map view {@code mv}. Replies a * polyline representing the way slice on screen.</p> * * @param mv the map view. Must not be null. * @param ws the way slice. Must not be null. * @return a polyline * */ public static Path2D project(@NotNull MapView mv, @NotNull WaySlice ws){ Validate.notNull(mv); Validate.notNull(ws); Path2D.Float polyline = new Path2D.Float(); if (ws.isInDirection()) { /* * a way slice of an open way, or a closed way between two nodes in * the ways direction */ for (int i = ws.getStart(); i <= ws.getEnd(); i++){ Point p = mv.getPoint(ws.getWay().getNode(i).getCoor()); if (i == ws.getStart()) { polyline.moveTo(p.x, p.y); } else { polyline.lineTo(p.x, p.y); } } } else { /* * a way slice of a closed way, between two nodes in the * direction *opposite* to the ways direction */ for (int i = ws.getStart(); i >= 0; i--){ Point p = mv.getPoint(ws.getWay().getNode(i).getCoor()); if (i == ws.getStart()) { polyline.moveTo(p.x, p.y); } else { polyline.lineTo(p.x, p.y); } } for (int i = ws.getWay().getNodesCount()-2; i >= ws.getEnd(); i--){ Point p = mv.getPoint(ws.getWay().getNode(i).getCoor()); if (i == ws.getStart()) { polyline.moveTo(p.x, p.y); } else { polyline.lineTo(p.x, p.y); } } } return polyline; } /** * <p>Projects this way slice onto the map view {@code mv}. Replies a * polyline representing the way slice on screen, displaced by the offset * {@code displacement.x} in x-direction and {@code displacement.y} * in y-direction. </p> * * @param mv the map view. Must not be null. * @param ws the way slice. Must not be null. * @param displacement the displacement. (0,0) is assumed, if null. * @return a polyline */ public Path2D project(@NotNull MapView mv, @NotNull WaySlice ws, Point displacement){ Validate.notNull(mv); Validate.notNull(ws); if (displacement == null) displacement = new Point(0,0); Path2D polyline = project(mv, ws); AffineTransform at = new AffineTransform(); at.setToTranslation(displacement.x, displacement.y); polyline = new Path2D.Float(polyline, at); return polyline; } protected void paintHelperLinesFromDragSourceToDraggedWaySlice( Graphics2D g, MapView mv){ getActiveModel() .filter(model -> model.isDragging()) .ifPresent(model -> { WaySlice dragSource = model.getDragSource(); Node lowerTearOffNode = dragSource.getStartTearOffNode(); Node upperTearOffNode = dragSource.getEndTearOffNode(); Point offset = model.getDragOffset(); // init the graphics attributes float[] dashPattern = { 2, 3, 2, 3 }; g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND,1f, dashPattern,0f)); boolean crossing = helperLinesAreCrossing(mv, dragSource, offset); if (lowerTearOffNode != null){ Point p1 = mv.getPoint(!crossing ? dragSource.getStartNode() : dragSource.getEndNode()); p1 = new Point(p1.x + offset.x, p1.y + offset.y); Point p2 = mv.getPoint(lowerTearOffNode); g.drawLine(p1.x,p1.y, p2.x,p2.y); } if (upperTearOffNode != null){ Point p1 = mv.getPoint(!crossing ? dragSource.getEndNode() : dragSource.getStartNode()); p1 = new Point(p1.x + offset.x, p1.y + offset.y); Point p2 = mv.getPoint(upperTearOffNode); g.drawLine(p1.x,p1.y, p2.x,p2.y); } }); } /** * <p>Checks whether the two helper lines from the drag source to the * drop target intersect, because the ways of the drag source and the * drop target don't have the same direction.</p> * * <p>If the helpers intersect, we will reverse the two end points of * the drop target, when we paint the helper lines.</p> * * @param mv the map view * @param dragSource * @param dropTarget * @return true, if the two helper lines from the drag source to the * drop target intersect */ protected boolean helperLinesAreCrossing(MapView mv, WaySlice dragSource, WaySlice dropTarget){ Node s1 = dragSource.getStartTearOffNode(); if (s1 == null) s1 = dragSource.getStartNode(); Node s2= dragSource.getEndTearOffNode(); if (s2 == null) s2 = dragSource.getEndNode(); Point sp1 = mv.getPoint(s1); Point sp2 = mv.getPoint(s2); Point tp1 = mv.getPoint(dropTarget.getStartNode()); Point tp2 = mv.getPoint(dropTarget.getEndNode()); return helperLinesAreCrossing(sp1,sp2,tp1,tp2); } protected boolean helperLinesAreCrossing(Point s1, Point s2, Point t1, Point t2){ Line2D l1 = new Line2D.Float(s1.x, s1.y, t1.x,t1.y); Line2D l2 = new Line2D.Float(s2.x, s2.y, t2.x,t2.y); return l1.intersectsLine(l2); } /** * <p>Checks whether the two helper lines from the drag source to the * currently painted drag object offset by {@code dragOffset} * intersect.</p> * * <p>If the helpers intersect, we will reverse the two end points of the * drop target, when we paint the helper lines.</p> * * @param mv the map view * @param dragSource * @param dropTarget * @return true, if the two helper lines from the drag source to the drop * target intersect */ protected boolean helperLinesAreCrossing(MapView mv, WaySlice dragSource, Point dragOffset){ Node s1 = dragSource.getStartTearOffNode(); if (s1 == null) s1 = dragSource.getStartNode(); Node s2= dragSource.getEndTearOffNode(); if (s2 == null) s2 = dragSource.getEndNode(); Point sp1 = mv.getPoint(s1); Point sp2 = mv.getPoint(s2); Point tp1 = mv.getPoint(dragSource.getStartNode()); tp1 = new Point(tp1.x + dragOffset.x, tp1.y + dragOffset.y); Point tp2 = mv.getPoint(dragSource.getEndNode()); tp2 = new Point(tp2.x + dragOffset.x, tp2.y + dragOffset.y); return helperLinesAreCrossing(sp1,sp2,tp1,tp2); } protected void paintHelperLinesFromDragSourceToDropTarget(Graphics2D g, MapView mv){ getActiveModel() .filter(model -> model.isDragging()) .ifPresent(model -> { WaySlice dragSource = model.getDragSource(); WaySlice dropTarget = model.getDropTarget(); Node lowerTearOffNode = dragSource.getStartTearOffNode(); Node upperTearOffNode = dragSource.getEndTearOffNode(); // init the graphics attributes float[] dashPattern = { 2, 3, 2, 3 }; g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND,1f, dashPattern,0f)); boolean crossing = helperLinesAreCrossing(mv, dragSource, dropTarget); if (lowerTearOffNode != null){ Point p1 = mv.getPoint(lowerTearOffNode); Point p2 = mv.getPoint(!crossing ? dropTarget.getStartNode() : dropTarget.getEndNode()); g.drawLine(p1.x,p1.y, p2.x,p2.y); } if (upperTearOffNode != null){ Point p1 = mv.getPoint(upperTearOffNode); Point p2 = mv.getPoint(!crossing ? dropTarget.getEndNode() : dropTarget.getStartNode()); g.drawLine(p1.x,p1.y, p2.x,p2.y); } }); } protected void paintDraggedWaySlice(Graphics2D g, MapView mv, Bounds bbox) { getActiveModel() .filter(model -> model.isDragging()) .ifPresent(model -> { WaySlice dragSource = model.getDragSource(); WaySlice dropTarget = model.getDropTarget(); if (dragSource == null) return; if (dropTarget == null) { /* * paint the temporary dragged way slice, unless the mouse is * currently over a potential drop target */ Path2D polyline = project(mv, dragSource, model.getDragOffset()); g.setColor(Color.RED); float[] dashPattern = { 10, 5, 10, 5 }; g.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND,1f, dashPattern,0f)); g.draw(polyline); paintHelperLinesFromDragSourceToDraggedWaySlice(g, mv); } else { /* * the mouse is over a suitable drop target. Paint only * two helper lines from the drag source to the drop target. * The drop target is highlighted elsewhere. */ paintHelperLinesFromDragSourceToDropTarget(g,mv); } }); } /* ---------------------------------------------------------------------- */ /* interface MapViewPaintable */ /* ---------------------------------------------------------------------- */ @Override public void paint(Graphics2D g, MapView mv, Bounds bbox) { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (!ContourMergePlugin.isEnabled()) return; getActiveModel() .filter(model -> model.getLayer().isVisible()) .ifPresent(model -> { decorateSelectedNodes(g, mv, bbox); decorateFeedbackNode(g, mv, bbox); WaySlice dragSourceSlice = model.getDragSource(); if (dragSourceSlice != null){ highlightWaySlice(g, mv, bbox, dragSourceSlice); } WaySlice dropTargetSlice = model.getDropTarget(); if (dropTargetSlice != null){ highlightWaySlice(g, mv, bbox, dropTargetSlice); } if (model.isDragging()){ paintDraggedWaySlice(g, mv, bbox); } }); } }