source: josm/trunk/src/org/openstreetmap/josm/tools/Territories.java@ 15913

Last change on this file since 15913 was 15913, checked in by simon04, 4 years ago

fix #18754, see #18729 - TaginfoRegionalInstance: fix deadlock

  • Property svn:eol-style set to native
File size: 8.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.IOException;
7import java.io.InputStream;
8import java.util.ArrayList;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.HashMap;
12import java.util.List;
13import java.util.Locale;
14import java.util.Map;
15import java.util.Map.Entry;
16import java.util.Objects;
17import java.util.Set;
18import java.util.TreeMap;
19import java.util.stream.Collectors;
20import java.util.stream.Stream;
21
22import javax.json.Json;
23import javax.json.JsonArray;
24import javax.json.JsonObject;
25import javax.json.JsonString;
26import javax.json.JsonValue;
27import javax.json.stream.JsonParser;
28import javax.json.stream.JsonParser.Event;
29
30import org.openstreetmap.josm.data.coor.LatLon;
31import org.openstreetmap.josm.data.osm.DataSet;
32import org.openstreetmap.josm.data.osm.OsmPrimitive;
33import org.openstreetmap.josm.data.osm.Relation;
34import org.openstreetmap.josm.data.osm.Way;
35import org.openstreetmap.josm.io.CachedFile;
36import org.openstreetmap.josm.io.IllegalDataException;
37import org.openstreetmap.josm.io.OsmReader;
38import org.openstreetmap.josm.spi.preferences.Config;
39
40/**
41 * Look up territories ISO3166 codes at a certain place.
42 */
43public final class Territories {
44
45 /** Internal OSM filename */
46 public static final String FILENAME = "boundaries.osm";
47
48 private static final String ISO3166_1 = "ISO3166-1:alpha2";
49 private static final String ISO3166_2 = "ISO3166-2";
50 private static final String ISO3166_1_LC = ISO3166_1.toLowerCase(Locale.ENGLISH);
51 private static final String ISO3166_2_LC = ISO3166_2.toLowerCase(Locale.ENGLISH);
52 private static final String TAGINFO = "taginfo";
53
54 private static DataSet dataSet;
55
56 private static volatile Map<String, GeoPropertyIndex<Boolean>> iso3166Cache;
57 private static volatile Map<String, TaginfoRegionalInstance> taginfoCache;
58 private static volatile Map<String, TaginfoRegionalInstance> taginfoGeofabrikCache;
59
60 private Territories() {
61 // Hide implicit public constructor for utility classes
62 }
63
64 /**
65 * Get all known ISO3166-1 and ISO3166-2 codes.
66 *
67 * @return the ISO3166-1 and ISO3166-2 codes for the given location
68 */
69 public static synchronized Set<String> getKnownIso3166Codes() {
70 return iso3166Cache.keySet();
71 }
72
73 /**
74 * Returns the {@link GeoPropertyIndex} for the given ISO3166-1 or ISO3166-2 code.
75 * @param code the ISO3166-1 or ISO3166-2 code
76 * @return the {@link GeoPropertyIndex} for the given {@code code}
77 * @since 14484
78 */
79 public static GeoPropertyIndex<Boolean> getGeoPropertyIndex(String code) {
80 return iso3166Cache.get(code);
81 }
82
83 /**
84 * Determine, if a point is inside a territory with the given ISO3166-1
85 * or ISO3166-2 code.
86 *
87 * @param code the ISO3166-1 or ISO3166-2 code
88 * @param ll the coordinates of the point
89 * @return true, if the point is inside a territory with the given code
90 */
91 public static synchronized boolean isIso3166Code(String code, LatLon ll) {
92 GeoPropertyIndex<Boolean> gpi = iso3166Cache.get(code);
93 if (gpi == null) {
94 Logging.warn(tr("Unknown territory id: {0}", code));
95 return false;
96 }
97 return Boolean.TRUE.equals(gpi.get(ll)); // avoid NPE, see #16491
98 }
99
100 /**
101 * Returns the original territories dataset. Be extra cautious when manipulating it!
102 * @return the original territories dataset
103 * @since 15565
104 */
105 public static synchronized DataSet getOriginalDataSet() {
106 return dataSet;
107 }
108
109 /**
110 * Returns a copy of the territories dataset.
111 * @return a copy of the territories dataset
112 */
113 public static synchronized DataSet getDataSet() {
114 return new DataSet(dataSet);
115 }
116
117 /**
118 * Initializes territories.
119 * TODO: Synchronization can be refined inside the {@link GeoPropertyIndex} as most look-ups are read-only.
120 */
121 public static synchronized void initialize() {
122 initializeInternalData();
123 initializeExternalData();
124 }
125
126 private static void initializeInternalData() {
127 iso3166Cache = new HashMap<>();
128 taginfoCache = new TreeMap<>();
129 try (CachedFile cf = new CachedFile("resource://data/" + FILENAME);
130 InputStream is = cf.getInputStream()) {
131 dataSet = OsmReader.parseDataSet(is, null);
132 Collection<OsmPrimitive> candidates = new ArrayList<>(dataSet.getWays());
133 candidates.addAll(dataSet.getRelations());
134 for (OsmPrimitive osm : candidates) {
135 String iso1 = osm.get(ISO3166_1);
136 String iso2 = osm.get(ISO3166_2);
137 if (iso1 != null || iso2 != null) {
138 GeoProperty<Boolean> gp;
139 if (osm instanceof Way) {
140 gp = new DefaultGeoProperty(Collections.singleton((Way) osm));
141 } else {
142 gp = new DefaultGeoProperty((Relation) osm);
143 }
144 GeoPropertyIndex<Boolean> gpi = new GeoPropertyIndex<>(gp, 24);
145 if (iso1 != null) {
146 iso3166Cache.put(iso1, gpi);
147 String taginfo = osm.get(TAGINFO);
148 if (taginfo != null) {
149 taginfoCache.put(iso1, new TaginfoRegionalInstance(taginfo, Collections.singleton(iso1)));
150 }
151 }
152 if (iso2 != null) {
153 iso3166Cache.put(iso2, gpi);
154 }
155 }
156 }
157 } catch (IOException | IllegalDataException ex) {
158 throw new JosmRuntimeException(ex);
159 }
160 }
161
162 private static void initializeExternalData() {
163 taginfoGeofabrikCache = new TreeMap<>();
164 try (CachedFile cf = new CachedFile(Config.getUrls().getJOSMWebsite() + "/remote/geofabrik-index-v1-nogeom.json");
165 InputStream is = cf.getInputStream();
166 JsonParser json = Json.createParser(is)) {
167 while (json.hasNext()) {
168 Event event = json.next();
169 if (event == Event.START_OBJECT) {
170 for (JsonValue feature : json.getObject().getJsonArray("features")) {
171 JsonObject props = feature.asJsonObject().getJsonObject("properties");
172 if (props != null) {
173 JsonObject urls = props.getJsonObject("urls");
174 if (urls != null) {
175 String taginfo = urls.getString(TAGINFO);
176 if (taginfo != null) {
177 JsonArray iso1 = props.getJsonArray(ISO3166_1_LC);
178 JsonArray iso2 = props.getJsonArray(ISO3166_2_LC);
179 if (iso1 != null) {
180 readExternalTaginfo(taginfo, iso1);
181 } else if (iso2 != null) {
182 readExternalTaginfo(taginfo, iso2);
183 }
184 }
185 }
186 }
187 }
188 }
189 }
190 } catch (IOException e) {
191 throw new JosmRuntimeException(e);
192 }
193 }
194
195 private static void readExternalTaginfo(String taginfo, JsonArray jsonCodes) {
196 Set<String> isoCodes = jsonCodes.getValuesAs(JsonString.class).stream().map(JsonString::getString).collect(Collectors.toSet());
197 isoCodes.forEach(s -> taginfoGeofabrikCache.put(s, new TaginfoRegionalInstance(taginfo, isoCodes, "Geofabrik")));
198 }
199
200 /**
201 * Returns regional taginfo instances for the given location.
202 * @param ll lat/lon where to look.
203 * @return regional taginfo instances for the given location (code / url)
204 * @since 15876
205 */
206 public static List<TaginfoRegionalInstance> getRegionalTaginfoUrls(LatLon ll) {
207 if (iso3166Cache == null) {
208 return Collections.emptyList();
209 }
210 return iso3166Cache.entrySet().parallelStream().distinct()
211 .filter(e -> Boolean.TRUE.equals(e.getValue().get(ll)))
212 .map(Entry<String, GeoPropertyIndex<Boolean>>::getKey)
213 .distinct()
214 .flatMap(code -> Stream.of(taginfoCache, taginfoGeofabrikCache).map(cache -> cache.get(code)))
215 .filter(Objects::nonNull)
216 .collect(Collectors.toList());
217 }
218}
Note: See TracBrowser for help on using the repository browser.