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

Last change on this file since 11527 was 11527, checked in by stoecker, 10 months ago

drop imagery entries which have an id, but are no longer in default list

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