[298] | 1 | // License: GPL. Copyright 2007 by Immanuel Scholz and others
|
---|
[1] | 2 | package org.openstreetmap.josm.data.osm;
|
---|
| 3 |
|
---|
[2204] | 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 |
|
---|
[1] | 6 | import java.util.ArrayList;
|
---|
[86] | 7 | import java.util.Arrays;
|
---|
[1] | 8 | import java.util.List;
|
---|
[3630] | 9 | import java.util.Set;
|
---|
[4671] | 10 | import java.util.HashSet;
|
---|
[1] | 11 |
|
---|
[3032] | 12 | import org.openstreetmap.josm.Main;
|
---|
[4321] | 13 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
[4100] | 14 | import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
|
---|
[8] | 15 | import org.openstreetmap.josm.data.osm.visitor.Visitor;
|
---|
[5059] | 16 | import org.openstreetmap.josm.gui.DefaultNameFormatter;
|
---|
[1862] | 17 | import org.openstreetmap.josm.tools.CopyList;
|
---|
[529] | 18 | import org.openstreetmap.josm.tools.Pair;
|
---|
[8] | 19 |
|
---|
[1] | 20 | /**
|
---|
[5408] | 21 | * One full way, consisting of a list of way {@link Node nodes}.
|
---|
[1] | 22 | *
|
---|
| 23 | * @author imi
|
---|
[5408] | 24 | * @since 64
|
---|
[1] | 25 | */
|
---|
[4098] | 26 | public final class Way extends OsmPrimitive implements IWay {
|
---|
[1] | 27 |
|
---|
[1169] | 28 | /**
|
---|
| 29 | * All way nodes in this way
|
---|
[1910] | 30 | *
|
---|
[1169] | 31 | */
|
---|
[2204] | 32 | private Node[] nodes = new Node[0];
|
---|
[2437] | 33 | private BBox bbox;
|
---|
[7] | 34 |
|
---|
[1862] | 35 | /**
|
---|
| 36 | *
|
---|
| 37 | * You can modify returned list but changes will not be propagated back
|
---|
| 38 | * to the Way. Use {@link #setNodes(List)} to update this way
|
---|
| 39 | * @return Nodes composing the way
|
---|
| 40 | * @since 1862
|
---|
| 41 | */
|
---|
| 42 | public List<Node> getNodes() {
|
---|
[2204] | 43 | return new CopyList<Node>(nodes);
|
---|
[1862] | 44 | }
|
---|
| 45 |
|
---|
| 46 | /**
|
---|
[2204] | 47 | * Set new list of nodes to way. This method is preferred to multiple calls to addNode/removeNode
|
---|
| 48 | * and similar methods because nodes are internally saved as array which means lower memory overhead
|
---|
| 49 | * but also slower modifying operations.
|
---|
[1910] | 50 | * @param nodes New way nodes. Can be null, in that case all way nodes are removed
|
---|
[1862] | 51 | * @since 1862
|
---|
| 52 | */
|
---|
| 53 | public void setNodes(List<Node> nodes) {
|
---|
[3348] | 54 | boolean locked = writeLock();
|
---|
| 55 | try {
|
---|
| 56 | for (Node node:this.nodes) {
|
---|
| 57 | node.removeReferrer(this);
|
---|
[5552] | 58 | node.clearCachedStyle();
|
---|
[3348] | 59 | }
|
---|
[2407] | 60 |
|
---|
[3348] | 61 | if (nodes == null) {
|
---|
| 62 | this.nodes = new Node[0];
|
---|
| 63 | } else {
|
---|
| 64 | this.nodes = nodes.toArray(new Node[nodes.size()]);
|
---|
| 65 | }
|
---|
[4053] | 66 | for (Node node: this.nodes) {
|
---|
[3348] | 67 | node.addReferrer(this);
|
---|
[5552] | 68 | node.clearCachedStyle();
|
---|
[3348] | 69 | }
|
---|
| 70 |
|
---|
[3943] | 71 | clearCachedStyle();
|
---|
[3348] | 72 | fireNodesChanged();
|
---|
| 73 | } finally {
|
---|
| 74 | writeUnlock(locked);
|
---|
[1910] | 75 | }
|
---|
[1862] | 76 | }
|
---|
| 77 |
|
---|
| 78 | /**
|
---|
[4053] | 79 | * Prevent directly following identical nodes in ways.
|
---|
| 80 | */
|
---|
| 81 | private List<Node> removeDouble(List<Node> nodes) {
|
---|
| 82 | Node last = null;
|
---|
| 83 | int count = nodes.size();
|
---|
[4054] | 84 | for(int i = 0; i < count && count > 2;) {
|
---|
[4053] | 85 | Node n = nodes.get(i);
|
---|
| 86 | if(last == n) {
|
---|
| 87 | nodes.remove(i);
|
---|
| 88 | --count;
|
---|
[4054] | 89 | } else {
|
---|
| 90 | last = n;
|
---|
| 91 | ++i;
|
---|
[4053] | 92 | }
|
---|
| 93 | }
|
---|
| 94 | return nodes;
|
---|
| 95 | }
|
---|
| 96 |
|
---|
| 97 | /**
|
---|
[5847] | 98 | * Replies the number of nodes in this way.
|
---|
[2204] | 99 | *
|
---|
[5847] | 100 | * @return the number of nodes in this way.
|
---|
[1862] | 101 | * @since 1862
|
---|
| 102 | */
|
---|
[4098] | 103 | @Override
|
---|
[1862] | 104 | public int getNodesCount() {
|
---|
[2204] | 105 | return nodes.length;
|
---|
[1862] | 106 | }
|
---|
[6069] | 107 |
|
---|
[5847] | 108 | /**
|
---|
| 109 | * Replies the real number of nodes in this way (full number of nodes minus one if this way is closed)
|
---|
| 110 | *
|
---|
| 111 | * @return the real number of nodes in this way.
|
---|
| 112 | * @since 5847
|
---|
[6069] | 113 | *
|
---|
[5847] | 114 | * @see #getNodesCount()
|
---|
| 115 | * @see #isClosed()
|
---|
| 116 | */
|
---|
| 117 | public int getRealNodesCount() {
|
---|
| 118 | int count = getNodesCount();
|
---|
| 119 | return isClosed() ? count-1 : count;
|
---|
| 120 | }
|
---|
[1862] | 121 |
|
---|
| 122 | /**
|
---|
[1934] | 123 | * Replies the node at position <code>index</code>.
|
---|
[2204] | 124 | *
|
---|
[1934] | 125 | * @param index the position
|
---|
| 126 | * @return the node at position <code>index</code>
|
---|
| 127 | * @exception IndexOutOfBoundsException thrown if <code>index</code> < 0
|
---|
[5266] | 128 | * or <code>index</code> >= {@link #getNodesCount()}
|
---|
[1862] | 129 | * @since 1862
|
---|
| 130 | */
|
---|
| 131 | public Node getNode(int index) {
|
---|
[2204] | 132 | return nodes[index];
|
---|
[1862] | 133 | }
|
---|
| 134 |
|
---|
[4098] | 135 | @Override
|
---|
| 136 | public long getNodeId(int idx) {
|
---|
| 137 | return nodes[idx].getUniqueId();
|
---|
| 138 | }
|
---|
| 139 |
|
---|
[1911] | 140 | /**
|
---|
| 141 | * Replies true if this way contains the node <code>node</code>, false
|
---|
| 142 | * otherwise. Replies false if <code>node</code> is null.
|
---|
[2204] | 143 | *
|
---|
[1911] | 144 | * @param node the node. May be null.
|
---|
| 145 | * @return true if this way contains the node <code>node</code>, false
|
---|
| 146 | * otherwise
|
---|
[5408] | 147 | * @since 1911
|
---|
[1911] | 148 | */
|
---|
| 149 | public boolean containsNode(Node node) {
|
---|
| 150 | if (node == null) return false;
|
---|
[3348] | 151 |
|
---|
| 152 | Node[] nodes = this.nodes;
|
---|
[6104] | 153 | for (Node n : nodes) {
|
---|
| 154 | if (n.equals(node))
|
---|
[2204] | 155 | return true;
|
---|
| 156 | }
|
---|
| 157 | return false;
|
---|
[1911] | 158 | }
|
---|
| 159 |
|
---|
[4671] | 160 | /**
|
---|
| 161 | * Return nodes adjacent to <code>node</code>
|
---|
| 162 | *
|
---|
| 163 | * @param node the node. May be null.
|
---|
| 164 | * @return Set of nodes adjacent to <code>node</code>
|
---|
[5408] | 165 | * @since 4671
|
---|
[4671] | 166 | */
|
---|
| 167 | public Set<Node> getNeighbours(Node node) {
|
---|
| 168 | HashSet<Node> neigh = new HashSet<Node>();
|
---|
| 169 |
|
---|
| 170 | if (node == null) return neigh;
|
---|
| 171 |
|
---|
| 172 | Node[] nodes = this.nodes;
|
---|
| 173 | for (int i=0; i<nodes.length; i++) {
|
---|
| 174 | if (nodes[i].equals(node)) {
|
---|
| 175 | if (i > 0)
|
---|
| 176 | neigh.add(nodes[i-1]);
|
---|
| 177 | if (i < nodes.length-1)
|
---|
| 178 | neigh.add(nodes[i+1]);
|
---|
| 179 | }
|
---|
| 180 | }
|
---|
| 181 | return neigh;
|
---|
| 182 | }
|
---|
| 183 |
|
---|
[5408] | 184 | /**
|
---|
[6069] | 185 | * Replies the ordered {@link List} of chunks of this way. Each chunk is replied as a {@link Pair} of {@link Node nodes}.
|
---|
| 186 | * @param sort If true, the nodes of each pair are sorted as defined by {@link Pair#sort}.
|
---|
| 187 | * If false, Pair.a and Pair.b are in the way order (i.e for a given Pair(n), Pair(n-1).b == Pair(n).a, Pair(n).b == Pair(n+1).a, etc.)
|
---|
[5408] | 188 | * @return The ordered list of chunks of this way.
|
---|
| 189 | * @since 3348
|
---|
| 190 | */
|
---|
[3348] | 191 | public List<Pair<Node,Node>> getNodePairs(boolean sort) {
|
---|
| 192 | List<Pair<Node,Node>> chunkSet = new ArrayList<Pair<Node,Node>>();
|
---|
[2578] | 193 | if (isIncomplete()) return chunkSet;
|
---|
[1169] | 194 | Node lastN = null;
|
---|
[3348] | 195 | Node[] nodes = this.nodes;
|
---|
| 196 | for (Node n : nodes) {
|
---|
[529] | 197 | if (lastN == null) {
|
---|
[1169] | 198 | lastN = n;
|
---|
| 199 | continue;
|
---|
| 200 | }
|
---|
| 201 | Pair<Node,Node> np = new Pair<Node,Node>(lastN, n);
|
---|
| 202 | if (sort) {
|
---|
| 203 | Pair.sort(np);
|
---|
| 204 | }
|
---|
| 205 | chunkSet.add(np);
|
---|
| 206 | lastN = n;
|
---|
| 207 | }
|
---|
| 208 | return chunkSet;
|
---|
| 209 | }
|
---|
[529] | 210 |
|
---|
[6009] | 211 | @Override public void accept(Visitor visitor) {
|
---|
[1169] | 212 | visitor.visit(this);
|
---|
| 213 | }
|
---|
[4431] | 214 |
|
---|
[6009] | 215 | @Override public void accept(PrimitiveVisitor visitor) {
|
---|
[4100] | 216 | visitor.visit(this);
|
---|
| 217 | }
|
---|
[66] | 218 |
|
---|
[2284] | 219 | protected Way(long id, boolean allowNegative) {
|
---|
| 220 | super(id, allowNegative);
|
---|
| 221 | }
|
---|
| 222 |
|
---|
[1169] | 223 | /**
|
---|
[5408] | 224 | * Contructs a new {@code Way} with id 0.
|
---|
| 225 | * @since 86
|
---|
[2070] | 226 | */
|
---|
[5408] | 227 | public Way() {
|
---|
[2284] | 228 | super(0, false);
|
---|
[2070] | 229 | }
|
---|
| 230 |
|
---|
| 231 | /**
|
---|
[5408] | 232 | * Contructs a new {@code Way} from an existing {@code Way}.
|
---|
| 233 | * @param original The original {@code Way} to be identically cloned. Must not be null
|
---|
[6140] | 234 | * @param clearMetadata If {@code true}, clears the OSM id and other metadata as defined by {@link #clearOsmMetadata}. If {@code false}, does nothing
|
---|
[5408] | 235 | * @since 2410
|
---|
[2410] | 236 | */
|
---|
[6140] | 237 | public Way(Way original, boolean clearMetadata) {
|
---|
[2410] | 238 | super(original.getUniqueId(), true);
|
---|
| 239 | cloneFrom(original);
|
---|
[6140] | 240 | if (clearMetadata) {
|
---|
| 241 | clearOsmMetadata();
|
---|
[2410] | 242 | }
|
---|
| 243 | }
|
---|
| 244 |
|
---|
| 245 | /**
|
---|
[5408] | 246 | * Contructs a new {@code Way} from an existing {@code Way} (including its id).
|
---|
| 247 | * @param original The original {@code Way} to be identically cloned. Must not be null
|
---|
| 248 | * @since 86
|
---|
[1169] | 249 | */
|
---|
[1934] | 250 | public Way(Way original) {
|
---|
[2410] | 251 | this(original, false);
|
---|
[1169] | 252 | }
|
---|
| 253 |
|
---|
| 254 | /**
|
---|
[5408] | 255 | * Contructs a new {@code Way} for the given id. If the id > 0, the way is marked
|
---|
[2620] | 256 | * as incomplete. If id == 0 then way is marked as new
|
---|
[2204] | 257 | *
|
---|
[2620] | 258 | * @param id the id. >= 0 required
|
---|
[5408] | 259 | * @throws IllegalArgumentException if id < 0
|
---|
| 260 | * @since 343
|
---|
[1169] | 261 | */
|
---|
[2070] | 262 | public Way(long id) throws IllegalArgumentException {
|
---|
[2284] | 263 | super(id, false);
|
---|
[1169] | 264 | }
|
---|
| 265 |
|
---|
[2284] | 266 | /**
|
---|
[5408] | 267 | * Contructs a new {@code Way} with given id and version.
|
---|
| 268 | * @param id the id. >= 0 required
|
---|
| 269 | * @param version the version
|
---|
| 270 | * @throws IllegalArgumentException if id < 0
|
---|
| 271 | * @since 2620
|
---|
[2620] | 272 | */
|
---|
[5408] | 273 | public Way(long id, int version) throws IllegalArgumentException {
|
---|
[2932] | 274 | super(id, version, false);
|
---|
[2620] | 275 | }
|
---|
| 276 |
|
---|
[2363] | 277 | @Override
|
---|
[2405] | 278 | public void load(PrimitiveData data) {
|
---|
[3348] | 279 | boolean locked = writeLock();
|
---|
| 280 | try {
|
---|
| 281 | super.load(data);
|
---|
[2284] | 282 |
|
---|
[3348] | 283 | WayData wayData = (WayData) data;
|
---|
[2284] | 284 |
|
---|
[3348] | 285 | List<Node> newNodes = new ArrayList<Node>(wayData.getNodes().size());
|
---|
| 286 | for (Long nodeId : wayData.getNodes()) {
|
---|
| 287 | Node node = (Node)getDataSet().getPrimitiveById(nodeId, OsmPrimitiveType.NODE);
|
---|
| 288 | if (node != null) {
|
---|
| 289 | newNodes.add(node);
|
---|
| 290 | } else
|
---|
| 291 | throw new AssertionError("Data consistency problem - way with missing node detected");
|
---|
| 292 | }
|
---|
| 293 | setNodes(newNodes);
|
---|
| 294 | } finally {
|
---|
| 295 | writeUnlock(locked);
|
---|
[2284] | 296 | }
|
---|
| 297 | }
|
---|
| 298 |
|
---|
[5408] | 299 | @Override
|
---|
| 300 | public WayData save() {
|
---|
[2284] | 301 | WayData data = new WayData();
|
---|
| 302 | saveCommonAttributes(data);
|
---|
[2741] | 303 | for (Node node:nodes) {
|
---|
[2284] | 304 | data.getNodes().add(node.getUniqueId());
|
---|
| 305 | }
|
---|
| 306 | return data;
|
---|
| 307 | }
|
---|
| 308 |
|
---|
[5408] | 309 | @Override
|
---|
| 310 | public void cloneFrom(OsmPrimitive osm) {
|
---|
[3348] | 311 | boolean locked = writeLock();
|
---|
| 312 | try {
|
---|
| 313 | super.cloneFrom(osm);
|
---|
| 314 | Way otherWay = (Way)osm;
|
---|
| 315 | setNodes(otherWay.getNodes());
|
---|
| 316 | } finally {
|
---|
| 317 | writeUnlock(locked);
|
---|
| 318 | }
|
---|
[1169] | 319 | }
|
---|
[86] | 320 |
|
---|
[5408] | 321 | @Override
|
---|
| 322 | public String toString() {
|
---|
[2578] | 323 | String nodesDesc = isIncomplete()?"(incomplete)":"nodes=" + Arrays.toString(nodes);
|
---|
[2363] | 324 | return "{Way id=" + getUniqueId() + " version=" + getVersion()+ " " + getFlagsAsString() + " " + nodesDesc + "}";
|
---|
[86] | 325 | }
|
---|
| 326 |
|
---|
[1690] | 327 | @Override
|
---|
| 328 | public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
|
---|
[6105] | 329 | if (!(other instanceof Way))
|
---|
[1690] | 330 | return false;
|
---|
| 331 | if (! super.hasEqualSemanticAttributes(other))
|
---|
| 332 | return false;
|
---|
| 333 | Way w = (Way)other;
|
---|
[2417] | 334 | if (getNodesCount() != w.getNodesCount()) return false;
|
---|
| 335 | for (int i=0;i<getNodesCount();i++) {
|
---|
| 336 | if (! getNode(i).hasEqualSemanticAttributes(w.getNode(i)))
|
---|
| 337 | return false;
|
---|
| 338 | }
|
---|
| 339 | return true;
|
---|
[1690] | 340 | }
|
---|
| 341 |
|
---|
[4098] | 342 | @Override
|
---|
[1169] | 343 | public int compareTo(OsmPrimitive o) {
|
---|
[1598] | 344 | if (o instanceof Relation)
|
---|
[1169] | 345 | return 1;
|
---|
[2555] | 346 | return o instanceof Way ? Long.valueOf(getUniqueId()).compareTo(o.getUniqueId()) : -1;
|
---|
[1169] | 347 | }
|
---|
[357] | 348 |
|
---|
[5408] | 349 | /**
|
---|
| 350 | * Removes the given {@link Node} from this way. Ignored, if n is null.
|
---|
| 351 | * @param n The node to remove. Ignored, if null
|
---|
| 352 | * @since 1463
|
---|
| 353 | */
|
---|
[1598] | 354 | public void removeNode(Node n) {
|
---|
[5408] | 355 | if (n == null || isIncomplete()) return;
|
---|
[3348] | 356 | boolean locked = writeLock();
|
---|
| 357 | try {
|
---|
| 358 | boolean closed = (lastNode() == n && firstNode() == n);
|
---|
| 359 | int i;
|
---|
| 360 | List<Node> copy = getNodes();
|
---|
| 361 | while ((i = copy.indexOf(n)) >= 0) {
|
---|
| 362 | copy.remove(i);
|
---|
| 363 | }
|
---|
| 364 | i = copy.size();
|
---|
| 365 | if (closed && i > 2) {
|
---|
| 366 | copy.add(copy.get(0));
|
---|
| 367 | } else if (i >= 2 && i <= 3 && copy.get(0) == copy.get(i-1)) {
|
---|
| 368 | copy.remove(i-1);
|
---|
| 369 | }
|
---|
[4053] | 370 | setNodes(removeDouble(copy));
|
---|
[5552] | 371 | n.clearCachedStyle();
|
---|
[3348] | 372 | } finally {
|
---|
| 373 | writeUnlock(locked);
|
---|
[1690] | 374 | }
|
---|
[1463] | 375 | }
|
---|
| 376 |
|
---|
[5408] | 377 | /**
|
---|
| 378 | * Removes the given set of {@link Node nodes} from this way. Ignored, if selection is null.
|
---|
| 379 | * @param selection The selection of nodes to remove. Ignored, if null
|
---|
| 380 | * @since 5408
|
---|
| 381 | */
|
---|
| 382 | public void removeNodes(Set<? extends Node> selection) {
|
---|
| 383 | if (selection == null || isIncomplete()) return;
|
---|
[3348] | 384 | boolean locked = writeLock();
|
---|
| 385 | try {
|
---|
[3630] | 386 | boolean closed = (lastNode() == firstNode() && selection.contains(lastNode()));
|
---|
| 387 | List<Node> copy = new ArrayList<Node>();
|
---|
| 388 |
|
---|
| 389 | for (Node n: nodes) {
|
---|
| 390 | if (!selection.contains(n)) {
|
---|
| 391 | copy.add(n);
|
---|
[3348] | 392 | }
|
---|
[1690] | 393 | }
|
---|
[3630] | 394 |
|
---|
| 395 | int i = copy.size();
|
---|
| 396 | if (closed && i > 2) {
|
---|
| 397 | copy.add(copy.get(0));
|
---|
| 398 | } else if (i >= 2 && i <= 3 && copy.get(0) == copy.get(i-1)) {
|
---|
| 399 | copy.remove(i-1);
|
---|
| 400 | }
|
---|
[4053] | 401 | setNodes(removeDouble(copy));
|
---|
[5552] | 402 | for (Node n : selection) {
|
---|
| 403 | n.clearCachedStyle();
|
---|
| 404 | }
|
---|
[3348] | 405 | } finally {
|
---|
| 406 | writeUnlock(locked);
|
---|
[1690] | 407 | }
|
---|
[1463] | 408 | }
|
---|
| 409 |
|
---|
[2077] | 410 | /**
|
---|
| 411 | * Adds a node to the end of the list of nodes. Ignored, if n is null.
|
---|
[2204] | 412 | *
|
---|
[5408] | 413 | * @param n the node. Ignored, if null
|
---|
[2077] | 414 | * @throws IllegalStateException thrown, if this way is marked as incomplete. We can't add a node
|
---|
| 415 | * to an incomplete way
|
---|
[5408] | 416 | * @since 1313
|
---|
[2077] | 417 | */
|
---|
| 418 | public void addNode(Node n) throws IllegalStateException {
|
---|
| 419 | if (n==null) return;
|
---|
[3348] | 420 |
|
---|
| 421 | boolean locked = writeLock();
|
---|
| 422 | try {
|
---|
| 423 | if (isIncomplete())
|
---|
| 424 | throw new IllegalStateException(tr("Cannot add node {0} to incomplete way {1}.", n.getId(), getId()));
|
---|
[3943] | 425 | clearCachedStyle();
|
---|
[3348] | 426 | n.addReferrer(this);
|
---|
| 427 | Node[] newNodes = new Node[nodes.length + 1];
|
---|
| 428 | System.arraycopy(nodes, 0, newNodes, 0, nodes.length);
|
---|
| 429 | newNodes[nodes.length] = n;
|
---|
| 430 | nodes = newNodes;
|
---|
[5552] | 431 | n.clearCachedStyle();
|
---|
[3348] | 432 | fireNodesChanged();
|
---|
| 433 | } finally {
|
---|
| 434 | writeUnlock(locked);
|
---|
| 435 | }
|
---|
[1313] | 436 | }
|
---|
| 437 |
|
---|
[2077] | 438 | /**
|
---|
| 439 | * Adds a node at position offs.
|
---|
[2204] | 440 | *
|
---|
[5408] | 441 | * @param offs the offset
|
---|
[2077] | 442 | * @param n the node. Ignored, if null.
|
---|
| 443 | * @throws IllegalStateException thrown, if this way is marked as incomplete. We can't add a node
|
---|
| 444 | * to an incomplete way
|
---|
| 445 | * @throws IndexOutOfBoundsException thrown if offs is out of bounds
|
---|
[5408] | 446 | * @since 1313
|
---|
[2077] | 447 | */
|
---|
| 448 | public void addNode(int offs, Node n) throws IllegalStateException, IndexOutOfBoundsException {
|
---|
| 449 | if (n==null) return;
|
---|
[3348] | 450 |
|
---|
| 451 | boolean locked = writeLock();
|
---|
| 452 | try {
|
---|
| 453 | if (isIncomplete())
|
---|
| 454 | throw new IllegalStateException(tr("Cannot add node {0} to incomplete way {1}.", n.getId(), getId()));
|
---|
| 455 |
|
---|
[3943] | 456 | clearCachedStyle();
|
---|
[3348] | 457 | n.addReferrer(this);
|
---|
| 458 | Node[] newNodes = new Node[nodes.length + 1];
|
---|
| 459 | System.arraycopy(nodes, 0, newNodes, 0, offs);
|
---|
| 460 | System.arraycopy(nodes, offs, newNodes, offs + 1, nodes.length - offs);
|
---|
| 461 | newNodes[offs] = n;
|
---|
| 462 | nodes = newNodes;
|
---|
[5552] | 463 | n.clearCachedStyle();
|
---|
[3348] | 464 | fireNodesChanged();
|
---|
| 465 | } finally {
|
---|
| 466 | writeUnlock(locked);
|
---|
| 467 | }
|
---|
[1313] | 468 | }
|
---|
| 469 |
|
---|
[2407] | 470 | @Override
|
---|
| 471 | public void setDeleted(boolean deleted) {
|
---|
[3348] | 472 | boolean locked = writeLock();
|
---|
| 473 | try {
|
---|
| 474 | for (Node n:nodes) {
|
---|
| 475 | if (deleted) {
|
---|
| 476 | n.removeReferrer(this);
|
---|
| 477 | } else {
|
---|
| 478 | n.addReferrer(this);
|
---|
| 479 | }
|
---|
[5552] | 480 | n.clearCachedStyle();
|
---|
[2407] | 481 | }
|
---|
[3348] | 482 | fireNodesChanged();
|
---|
| 483 | super.setDeleted(deleted);
|
---|
| 484 | } finally {
|
---|
| 485 | writeUnlock(locked);
|
---|
[2407] | 486 | }
|
---|
| 487 | }
|
---|
| 488 |
|
---|
[4100] | 489 | @Override
|
---|
[1598] | 490 | public boolean isClosed() {
|
---|
[2578] | 491 | if (isIncomplete()) return false;
|
---|
[3348] | 492 |
|
---|
| 493 | Node[] nodes = this.nodes;
|
---|
| 494 | return nodes.length >= 3 && nodes[nodes.length-1] == nodes[0];
|
---|
[1190] | 495 | }
|
---|
[6069] | 496 |
|
---|
[5490] | 497 | /**
|
---|
| 498 | * Determines if this way denotes an area (closed way with at least three distinct nodes).
|
---|
| 499 | * @return {@code true} if this way is closed and contains at least three distinct nodes
|
---|
| 500 | * @see #isClosed
|
---|
| 501 | * @since 5490
|
---|
| 502 | */
|
---|
| 503 | public boolean isArea() {
|
---|
| 504 | if (this.nodes.length >= 4 && isClosed()) {
|
---|
| 505 | Node distinctNode = null;
|
---|
| 506 | for (int i=1; i<nodes.length-1; i++) {
|
---|
| 507 | if (distinctNode == null && nodes[i] != nodes[0]) {
|
---|
| 508 | distinctNode = nodes[i];
|
---|
| 509 | } else if (distinctNode != null && nodes[i] != nodes[0] && nodes[i] != distinctNode) {
|
---|
| 510 | return true;
|
---|
| 511 | }
|
---|
| 512 | }
|
---|
| 513 | }
|
---|
| 514 | return false;
|
---|
| 515 | }
|
---|
[1423] | 516 |
|
---|
[4682] | 517 | /**
|
---|
| 518 | * Returns the last node of this way.
|
---|
| 519 | * The result equals <tt>{@link #getNode getNode}({@link #getNodesCount getNodesCount} - 1)</tt>.
|
---|
| 520 | * @return the last node of this way
|
---|
[5408] | 521 | * @since 1400
|
---|
[4682] | 522 | */
|
---|
[1400] | 523 | public Node lastNode() {
|
---|
[3348] | 524 | Node[] nodes = this.nodes;
|
---|
[2578] | 525 | if (isIncomplete() || nodes.length == 0) return null;
|
---|
[2204] | 526 | return nodes[nodes.length-1];
|
---|
[1400] | 527 | }
|
---|
[1423] | 528 |
|
---|
[4682] | 529 | /**
|
---|
| 530 | * Returns the first node of this way.
|
---|
| 531 | * The result equals {@link #getNode getNode}{@code (0)}.
|
---|
| 532 | * @return the first node of this way
|
---|
[5408] | 533 | * @since 1400
|
---|
[4682] | 534 | */
|
---|
[1400] | 535 | public Node firstNode() {
|
---|
[3348] | 536 | Node[] nodes = this.nodes;
|
---|
[2578] | 537 | if (isIncomplete() || nodes.length == 0) return null;
|
---|
[2204] | 538 | return nodes[0];
|
---|
[1400] | 539 | }
|
---|
[1423] | 540 |
|
---|
[5408] | 541 | /**
|
---|
| 542 | * Replies true if the given node is the first or the last one of this way, false otherwise.
|
---|
| 543 | * @param n The node to test
|
---|
| 544 | * @return true if the {@code n} is the first or the last node, false otherwise.
|
---|
| 545 | * @since 1400
|
---|
| 546 | */
|
---|
[1400] | 547 | public boolean isFirstLastNode(Node n) {
|
---|
[3348] | 548 | Node[] nodes = this.nodes;
|
---|
[2578] | 549 | if (isIncomplete() || nodes.length == 0) return false;
|
---|
[3348] | 550 | return n == nodes[0] || n == nodes[nodes.length -1];
|
---|
[1400] | 551 | }
|
---|
[1990] | 552 |
|
---|
[5408] | 553 | /**
|
---|
| 554 | * Replies true if the given node is an inner node of this way, false otherwise.
|
---|
| 555 | * @param n The node to test
|
---|
| 556 | * @return true if the {@code n} is an inner node, false otherwise.
|
---|
| 557 | * @since 3515
|
---|
| 558 | */
|
---|
[3515] | 559 | public boolean isInnerNode(Node n) {
|
---|
| 560 | Node[] nodes = this.nodes;
|
---|
| 561 | if (isIncomplete() || nodes.length <= 2) return false;
|
---|
| 562 | /* circular ways have only inner nodes, so return true for them! */
|
---|
| 563 | if (n == nodes[0] && n == nodes[nodes.length-1]) return true;
|
---|
[5408] | 564 | for (int i = 1; i < nodes.length - 1; ++i) {
|
---|
| 565 | if (nodes[i] == n) return true;
|
---|
[3515] | 566 | }
|
---|
| 567 | return false;
|
---|
| 568 | }
|
---|
| 569 |
|
---|
[5408] | 570 | @Override
|
---|
[1990] | 571 | public String getDisplayName(NameFormatter formatter) {
|
---|
| 572 | return formatter.format(this);
|
---|
| 573 | }
|
---|
[2399] | 574 |
|
---|
[4098] | 575 | @Override
|
---|
[2399] | 576 | public OsmPrimitiveType getType() {
|
---|
| 577 | return OsmPrimitiveType.WAY;
|
---|
| 578 | }
|
---|
[2419] | 579 |
|
---|
[3844] | 580 | @Override
|
---|
| 581 | public OsmPrimitiveType getDisplayType() {
|
---|
| 582 | return isClosed() ? OsmPrimitiveType.CLOSEDWAY : OsmPrimitiveType.WAY;
|
---|
| 583 | }
|
---|
| 584 |
|
---|
[2970] | 585 | private void checkNodes() {
|
---|
[2963] | 586 | DataSet dataSet = getDataSet();
|
---|
| 587 | if (dataSet != null) {
|
---|
[3348] | 588 | Node[] nodes = this.nodes;
|
---|
[2963] | 589 | for (Node n: nodes) {
|
---|
| 590 | if (n.getDataSet() != dataSet)
|
---|
[5059] | 591 | throw new DataIntegrityProblemException("Nodes in way must be in the same dataset",
|
---|
| 592 | tr("Nodes in way must be in the same dataset"));
|
---|
[3254] | 593 | if (n.isDeleted())
|
---|
[5059] | 594 | throw new DataIntegrityProblemException("Deleted node referenced: " + toString(),
|
---|
| 595 | "<html>" + tr("Deleted node referenced by {0}", DefaultNameFormatter.getInstance().formatAsHtmlUnorderedList(this)) + "</html>");
|
---|
[2963] | 596 | }
|
---|
[3254] | 597 | if (Main.pref.getBoolean("debug.checkNullCoor", true)) {
|
---|
[3032] | 598 | for (Node n: nodes) {
|
---|
[5408] | 599 | if (n.isVisible() && !n.isIncomplete() && (n.getCoor() == null || n.getEastNorth() == null))
|
---|
[5694] | 600 | throw new DataIntegrityProblemException("Complete visible node with null coordinates: " + toString(),
|
---|
[5059] | 601 | "<html>" + tr("Complete node {0} with null coordinates in way {1}",
|
---|
| 602 | DefaultNameFormatter.getInstance().formatAsHtmlUnorderedList(n),
|
---|
| 603 | DefaultNameFormatter.getInstance().formatAsHtmlUnorderedList(this)) + "</html>");
|
---|
[3032] | 604 | }
|
---|
| 605 | }
|
---|
[2970] | 606 | }
|
---|
| 607 | }
|
---|
| 608 |
|
---|
| 609 | private void fireNodesChanged() {
|
---|
| 610 | checkNodes();
|
---|
| 611 | if (getDataSet() != null) {
|
---|
[2437] | 612 | getDataSet().fireWayNodesChanged(this);
|
---|
[2419] | 613 | }
|
---|
| 614 | }
|
---|
[2427] | 615 |
|
---|
| 616 | @Override
|
---|
[2970] | 617 | public void setDataset(DataSet dataSet) {
|
---|
| 618 | super.setDataset(dataSet);
|
---|
| 619 | checkNodes();
|
---|
| 620 | }
|
---|
| 621 |
|
---|
| 622 | @Override
|
---|
[2427] | 623 | public BBox getBBox() {
|
---|
[2437] | 624 | if (getDataSet() == null)
|
---|
| 625 | return new BBox(this);
|
---|
| 626 | if (bbox == null) {
|
---|
| 627 | bbox = new BBox(this);
|
---|
| 628 | }
|
---|
[3153] | 629 | return new BBox(bbox);
|
---|
[2427] | 630 | }
|
---|
[2437] | 631 |
|
---|
| 632 | @Override
|
---|
| 633 | public void updatePosition() {
|
---|
| 634 | bbox = new BBox(this);
|
---|
| 635 | }
|
---|
[2587] | 636 |
|
---|
[5408] | 637 | /**
|
---|
| 638 | * Replies true if this way has incomplete nodes, false otherwise.
|
---|
| 639 | * @return true if this way has incomplete nodes, false otherwise.
|
---|
| 640 | * @since 2587
|
---|
| 641 | */
|
---|
[2609] | 642 | public boolean hasIncompleteNodes() {
|
---|
[3348] | 643 | Node[] nodes = this.nodes;
|
---|
[5408] | 644 | for (Node node : nodes) {
|
---|
[2609] | 645 | if (node.isIncomplete())
|
---|
| 646 | return true;
|
---|
[2591] | 647 | }
|
---|
[2609] | 648 | return false;
|
---|
[2587] | 649 | }
|
---|
| 650 |
|
---|
| 651 | @Override
|
---|
| 652 | public boolean isUsable() {
|
---|
| 653 | return super.isUsable() && !hasIncompleteNodes();
|
---|
| 654 | }
|
---|
[2678] | 655 |
|
---|
| 656 | @Override
|
---|
| 657 | public boolean isDrawable() {
|
---|
| 658 | return super.isDrawable() && !hasIncompleteNodes();
|
---|
| 659 | }
|
---|
[4138] | 660 |
|
---|
[5408] | 661 | /**
|
---|
| 662 | * Replies the length of the way, in metres, as computed by {@link LatLon#greatCircleDistance}.
|
---|
| 663 | * @return The length of the way, in metres
|
---|
| 664 | * @since 4138
|
---|
| 665 | */
|
---|
[4138] | 666 | public double getLength() {
|
---|
| 667 | double length = 0;
|
---|
| 668 | Node lastN = null;
|
---|
| 669 | for (Node n:nodes) {
|
---|
[4321] | 670 | if (lastN != null) {
|
---|
[5187] | 671 | LatLon lastNcoor = lastN.getCoor();
|
---|
[5490] | 672 | LatLon coor = n.getCoor();
|
---|
[5187] | 673 | if (lastNcoor != null && coor != null) {
|
---|
| 674 | length += coor.greatCircleDistance(lastNcoor);
|
---|
[4321] | 675 | }
|
---|
| 676 | }
|
---|
[4138] | 677 | lastN = n;
|
---|
| 678 | }
|
---|
| 679 | return length;
|
---|
| 680 | }
|
---|
[5199] | 681 |
|
---|
| 682 | /**
|
---|
| 683 | * Tests if this way is a oneway.
|
---|
[6069] | 684 | * @return {@code 1} if the way is a oneway,
|
---|
[5408] | 685 | * {@code -1} if the way is a reversed oneway,
|
---|
| 686 | * {@code 0} otherwise.
|
---|
| 687 | * @since 5199
|
---|
[5199] | 688 | */
|
---|
| 689 | public int isOneway() {
|
---|
| 690 | String oneway = get("oneway");
|
---|
| 691 | if (oneway != null) {
|
---|
| 692 | if ("-1".equals(oneway)) {
|
---|
| 693 | return -1;
|
---|
| 694 | } else {
|
---|
| 695 | Boolean isOneway = OsmUtils.getOsmBoolean(oneway);
|
---|
| 696 | if (isOneway != null && isOneway) {
|
---|
| 697 | return 1;
|
---|
| 698 | }
|
---|
| 699 | }
|
---|
| 700 | }
|
---|
| 701 | return 0;
|
---|
| 702 | }
|
---|
| 703 |
|
---|
[5408] | 704 | /**
|
---|
| 705 | * Replies the first node of this way, respecting or not its oneway state.
|
---|
[5694] | 706 | * @param respectOneway If true and if this way is a reversed oneway, replies the last node. Otherwise, replies the first node.
|
---|
[5408] | 707 | * @return the first node of this way, according to {@code respectOneway} and its oneway state.
|
---|
| 708 | * @since 5199
|
---|
| 709 | */
|
---|
[5199] | 710 | public Node firstNode(boolean respectOneway) {
|
---|
| 711 | return !respectOneway || isOneway() != -1 ? firstNode() : lastNode();
|
---|
| 712 | }
|
---|
| 713 |
|
---|
[5408] | 714 | /**
|
---|
| 715 | * Replies the last node of this way, respecting or not its oneway state.
|
---|
[5694] | 716 | * @param respectOneway If true and if this way is a reversed oneway, replies the first node. Otherwise, replies the last node.
|
---|
[5408] | 717 | * @return the last node of this way, according to {@code respectOneway} and its oneway state.
|
---|
| 718 | * @since 5199
|
---|
| 719 | */
|
---|
[5199] | 720 | public Node lastNode(boolean respectOneway) {
|
---|
| 721 | return !respectOneway || isOneway() != -1 ? lastNode() : firstNode();
|
---|
| 722 | }
|
---|
[1] | 723 | }
|
---|