Index: trunk/src/org/openstreetmap/josm/Main.java
===================================================================
--- trunk/src/org/openstreetmap/josm/Main.java	(revision 11244)
+++ trunk/src/org/openstreetmap/josm/Main.java	(revision 11247)
@@ -108,5 +108,7 @@
 import org.openstreetmap.josm.tools.PlatformHookUnixoid;
 import org.openstreetmap.josm.tools.PlatformHookWindows;
+import org.openstreetmap.josm.tools.RightAndLefthandTraffic;
 import org.openstreetmap.josm.tools.Shortcut;
+import org.openstreetmap.josm.tools.Territories;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -516,4 +518,8 @@
                 }
             }));
+
+        tasks.add(new InitializationTask(tr("Initializing internal boundaries data"), Territories::initialize));
+
+        tasks.add(new InitializationTask(tr("Initializing internal traffic data"), RightAndLefthandTraffic::initialize));
 
         tasks.add(new InitializationTask(tr("Initializing validator"), OsmValidator::initialize));
Index: trunk/src/org/openstreetmap/josm/actions/SelectByInternalPointAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/SelectByInternalPointAction.java	(revision 11244)
+++ trunk/src/org/openstreetmap/josm/actions/SelectByInternalPointAction.java	(revision 11247)
@@ -37,5 +37,5 @@
      */
     public static Collection<OsmPrimitive> getSurroundingObjects(EastNorth internalPoint) {
-        return getSurroundingObjects(Main.getLayerManager().getEditDataSet(), internalPoint);
+        return getSurroundingObjects(Main.getLayerManager().getEditDataSet(), internalPoint, false);
     }
 
@@ -46,8 +46,9 @@
      * @param ds the data set
      * @param internalPoint the internal point.
+     * @param includeMultipolygonWays whether to include multipolygon ways in the result (false by default)
      * @return the surrounding polygons/multipolygons
-     * @since 11240
+     * @since 11247
      */
