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 * Returns the {@link GeoPropertyIndex} for the given ISO3166-1 or ISO3166-2 code. 054 * @param code the ISO3166-1 or ISO3166-2 code 055 * @return the {@link GeoPropertyIndex} for the given {@code code} 056 * @since 14484 057 */ 058 public static GeoPropertyIndex<Boolean> getGeoPropertyIndex(String code) { 059 return iso3166Cache.get(code); 060 } 061 062 /** 063 * Determine, if a point is inside a territory with the given ISO3166-1 064 * or ISO3166-2 code. 065 * 066 * @param code the ISO3166-1 or ISO3166-2 code 067 * @param ll the coordinates of the point 068 * @return true, if the point is inside a territory with the given code 069 */ 070 public static synchronized boolean isIso3166Code(String code, LatLon ll) { 071 GeoPropertyIndex<Boolean> gpi = iso3166Cache.get(code); 072 if (gpi == null) { 073 Logging.warn(tr("Unknown territory id: {0}", code)); 074 return false; 075 } 076 return Boolean.TRUE.equals(gpi.get(ll)); // avoid NPE, see #16491 077 } 078 079 /** 080 * Returns the territories dataset. 081 * @return the territories dataset 082 */ 083 public static synchronized DataSet getDataSet() { 084 return new DataSet(dataSet); 085 } 086 087 /** 088 * Initializes territories. 089 * TODO: Synchronization can be refined inside the {@link GeoPropertyIndex} as most look-ups are read-only. 090 */ 091 public static synchronized void initialize() { 092 iso3166Cache = new HashMap<>(); 093 try (CachedFile cf = new CachedFile("resource://data/" + FILENAME); 094 InputStream is = cf.getInputStream()) { 095 dataSet = OsmReader.parseDataSet(is, null); 096 Collection<OsmPrimitive> candidates = new ArrayList<>(dataSet.getWays()); 097 candidates.addAll(dataSet.getRelations()); 098 for (OsmPrimitive osm : candidates) { 099 String iso1 = osm.get(ISO3166_1); 100 String iso2 = osm.get(ISO3166_2); 101 if (iso1 != null || iso2 != null) { 102 GeoProperty<Boolean> gp; 103 if (osm instanceof Way) { 104 gp = new DefaultGeoProperty(Collections.singleton((Way) osm)); 105 } else { 106 gp = new DefaultGeoProperty((Relation) osm); 107 } 108 GeoPropertyIndex<Boolean> gpi = new GeoPropertyIndex<>(gp, 24); 109 if (iso1 != null) { 110 iso3166Cache.put(iso1, gpi); 111 } 112 if (iso2 != null) { 113 iso3166Cache.put(iso2, gpi); 114 } 115 } 116 } 117 } catch (IOException | IllegalDataException ex) { 118 throw new JosmRuntimeException(ex); 119 } 120 } 121}