source: josm/trunk/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java@ 12846

Last change on this file since 12846 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

  • Property svn:eol-style set to native
File size: 14.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.imagery;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.IOException;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.List;
14import java.util.Map;
15import java.util.Objects;
16import java.util.Set;
17import java.util.TreeSet;
18import java.util.concurrent.ExecutorService;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryPreferenceEntry;
22import org.openstreetmap.josm.gui.PleaseWaitRunnable;
23import org.openstreetmap.josm.io.CachedFile;
24import org.openstreetmap.josm.io.OfflineAccessException;
25import org.openstreetmap.josm.io.OnlineResource;
26import org.openstreetmap.josm.io.imagery.ImageryReader;
27import org.openstreetmap.josm.spi.preferences.Config;
28import org.openstreetmap.josm.tools.Logging;
29import org.openstreetmap.josm.tools.Utils;
30import org.xml.sax.SAXException;
31
32/**
33 * Manages the list of imagery entries that are shown in the imagery menu.
34 */
35public class ImageryLayerInfo {
36
37 /** Unique instance */
38 public static final ImageryLayerInfo instance = new ImageryLayerInfo();
39 /** List of all usable layers */
40 private final List<ImageryInfo> layers = new ArrayList<>();
41 /** List of layer ids of all usable layers */
42 private final Map<String, ImageryInfo> layerIds = new HashMap<>();
43 /** List of all available default layers */
44 private static final List<ImageryInfo> defaultLayers = new ArrayList<>();
45 /** List of all available default layers (including mirrors) */
46 private static final List<ImageryInfo> allDefaultLayers = new ArrayList<>();
47 /** List of all layer ids of available default layers (including mirrors) */
48 private static final Map<String, ImageryInfo> defaultLayerIds = new HashMap<>();
49
50 private static final String[] DEFAULT_LAYER_SITES = {
51 Main.getJOSMWebsite()+"/maps"
52 };
53
54 /**
55 * Returns the list of imagery layers sites.
56 * @return the list of imagery layers sites
57 * @since 7434
58 */
59 public static Collection<String> getImageryLayersSites() {
60 return Config.getPref().getList("imagery.layers.sites", Arrays.asList(DEFAULT_LAYER_SITES));
61 }
62
63 private ImageryLayerInfo() {
64 }
65
66 /**
67 * Constructs a new {@code ImageryLayerInfo} from an existing one.
68 * @param info info to copy
69 */
70 public ImageryLayerInfo(ImageryLayerInfo info) {
71 layers.addAll(info.layers);
72 }
73
74 /**
75 * Clear the lists of layers.
76 */
77 public void clear() {
78 layers.clear();
79 layerIds.clear();
80 }
81
82 /**
83 * Loads the custom as well as default imagery entries.
84 * @param fastFail whether opening HTTP connections should fail fast, see {@link ImageryReader#setFastFail(boolean)}
85 */
86 public void load(boolean fastFail) {
87 clear();
88 List<ImageryPreferenceEntry> entries = Main.pref.getListOfStructs("imagery.entries", null, ImageryPreferenceEntry.class);
89 if (entries != null) {
90 for (ImageryPreferenceEntry prefEntry : entries) {
91 try {
92 ImageryInfo i = new ImageryInfo(prefEntry);
93 add(i);
94 } catch (IllegalArgumentException e) {
95 Logging.warn("Unable to load imagery preference entry:"+e);
96 }
97 }
98 Collections.sort(layers);
99 }
100 loadDefaults(false, null, fastFail);
101 }
102
103 /**
104 * Loads the available imagery entries.
105 *
106 * The data is downloaded from the JOSM website (or loaded from cache).
107 * Entries marked as "default" are added to the user selection, if not already present.
108 *
109 * @param clearCache if true, clear the cache and start a fresh download.
110 * @param worker executor service which will perform the loading.
111 * If null, it should be performed using a {@link PleaseWaitRunnable} in the background
112 * @param fastFail whether opening HTTP connections should fail fast, see {@link ImageryReader#setFastFail(boolean)}
113 * @since 12634
114 */
115 public void loadDefaults(boolean clearCache, ExecutorService worker, boolean fastFail) {
116 final DefaultEntryLoader loader = new DefaultEntryLoader(clearCache, fastFail);
117 if (worker == null) {
118 loader.realRun();
119 loader.finish();
120 } else {
121 worker.execute(loader);
122 }
123 }
124
125 /**
126 * Loader/updater of the available imagery entries
127 */
128 class DefaultEntryLoader extends PleaseWaitRunnable {
129
130 private final boolean clearCache;
131 private final boolean fastFail;
132 private final List<ImageryInfo> newLayers = new ArrayList<>();
133 private ImageryReader reader;
134 private boolean canceled;
135 private boolean loadError;
136
137 DefaultEntryLoader(boolean clearCache, boolean fastFail) {
138 super(tr("Update default entries"));
139 this.clearCache = clearCache;
140 this.fastFail = fastFail;
141 }
142
143 @Override
144 protected void cancel() {
145 canceled = true;
146 Utils.close(reader);
147 }
148
149 @Override
150 protected void realRun() {
151 for (String source : getImageryLayersSites()) {
152 if (canceled) {
153 return;
154 }
155 loadSource(source);
156 }
157 }
158
159 protected void loadSource(String source) {
160 boolean online = true;
161 try {
162 OnlineResource.JOSM_WEBSITE.checkOfflineAccess(source, Main.getJOSMWebsite());
163 } catch (OfflineAccessException e) {
164 Logging.log(Logging.LEVEL_WARN, e);
165 online = false;
166 }
167 if (clearCache && online) {
168 CachedFile.cleanup(source);
169 }
170 try {
171 reader = new ImageryReader(source);
172 reader.setFastFail(fastFail);
173 Collection<ImageryInfo> result = reader.parse();
174 newLayers.addAll(result);
175 } catch (IOException ex) {
176 loadError = true;
177 Logging.log(Logging.LEVEL_ERROR, ex);
178 } catch (SAXException ex) {
179 loadError = true;
180 Logging.error(ex);
181 }
182 }
183
184 @Override
185 protected void finish() {
186 defaultLayers.clear();
187 allDefaultLayers.clear();
188 defaultLayers.addAll(newLayers);
189 for (ImageryInfo layer : newLayers) {
190 allDefaultLayers.add(layer);
191 for (ImageryInfo sublayer : layer.getMirrors()) {
192 allDefaultLayers.add(sublayer);
193 }
194 }
195 defaultLayerIds.clear();
196 Collections.sort(defaultLayers);
197 Collections.sort(allDefaultLayers);
198 buildIdMap(allDefaultLayers, defaultLayerIds);
199 updateEntriesFromDefaults(!loadError);
200 buildIdMap(layers, layerIds);
201 if (!loadError && !defaultLayerIds.isEmpty()) {
202 dropOldEntries();
203 }
204 }
205 }
206
207 /**
208 * Build the mapping of unique ids to {@link ImageryInfo}s.
209 * @param lst input list
210 * @param idMap output map
211 */
212 private static void buildIdMap(List<ImageryInfo> lst, Map<String, ImageryInfo> idMap) {
213 idMap.clear();
214 Set<String> notUnique = new HashSet<>();
215 for (ImageryInfo i : lst) {
216 if (i.getId() != null) {
217 if (idMap.containsKey(i.getId())) {
218 notUnique.add(i.getId());
219 Logging.error("Id ''{0}'' is not unique - used by ''{1}'' and ''{2}''!",
220 i.getId(), i.getName(), idMap.get(i.getId()).getName());
221 continue;
222 }
223 idMap.put(i.getId(), i);
224 }
225 }
226 for (String i : notUnique) {
227 idMap.remove(i);
228 }
229 }
230
231 /**
232 * Update user entries according to the list of default entries.
233 * @param dropold if <code>true</code> old entries should be removed
234 * @since 11706
235 */
236 public void updateEntriesFromDefaults(boolean dropold) {
237 // add new default entries to the user selection
238 boolean changed = false;
239 Collection<String> knownDefaults = new TreeSet<>(Config.getPref().getList("imagery.layers.default"));
240 Collection<String> newKnownDefaults = new TreeSet<>();
241 for (ImageryInfo def : defaultLayers) {
242 if (def.isDefaultEntry()) {
243 boolean isKnownDefault = false;
244 for (String entry : knownDefaults) {
245 if (entry.equals(def.getId())) {
246 isKnownDefault = true;
247 newKnownDefaults.add(entry);
248 knownDefaults.remove(entry);
249 break;
250 } else if (isSimilar(entry, def.getUrl())) {
251 isKnownDefault = true;
252 if (def.getId() != null) {
253 newKnownDefaults.add(def.getId());
254 }
255 knownDefaults.remove(entry);
256 break;
257 }
258 }
259 boolean isInUserList = false;
260 if (!isKnownDefault) {
261 if (def.getId() != null) {
262 newKnownDefaults.add(def.getId());
263 for (ImageryInfo i : layers) {
264 if (isSimilar(def, i)) {
265 isInUserList = true;
266 break;
267 }
268 }
269 } else {
270 Logging.error("Default imagery ''{0}'' has no id. Skipping.", def.getName());
271 }
272 }
273 if (!isKnownDefault && !isInUserList) {
274 add(new ImageryInfo(def));
275 changed = true;
276 }
277 }
278 }
279 if (!dropold && !knownDefaults.isEmpty()) {
280 newKnownDefaults.addAll(knownDefaults);
281 }
282 Config.getPref().putList("imagery.layers.default", new ArrayList<>(newKnownDefaults));
283
284 // automatically update user entries with same id as a default entry
285 for (int i = 0; i < layers.size(); i++) {
286 ImageryInfo info = layers.get(i);
287 if (info.getId() == null) {
288 continue;
289 }
290 ImageryInfo matchingDefault = defaultLayerIds.get(info.getId());
291 if (matchingDefault != null && !matchingDefault.equalsPref(info)) {
292 layers.set(i, matchingDefault);
293 Logging.info(tr("Update imagery ''{0}''", info.getName()));
294 changed = true;
295 }
296 }
297
298 if (changed) {
299 save();
300 }
301 }
302
303 /**
304 * Drop entries with Id which do no longer exist (removed from defaults).
305 * @since 11527
306 */
307 public void dropOldEntries() {
308 List<String> drop = new ArrayList<>();
309
310 for (Map.Entry<String, ImageryInfo> info : layerIds.entrySet()) {
311 if (!defaultLayerIds.containsKey(info.getKey())) {
312 remove(info.getValue());
313 drop.add(info.getKey());
314 Logging.info(tr("Drop old imagery ''{0}''", info.getValue().getName()));
315 }
316 }
317
318 if (!drop.isEmpty()) {
319 for (String id : drop) {
320 layerIds.remove(id);
321 }
322 save();
323 }
324 }
325
326 private static boolean isSimilar(ImageryInfo iiA, ImageryInfo iiB) {
327 if (iiA == null)
328 return false;
329 if (!iiA.getImageryType().equals(iiB.getImageryType()))
330 return false;
331 if (iiA.getId() != null && iiB.getId() != null) return iiA.getId().equals(iiB.getId());
332 return isSimilar(iiA.getUrl(), iiB.getUrl());
333 }
334
335 // some additional checks to respect extended URLs in preferences (legacy workaround)
336 private static boolean isSimilar(String a, String b) {
337 return Objects.equals(a, b) || (a != null && b != null && !a.isEmpty() && !b.isEmpty() && (a.contains(b) || b.contains(a)));
338 }
339
340 /**
341 * Add a new imagery entry.
342 * @param info imagery entry to add
343 */
344 public void add(ImageryInfo info) {
345 layers.add(info);
346 }
347
348 /**
349 * Remove an imagery entry.
350 * @param info imagery entry to remove
351 */
352 public void remove(ImageryInfo info) {
353 layers.remove(info);
354 }
355
356 /**
357 * Save the list of imagery entries to preferences.
358 */
359 public void save() {
360 List<ImageryPreferenceEntry> entries = new ArrayList<>();
361 for (ImageryInfo info : layers) {
362 entries.add(new ImageryPreferenceEntry(info));
363 }
364 Main.pref.putListOfStructs("imagery.entries", entries, ImageryPreferenceEntry.class);
365 }
366
367 /**
368 * List of usable layers
369 * @return unmodifiable list containing usable layers
370 */
371 public List<ImageryInfo> getLayers() {
372 return Collections.unmodifiableList(layers);
373 }
374
375 /**
376 * List of available default layers
377 * @return unmodifiable list containing available default layers
378 */
379 public List<ImageryInfo> getDefaultLayers() {
380 return Collections.unmodifiableList(defaultLayers);
381 }
382
383 /**
384 * List of all available default layers (including mirrors)
385 * @return unmodifiable list containing available default layers
386 * @since 11570
387 */
388 public List<ImageryInfo> getAllDefaultLayers() {
389 return Collections.unmodifiableList(allDefaultLayers);
390 }
391
392 public static void addLayer(ImageryInfo info) {
393 instance.add(info);
394 instance.save();
395 }
396
397 public static void addLayers(Collection<ImageryInfo> infos) {
398 for (ImageryInfo i : infos) {
399 instance.add(i);
400 }
401 instance.save();
402 Collections.sort(instance.layers);
403 }
404
405 /**
406 * Get unique id for ImageryInfo.
407 *
408 * This takes care, that no id is used twice (due to a user error)
409 * @param info the ImageryInfo to look up
410 * @return null, if there is no id or the id is used twice,
411 * the corresponding id otherwise
412 */
413 public String getUniqueId(ImageryInfo info) {
414 if (info.getId() != null && layerIds.get(info.getId()) == info) {
415 return info.getId();
416 }
417 return null;
418 }
419}
Note: See TracBrowser for help on using the repository browser.