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

Last change on this file since 15937 was 15937, checked in by GerdP, 4 years ago

fix #18772: log warning instead of showing bug report popup and continue initialization

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