source: josm/trunk/src/org/openstreetmap/josm/actions/mapmode/ParallelWays.java@ 6246

Last change on this file since 6246 was 6069, checked in by stoecker, 11 years ago

see #8853 remove tabs, trailing spaces, windows line ends, strange characters

  • Property svn:eol-style set to native
File size: 7.6 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.actions.mapmode;
3
4import java.util.ArrayList;
5import java.util.Collection;
6import java.util.Collections;
7import java.util.HashMap;
8import java.util.HashSet;
9import java.util.List;
10import java.util.Set;
11
12import org.openstreetmap.josm.Main;
13import org.openstreetmap.josm.actions.CombineWayAction;
14import org.openstreetmap.josm.command.AddCommand;
15import org.openstreetmap.josm.command.Command;
16import org.openstreetmap.josm.command.SequenceCommand;
17import org.openstreetmap.josm.data.coor.EastNorth;
18import org.openstreetmap.josm.data.osm.Node;
19import org.openstreetmap.josm.data.osm.Way;
20import org.openstreetmap.josm.tools.Geometry;
21
22/**
23 * Helper for ParallelWayAction
24 *
25 * @author Ole Jørgen Brønner (olejorgenb)
26 */
27public class ParallelWays {
28 final List<Way> ways;
29 private final List<Node> sortedNodes;
30
31 private final int nodeCount;
32
33 private final EastNorth[] pts;
34 private final EastNorth[] normals;
35
36 // Need a reference way to determine the direction of the offset when we manage multiple ways
37 public ParallelWays(Collection<Way> sourceWays, boolean copyTags, int refWayIndex) {
38 // Possible/sensible to use PrimetiveDeepCopy here?
39
40 // Make a deep copy of the ways, keeping the copied ways connected
41 // TODO: This assumes the first/last nodes of the ways are the only possible shared nodes.
42 HashMap<Node, Node> splitNodeMap = new HashMap<Node, Node>(sourceWays.size());
43 for (Way w : sourceWays) {
44 if (!splitNodeMap.containsKey(w.firstNode())) {
45 splitNodeMap.put(w.firstNode(), copyNode(w.firstNode(), copyTags));
46 }
47 if (!splitNodeMap.containsKey(w.lastNode())) {
48 splitNodeMap.put(w.lastNode(), copyNode(w.lastNode(), copyTags));
49 }
50 }
51 ways = new ArrayList<Way>(sourceWays.size());
52 for (Way w : sourceWays) {
53 Way wCopy = new Way();
54 wCopy.addNode(splitNodeMap.get(w.firstNode()));
55 for (int i = 1; i < w.getNodesCount() - 1; i++) {
56 wCopy.addNode(copyNode(w.getNode(i), copyTags));
57 }
58 wCopy.addNode(splitNodeMap.get(w.lastNode()));
59 if (copyTags) {
60 wCopy.setKeys(w.getKeys());
61 }
62 ways.add(wCopy);
63 }
64 sourceWays = null; // Ensure that we only use the copies from now
65
66 // Find a linear ordering of the nodes. Fail if there isn't one.
67 CombineWayAction.NodeGraph nodeGraph = CombineWayAction.NodeGraph.createUndirectedGraphFromNodeWays(ways);
68 List<Node> sortedNodesPath = nodeGraph.buildSpanningPath();
69 if (sortedNodesPath == null)
70 throw new IllegalArgumentException("Ways must have spanning path"); // Create a dedicated exception?
71
72 // Fix #8631 - Remove duplicated nodes from graph to be robust with self-intersecting ways
73 Set<Node> removedNodes = new HashSet<Node>();
74 sortedNodes = new ArrayList<Node>();
75 for (int i = 0; i < sortedNodesPath.size(); i++) {
76 Node n = sortedNodesPath.get(i);
77 if (i < sortedNodesPath.size()-1) {
78 if (sortedNodesPath.get(i+1).getCoor().equals(n.getCoor())) {
79 removedNodes.add(n);
80 for (Way w : ways)
81 w.removeNode(n);
82 continue;
83 }
84 }
85 if (!removedNodes.contains(n)) {
86 sortedNodes.add(n);
87 }
88 }
89
90 // Ugly method of ensuring that the offset isn't inverted. I'm sure there is a better and more elegant way, but I'm starting to get sleepy, so I do this for now.
91 {
92 Way refWay = ways.get(refWayIndex);
93 boolean refWayReversed = true;
94 for (int i = 0; i < sortedNodes.size() - 1; i++) {
95 if (sortedNodes.get(i) == refWay.firstNode() && sortedNodes.get(i + 1) == refWay.getNode(1)) {
96 refWayReversed = false;
97 break;
98 }
99 }
100 if (refWayReversed) {
101 Collections.reverse(sortedNodes); // need to keep the orientation of the reference way.
102 }
103 }
104
105 // Initialize the required parameters. (segment normals, etc.)
106 nodeCount = sortedNodes.size();
107 pts = new EastNorth[nodeCount];
108 normals = new EastNorth[nodeCount - 1];
109 int i = 0;
110 for (Node n : sortedNodes) {
111 EastNorth t = n.getEastNorth();
112 pts[i] = t;
113 i++;
114 }
115 for (i = 0; i < nodeCount - 1; i++) {
116 double dx = pts[i + 1].getX() - pts[i].getX();
117 double dy = pts[i + 1].getY() - pts[i].getY();
118 double len = Math.sqrt(dx * dx + dy * dy);
119 normals[i] = new EastNorth(-dy / len, dx / len);
120 }
121 }
122
123 public boolean isClosedPath() {
124 return sortedNodes.get(0) == sortedNodes.get(sortedNodes.size() - 1);
125 }
126
127 /**
128 * Offsets the way(s) d units. Positive d means to the left (relative to the reference way)
129 * @param d
130 */
131 public void changeOffset(double d) {
132 // This is the core algorithm:
133 /* 1. Calculate a parallel line, offset by 'd', to each segment in the path
134 * 2. Find the intersection of lines belonging to neighboring segments. These become the new node positions
135 * 3. Do some special casing for closed paths
136 *
137 * Simple and probably not even close to optimal performance wise
138 */
139
140 EastNorth[] ppts = new EastNorth[nodeCount];
141
142 EastNorth prevA = pts[0].add(normals[0].scale(d));
143 EastNorth prevB = pts[1].add(normals[0].scale(d));
144 for (int i = 1; i < nodeCount - 1; i++) {
145 EastNorth A = pts[i].add(normals[i].scale(d));
146 EastNorth B = pts[i + 1].add(normals[i].scale(d));
147 if (Geometry.segmentsParallel(A, B, prevA, prevB)) {
148 ppts[i] = A;
149 } else {
150 ppts[i] = Geometry.getLineLineIntersection(A, B, prevA, prevB);
151 }
152 prevA = A;
153 prevB = B;
154 }
155 if (isClosedPath()) {
156 EastNorth A = pts[0].add(normals[0].scale(d));
157 EastNorth B = pts[1].add(normals[0].scale(d));
158 if (Geometry.segmentsParallel(A, B, prevA, prevB)) {
159 ppts[0] = A;
160 } else {
161 ppts[0] = Geometry.getLineLineIntersection(A, B, prevA, prevB);
162 }
163 ppts[nodeCount - 1] = ppts[0];
164 } else {
165 ppts[0] = pts[0].add(normals[0].scale(d));
166 ppts[nodeCount - 1] = pts[nodeCount - 1].add(normals[nodeCount - 2].scale(d));
167 }
168
169 for (int i = 0; i < nodeCount; i++) {
170 sortedNodes.get(i).setEastNorth(ppts[i]);
171 }
172 }
173
174 public void commit() {
175 SequenceCommand undoCommand = new SequenceCommand("Make parallel way(s)", makeAddWayAndNodesCommandList());
176 Main.main.undoRedo.add(undoCommand);
177 }
178
179 private List<Command> makeAddWayAndNodesCommandList() {
180 ArrayList<Command> commands = new ArrayList<Command>(sortedNodes.size() + ways.size());
181 for (int i = 0; i < sortedNodes.size() - (isClosedPath() ? 1 : 0); i++) {
182 commands.add(new AddCommand(sortedNodes.get(i)));
183 }
184 for (Way w : ways) {
185 commands.add(new AddCommand(w));
186 }
187 return commands;
188 }
189
190 static private Node copyNode(Node source, boolean copyTags) {
191 if (copyTags)
192 return new Node(source, true);
193 else {
194 Node n = new Node();
195 n.setCoor(source.getCoor());
196 return n;
197 }
198 }
199}
Note: See TracBrowser for help on using the repository browser.