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

Last change on this file since 12466 was 11707, checked in by stoecker, 7 years ago

only allow defaults with ID

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