View Javadoc
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   * Data class for objects that are drawn at a lane position. Implementations must call setLinkNode() in their constructor or by
27   * some other dynamic means, or the XSD node must have a Link attribute that points to the XSD node of a link by a keyref. This
28   * class will listen to attributes Id, Link, Lane and Position, and update visualization as needed. Attributes Id and Link are
29   * optional.
30   * <p>
31   * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
32   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
33   * </p>
34   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
35   */
36  public abstract class MapLaneBasedObjectData extends MapData implements LaneBasedObjectData, EventListener
37  {
38  
39      /** */
40      private static final long serialVersionUID = 20240310L;
41  
42      /** Id. */
43      private String id = "";
44  
45      /** Lane. */
46      private String lane;
47  
48      /** Position as entered, e.g. END-20m. */
49      private LengthBeginEnd position;
50  
51      /** Position from start. */
52      private Length positionFromStart;
53  
54      /** Lane width. */
55      private Length laneWidth;
56  
57      /** Location. */
58      private OrientedPoint2d location;
59  
60      /** Bounds. */
61      private Bounds2d bounds;
62  
63      /** Contour. */
64      private Polygon2d contour;
65  
66      /** Shape (cached). */
67      private OtsShape shape;
68  
69      /** Line on lane. */
70      private PolyLine2d line;
71  
72      /** Node of link. */
73      private XsdTreeNode lastLinkNode;
74  
75      /**
76       * Constructor.
77       * @param map map.
78       * @param node node.
79       * @param editor editor.
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      * Sets a node as link. Sub-classes may call this in their constructor if it is a fixed node. This class will listen to
109      * changes in the Link attribute, and set a coupled node as link node if it exists.
110      * @param linkNode link node.
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      * Returns an id in the form {linkId}.{laneId}.{id} or {linkId}.{laneId}@{position} if the id is empty.
192      * @return id in link/lane/position form.
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         // else: MapLinkData.LAYOUT_REBUILT
258         setLocation();
259     }
260 
261     /**
262      * Set the location from the coordinate and direction. Notify when invalid or valid.
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             // animation (see EditorMap.setValid) stores static data that depends on the lane width
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 }