View Javadoc
1   package nl.tudelft.simulation.dsol.animation.gis.osm;
2   
3   import java.awt.geom.Path2D;
4   import java.util.Collection;
5   import java.util.HashMap;
6   import java.util.Iterator;
7   import java.util.List;
8   import java.util.Map;
9   
10  import org.openstreetmap.osmosis.core.container.v0_6.EntityContainer;
11  import org.openstreetmap.osmosis.core.domain.v0_6.Entity;
12  import org.openstreetmap.osmosis.core.domain.v0_6.Node;
13  import org.openstreetmap.osmosis.core.domain.v0_6.Relation;
14  import org.openstreetmap.osmosis.core.domain.v0_6.Tag;
15  import org.openstreetmap.osmosis.core.domain.v0_6.Way;
16  import org.openstreetmap.osmosis.core.domain.v0_6.WayNode;
17  import org.openstreetmap.osmosis.core.task.v0_6.Sink;
18  
19  import nl.tudelft.simulation.dsol.animation.gis.FeatureInterface;
20  import nl.tudelft.simulation.dsol.animation.gis.GisObject;
21  import nl.tudelft.simulation.dsol.animation.gis.SerializablePath;
22  import nl.tudelft.simulation.dsol.animation.gis.transform.CoordinateTransform;
23  
24  /**
25   * OsmLayerSink.java.
26   * <p>
27   * Copyright (c) 2021-2023 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
28   * for project information <a href="https://simulation.tudelft.nl/dsol/manual/" target="_blank">DSOL Manual</a>. The DSOL
29   * project is distributed under a three-clause BSD-style license, which can be found at
30   * <a href="https://https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">DSOL License</a>.
31   * </p>
32   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
33   */
34  public class OsmLayerSink implements Sink
35  {
36      /** the ways in the OSM file. */
37      private Map<Long, MiniWay> ways = new HashMap<Long, MiniWay>();
38  
39      /** the nodes in the OSM file. */
40      private Map<Long, MiniNode> nodes = new HashMap<Long, MiniNode>();
41  
42      /** the key - value pairs to read. There can be multiple values per key, or '*' for all. */
43      private final List<FeatureInterface> featuresToRead;
44  
45      /** an optional transformation of the lat/lon (or other) coordinates. */
46      private final CoordinateTransform coordinateTransform;
47  
48      /**
49       * Construct a sink to read the features form the OSM file. For OSM this the Feature list is typically the complete set of
50       * features that needs to be read. It is rare that multiple OSM files are available for the data, but this could be the case
51       * (e.g., state or country OSM files).<br>
52       * TODO: add an optional initial extent in case the sounrce's extent is much larger than the extent we want to display
53       * @param featuresToRead the features that the sink needs to read.
54       * @param coordinateTransform CoordinateTransform; the transformation of (x, y) coordinates to (x', y') coordinates.
55       */
56      public OsmLayerSink(final List<FeatureInterface> featuresToRead, final CoordinateTransform coordinateTransform)
57      {
58          this.featuresToRead = featuresToRead;
59          this.coordinateTransform = coordinateTransform;
60      }
61  
62      /** {@inheritDoc} */
63      @Override
64      public void process(final EntityContainer entityContainer)
65      {
66          Entity entity = entityContainer.getEntity();
67  
68          if (entity instanceof Node)
69          {
70              Node node = (Node) entity;
71              MiniNode miniNode = new MiniNode(node.getId(), (float) node.getLatitude(), (float) node.getLongitude());
72              this.nodes.put(miniNode.id, miniNode);
73              /*-
74              Iterator<Tag> tagIterator = entity.getTags().iterator();
75              while (tagIterator.hasNext())
76              {
77                  Tag tag = tagIterator.next();
78                  String key = tag.getKey();
79                  String value = tag.getValue();
80                  // TODO: look whether we want to display special nodes.
81              }
82              */
83          }
84  
85          else if (entity instanceof Way)
86          {
87              boolean read = false;
88              Iterator<Tag> tagIterator = entity.getTags().iterator();
89              FeatureInterface featureToUse = null;
90              while (tagIterator.hasNext())
91              {
92                  Tag tag = tagIterator.next();
93                  String key = tag.getKey();
94                  String value = tag.getValue();
95                  for (FeatureInterface feature : this.featuresToRead)
96                  {
97                      if (feature.getKey().equals("*"))
98                      {
99                          featureToUse = feature;
100                         read = true;
101                         break;
102                     }
103                     if (feature.getKey().equals(key))
104                     {
105                         if (feature.getValue().equals("*") || feature.getValue().equals(value))
106                         {
107                             featureToUse = feature;
108                             read = true;
109                             break;
110                         }
111                     }
112                 }
113                 if (read)
114                 {
115                     break;
116                 }
117             }
118             if (read)
119             {
120                 Way way = (Way) entity;
121                 MiniWay miniWay = new MiniWay(way.getId(), featureToUse, way.getWayNodes());
122                 this.ways.put(miniWay.id, miniWay);
123             }
124         }
125 
126         else if (entity instanceof Relation)
127         {
128             Iterator<Tag> tagint = entity.getTags().iterator();
129             while (tagint.hasNext())
130             {
131                 Tag route = tagint.next();
132             }
133         }
134 
135     }
136 
137     /** {@inheritDoc} */
138     @Override
139     public void initialize(final Map<String, Object> metaData)
140     {
141         // nothing to do right now.
142     }
143 
144     /** {@inheritDoc} */
145     @Override
146     public void complete()
147     {
148         for (MiniWay way : this.ways.values())
149         {
150             addWay(way);
151         }
152     }
153 
154     /**
155      * Add a way to a feature.
156      * @param way Way; the way to add to the feature shape list
157      */
158     private void addWay(final MiniWay way)
159     {
160         SerializablePath path = new SerializablePath(Path2D.WIND_NON_ZERO, way.wayNodesLat.length);
161         boolean start = false;
162         for (int i = 0; i < way.wayNodesLat.length; i++)
163         {
164             float[] coordinate;
165             if (way.wayNodesId[i] != 0)
166             {
167                 MiniNode node = this.nodes.get(way.wayNodesId[i]);
168                 coordinate = this.coordinateTransform.floatTransform(node.lon, node.lat);
169             }
170             else
171             {
172                 coordinate = this.coordinateTransform.floatTransform(way.wayNodesLon[i], way.wayNodesLat[i]);
173             }
174             if (!start)
175             {
176                 path.moveTo(coordinate[0], coordinate[1]);
177                 start = true;
178             }
179             path.lineTo(coordinate[0], coordinate[1]);
180         }
181         String[] att = new String[0];
182         way.feature.getShapes().add(new GisObject(path, att));
183     }
184 
185     /** {@inheritDoc} */
186     @Override
187     public void close()
188     {
189         // nothing to do right now.
190     }
191 
192     /**
193      * Store only the id, lat and lon of a Node to reduce the memory footprint of the Node storage during parsing. <br>
194      * <br>
195      * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
196      */
197     protected static class MiniNode
198     {
199         /** the node id. */
200         protected long id;
201 
202         /** the latitude of the node. */
203         protected float lat;
204 
205         /** the longitude of the node. */
206         protected float lon;
207 
208         /**
209          * Create a MniniNode.
210          * @param id long; the node id
211          * @param lat double; the latitude of the node
212          * @param lon double; the longitude of the node
213          */
214         public MiniNode(final long id, final float lat, final float lon)
215         {
216             this.id = id;
217             this.lat = lat;
218             this.lon = lon;
219         }
220     }
221 
222     /**
223      * Store the minimum set of features of a Way to reduce the memory footprint of the Way storage during parsing. <br>
224      * <br>
225      * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
226      */
227     protected static class MiniWay
228     {
229         /** the way id. */
230         protected long id;
231 
232         /** the feature that characterizes this way. */
233         protected FeatureInterface feature;
234 
235         /** the latitude of the way nodes that define e.g. a contour. */
236         protected float[] wayNodesLat;
237 
238         /** the longitude of the way nodes that define e.g. a contour. */
239         protected float[] wayNodesLon;
240 
241         /** the possible nodeIds. */
242         protected long[] wayNodesId;
243 
244         /**
245          * Create a MniniWay.
246          * @param id long; the way id
247          * @param feature FeatureInterface; the feature that characterizes this way
248          * @param wayNodes Collection&lt;WayNode&gt;; the way nodes
249          */
250         public MiniWay(final long id, final FeatureInterface feature, final Collection<WayNode> wayNodes)
251         {
252             this.id = id;
253             this.feature = feature;
254             this.wayNodesLat = new float[wayNodes.size()];
255             this.wayNodesLon = new float[wayNodes.size()];
256             this.wayNodesId = new long[wayNodes.size()];
257             int i = 0;
258             for (WayNode wayNode : wayNodes)
259             {
260                 this.wayNodesLat[i] = (float) wayNode.getLatitude();
261                 this.wayNodesLon[i] = (float) wayNode.getLongitude();
262                 this.wayNodesId[i] = wayNode.getNodeId();
263                 i++;
264             }
265         }
266 
267     }
268 }