001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.IOException; 007import java.io.InputStream; 008import java.util.ArrayList; 009import java.util.Collection; 010import java.util.Collections; 011import java.util.HashMap; 012import java.util.Map; 013import java.util.Set; 014 015import org.openstreetmap.josm.data.coor.LatLon; 016import org.openstreetmap.josm.data.osm.DataSet; 017import org.openstreetmap.josm.data.osm.OsmPrimitive; 018import org.openstreetmap.josm.data.osm.Relation; 019import org.openstreetmap.josm.data.osm.Way; 020import org.openstreetmap.josm.io.CachedFile; 021import org.openstreetmap.josm.io.IllegalDataException; 022import org.openstreetmap.josm.io.OsmReader; 023 024/** 025 * Look up territories ISO3166 codes at a certain place. 026 */ 027public final class Territories { 028 029 /** Internal OSM filename */ 030 public static final String FILENAME = "boundaries.osm"; 031 032 private static final String ISO3166_1 = "ISO3166-1:alpha2"; 033 private static final String ISO3166_2 = "ISO3166-2"; 034 035 private static DataSet dataSet; 036 037 private static volatile Map<String, GeoPropertyIndex<Boolean>> iso3166Cache; 038 039 private Territories() { 040 // Hide implicit public constructor for utility classes 041 } 042 043 /** 044 * Get all known ISO3166-1 and ISO3166-2 codes. 045 * 046 * @return the ISO3166-1 and ISO3166-2 codes for the given location 047 */ 048 public static synchronized Set<String> getKnownIso3166Codes() { 049 return iso3166Cache.keySet(); 050 } 051 052 /** 053 * Determine, if a point is inside a territory with the given the ISO3166-1 054 * or ISO3166-2 code. 055 * 056 * @param code the ISO3166-1 or ISO3166-2 code 057 * @param ll the coordinates of the point 058 * @return true, if the point is inside a territory with the given code 059 */ 060 public static synchronized boolean isIso3166Code(String code, LatLon ll) { 061 GeoPropertyIndex<Boolean> gpi = iso3166Cache.get(code); 062 if (gpi == null) { 063 Logging.warn(tr("Unknown territory id: {0}", code)); 064 return false; 065 } 066 return Boolean.TRUE.equals(gpi.get(ll)); // avoid NPE, see #16491 067 } 068 069 /** 070 * Returns the territories dataset. 071 * @return the territories dataset 072 */ 073 public static synchronized DataSet getDataSet() { 074 return new DataSet(dataSet); 075 } 076 077 /** 078 * Initializes territories. 079 * TODO: Synchronization can be refined inside the {@link GeoPropertyIndex} as most look-ups are read-only. 080 */ 081 public static synchronized void initialize() { 082 iso3166Cache = new HashMap<>(); 083 try (CachedFile cf = new CachedFile("resource://data/" + FILENAME); 084 InputStream is = cf.getInputStream()) { 085 dataSet = OsmReader.parseDataSet(is, null); 086 Collection<OsmPrimitive> candidates = new ArrayList<>(dataSet.getWays()); 087 candidates.addAll(dataSet.getRelations()); 088 for (OsmPrimitive osm : candidates) { 089 String iso1 = osm.get(ISO3166_1); 090 String iso2 = osm.get(ISO3166_2); 091 if (iso1 != null || iso2 != null) { 092 GeoProperty<Boolean> gp; 093 if (osm instanceof Way) { 094 gp = new DefaultGeoProperty(Collections.singleton((Way) osm)); 095 } else { 096 gp = new DefaultGeoProperty((Relation) osm); 097 } 098 GeoPropertyIndex<Boolean> gpi = new GeoPropertyIndex<>(gp, 24); 099 if (iso1 != null) { 100 iso3166Cache.put(iso1, gpi); 101 } 102 if (iso2 != null) { 103 iso3166Cache.put(iso2, gpi); 104 } 105 } 106 } 107 } catch (IOException | IllegalDataException ex) { 108 throw new JosmRuntimeException(ex); 109 } 110 } 111}