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