001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.Collections; 007import java.util.Set; 008 009import org.openstreetmap.josm.data.coor.LatLon; 010import org.openstreetmap.josm.data.osm.DataSet; 011import org.openstreetmap.josm.data.osm.OsmPrimitive; 012import org.openstreetmap.josm.data.osm.Relation; 013import org.openstreetmap.josm.data.osm.RelationMember; 014import org.openstreetmap.josm.data.osm.Way; 015 016/** 017 * Look up, if there is right- or left-hand traffic at a certain place. 018 * See <a href="https://en.wikipedia.org/wiki/Left-_and_right-hand_traffic">Left- and right-hand traffic</a> 019 */ 020public final class RightAndLefthandTraffic { 021 022 private static final String DRIVING_SIDE = "driving_side"; 023 private static final String LEFT = "left"; 024 private static final String RIGHT = "right"; 025 026 private static volatile GeoPropertyIndex<Boolean> rlCache; 027 028 private RightAndLefthandTraffic() { 029 // Hide implicit public constructor for utility classes 030 } 031 032 /** 033 * Check if there is right-hand traffic at a certain location. 034 * 035 * @param ll the coordinates of the point 036 * @return true if there is right-hand traffic, false if there is left-hand traffic 037 */ 038 public static synchronized boolean isRightHandTraffic(LatLon ll) { 039 Boolean value = rlCache.get(ll); 040 return value == null || !value; 041 } 042 043 /** 044 * Initializes Right and lefthand traffic data. 045 * TODO: Synchronization can be refined inside the {@link GeoPropertyIndex} as most look-ups are read-only. 046 */ 047 public static synchronized void initialize() { 048 rlCache = new GeoPropertyIndex<>(computeLeftDrivingBoundaries(), 24); 049 } 050 051 private static DefaultGeoProperty computeLeftDrivingBoundaries() { 052 Collection<Way> ways = new ArrayList<>(); 053 // Find all outer ways of left-driving countries. Many of them are adjacent (African and Asian states) 054 DataSet data = Territories.getDataSet(); 055 for (Way w : data.getWays()) { 056 if (LEFT.equals(w.get(DRIVING_SIDE))) { 057 addWayIfNotInner(ways, w); 058 } 059 } 060 for (Relation r : data.getRelations()) { 061 if (r.isMultipolygon() && LEFT.equals(r.get(DRIVING_SIDE))) { 062 for (RelationMember rm : r.getMembers()) { 063 if (rm.isWay() && "outer".equals(rm.getRole()) && !RIGHT.equals(rm.getMember().get(DRIVING_SIDE))) { 064 addWayIfNotInner(ways, (Way) rm.getMember()); 065 } 066 } 067 } 068 } 069 // Combine adjacent countries into a single polygon 070 return new DefaultGeoProperty(ways); 071 } 072 073 /** 074 * Adds w to ways, except if it is an inner way of another lefthand driving multipolygon, 075 * as Lesotho in South Africa and Cyprus village in British Cyprus base. 076 * @param ways ways 077 * @param w way 078 */ 079 private static void addWayIfNotInner(Collection<Way> ways, Way w) { 080 Set<Way> s = Collections.singleton(w); 081 for (Relation r : OsmPrimitive.getParentRelations(s)) { 082 if (r.isMultipolygon() && LEFT.equals(r.get(DRIVING_SIDE)) && 083 "inner".equals(r.getMembersFor(s).iterator().next().getRole())) { 084 if (Logging.isDebugEnabled()) { 085 Logging.debug("Skipping {0} because inner part of {1}", w.get("name:en"), r.get("name:en")); 086 } 087 return; 088 } 089 } 090 ways.add(w); 091 } 092}