View Javadoc
1   package org.opentrafficsim.importexport.osm;
2   
3   import java.io.IOException;
4   import java.util.ArrayList;
5   import java.util.HashMap;
6   import java.util.List;
7   
8   /**
9    * <p>
10   * Copyright (c) 2013-2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights
11   * reserved. <br>
12   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
13   * <p>
14   * @version 31 dec. 2014 <br>
15   * @author <a>Moritz Bergmann</a>
16   */
17  public class Network
18  {
19      /** The Nodes of the Network. */
20      private HashMap<Long, Node> nodes;
21  
22      /** The Ways of the Network. */
23      private HashMap<Long, Way> ways;
24  
25      /** The Relations of the Network. */
26      private HashMap<Long, Relation> relations;
27  
28      /** The name of the Network. */
29      private String name;
30  
31      /** The Links of the Network. */
32      private List<Link> links;
33  
34      /**
35       * Construct a new Network.
36       * @param name String; the name of the new Network
37       */
38      public Network(final String name)
39      {
40          this.setName(name);
41          this.nodes = new HashMap<Long, Node>();
42          this.ways = new HashMap<Long, Way>();
43          this.relations = new HashMap<Long, Relation>();
44          this.links = new ArrayList<Link>();
45      }
46      
47      /**
48       * Construct a new Network out of another Network.
49       * @param net 
50       */
51      public Network(final Network net)
52      {
53          this.setName(net.getName());
54          this.nodes = new HashMap<Long, Node>(net.getNodes());
55          this.ways = new HashMap<Long, Way>(net.getWays());
56          this.relations = new HashMap<Long, Relation>(net.getRelations());
57          this.links = new ArrayList<Link>(net.getLinks());
58      }
59  
60      /**
61       * Retrieve a list of Nodes that form a Way from this Network.
62       * @param wayid long; the id of the Way
63       * @return List&lt;Long*gt;; the list of NodeIds of way with the specified id
64       * @throws IOException when no Way with the specified id exists in this Network
65       */
66      public final List<Long> getNodesFromWay(final Long wayid) throws IOException
67      {
68          if (this.ways.get(wayid) == null)
69          {
70              throw new IOException("Way with the ID: " + wayid + "was not found");
71          }
72          else
73          {
74              return this.ways.get(wayid).getNodes();
75          }
76      }
77  
78      /**
79       * Retrieve a Node from this Network.
80       * @param nodeid long; the id of the Node
81       * @return node Node; the node with the specified id
82       * @throws IOException when no Node with the specified id exist in this Network
83       */
84      public final Node getNode(final long nodeid) throws IOException
85      {
86          if (this.nodes.get(nodeid) == null)
87          {
88              throw new IOException("Node with the ID: " + nodeid + "was not found");
89          }
90          else
91          {
92              return this.nodes.get(nodeid);
93          }
94      }
95  
96      /**
97       * @return HashMap of all Nodes in this Network
98       */
99      public final HashMap<Long, Node> getNodes()
100     {
101         return this.nodes;
102     }
103 
104     /**
105      * Retrieve a Relation from this Network.
106      * @param relid long; the id of the Relation
107      * @return Relation
108      * @throws IOException when no Relation with the specified id exists in this Network
109      */
110     public final Relation getRelation(final long relid) throws IOException
111     {
112         if (this.relations.get(relid) == null)
113         {
114             throw new IOException("Relation with the ID: " + relid + "was not found");
115         }
116         else
117         {
118             return this.relations.get(relid);
119         }
120     }
121 
122     /**
123      * @return HashMap of all relations in the network
124      */
125     public final HashMap<Long, Relation> getRelations()
126     {
127         return this.relations;
128     }
129 
130     /**
131      * Retrieve a Way from this Network.
132      * @param wayid long; the id of a Way
133      * @return Way
134      * @throws IOException when no Way with the specified id exist in this network
135      */
136     public final Way getWay(final long wayid) throws IOException
137     {
138         if (this.ways.get(wayid) == null)
139         {
140             throw new IOException("Way with the ID: " + wayid + "was not found");
141         }
142         else
143         {
144             return this.ways.get(wayid);
145         }
146     }
147 
148     /**
149      * @return name
150      */
151     public final String getName()
152     {
153         return this.name;
154     }
155 
156     /**
157      * Set/update the name of this Network.
158      * @param name String; the new name
159      */
160     public final void setName(final String name)
161     {
162         this.name = name;
163     }
164 
165     /**
166      * Set/replace the Nodes of this Network.<br>
167      * The provided list is <b>not copied</b>; the caller should not modify the list after setting it.
168      * @param newnodes HashMap&lt;Long, Node&gt;; the (new) Nodes for this Network
169      */
170     public final void setNodes(final HashMap<Long, Node> newnodes)
171     {
172         this.nodes = newnodes;
173     }
174 
175     /**
176      * Add a Node to this Network.
177      * @param node Node; the node to add to this Network
178      */
179     public final void addNode(final Node node)
180     {
181         this.nodes.put(node.getID(), node);
182     }
183 
184     /**
185      * Delete a Node from this Network.
186      * @param node Node; the Node to delete
187      */
188     public final void delNode(final Node node)
189     {
190         this.nodes.remove(node.getID());
191     }
192 
193     /**
194      * Delete a Node from this Network.
195      * @param nodeid Long; the id of the Node to delete
196      */
197     public final void delNode(final Long nodeid)
198     {
199         this.nodes.remove(nodeid);
200     }
201 
202     /**
203      * Add a Way to this Network.
204      * @param way Way; the Way to add
205      */
206     public final void addWay(final Way way)
207     {
208         this.ways.put(way.getID(), way);
209     }
210 
211     /**
212      * Add a Relation to this Network.
213      * @param rel Relation; the relation to add
214      */
215     public final void addRelation(final Relation rel)
216     {
217         this.relations.put(rel.getID(), rel);
218     }
219 
220     /**
221      * @return ways
222      */
223     public final HashMap<Long, Way> getWays()
224     {
225         return this.ways;
226     }
227 
228     /**
229      * @return links
230      */
231     public final List<Link> getLinks()
232     {
233         return this.links;
234     }
235 
236     /**
237      * Creates links out of the ways in this network.
238      * @throws IOException on read errors
239      */
240     public final void makeLinks() throws IOException
241     {
242         List<Link> links2 = new ArrayList<Link>();
243         double length;
244         Node n1;
245         Node n2;
246         double x1;
247         double x2;
248         double y1;
249         double y2;
250         for (Long wayid : this.ways.keySet())
251         {
252             List<Long> waynodes = this.getWay(wayid).getNodes();
253             for (int i = 0; i < (waynodes.size() - 1); i++)
254             {
255                 n1 = this.getNode(waynodes.get(i).longValue());
256                 n2 = this.getNode(waynodes.get(i + 1).longValue());
257                 // Workaround for bug in OSM near Mettmann Germany
258                 if (n1 == n2)
259                 {
260                     continue;
261                 }
262                 x1 = n1.getLatitude();
263                 x2 = n2.getLatitude();
264                 y1 = n1.getLongitude();
265                 y2 = n2.getLongitude();
266                 length = distanceLongLat(x1, y1, x2, y2);
267                 Link e = new Link(n1, n2, this.getWay(wayid).getTags(), length);
268                 links2.add(e);
269             }
270         }
271         this.links = links2;
272     }
273 
274     /**
275      * @param xx1 Latitude of point 1
276      * @param yy1 Longitude of point 1
277      * @param xx2 Latitude of point 2
278      * @param yy2 Longitude of point 2
279      * @return distance in meter between the (x1,y1) and (x2,y2) This method utilizes great circle calculation
280      */
281     private static double distanceLongLat(final double xx1, final double yy1, final double xx2, final double yy2)
282     {
283 
284         double y1 = yy1;
285         double y2 = yy2;
286         if (y1 < 0)
287         {
288             y1 = 360 + yy1;
289         }
290         if (y2 < 0)
291         {
292             y2 = 360 + yy2;
293         }
294         double x1 = 90 - xx1;
295         double x2 = 90 - xx2;
296         x1 = x1 * (2 * Math.PI / 360);
297         x2 = x2 * (2 * Math.PI / 360);
298         y1 = y1 * (2 * Math.PI / 360);
299         y2 = y2 * (2 * Math.PI / 360);
300         double distance;
301         distance =
302                 Math.acos(Math.sin(x1) * Math.sin(x2) * Math.cos(y1 - y2) + Math.cos(x1) * Math.cos(x2)) * 6371 * 1000;
303         return distance;
304     }
305 
306     /**
307      * This function checks for and removes redundancies between the networks links.
308      */
309     public final void removeRedundancy()
310     {
311         boolean again = false;
312         do
313         {
314             again = this.redundancyCheck();
315         }
316         while (again);
317     }
318 
319     /**
320      * This function checks the networks links for redundancy.
321      * @return boolean; true if the Network was modified (further reduction may be possible by calling this method
322      *         again)
323      */
324     private boolean redundancyCheck()
325     {
326         List<Link> checkedLinks = new ArrayList<Link>();
327         List<Link> removedLinks = new ArrayList<Link>();
328         boolean redundancy = false;
329         for (Link l1 : this.links)
330         {
331             if (removedLinks.contains(l1))
332             {
333                 continue;
334             }
335             String name1 = "1";
336             for (Tag t1 : l1.getTags())
337             {
338                 if (t1.getKey().equals("name"))
339                 {
340                     name1 = t1.getValue();
341                 }
342             }
343             for (Link l2 : this.links)
344             {
345                 if (removedLinks.contains(l2))
346                 {
347                     continue;
348                 }
349                 String name2 = "2";
350                 for (Tag t2 : l2.getTags())
351                 {
352                     if (t2.getKey().equals("name"))
353                     {
354                         name2 = t2.getValue();
355                     }
356                 }
357                 if (l1.getEnd().equals(l2.getStart()) && name1.equalsIgnoreCase(name2)
358                         && l1.getEnd().getTags().isEmpty() && l1.getTags().containsAll(l2.getTags())
359                         && l1.getLanes() == l2.getLanes() && l1.getForwardLanes() == l2.getForwardLanes()
360                         && l1.getStart() != l2.getEnd())
361                 {
362                     if (removedLinks.contains(l1))
363                     {
364                         for (int i = 0; i < this.links.size(); i++)
365                         {
366                             Link l = this.links.get(i);
367                             if (l == l1)
368                             {
369                                 System.out.println("found link " + l + " at position " + i);
370                             }
371                         }
372                         throw new Error("about to add " + l1 + " to removeLinks which already contains that link");
373                     }
374                     redundancy = true;
375                     Link lnew =
376                             new Link(l1.getStart(), l2.getEnd(), l1.getTags(), l1.getLength() + l2.getLength(),
377                                     l1.getLanes(), l1.getForwardLanes());
378                     if (!l1.getSplineList().isEmpty())
379                     {
380                         for (Node n1 : l1.getSplineList())
381                         {
382                             lnew.addSpline(n1);
383                         }
384                     }
385                     if (!l2.getSplineList().isEmpty())
386                     {
387                         for (Node n2 : l2.getSplineList())
388                         {
389                             lnew.addSpline(n2);
390                         }
391                     }
392                     // System.out.println("removing " + l1 + " and " + l2);
393                     // System.out.println("adding " + lnew);
394                     checkedLinks.add(lnew);
395                     removedLinks.add(l1);
396                     removedLinks.add(l2);
397                     break; // don't merge any other links with l1; do that on the next iteration.
398                 }
399             }
400         }
401         // System.out.println("Combining " + removedLinks.size() + " to " + checkedLinks.size());
402         // if (removedLinks.size() < 40)
403         // {
404         // for (Link l : removedLinks)
405         // System.out.println("Removing link " + l);
406         // }
407         this.links.removeAll(removedLinks);
408         this.links.addAll(checkedLinks);
409         //System.out.println("there are now " + this.links.size() + " links");
410         return redundancy;
411     }
412     
413     /**
414      * Finds the link which follows the given link. If it exists.
415      * @param l 
416      * @return Link
417      */
418     public final Link findFollowingLink(final Link l)
419     {
420         if (!this.links.contains(l))
421         {
422             throw new Error("This link does not exist in this network: " + l);
423         }
424         for (Link l2: this.links)
425         {
426             if (l.getEnd().equals(l2.getStart()))
427             {
428                 return l2;
429             }
430         }
431         return null;
432     }
433     
434     /**
435      * Finds the link which precedes the given link. If it exists.
436      * @param l 
437      * @return Link
438      */
439     public final Link findPrecedingLink(final Link l)
440     {
441         if (!this.links.contains(l))
442         {
443             throw new Error("This link does not exist in this network: " + l);
444         }
445         for (Link l2: this.links)
446         {
447             if (l.getStart().equals(l2.getEnd()))
448             {
449                 return l2;
450             }
451         }
452         return null;
453     }
454     
455     /**
456      * Returns true if the given link has a preceding link.
457      * @param l 
458      * @return boolean
459      */
460     public final boolean hasPrecedingLink(final Link l)
461     {
462         if (!this.links.contains(l))
463         {
464             throw new Error("This link does not exist in this network: " + l);
465         }
466         for (Link l2: this.links)
467         {
468             if (l.getStart().equals(l2.getEnd()))
469             {
470                 return true;
471             }
472         }
473         return false;
474     }
475     
476     /**
477      * Returns true if the given link has a following link.
478      * @param l 
479      * @return boolean
480      */
481     public final boolean hasFollowingLink(final Link l)
482     {
483         if (!this.links.contains(l))
484         {
485             throw new Error("This link does not exist in this network: " + l);
486         }
487         for (Link l2: this.links)
488         {
489             if (l.getEnd().equals(l2.getStart()))
490             {
491                 return true;
492             }
493         }
494         return false;
495     }
496 }