1 package org.opentrafficsim.road.network.factory.osm;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.List;
6 import java.util.Objects;
7
8 import org.opentrafficsim.base.Identifiable;
9 import org.opentrafficsim.road.network.factory.osm.events.WarningEvent;
10 import org.opentrafficsim.road.network.factory.osm.events.WarningListener;
11
12 /**
13 * OpenStreetMap Link.
14 * <p>
15 * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
16 * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
17 * <p>
18 * $LastChangedDate: 2015-07-26 01:01:13 +0200 (Sun, 26 Jul 2015) $, @version $Revision: 1155 $, by $Author: averbraeck $,
19 * initial version 31 dec. 2014 <br>
20 * @author <a>Moritz Bergmann</a>
21 */
22 public class OSMLink implements Serializable, Identifiable
23 {
24 /** */
25 private static final long serialVersionUID = 20141231L;
26
27 /** The Link ID. It is generated out of the Start ID and the End ID and (if present) the name tag. */
28 private final String id;
29
30 /** The start Node of the OSMLink. */
31 private final OSMNode start;
32
33 /** The end Node of the OSMLink. */
34 private final OSMNode end;
35
36 /** The List of nodes that are used only to define shape (for mapping purposes). */
37 private List<OSMNode> intermediateNodes;
38
39 /** The length of the OSMLink. */
40 private final double length;
41
42 /** The tags that this OSMLink inherits from it's way. */
43 private List<OSMTag> tags;
44
45 /** The number of lanes on this OSMLink. */
46 private byte lanes;
47
48 /** The number of lanes going forward (the <i>design</i> direction). */
49 private byte forwardLanes;
50
51 /** Is this OSMLink one way? */
52 private boolean oneway;
53
54 /**
55 * Construct a new OSMLink.
56 * @param fromNode OSMNode; the OSMNode where this OSMLinks begins
57 * @param toNode OSMNode; the OSMNode where this OSMLink ends
58 * @param tags List<OSMTag>; the OSMTags (inherited from the OSMWay that causes this OSMLink to be constructed)
59 * @param length double; the length of the new OSMLink
60 * @param warningListener WarningListener; the warning listener that will receive warning events
61 */
62 public OSMLink(final OSMNode fromNode, final OSMNode toNode, final List<OSMTag> tags, final double length,
63 final WarningListener warningListener)
64 {
65 if (fromNode == toNode)
66 {
67 throw new Error("Start and end of link are the same Node: " + fromNode);
68 }
69 String name = "";
70 for (OSMTag tag : tags)
71 {
72 if (tag.getKey().equals("name"))
73 {
74 name = ": " + tag.getValue();
75 }
76 }
77 this.id = Objects.toString(fromNode.getId()) + Objects.toString(toNode.getId()) + name;
78 this.start = fromNode;
79 this.end = toNode;
80 this.length = length;
81 this.lanes = 1;
82 this.forwardLanes = 1;
83 boolean forwardDefined = false;
84
85 List<OSMTag> linkTags = new ArrayList<OSMTag>(tags);
86 for (OSMTag tag : tags)
87 {
88 if (tag.getKey().equals("oneway") && tag.getValue().equals("yes"))
89 {
90 this.setOneway(true);
91 linkTags.remove(tag);
92 this.forwardLanes = this.lanes;
93 }
94 if (tag.getKey().equals("highway") && tag.getValue().equals("motorway_link"))
95 {
96 this.setOneway(true);
97 this.lanes = 1;
98 this.forwardLanes = this.lanes;
99 }
100 if (tag.getKey().equals("highway") && tag.getValue().equals("motorway"))
101 {
102 this.setOneway(true);
103 this.forwardLanes = this.lanes;
104 }
105 if (tag.getKey().equals("highway") && (tag.getValue().equals("cycleway") || tag.getValue().equals("footway")
106 || tag.getValue().equals("pedestrian") || tag.getValue().equals("steps")))
107 {
108 this.lanes = 1;
109 }
110 }
111
112 for (OSMTag tag2 : new ArrayList<OSMTag>(linkTags))
113 {
114 if (tag2.getKey().equals("lanes"))
115 {
116 if (isByte(tag2.getValue()))
117 {
118 this.lanes = Byte.parseByte(tag2.getValue());
119 linkTags.remove(tag2);
120 if (this.oneway)
121 {
122 this.forwardLanes = this.lanes;
123 forwardDefined = true;
124 }
125 }
126 else
127 {
128 String warning = "Illegal value for the tag 'lanes' at link " + this.id;
129 warningListener.warning(new WarningEvent(this, warning));
130 }
131 }
132 if (tag2.getKey().equals("lanes:forward"))
133 {
134 if (isByte(tag2.getValue()))
135 {
136 this.forwardLanes = Byte.parseByte(tag2.getValue());
137 linkTags.remove(tag2);
138 forwardDefined = true;
139 }
140 else
141 {
142 String warning = "Illegal value for the tag 'lanes:forward' at link " + this.id;
143 warningListener.warning(new WarningEvent(this, warning));
144 }
145 }
146 }
147 this.tags = linkTags;
148 if (!forwardDefined && this.lanes > 1)
149 {
150 this.forwardLanes = (byte) (this.lanes / 2);
151 String warning = "No forward lanes defined at link " + this.id;
152 warningListener.warning(new WarningEvent(this, warning));
153 }
154 this.intermediateNodes = new ArrayList<OSMNode>();
155 }
156
157 /**
158 * Construct a new OSMLink with specified number of lanes and forward lanes.
159 * @param startNode OSMNode; the start OSMNode of the new OSMLink
160 * @param endNode OSMNode; the end OSMNode of the new OSMLink
161 * @param tags List<OSMTag>; List of Tags inherited from way
162 * @param length double; length of link
163 * @param lanes byte; the total number of lanes
164 * @param flanes byte; the number of forward lanes
165 */
166 public OSMLink(final OSMNode startNode, final OSMNode endNode, final List<OSMTag> tags, final double length,
167 final byte lanes, final byte flanes)
168 {
169 if (startNode == endNode)
170 {
171 throw new Error("start and end of link are the same Node: " + startNode);
172 }
173 this.id = Objects.toString(startNode.getId()) + Objects.toString(endNode.getId());
174 this.start = startNode;
175 this.end = endNode;
176 this.tags = tags;
177 this.length = length;
178 this.lanes = lanes;
179 this.forwardLanes = flanes;
180 this.intermediateNodes = new ArrayList<OSMNode>();
181 }
182
183 /** {@inheritDoc} */
184 @Override
185 public final String getId()
186 {
187 return this.id;
188 }
189
190 /**
191 * @return start.
192 */
193 public final OSMNode getStart()
194 {
195 return this.start;
196 }
197
198 /**
199 * @return end.
200 */
201 public final OSMNode getEnd()
202 {
203 return this.end;
204 }
205
206 /**
207 * Retrieve the tags of this OSMLink.
208 * @return List<OSMTab>; the returned object is a copy; modifications of the returned object do not affect this
209 * OSMLink
210 */
211 public final List<OSMTag> getTags()
212 {
213 return new ArrayList<OSMTag>(this.tags); // Create and return a copy (the tags themselves are immutable).
214 }
215
216 /**
217 * Indicate if this OSMLink is one way.
218 * @return boolean; true if this OSMLink is one way; false if this OSMLink is not one way
219 */
220 public final boolean isOneway()
221 {
222 return this.oneway;
223 }
224
225 /**
226 * Set the one way status of this OSMLink.
227 * @param isOneWay boolean; the new value for the one way status of this OSMLink
228 */
229 public final void setOneway(final boolean isOneWay)
230 {
231 this.oneway = isOneWay;
232 }
233
234 /**
235 * Retrieve the total number of lanes on this OSMLink.
236 * @return byte; the total number of lanes on this OSMLink
237 */
238 public final byte getLanes()
239 {
240 return this.lanes;
241 }
242
243 /**
244 * Retrieve the total number of forward lanes on this OSMLink; forward lanes are lanes that may only be traveled from
245 * startNode towards endNode.
246 * @return byte; the number of forward lanes on this OSMLink
247 */
248 public final byte getForwardLanes()
249 {
250 return this.forwardLanes;
251 }
252
253 /**
254 * Add an OSMTag to this Link.
255 * @param tag OSMTag; the OSMTag that must be added
256 */
257 public final void addTag(final OSMTag tag)
258 {
259 this.tags.add(tag);
260 }
261
262 /**
263 * Retrieve the length of this OSMLink.
264 * @return double; the length of this OSMLink in meters
265 */
266 public final double getLength()
267 {
268 return this.length;
269 }
270
271 /**
272 * Retrieve the list of OSMNodes that define the shape of this OSMLink.
273 * @return List<OSMNode>; the list of OSMNodes that define the shape of this OSMLink
274 */
275 public final List<OSMNode> getSplineList()
276 {
277 return this.intermediateNodes;
278 }
279
280 /**
281 * Append a Node to the list of OSMNodes of this OSMLink that define the shape of this OSMLink.
282 * @param shapeNode OSMNode; the OSMNode to add to the list of OSMNodes that define the shape of this OSMLink
283 */
284 public final void addSpline(final OSMNode shapeNode)
285 {
286 this.intermediateNodes.add(shapeNode);
287 }
288
289 /**
290 * Returns true if the link has an OSMTag with the specified key.
291 * @param key String; the key of the sought OSMTag
292 * @return boolean; true if this OSMLink has (one or more) OSMTag(s) with the specified key
293 */
294 public final boolean hasTag(final String key)
295 {
296 for (OSMTag t : this.tags)
297 {
298 if (t.getKey().equals(key))
299 {
300 return true;
301 }
302 }
303 return false;
304 }
305
306 /**
307 * Determine if a string represents a number that can stored in a byte.
308 * @param s String; the string
309 * @return is the given String a byte.
310 */
311 private boolean isByte(final String s)
312 {
313 try
314 {
315 Byte.parseByte(s);
316 }
317 catch (NumberFormatException e)
318 {
319 return false;
320 }
321 return true;
322 }
323
324 /** {@inheritDoc} */
325 @Override
326 public final String toString()
327 {
328 return String.format("Link %s from %d to %d", getId(), getStart().getId(), getEnd().getId());
329 }
330
331 /**
332 * Report if this OSMLink has all tags in a supplied set.
333 * @param tagsToCheck List<OSMTag>; the supplied set of tags
334 * @return boolean; true if this Link has all the supplied tags; false otherwise
335 */
336 public final boolean containsAllTags(final List<OSMTag> tagsToCheck)
337 {
338 return this.tags.containsAll(tagsToCheck);
339 }
340
341 }