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

Last change on this file since 12634 was 12634, checked in by Don-vip, 7 years ago

see #15182 - deprecate Main.worker, replace it by gui.MainApplication.worker + code refactoring to make sure only editor packages use it

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