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

Last change on this file since 12841 was 12841, checked in by bastiK, 3 months ago

see #15229 - fix deprecations caused by [12840]

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