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