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