-    public static Collection<OsmPrimitive> getSurroundingObjects(DataSet ds, EastNorth internalPoint) {
+    public static Collection<OsmPrimitive> getSurroundingObjects(DataSet ds, EastNorth internalPoint, boolean includeMultipolygonWays) {
         if (ds == null) {
             return Collections.emptySet();
@@ -62,7 +63,9 @@
         for (Relation r : ds.getRelations()) {
             if (r.isUsable() && r.isMultipolygon() && r.isSelectable() && Geometry.isNodeInsideMultiPolygon(n, r, null)) {
-                for (RelationMember m : r.getMembers()) {
-                    if (m.isWay() && m.getWay().isClosed()) {
-                        found.values().remove(m.getWay());
+                if (!includeMultipolygonWays) {
+                    for (RelationMember m : r.getMembers()) {
+                        if (m.isWay() && m.getWay().isClosed()) {
+                            found.values().remove(m.getWay());
+                        }
                     }
                 }
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java	(revision 11244)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java	(revision 11247)
@@ -16,5 +16,7 @@
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
+import java.util.Set;
 import java.util.TreeSet;
 import java.util.function.Function;
@@ -27,4 +29,5 @@
 import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
 import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
+import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
@@ -40,4 +43,5 @@
 import org.openstreetmap.josm.tools.RightAndLefthandTraffic;
 import org.openstreetmap.josm.tools.SubclassFilteredCollection;
+import org.openstreetmap.josm.tools.Territories;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -867,7 +871,5 @@
          */
         public static boolean is_right_hand_traffic(Environment env) {
-            if (env.osm instanceof Node)
-                return RightAndLefthandTraffic.isRightHandTraffic(((Node) env.osm).getCoor());
-            return RightAndLefthandTraffic.isRightHandTraffic(env.osm.getBBox().getCenter());
+            return RightAndLefthandTraffic.isRightHandTraffic(center(env));
         }
 
@@ -944,4 +946,42 @@
         public static Object setting(Environment env, String key) { // NO_UCD (unused code)
             return env.source.settingValues.get(key);
+        }
+
+        /**
+         * Returns the center of the environment OSM primitive.
+         * @param env the environment
+         * @return the center of the environment OSM primitive
+         * @since 11247
+         */
+        public static LatLon center(Environment env) { // NO_UCD (unused code)
+            return env.osm instanceof Node ? ((Node) env.osm).getCoor() : env.osm.getBBox().getCenter();
+        }
+
+        /**
+         * Determines if the object is inside territories matching given ISO3166 codes.
+         * @param env the environment
+         * @param codes comma-separated list of ISO3166-1-alpha2 or ISO3166-2 country/subdivision codes
+         * @return {@code true} if the object is inside territory matching given ISO3166 codes
+         * @since 11247
+         */
+        public static boolean inside(Environment env, String codes) { // NO_UCD (unused code)
+            Set<String> osmCodes = Territories.getIso3166Codes(center(env));
+            for (String code : codes.toUpperCase(Locale.ENGLISH).split(",")) {
+                if (osmCodes.contains(code.trim())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Determines if the object is outside territories matching given ISO3166 codes.
+         * @param env the environment
+         * @param codes comma-separated list of ISO3166-1-alpha2 or ISO3166-2 country/subdivision codes
+         * @return {@code true} if the object is outside territory matching given ISO3166 codes
+         * @since 11247
+         */
+        public static boolean outside(Environment env, String codes) { // NO_UCD (unused code)
+            return !inside(env, codes);
         }
     }
Index: trunk/src/org/openstreetmap/josm/gui/preferences/validator/ValidatorTagCheckerRulesPreference.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/validator/ValidatorTagCheckerRulesPreference.java	(revision 11244)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/validator/ValidatorTagCheckerRulesPreference.java	(revision 11247)
@@ -154,4 +154,5 @@
             addDefault(def, "religion",     tr("Religion"),            tr("Checks for errors on religious objects"));
             addDefault(def, "relation",     tr("Relations"),           tr("Checks for errors on relations"));
+            addDefault(def, "territories",  tr("Territories"),         tr("Checks for territories-specific features"));
             addDefault(def, "unnecessary",  tr("Unnecessary tags"),    tr("Checks for unnecessary tags"));
             addDefault(def, "wikipedia",    tr("Wikipedia"),           tr("Checks for wrong wikipedia tags"));
Index: trunk/src/org/openstreetmap/josm/tools/RightAndLefthandTraffic.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/RightAndLefthandTraffic.java	(revision 11244)
+++ trunk/src/org/openstreetmap/josm/tools/RightAndLefthandTraffic.java	(revision 11247)
@@ -3,16 +3,35 @@
 
 import java.awt.geom.Area;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
-
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JoinAreasAction;
+import org.openstreetmap.josm.actions.JoinAreasAction.JoinAreasResult;
+import org.openstreetmap.josm.actions.JoinAreasAction.Multipolygon;
+import org.openstreetmap.josm.actions.PurgeAction;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.osm.BBox;
 import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
 import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.io.CachedFile;
 import org.openstreetmap.josm.io.IllegalDataException;
 import org.openstreetmap.josm.io.OsmReader;
+import org.openstreetmap.josm.io.OsmWriter;
+import org.openstreetmap.josm.io.OsmWriterFactory;
 import org.openstreetmap.josm.tools.GeoPropertyIndex.GeoProperty;
 import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
@@ -58,28 +77,126 @@
      * Check if there is right-hand traffic at a certain location.
      *
-     * TODO: Synchronization can be refined inside the {@link GeoPropertyIndex}
-     *       as most look-ups are read-only.
      * @param ll the coordinates of the point
      * @return true if there is right-hand traffic, false if there is left-hand traffic
      */
     public static synchronized boolean isRightHandTraffic(LatLon ll) {
-        if (leftHandTrafficPolygons == null) {
-            initialize();
-        }
         return !rlCache.get(ll);
     }
 
-    private static void initialize() {
+    /**
+     * Initializes Right and lefthand traffic data.
+     * TODO: Synchronization can be refined inside the {@link GeoPropertyIndex} as most look-ups are read-only.
+     */
+    public static synchronized void initialize() {
         leftHandTrafficPolygons = new ArrayList<>();
-        try (CachedFile cf = new CachedFile("resource://data/left-right-hand-traffic.osm");
-                InputStream is = cf.getInputStream()) {
-            DataSet data = OsmReader.parseDataSet(is, null);
-            for (Way w : data.getWays()) {
-                leftHandTrafficPolygons.add(Geometry.getAreaLatLon(w.getNodes()));
-            }
-        } catch (IOException | IllegalDataException ex) {
+        Collection<Way> optimizedWays = loadOptimizedBoundaries();
+        if (optimizedWays.isEmpty()) {
+            optimizedWays = computeOptimizedBoundaries();
+            saveOptimizedBoundaries(optimizedWays);
+        }
+        for (Way w : optimizedWays) {
+            leftHandTrafficPolygons.add(Geometry.getAreaLatLon(w.getNodes()));
+        }
+        rlCache = new GeoPropertyIndex<>(new RLTrafficGeoProperty(), 24);
+    }
+
+    private static Collection<Way> computeOptimizedBoundaries() {
+        Collection<Way> ways = new ArrayList<>();
+        Collection<OsmPrimitive> toPurge = new ArrayList<>();
+        // Find all outer ways of left-driving countries. Many of them are adjacent (African and Asian states)
+        DataSet data = Territories.getDataSet();
+        Collection<Relation> allRelations = data.getRelations();
+        Collection<Way> allWays = data.getWays();
+        for (Way w : allWays) {
+            if ("left".equals(w.get("driving_side"))) {
+                addWayIfNotInner(ways, w);
+            }
+        }
+        for (Relation r : allRelations) {
+            if (r.isMultipolygon() && "left".equals(r.get("driving_side"))) {
+                for (RelationMember rm : r.getMembers()) {
+                    if (rm.isWay() && "outer".equals(rm.getRole())) {
+                        addWayIfNotInner(ways, (Way) rm.getMember());
+                    }
+                }
+            }
+        }
+        toPurge.addAll(allRelations);
+        toPurge.addAll(allWays);
+        toPurge.removeAll(ways);
+        // Remove ways from parent relations for following optimizations
+        for (Relation r : OsmPrimitive.getParentRelations(ways)) {
+            r.setMembers(null);
+        }
+        // Remove all tags to avoid any conflict
+        for (Way w : ways) {
+            w.removeAll();
+        }
+        // Purge all other ways and relations so dataset only contains lefthand traffic data
+        new PurgeAction().doPurge(toPurge, false);
+        // Combine adjacent countries into a single polygon
+        Collection<Way> optimizedWays = new ArrayList<>();
+        List<Multipolygon> areas = JoinAreasAction.collectMultipolygons(ways);
+        if (areas != null) {
+            try {
+                JoinAreasResult result = new JoinAreasAction().joinAreas(areas);
+                if (result.hasChanges) {
+                    for (Multipolygon mp : result.polygons) {
+                        optimizedWays.add(mp.outerWay);
+                    }
+                }
+            } catch (UserCancelException ex) {
+                Main.warn(ex);
+            }
+        }
+        if (optimizedWays.isEmpty()) {
+            // Problem: don't optimize
+            Main.warn("Unable to join left-driving countries polygons");
+            optimizedWays.addAll(ways);
+        }
+        return optimizedWays;
+    }
+
+    /**
+     * Adds w to ways, except if it is an inner way of another lefthand driving multipolygon,
+     * as Lesotho in South Africa and Cyprus village in British Cyprus base.
+     * @param ways ways
+     * @param w way
+     */
+    private static void addWayIfNotInner(Collection<Way> ways, Way w) {
+        Set<Way> s = Collections.singleton(w);
+        for (Relation r : OsmPrimitive.getParentRelations(s)) {
+            if (r.isMultipolygon() && "left".equals(r.get("driving_side")) &&
+                "inner".equals(r.getMembersFor(s).iterator().next().getRole())) {
+                if (Main.isDebugEnabled()) {
+                    Main.debug("Skipping " + w.get("name:en") + " because inner part of " + r.get("name:en"));
+                }
+                return;
+            }
+        }
+        ways.add(w);
+    }
+
+    private static void saveOptimizedBoundaries(Collection<Way> optimizedWays) {
+        DataSet ds = optimizedWays.iterator().next().getDataSet();
+        File file = new File(Main.pref.getCacheDirectory(), "left-right-hand-traffic.osm");
+        try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8);
+             OsmWriter w = OsmWriterFactory.createOsmWriter(new PrintWriter(writer), false, ds.getVersion())
+            ) {
+            w.header(false);
+            w.writeContent(ds);
+            w.footer();
+        } catch (IOException ex) {
             throw new RuntimeException(ex);
         }
-        rlCache = new GeoPropertyIndex<>(new RLTrafficGeoProperty(), 24);
+    }
+
+    private static Collection<Way> loadOptimizedBoundaries() {
+        try (InputStream is = new FileInputStream(new File(Main.pref.getCacheDirectory(), "left-right-hand-traffic.osm"))) {
+           return OsmReader.parseDataSet(is, null).getWays();
+        } catch (IllegalDataException | IOException ex) {
+            Main.trace(ex);
+            return Collections.emptyList();
+        }
     }
 }
Index: trunk/src/org/openstreetmap/josm/tools/Territories.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/Territories.java	(revision 11247)
+++ trunk/src/org/openstreetmap/josm/tools/Territories.java	(revision 11247)
@@ -0,0 +1,118 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.tools;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.SelectByInternalPointAction;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.BBox;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.io.CachedFile;
+import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.io.OsmReader;
+import org.openstreetmap.josm.tools.GeoPropertyIndex.GeoProperty;
+
+/**
+ * Look up territories ISO3166 codes at a certain place.
+ */
+public final class Territories {
+
+    private static final String ISO3166_1 = "ISO3166-1:alpha2";
+    private static final String ISO3166_2 = "ISO3166-2";
+
+    private static class Iso3166GeoProperty implements GeoProperty<Set<String>> {
+
+        @Override
+        public Set<String> get(LatLon ll) {
+            Set<String> result = new HashSet<>();
+            for (OsmPrimitive surrounding :
+                    SelectByInternalPointAction.getSurroundingObjects(dataSet, Main.getProjection().latlon2eastNorth(ll), true)) {
+                String iso1 = surrounding.get(ISO3166_1);
+                if (iso1 != null) {
+                    result.add(iso1);
+                }
+                String iso2 = surrounding.get(ISO3166_2);
+                if (iso2 != null) {
+                    result.add(iso2);
+                }
+            }
+            return result;
+        }
+
+        @Override
+        public Set<String> get(BBox box) {
+            return null; // TODO
+        }
+    }
+
+    private static DataSet dataSet;
+    private static final Map<String, OsmPrimitive> iso3166Map = new ConcurrentHashMap<>();
+
+    private static volatile GeoPropertyIndex<Set<String>> iso3166Cache;
+
+    private Territories() {
+        // Hide implicit public constructor for utility classes
+    }
+
+    /**
+     * Get all known ISO3166-1 and ISO3166-2 codes.
+     *
+     * @return the ISO3166-1 and ISO3166-2 codes for the given location
+     */
+    public static synchronized Set<String> getKnownIso3166Codes() {
+        return iso3166Map.keySet();
+    }
+
+    /**
+     * Get the ISO3166-1 and ISO3166-2 codes for the given location.
+     *
+     * @param ll the coordinates of the point
+     * @return the ISO3166-1 and ISO3166-2 codes for the given location
+     */
+    public static synchronized Set<String> getIso3166Codes(LatLon ll) {
+        return iso3166Cache.get(ll);
+    }
+
+    /**
+     * Returns the territories dataset.
+     * @return the territories dataset
+     */
+    public static synchronized DataSet getDataSet() {
+        return new DataSet(dataSet);
+    }
+
+    /**
+     * Initializes territories.
+     * TODO: Synchronization can be refined inside the {@link GeoPropertyIndex} as most look-ups are read-only.
+     */
+    public static synchronized void initialize() {
+        iso3166Cache = new GeoPropertyIndex<>(new Iso3166GeoProperty(), 24);
+        try (CachedFile cf = new CachedFile("resource://data/boundaries.osm");
+                InputStream is = cf.getInputStream()) {
+            dataSet = OsmReader.parseDataSet(is, null);
+            Collection<OsmPrimitive> candidates = new ArrayList<>(dataSet.getWays());
+            candidates.addAll(dataSet.getRelations());
+            for (OsmPrimitive osm : candidates) {
+                String iso1 = osm.get(ISO3166_1);
+                if (iso1 != null) {
+                    iso3166Map.put(iso1, osm);
+                }
+                String iso2 = osm.get(ISO3166_2);
+                if (iso2 != null) {
+                    iso3166Map.put(iso2, osm);
+                }
+            }
+        } catch (IOException | IllegalDataException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+}
