View Javadoc
1   package org.opentrafficsim.road.network.factory.osm;
2   
3   import java.io.IOException;
4   import java.io.Serializable;
5   import java.util.ArrayList;
6   import java.util.HashMap;
7   import java.util.List;
8   import java.util.Map;
9   
10  import org.opentrafficsim.road.network.factory.osm.events.ProgressEvent;
11  import org.opentrafficsim.road.network.factory.osm.events.ProgressListener;
12  import org.opentrafficsim.road.network.factory.osm.events.WarningEvent;
13  import org.opentrafficsim.road.network.factory.osm.events.WarningListener;
14  
15  /**
16   * Container for all imported entities of an OpenStreetMap file.
17   * <p>
18   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
19   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
20   * <p>
21   * $LastChangedDate: 2015-07-26 01:01:13 +0200 (Sun, 26 Jul 2015) $, @version $Revision: 1155 $, by $Author: averbraeck $,
22   * initial version 31 dec. 2014 <br>
23   * @author <a>Moritz Bergmann</a>
24   */
25  public class OSMNetwork implements Serializable
26  {
27      /** */
28      private static final long serialVersionUID = 20141231L;
29  
30      /** The name of the Network (immutable). */
31      private final String name;
32  
33      /** The Nodes of the Network. */
34      private Map<Long, OSMNode> nodes = new HashMap<Long, OSMNode>();
35  
36      /** The Ways of the Network. */
37      private Map<Long, OSMWay> ways = new HashMap<Long, OSMWay>();
38  
39      /** The Relations of the Network. */
40      private Map<Long, OSMRelation> relations = new HashMap<Long, OSMRelation>();
41  
42      /** The Links of the Network. */
43      private List<OSMLink> links = new ArrayList<OSMLink>();
44  
45      /**
46       * Construct a new OSMNetwork.
47       * @param name String; the name of the new Network
48       */
49      public OSMNetwork(final String name)
50      {
51          this.name = name;
52      }
53  
54      /**
55       * Retrieve a list of Nodes that form a Way from this Network.
56       * @param wayId Long; the id of the Way
57       * @return List&lt;Long&gt;; the list of OSMNode ids of the OSMWay with the specified id
58       * @throws IOException when no Way with the specified id exists in this Network
59       */
60      public final List<Long> getNodesFromWay(final Long wayId) throws IOException
61      {
62          OSMWay osmWay = this.ways.get(wayId);
63          if (osmWay == null)
64          {
65              throw new IOException("Way with the ID: " + wayId + "was not found");
66          }
67          return osmWay.getNodes();
68      }
69  
70      /**
71       * Retrieve a Node from this Network.
72       * @param nodeId long; the id of the Node
73       * @return node OSMNode; the node with the specified id
74       * @throws IOException when no OSMNode with the specified id exist in this Network
75       */
76      public final OSMNode getNode(final long nodeId) throws IOException
77      {
78          OSMNode result = this.nodes.get(nodeId);
79          if (result == null)
80          {
81              throw new IOException("Node with the id: " + nodeId + "was not found");
82          }
83          return result;
84      }
85  
86      /**
87       * Retrieve the map of OSMNode ids to OSMNodes of this OSMNetwork.
88       * @return Map&lt;Long, OSMNode&gt;; the map of all Nodes in this Network (modifications of the returned object are
89       *         reflected in this OSMNetwork)
90       */
91      public final Map<Long, OSMNode> getNodes()
92      {
93          return this.nodes;
94      }
95  
96      /**
97       * Retrieve a Relation from this Network.
98       * @param relid long; the id of the Relation
99       * @return OSMRelation; modifications of the result are reflected in this OSMNetwork
100      * @throws IOException when no Relation with the specified id exists in this Network
101      */
102     public final OSMRelation getRelation(final long relid) throws IOException
103     {
104         OSMRelation result = this.relations.get(relid);
105         if (result == null)
106         {
107             throw new IOException("Relation with the ID: " + relid + "was not found");
108         }
109         return result;
110     }
111 
112     /**
113      * Retrieve the map of OSMRelations of this OSMNetwork.
114      * @return Map&lt;Long, OSMRelation&gt;; the map of all OSMRelations in the network (modifications of the returned object
115      *         are reflected in this OSMNetwork)
116      */
117     public final Map<Long, OSMRelation> getRelations()
118     {
119         return this.relations;
120     }
121 
122     /**
123      * Retrieve a Way from this Network.
124      * @param wayid long; the id of a Way
125      * @return Way
126      * @throws IOException when no Way with the specified id exist in this network
127      */
128     public final OSMWay getWay(final long wayid) throws IOException
129     {
130         OSMWay w = this.ways.get(wayid);
131         if (w == null)
132         {
133             throw new IOException("Way with the ID: " + wayid + "was not found");
134         }
135         else
136         {
137             return w;
138         }
139     }
140 
141     /**
142      * Retrieve the name of this OSMNetwork.
143      * @return name String; the name of this OSMNetwork
144      */
145     public final String getName()
146     {
147         return this.name;
148     }
149 
150     /**
151      * Set/replace the Nodes of this Network.<br>
152      * The provided list is <b>not copied</b>; the caller should not modify the list after setting it.
153      * @param newnodes HashMap&lt;Long, OSMNode&gt;; the (new) Nodes for this Network
154      */
155     public final void setNodes(final HashMap<Long, OSMNode> newnodes)
156     {
157         this.nodes = newnodes;
158     }
159 
160     /**
161      * Add one OSMNode to this OSMNetwork.
162      * @param node OSMNode; the node to add to this OSMNetwork
163      */
164     public final void addNode(final OSMNode node)
165     {
166         this.nodes.put(node.getId(), node);
167     }
168 
169     /**
170      * Add one OSMWay to this OSMNetwork.
171      * @param way OSMWay; the OSMWay to add
172      */
173     public final void addWay(final OSMWay way)
174     {
175         this.ways.put(way.getId(), way);
176     }
177 
178     /**
179      * Add one OSMRelation to this Network.
180      * @param osmRelation OSMRelation; the OSMRelation to add
181      */
182     public final void addRelation(final OSMRelation osmRelation)
183     {
184         this.relations.put(osmRelation.getId(), osmRelation);
185     }
186 
187     /**
188      * Retrieve the map of OSMWays of this OSMNetwork.
189      * @return ways Map&lt;Long, OSMWay&gt;; the map of OSMWays of this OSMNetwork; modifications of the result are reflected in
190      *         this OSMNetwork
191      */
192     public final Map<Long, OSMWay> getWays()
193     {
194         return this.ways;
195     }
196 
197     /**
198      * Retrieve the list of OSMLinks of this OSMNetwork.
199      * @return links List&lt;OSMLink&gt;; the list of OSMLinks of this OSMNetwork; modifications of the result are reflected in
200      *         this OSMNetwork
201      */
202     public final List<OSMLink> getLinks()
203     {
204         return this.links;
205     }
206 
207     /**
208      * Creates links out of the ways in this network.
209      * @param warningListener WarningListener; the warning listener that will receive warning events
210      * @param progressListener ProgressListener; the progress listener that will receive progress events
211      * @throws IOException on read errors
212      */
213     public final void makeLinks(final WarningListener warningListener, final ProgressListener progressListener)
214             throws IOException
215     {
216         progressListener.progress(new ProgressEvent(this, "Starting link creation:"));
217 
218         List<OSMLink> newLinks = new ArrayList<OSMLink>();
219         for (Long wayId : this.ways.keySet())
220         {
221             OSMWay osmWay = this.getWay(wayId);
222             List<Long> waynodes = osmWay.getNodes();
223             for (int i = 0; i < (waynodes.size() - 1); i++)
224             {
225                 OSMNode fromNode = this.getNode(waynodes.get(i).longValue());
226                 OSMNode toNode = this.getNode(waynodes.get(i + 1).longValue());
227                 // Workaround for bug in OSM near Mettmann Germany
228                 if (fromNode == toNode)
229                 {
230                     continue;
231                 }
232                 // Something similar occurs in Duesseldorf (PK), but here the node ids are different; work-around below
233                 if (fromNode.getLatitude() == toNode.getLatitude() && fromNode.getLongitude() == toNode.getLongitude())
234                 {
235                     warningListener.warning(new WarningEvent(this, String.format("Node clash %s vs %s", fromNode, toNode)));
236                     // FIXME: should probably assign all link end points of toNode to fromNode
237                     continue;
238                 }
239                 newLinks.add(
240                         new OSMLink(fromNode, toNode, osmWay.getTags(), distanceLongLat(fromNode, toNode), warningListener));
241             }
242         }
243         this.links = newLinks;
244         progressListener.progress(new ProgressEvent(this, "Link creation finished. Created " + this.links.size() + " links."));
245     }
246 
247     /**
248      * Compute the distance between two OSMNodes.
249      * @param fromNode OSMNode; the first location
250      * @param toNode OSMNode; the second location
251      * @return distance in meters from point 1 to point 2. This method utilizes great circle calculation
252      */
253     private double distanceLongLat(final OSMNode fromNode, final OSMNode toNode)
254     {
255         double y1 = fromNode.getLatitude();
256         double y2 = toNode.getLatitude();
257         if (y1 < 0)
258         {
259             y1 += 360;
260         }
261         if (y2 < 0)
262         {
263             y2 += 360;
264         }
265         double x1 = 90 - fromNode.getLongitude();
266         double x2 = 90 - toNode.getLongitude();
267         x1 = Math.toRadians(x1);
268         x2 = Math.toRadians(x2);
269         y1 = Math.toRadians(y1);
270         y2 = Math.toRadians(y2);
271         final double earthRadius = 6371 * 1000;
272         return Math.acos(Math.sin(x1) * Math.sin(x2) * Math.cos(y1 - y2) + Math.cos(x1) * Math.cos(x2)) * earthRadius;
273     }
274 
275     /**
276      * FIXME Network looks 'crooked' after using this. This function checks for and removes redundancies between the networks
277      * links.
278      */
279     public final void removeRedundancy()
280     {
281         boolean again = false;
282         do
283         {
284             again = this.redundancyCheck();
285         }
286         while (again);
287     }
288 
289     /**
290      * This function checks the networks links for redundancy.
291      * @return boolean; true if the Network was modified (further reduction may be possible by calling this method again)
292      */
293     private boolean redundancyCheck()
294     {
295         List<OSMLink> checkedLinks = new ArrayList<OSMLink>();
296         List<OSMLink> removedLinks = new ArrayList<OSMLink>();
297         boolean redundancyRemoved = false;
298         // FIXME PK thinks that this method could remove a junction from a through-road
299         for (OSMLink link1 : this.links)
300         {
301             if (removedLinks.contains(link1))
302             {
303                 continue;
304             }
305             String link1Name = "1";
306             for (OSMTag t1 : link1.getTags())
307             {
308                 if (t1.getKey().equals("name"))
309                 {
310                     link1Name = t1.getValue();
311                 }
312             }
313             for (OSMLink link2 : this.links)
314             {
315                 if (removedLinks.contains(link2))
316                 {
317                     continue;
318                 }
319                 String link2Name = "2";
320                 for (OSMTag t2 : link2.getTags())
321                 {
322                     if (t2.getKey().equals("name"))
323                     {
324                         link2Name = t2.getValue();
325                     }
326                 }
327                 if (link1.getEnd().equals(link2.getStart()) && link1Name.equalsIgnoreCase(link2Name)
328                         && link1.getEnd().hasNoTags() && link1.containsAllTags(link2.getTags())
329                         && link1.getLanes() == link2.getLanes() && link1.getForwardLanes() == link2.getForwardLanes()
330                         && link1.getStart() != link2.getEnd())
331                 {
332                     if (removedLinks.contains(link1))
333                     {
334                         for (int i = 0; i < this.links.size(); i++)
335                         {
336                             OSMLink l = this.links.get(i);
337                             if (l == link1)
338                             {
339                                 System.out.println("found link " + l + " at position " + i);
340                             }
341                         }
342                         throw new Error("about to add " + link1 + " to removeLinks which already contains that link");
343                     }
344                     redundancyRemoved = true;
345                     OSMLink replacementLink = new OSMLink(link1.getStart(), link2.getEnd(), link1.getTags(),
346                             link1.getLength() + link2.getLength(), link1.getLanes(), link1.getForwardLanes());
347                     if (!link1.getSplineList().isEmpty())
348                     {
349                         for (OSMNode n1 : link1.getSplineList())
350                         {
351                             replacementLink.addSpline(n1);
352                         }
353                     }
354                     if (!link2.getSplineList().isEmpty())
355                     {
356                         for (OSMNode n2 : link2.getSplineList())
357                         {
358                             replacementLink.addSpline(n2);
359                         }
360                     }
361                     checkedLinks.add(replacementLink);
362                     removedLinks.add(link1);
363                     removedLinks.add(link2);
364                     break; // don't merge any other links with l1; do that on the next iteration.
365                 }
366             }
367         }
368         this.links.removeAll(removedLinks);
369         this.links.addAll(checkedLinks);
370         return redundancyRemoved;
371     }
372 
373     /**
374      * Finds the link that follows a given OSMLink.
375      * @param link OSMLink; OSMLink for which a successor OSMLink is sought
376      * @return OSMLink (one of) the successor OSMLink(s) of the given OSMLink, or null if the given OSMLink has no successors
377      */
378     public final OSMLink findFollowingLink(final OSMLink link)
379     {
380         final OSMNode startNode = link.getEnd();
381         for (OSMLink l2 : this.links)
382         {
383             if (startNode.equals(l2.getStart()))
384             {
385                 return l2;
386             }
387         }
388         return null;
389     }
390 
391     /**
392      * Finds an OSMLink that precedes the given OSMLink.
393      * @param link OSMLink; the OSMLink for which a predecessor OSMLink is sought
394      * @return OSMLink (one of) the predecessor OSMLink(s) of the given OSMLink, or null if the given OSMLink has no
395      *         predecessors
396      */
397     public final OSMLink findPrecedingLink(final OSMLink link)
398     {
399         final OSMNode endNode = link.getStart();
400         for (OSMLink l2 : this.links)
401         {
402             if (endNode.equals(l2.getEnd()))
403             {
404                 return l2;
405             }
406         }
407         return null;
408     }
409 
410     /**
411      * Returns true if the given link has a preceding link.
412      * @param link OSMLink; the link for which the caller wants to know whether there is a preceding link
413      * @return boolean; true if the given link has one or more predecessors; false otherwise
414      */
415     public final boolean hasPrecedingLink(final OSMLink link)
416     {
417         return null != findPrecedingLink(link);
418     }
419 
420     /**
421      * Returns true if the given OSMLink has a following OSMLink.
422      * @param link OSMLink; the OSMLink for which the caller wants to know if it has a follower OSMLink
423      * @return boolean; true if the specified OSMLink has a follower link in this OSMNetwork
424      */
425     public final boolean hasFollowingLink(final OSMLink link)
426     {
427         return null != findFollowingLink(link);
428     }
429 
430     /** {@inheritDoc} */
431     @Override
432     public final String toString()
433     {
434         return "OSMNetwork [name=" + this.name + ", nodes.size=" + this.nodes.size() + ", ways.size=" + this.ways.size()
435                 + ", relations.size=" + this.relations.size() + ", links.size=" + this.links.size() + "]";
436     }
437 }