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