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.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   * Data class for objects that are drawn at a lane position. Implementations must call setLinkNode() in their constructor or
25   * by 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.
26   * This class will listen to attributes Id, Link, Lane and Position, and update visualization as needed. Attributes Id and Link
27   * are optional.
28   * <p>
29   * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
30   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
31   * </p>
32   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
33   */
34  public abstract class MapLaneBasedObjectData extends MapData implements LaneBasedObjectData, EventListener
35  {
36  
37      /** */
38      private static final long serialVersionUID = 20240310L;
39  
40      /** Id. */
41      private String id = "";
42  
43      /** Lane. */
44      private String lane;
45  
46      /** Position as entered, e.g. END-20m. */
47      private LengthBeginEnd position;
48  
49      /** Position from start. */
50      private Length positionFromStart;
51  
52      /** Lane width. */
53      private Length laneWidth;
54  
55      /** Location. */
56      private OrientedPoint2d location;
57  
58      /** Bounds. */
59      private OtsBounds2d bounds = new BoundingBox(1.0, 0.25);
60  
61      /** Node of link. */
62      private XsdTreeNode lastLinkNode;
63  
64      /**
65       * Constructor.
66       * @param map Map; map.
67       * @param node XsdTreeNode; node.
68       * @param editor OtsEditor; editor.
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       * Sets a node as link. Sub-classes may call this in their constructor if it is a fixed node. This class will listen to
98       * changes in the Link attribute, and set a coupled node as link node if it exists.
99       * @param linkNode XsdTreeNode; link node.
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     /** {@inheritDoc} */
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     /** {@inheritDoc} */
139     @Override
140     public Length getLaneWidth()
141     {
142         return this.laneWidth;
143     }
144 
145     /** {@inheritDoc} */
146     @Override
147     public OrientedPoint2d getLocation()
148     {
149         return this.location;
150     }
151 
152     /** {@inheritDoc} */
153     @Override
154     public OtsBounds2d getBounds()
155     {
156         return this.bounds;
157     }
158 
159     /** {@inheritDoc} */
160     @Override
161     public String getId()
162     {
163         return this.id;
164     }
165 
166     /**
167      * Returns an id in the form {linkId}.{laneId}.{id} or {linkId}.{laneId}@{position} if the id is empty.
168      * @return String; id in link/lane/position form.
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     /** {@inheritDoc} */
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     /** {@inheritDoc} */
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         // else: MapLinkData.LAYOUT_REBUILT
236         setLocation();
237     }
238 
239     /**
240      * Set the location from the coordinate and direction. Notify when invalid or valid.
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             // animation (see EditorMap.setValid) stores static data that depends on the lane width
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      * Calculates the bounds. Can be overridden for objects with non-line shapes.
280      * @return OtsBounds2d; bounds of the object.
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 }