1 package org.opentrafficsim.editor.extensions.map;
2
3 import java.util.Locale;
4 import java.util.Optional;
5
6 import javax.swing.SwingUtilities;
7
8 import org.djunits.value.vdouble.scalar.Length;
9 import org.djutils.draw.bounds.Bounds2d;
10 import org.djutils.draw.line.PolyLine2d;
11 import org.djutils.draw.line.Polygon2d;
12 import org.djutils.draw.point.DirectedPoint2d;
13 import org.djutils.event.Event;
14 import org.djutils.event.EventListener;
15 import org.djutils.event.reference.ReferenceType;
16 import org.opentrafficsim.animation.road.AbstractLineAnimation.LaneBasedObjectData;
17 import org.opentrafficsim.base.geometry.OtsShape;
18 import org.opentrafficsim.editor.OtsEditor;
19 import org.opentrafficsim.editor.XsdTreeNode;
20 import org.opentrafficsim.editor.extensions.Adapters;
21 import org.opentrafficsim.road.network.factory.xml.utils.ParseUtil;
22 import org.opentrafficsim.xml.bindings.types.LengthBeginEndType.LengthBeginEnd;
23
24
25
26
27
28
29
30
31
32
33
34
35 public abstract class MapLaneBasedObjectData extends MapData implements LaneBasedObjectData, EventListener
36 {
37
38
39 private String id = "";
40
41
42 private String lane;
43
44
45 private LengthBeginEnd position;
46
47
48 private Length positionFromStart;
49
50
51 private Length laneWidth;
52
53
54 private DirectedPoint2d location;
55
56
57 private Bounds2d bounds;
58
59
60 private Polygon2d absoluteContour;
61
62
63 private Polygon2d relativeContour;
64
65
66 private PolyLine2d line;
67
68
69 private XsdTreeNode lastLinkNode;
70
71
72
73
74
75
76
77 public MapLaneBasedObjectData(final EditorMap map, final XsdTreeNode node, final OtsEditor editor)
78 {
79 super(map, node, editor);
80 getNode().addListener(this, XsdTreeNode.ATTRIBUTE_CHANGED, ReferenceType.WEAK);
81 if (getNode().isActive())
82 {
83 if (getNode().isIdentifiable())
84 {
85 notify(new Event(XsdTreeNode.ATTRIBUTE_CHANGED, new Object[] {getNode(), "Id", null}));
86 }
87 if (getNode().hasAttribute("Link"))
88 {
89 notify(new Event(XsdTreeNode.ATTRIBUTE_CHANGED, new Object[] {getNode(), "Link", null}));
90 }
91 notify(new Event(XsdTreeNode.ATTRIBUTE_CHANGED, new Object[] {getNode(), "Lane", null}));
92 notify(new Event(XsdTreeNode.ATTRIBUTE_CHANGED, new Object[] {getNode(), "Position", null}));
93 }
94 }
95
96
97
98
99
100
101 protected void setLinkNode(final XsdTreeNode linkNode)
102 {
103 if (this.lastLinkNode != null)
104 {
105 Optional<MapData> data = getMap().getData(linkNode);
106 if (data.isPresent())
107 {
108 ((MapLinkData) data.get()).removeListener(this, MapLinkData.LAYOUT_REBUILT);
109 }
110 }
111 this.lastLinkNode = linkNode;
112 Optional<MapData> data = getMap().getData(linkNode);
113 if (data.isPresent())
114 {
115 ((MapLinkData) data.get()).addListener(this, MapLinkData.LAYOUT_REBUILT, ReferenceType.WEAK);
116 }
117 }
118
119 @Override
120 public void destroy()
121 {
122 super.destroy();
123 getNode().removeListener(this, XsdTreeNode.ATTRIBUTE_CHANGED);
124 if (this.lastLinkNode != null)
125 {
126 this.lastLinkNode.removeListener(this, MapLinkData.LAYOUT_REBUILT);
127 }
128 }
129
130 @Override
131 public Length getLaneWidth()
132 {
133 return this.laneWidth;
134 }
135
136 @Override
137 public DirectedPoint2d getLocation()
138 {
139 return this.location;
140 }
141
142 @Override
143 public Bounds2d getRelativeBounds()
144 {
145 return this.bounds;
146 }
147
148 @Override
149 public Polygon2d getAbsoluteContour()
150 {
151 return this.absoluteContour;
152 }
153
154 @Override
155 public Polygon2d getRelativeContour()
156 {
157 return this.relativeContour;
158 }
159
160 @Override
161 public PolyLine2d getLine()
162 {
163 return this.line;
164 }
165
166 @Override
167 public String getId()
168 {
169 return this.id;
170 }
171
172
173
174
175
176 protected String getLinkLanePositionId()
177 {
178 StringBuilder str = new StringBuilder();
179 String sep = "";
180 if (this.lastLinkNode != null)
181 {
182 str.append(this.lastLinkNode.getId());
183 sep = ".";
184 }
185 if (this.lane != null)
186 {
187 str.append(sep).append(this.lane);
188 }
189 if (this.positionFromStart != null)
190 {
191 str.append(String.format(Locale.US, "@%.3fm", this.positionFromStart.si));
192 }
193 return str.toString();
194 }
195
196 @Override
197 public void evalChanged()
198 {
199 if (getNode().isIdentifiable())
200 {
201 this.id = getNode().getId() == null ? "" : getNode().getId();
202 }
203 if (getNode().hasAttribute("Link"))
204 {
205 XsdTreeNode linkNode = getNode().getCoupledNodeAttribute("Link").orElse(null);
206 setLinkNode(linkNode);
207 }
208 setValue((v) -> this.lane = v, Adapters.get(String.class), getNode(), "Lane");
209 setValue((v) -> this.position = v, Adapters.get(LengthBeginEnd.class), getNode(), "Position");
210 setLocation();
211 }
212
213 @Override
214 public void notify(final Event event)
215 {
216 if (event.getType().equals(XsdTreeNode.ATTRIBUTE_CHANGED))
217 {
218 String attribute = (String) ((Object[]) event.getContent())[1];
219 String value = getNode().getAttributeValueOrDefault(attribute);
220 if ("Id".equals(attribute))
221 {
222 this.id = value == null ? "" : value;
223 }
224 else if ("Link".equals(attribute))
225 {
226 XsdTreeNode linkNode = getNode().getCoupledNodeAttribute("Link").orElse(null);
227 setLinkNode(linkNode);
228 setLocation();
229 }
230 else if ("Lane".equals(attribute))
231 {
232 setValue((v) -> this.lane = v, Adapters.get(String.class), getNode(), "Lane");
233 setLocation();
234 }
235 else if ("Position".equals(attribute))
236 {
237 setValue((v) -> this.position = v, Adapters.get(LengthBeginEnd.class), getNode(), "Position");
238 setLocation();
239 }
240 }
241 else
242 {
243
244 setLocation();
245 }
246 SwingUtilities.invokeLater(() -> getMap().update());
247 }
248
249
250
251
252
253 public Optional<PolyLine2d> getLaneCenterLine()
254 {
255 if (this.lane == null || this.lastLinkNode == null)
256 {
257 return Optional.empty();
258 }
259 Optional<MapData> linkDataOptional = getMap().getData(this.lastLinkNode);
260 if (linkDataOptional.isEmpty())
261 {
262 return Optional.empty();
263 }
264 MapLinkData linkData = (MapLinkData) linkDataOptional.get();
265 MapLaneData laneData = linkData.getLaneData(this.lane);
266 if (laneData == null)
267 {
268 return Optional.empty();
269 }
270 return Optional.of(laneData.getCenterLine());
271 }
272
273
274
275
276 private void setLocation()
277 {
278 if (this.lane == null || this.position == null || this.lastLinkNode == null)
279 {
280 setInvalid();
281 return;
282 }
283 Optional<MapData> linkDataOptional = getMap().getData(this.lastLinkNode);
284 if (linkDataOptional.isEmpty())
285 {
286 setInvalid();
287 return;
288 }
289 MapLinkData linkData = (MapLinkData) linkDataOptional.get();
290 MapLaneData laneData = linkData.getLaneData(this.lane);
291 if (laneData == null)
292 {
293 setInvalid();
294 return;
295 }
296 this.positionFromStart =
297 ParseUtil.parseLengthBeginEnd(this.position, Length.ofSI(laneData.getCenterLine().getLength()));
298
299 this.laneWidth = laneData.getWidth(this.positionFromStart);
300 double w45 = 0.45 * getLaneWidth().si;
301 DirectedPoint2d point = laneData.getCenterLine().getLocationExtended(this.positionFromStart.si);
302 this.location = new DirectedPoint2d(point.x, point.y, point.dirZ);
303 this.line = new PolyLine2d(new double[] {0.0, 0.0}, new double[] {-w45, w45});
304 this.relativeContour = new Polygon2d(PolyLine2d.concatenate(this.line, this.line.reverse()).getPointList());
305 this.bounds = LaneBasedObjectData.super.getRelativeBounds();
306 this.absoluteContour =
307 new Polygon2d(0.0, OtsShape.toAbsoluteTransform(this.location).transform(this.relativeContour.iterator()));
308 setValid();
309 }
310
311 }