| 1 | // License: GPL. For details, see LICENSE file. |
|---|
| 2 | package org.openstreetmap.josm.actions; |
|---|
| 3 | |
|---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr; |
|---|
| 5 | |
|---|
| 6 | import java.awt.event.ActionEvent; |
|---|
| 7 | import java.awt.event.KeyEvent; |
|---|
| 8 | import java.util.Collection; |
|---|
| 9 | import java.util.Iterator; |
|---|
| 10 | import java.util.List; |
|---|
| 11 | import java.util.Set; |
|---|
| 12 | |
|---|
| 13 | import org.openstreetmap.josm.Main; |
|---|
| 14 | import org.openstreetmap.josm.actions.mapmode.DrawAction; |
|---|
| 15 | import org.openstreetmap.josm.command.ChangeCommand; |
|---|
| 16 | import org.openstreetmap.josm.data.osm.Node; |
|---|
| 17 | import org.openstreetmap.josm.data.osm.OsmPrimitive; |
|---|
| 18 | import org.openstreetmap.josm.data.osm.OsmPrimitiveType; |
|---|
| 19 | import org.openstreetmap.josm.data.osm.Way; |
|---|
| 20 | import org.openstreetmap.josm.gui.layer.OsmDataLayer; |
|---|
| 21 | import org.openstreetmap.josm.tools.Shortcut; |
|---|
| 22 | |
|---|
| 23 | /** |
|---|
| 24 | * Follow line action - Makes easier to draw a line that shares points with another line |
|---|
| 25 | * |
|---|
| 26 | * Aimed at those who want to draw two or more lines related with |
|---|
| 27 | * each other, but carry different information (i.e. a river acts as boundary at |
|---|
| 28 | * some part of its course. It preferable to have a separated boundary line than to |
|---|
| 29 | * mix totally different kind of features in one single way). |
|---|
| 30 | * |
|---|
| 31 | * @author Germán Márquez MejÃa |
|---|
| 32 | */ |
|---|
| 33 | public class FollowLineAction extends JosmAction { |
|---|
| 34 | |
|---|
| 35 | public FollowLineAction() { |
|---|
| 36 | super( |
|---|
| 37 | tr("Follow line"), |
|---|
| 38 | "followline.png", |
|---|
| 39 | tr("Continues drawing a line that shares nodes with another line."), |
|---|
| 40 | Shortcut.registerShortcut("tools:followline", tr( |
|---|
| 41 | "Tool: {0}", tr("Follow")), |
|---|
| 42 | KeyEvent.VK_F, Shortcut.DIRECT), true); |
|---|
| 43 | } |
|---|
| 44 | |
|---|
| 45 | @Override |
|---|
| 46 | protected void updateEnabledState() { |
|---|
| 47 | if (getCurrentDataSet() == null) { |
|---|
| 48 | setEnabled(false); |
|---|
| 49 | } else { |
|---|
| 50 | updateEnabledState(getCurrentDataSet().getSelected()); |
|---|
| 51 | } |
|---|
| 52 | } |
|---|
| 53 | |
|---|
| 54 | @Override |
|---|
| 55 | protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { |
|---|
| 56 | setEnabled(selection != null && !selection.isEmpty()); |
|---|
| 57 | } |
|---|
| 58 | |
|---|
| 59 | @Override |
|---|
| 60 | public void actionPerformed(ActionEvent evt) { |
|---|
| 61 | OsmDataLayer osmLayer = Main.main.getEditLayer(); |
|---|
| 62 | if (osmLayer == null) |
|---|
| 63 | return; |
|---|
| 64 | if (!(Main.map.mapMode instanceof DrawAction)) return; // We are not on draw mode |
|---|
| 65 | |
|---|
| 66 | Collection<Node> selectedPoints = osmLayer.data.getSelectedNodes(); |
|---|
| 67 | Collection<Way> selectedLines = osmLayer.data.getSelectedWays(); |
|---|
| 68 | if ((selectedPoints.size() > 1) || (selectedLines.size() != 1)) // Unsuitable selection |
|---|
| 69 | return; |
|---|
| 70 | |
|---|
| 71 | Node last = ((DrawAction) Main.map.mapMode).getCurrentBaseNode(); |
|---|
| 72 | if (last == null) |
|---|
| 73 | return; |
|---|
| 74 | Way follower = selectedLines.iterator().next(); |
|---|
| 75 | if (follower.isClosed()) /* Don't loop until OOM */ |
|---|
| 76 | return; |
|---|
| 77 | Node prev = follower.getNode(1); |
|---|
| 78 | boolean reversed = true; |
|---|
| 79 | if (follower.lastNode().equals(last)) { |
|---|
| 80 | prev = follower.getNode(follower.getNodesCount() - 2); |
|---|
| 81 | reversed = false; |
|---|
| 82 | } |
|---|
| 83 | List<OsmPrimitive> referrers = last.getReferrers(); |
|---|
| 84 | if (referrers.size() < 2) return; // There's nothing to follow |
|---|
| 85 | |
|---|
| 86 | Node newPoint = null; |
|---|
| 87 | Iterator<OsmPrimitive> i = referrers.iterator(); |
|---|
| 88 | while (i.hasNext()) { |
|---|
| 89 | OsmPrimitive referrer = i.next(); |
|---|
| 90 | if (!referrer.getType().equals(OsmPrimitiveType.WAY)) { // Can't follow points or relations |
|---|
| 91 | continue; |
|---|
| 92 | } |
|---|
| 93 | Way toFollow = (Way) referrer; |
|---|
| 94 | if (toFollow.equals(follower)) { |
|---|
| 95 | continue; |
|---|
| 96 | } |
|---|
| 97 | Set<Node> points = toFollow.getNeighbours(last); |
|---|
| 98 | if (!points.remove(prev) || (points.size() == 0)) |
|---|
| 99 | continue; |
|---|
| 100 | if (points.size() > 1) // Ambiguous junction? |
|---|
| 101 | return; |
|---|
| 102 | |
|---|
| 103 | Node newPointCandidate = points.iterator().next(); |
|---|
| 104 | |
|---|
| 105 | if ((newPoint != null) && (newPoint != newPointCandidate)) |
|---|
| 106 | return; // Ambiguous junction, force to select next |
|---|
| 107 | |
|---|
| 108 | newPoint = newPointCandidate; |
|---|
| 109 | } |
|---|
| 110 | if (newPoint != null) { |
|---|
| 111 | Way newFollower = new Way(follower); |
|---|
| 112 | if (reversed) { |
|---|
| 113 | newFollower.addNode(0, newPoint); |
|---|
| 114 | } else { |
|---|
| 115 | newFollower.addNode(newPoint); |
|---|
| 116 | } |
|---|
| 117 | Main.main.undoRedo.add(new ChangeCommand(follower, newFollower)); |
|---|
| 118 | osmLayer.data.clearSelection(); |
|---|
| 119 | osmLayer.data.addSelected(newFollower); |
|---|
| 120 | osmLayer.data.addSelected(newPoint); |
|---|
| 121 | // "viewport following" mode for tracing long features |
|---|
| 122 | // from aerial imagery or GPS tracks. |
|---|
| 123 | if (Main.map.mapView.viewportFollowing) { |
|---|
| 124 | Main.map.mapView.smoothScrollTo(newPoint.getEastNorth()); |
|---|
| 125 | }; |
|---|
| 126 | } |
|---|
| 127 | } |
|---|
| 128 | } |
|---|