[3719] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[3715] | 2 | package org.openstreetmap.josm.data.imagery;
|
---|
| 3 |
|
---|
| 4 | import java.io.IOException;
|
---|
| 5 | import java.util.ArrayList;
|
---|
| 6 | import java.util.Arrays;
|
---|
| 7 | import java.util.Collection;
|
---|
| 8 | import java.util.Collections;
|
---|
[7186] | 9 | import java.util.HashMap;
|
---|
| 10 | import java.util.HashSet;
|
---|
[3715] | 11 | import java.util.List;
|
---|
[7186] | 12 | import java.util.Map;
|
---|
[7083] | 13 | import java.util.Objects;
|
---|
[7186] | 14 | import java.util.Set;
|
---|
| 15 | import java.util.TreeSet;
|
---|
[7434] | 16 |
|
---|
[3715] | 17 | import org.openstreetmap.josm.Main;
|
---|
[4450] | 18 | import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryPreferenceEntry;
|
---|
[7248] | 19 | import org.openstreetmap.josm.io.CachedFile;
|
---|
[7434] | 20 | import org.openstreetmap.josm.io.OfflineAccessException;
|
---|
| 21 | import org.openstreetmap.josm.io.OnlineResource;
|
---|
[4240] | 22 | import org.openstreetmap.josm.io.imagery.ImageryReader;
|
---|
[6248] | 23 | import org.xml.sax.SAXException;
|
---|
[3715] | 24 |
|
---|
[4450] | 25 | /**
|
---|
| 26 | * Manages the list of imagery entries that are shown in the imagery menu.
|
---|
| 27 | */
|
---|
[3715] | 28 | public class ImageryLayerInfo {
|
---|
[3826] | 29 |
|
---|
[3715] | 30 | public static final ImageryLayerInfo instance = new ImageryLayerInfo();
|
---|
[7186] | 31 | private final List<ImageryInfo> layers = new ArrayList<>();
|
---|
| 32 | private final Map<String, ImageryInfo> layerIds = new HashMap<>();
|
---|
[8130] | 33 | private static final List<ImageryInfo> defaultLayers = new ArrayList<>();
|
---|
| 34 | private static final Map<String, ImageryInfo> defaultLayerIds = new HashMap<>();
|
---|
[3715] | 35 |
|
---|
[6883] | 36 | private static final String[] DEFAULT_LAYER_SITES = {
|
---|
[6897] | 37 | Main.getJOSMWebsite()+"/maps"
|
---|
[3826] | 38 | };
|
---|
| 39 |
|
---|
[7434] | 40 | /**
|
---|
| 41 | * Returns the list of imagery layers sites.
|
---|
| 42 | * @return the list of imagery layers sites
|
---|
| 43 | * @since 7434
|
---|
| 44 | */
|
---|
| 45 | public static Collection<String> getImageryLayersSites() {
|
---|
| 46 | return Main.pref.getCollection("imagery.layers.sites", Arrays.asList(DEFAULT_LAYER_SITES));
|
---|
| 47 | }
|
---|
| 48 |
|
---|
[4016] | 49 | private ImageryLayerInfo() {
|
---|
| 50 | }
|
---|
| 51 |
|
---|
| 52 | public ImageryLayerInfo(ImageryLayerInfo info) {
|
---|
| 53 | layers.addAll(info.layers);
|
---|
| 54 | }
|
---|
| 55 |
|
---|
[4216] | 56 | public void clear() {
|
---|
| 57 | layers.clear();
|
---|
[7186] | 58 | layerIds.clear();
|
---|
[4216] | 59 | }
|
---|
| 60 |
|
---|
[4016] | 61 | public void load() {
|
---|
[7186] | 62 | clear();
|
---|
[4450] | 63 | List<ImageryPreferenceEntry> entries = Main.pref.getListOfStructs("imagery.entries", null, ImageryPreferenceEntry.class);
|
---|
[4881] | 64 | if (entries != null) {
|
---|
[4450] | 65 | for (ImageryPreferenceEntry prefEntry : entries) {
|
---|
[4853] | 66 | try {
|
---|
| 67 | ImageryInfo i = new ImageryInfo(prefEntry);
|
---|
| 68 | add(i);
|
---|
| 69 | } catch (IllegalArgumentException e) {
|
---|
[6248] | 70 | Main.warn("Unable to load imagery preference entry:"+e);
|
---|
[4853] | 71 | }
|
---|
[4450] | 72 | }
|
---|
| 73 | Collections.sort(layers);
|
---|
| 74 | }
|
---|
[7186] | 75 | loadDefaults(false);
|
---|
[4450] | 76 | }
|
---|
| 77 |
|
---|
[5729] | 78 | /**
|
---|
| 79 | * Loads the available imagery entries.
|
---|
| 80 | *
|
---|
| 81 | * The data is downloaded from the JOSM website (or loaded from cache).
|
---|
| 82 | * Entries marked as "default" are added to the user selection, if not
|
---|
| 83 | * already present.
|
---|
| 84 | *
|
---|
| 85 | * @param clearCache if true, clear the cache and start a fresh download.
|
---|
| 86 | */
|
---|
[4016] | 87 | public void loadDefaults(boolean clearCache) {
|
---|
| 88 | defaultLayers.clear();
|
---|
[7186] | 89 | defaultLayerIds.clear();
|
---|
[7434] | 90 | for (String source : getImageryLayersSites()) {
|
---|
| 91 | boolean online = true;
|
---|
| 92 | try {
|
---|
| 93 | OnlineResource.JOSM_WEBSITE.checkOfflineAccess(source, Main.getJOSMWebsite());
|
---|
| 94 | } catch (OfflineAccessException e) {
|
---|
| 95 | Main.warn(e.getMessage());
|
---|
| 96 | online = false;
|
---|
| 97 | }
|
---|
| 98 | if (clearCache && online) {
|
---|
[7248] | 99 | CachedFile.cleanup(source);
|
---|
[4240] | 100 | }
|
---|
| 101 | try {
|
---|
| 102 | ImageryReader reader = new ImageryReader(source);
|
---|
| 103 | Collection<ImageryInfo> result = reader.parse();
|
---|
| 104 | defaultLayers.addAll(result);
|
---|
| 105 | } catch (IOException ex) {
|
---|
[6642] | 106 | Main.error(ex, false);
|
---|
| 107 | } catch (SAXException ex) {
|
---|
| 108 | Main.error(ex);
|
---|
[4240] | 109 | }
|
---|
| 110 | }
|
---|
[8465] | 111 | while (defaultLayers.remove(null)) {
|
---|
| 112 | // Do nothing
|
---|
| 113 | }
|
---|
[7186] | 114 | Collections.sort(defaultLayers);
|
---|
| 115 | buildIdMap(defaultLayers, defaultLayerIds);
|
---|
| 116 | updateEntriesFromDefaults();
|
---|
| 117 | buildIdMap(layers, layerIds);
|
---|
| 118 | }
|
---|
[7434] | 119 |
|
---|
[7186] | 120 | /**
|
---|
| 121 | * Build the mapping of unique ids to {@link ImageryInfo}s.
|
---|
| 122 | * @param lst input list
|
---|
| 123 | * @param idMap output map
|
---|
| 124 | */
|
---|
| 125 | private static void buildIdMap(List<ImageryInfo> lst, Map<String, ImageryInfo> idMap) {
|
---|
| 126 | idMap.clear();
|
---|
| 127 | Set<String> notUnique = new HashSet<>();
|
---|
| 128 | for (ImageryInfo i : lst) {
|
---|
| 129 | if (i.getId() != null) {
|
---|
| 130 | if (idMap.containsKey(i.getId())) {
|
---|
| 131 | notUnique.add(i.getId());
|
---|
| 132 | Main.error("Id ''{0}'' is not unique - used by ''{1}'' and ''{2}''!",
|
---|
| 133 | i.getId(), i.getName(), idMap.get(i.getId()).getName());
|
---|
| 134 | continue;
|
---|
| 135 | }
|
---|
| 136 | idMap.put(i.getId(), i);
|
---|
| 137 | }
|
---|
| 138 | }
|
---|
| 139 | for (String i : notUnique) {
|
---|
| 140 | idMap.remove(i);
|
---|
| 141 | }
|
---|
| 142 | }
|
---|
[7434] | 143 |
|
---|
[7186] | 144 | /**
|
---|
| 145 | * Update user entries according to the list of default entries.
|
---|
| 146 | */
|
---|
| 147 | public void updateEntriesFromDefaults() {
|
---|
| 148 | // add new default entries to the user selection
|
---|
| 149 | boolean changed = false;
|
---|
| 150 | Collection<String> knownDefaults = Main.pref.getCollection("imagery.layers.default");
|
---|
| 151 | Collection<String> newKnownDefaults = new TreeSet<>(knownDefaults);
|
---|
[4240] | 152 | for (ImageryInfo def : defaultLayers) {
|
---|
[8344] | 153 | // temporary migration code, so all user preferences will get updated with new settings from JOSM site (can be removed ~Dez. 2015)
|
---|
[8444] | 154 | if (def.getNoTileHeaders() != null || def.getTileSize() > 0 || def.getMetadataHeaders() != null) {
|
---|
[8344] | 155 | for (ImageryInfo i: layers) {
|
---|
| 156 | if (isSimilar(def, i)) {
|
---|
[8349] | 157 | if (def.getNoTileHeaders() != null) {
|
---|
| 158 | i.setNoTileHeaders(def.getNoTileHeaders());
|
---|
| 159 | }
|
---|
| 160 | if (def.getTileSize() > 0) {
|
---|
| 161 | i.setTileSize(def.getTileSize());
|
---|
| 162 | }
|
---|
[8418] | 163 | if (def.getMetadataHeaders() != null && def.getMetadataHeaders().size() > 0) {
|
---|
| 164 | i.setMetadataHeaders(def.getMetadataHeaders());
|
---|
| 165 | }
|
---|
[8344] | 166 | changed = true;
|
---|
| 167 | }
|
---|
| 168 | }
|
---|
| 169 | }
|
---|
| 170 |
|
---|
[4240] | 171 | if (def.isDefaultEntry()) {
|
---|
| 172 | boolean isKnownDefault = false;
|
---|
[7186] | 173 | for (String url : knownDefaults) {
|
---|
[4240] | 174 | if (isSimilar(url, def.getUrl())) {
|
---|
| 175 | isKnownDefault = true;
|
---|
| 176 | break;
|
---|
| 177 | }
|
---|
[3978] | 178 | }
|
---|
[4240] | 179 | boolean isInUserList = false;
|
---|
| 180 | if (!isKnownDefault) {
|
---|
[7186] | 181 | newKnownDefaults.add(def.getUrl());
|
---|
[4240] | 182 | for (ImageryInfo i : layers) {
|
---|
[7186] | 183 | if (isSimilar(def, i)) {
|
---|
[4240] | 184 | isInUserList = true;
|
---|
| 185 | break;
|
---|
[3715] | 186 | }
|
---|
| 187 | }
|
---|
| 188 | }
|
---|
[4240] | 189 | if (!isKnownDefault && !isInUserList) {
|
---|
| 190 | add(new ImageryInfo(def));
|
---|
[7186] | 191 | changed = true;
|
---|
[4240] | 192 | }
|
---|
[3715] | 193 | }
|
---|
| 194 | }
|
---|
[7186] | 195 | Main.pref.putCollection("imagery.layers.default", newKnownDefaults);
|
---|
[3715] | 196 |
|
---|
[7186] | 197 | // Add ids to user entries without id.
|
---|
| 198 | // Only do this the first time for each id, so the user can have
|
---|
| 199 | // custom entries that don't get updated automatically
|
---|
| 200 | Collection<String> addedIds = Main.pref.getCollection("imagery.layers.addedIds");
|
---|
| 201 | Collection<String> newAddedIds = new TreeSet<>(addedIds);
|
---|
| 202 | for (ImageryInfo info : layers) {
|
---|
| 203 | for (ImageryInfo def : defaultLayers) {
|
---|
| 204 | if (isSimilar(def, info)) {
|
---|
| 205 | if (def.getId() != null && !addedIds.contains(def.getId())) {
|
---|
| 206 | if (!defaultLayerIds.containsKey(def.getId())) {
|
---|
| 207 | // ignore ids used more than once (have been purged from the map)
|
---|
| 208 | continue;
|
---|
| 209 | }
|
---|
| 210 | newAddedIds.add(def.getId());
|
---|
| 211 | if (info.getId() == null) {
|
---|
| 212 | info.setId(def.getId());
|
---|
| 213 | changed = true;
|
---|
| 214 | }
|
---|
| 215 | }
|
---|
| 216 | }
|
---|
| 217 | }
|
---|
| 218 | }
|
---|
[7203] | 219 | Main.pref.putCollection("imagery.layers.addedIds", newAddedIds);
|
---|
[7434] | 220 |
|
---|
[7186] | 221 | // automatically update user entries with same id as a default entry
|
---|
[8510] | 222 | for (int i = 0; i < layers.size(); i++) {
|
---|
[7186] | 223 | ImageryInfo info = layers.get(i);
|
---|
| 224 | if (info.getId() == null) {
|
---|
| 225 | continue;
|
---|
| 226 | }
|
---|
| 227 | ImageryInfo matchingDefault = defaultLayerIds.get(info.getId());
|
---|
| 228 | if (matchingDefault != null && !matchingDefault.equalsPref(info)) {
|
---|
| 229 | layers.set(i, matchingDefault);
|
---|
| 230 | changed = true;
|
---|
| 231 | }
|
---|
| 232 | }
|
---|
[7434] | 233 |
|
---|
[7186] | 234 | if (changed) {
|
---|
| 235 | save();
|
---|
| 236 | }
|
---|
[3715] | 237 | }
|
---|
[4428] | 238 |
|
---|
[7186] | 239 | private boolean isSimilar(ImageryInfo iiA, ImageryInfo iiB) {
|
---|
| 240 | if (iiA.getId() != null && iiB.getId() != null) return iiA.getId().equals(iiB.getId());
|
---|
| 241 | return isSimilar(iiA.getUrl(), iiB.getUrl());
|
---|
| 242 | }
|
---|
[7434] | 243 |
|
---|
[4240] | 244 | // some additional checks to respect extended URLs in preferences (legacy workaround)
|
---|
| 245 | private boolean isSimilar(String a, String b) {
|
---|
[7083] | 246 | return Objects.equals(a, b) || (a != null && b != null && !a.isEmpty() && !b.isEmpty() && (a.contains(b) || b.contains(a)));
|
---|
[4240] | 247 | }
|
---|
[7434] | 248 |
|
---|
[3715] | 249 | public void add(ImageryInfo info) {
|
---|
| 250 | layers.add(info);
|
---|
| 251 | }
|
---|
| 252 |
|
---|
| 253 | public void remove(ImageryInfo info) {
|
---|
| 254 | layers.remove(info);
|
---|
| 255 | }
|
---|
| 256 |
|
---|
| 257 | public void save() {
|
---|
[7005] | 258 | List<ImageryPreferenceEntry> entries = new ArrayList<>();
|
---|
[3715] | 259 | for (ImageryInfo info : layers) {
|
---|
[4450] | 260 | entries.add(new ImageryPreferenceEntry(info));
|
---|
[3715] | 261 | }
|
---|
[4450] | 262 | Main.pref.putListOfStructs("imagery.entries", entries, ImageryPreferenceEntry.class);
|
---|
[3715] | 263 | }
|
---|
| 264 |
|
---|
| 265 | public List<ImageryInfo> getLayers() {
|
---|
| 266 | return Collections.unmodifiableList(layers);
|
---|
| 267 | }
|
---|
| 268 |
|
---|
| 269 | public List<ImageryInfo> getDefaultLayers() {
|
---|
| 270 | return Collections.unmodifiableList(defaultLayers);
|
---|
| 271 | }
|
---|
| 272 |
|
---|
| 273 | public static void addLayer(ImageryInfo info) {
|
---|
| 274 | instance.add(info);
|
---|
| 275 | instance.save();
|
---|
| 276 | }
|
---|
[5369] | 277 |
|
---|
| 278 | public static void addLayers(Collection<ImageryInfo> infos) {
|
---|
| 279 | for (ImageryInfo i : infos) {
|
---|
| 280 | instance.add(i);
|
---|
| 281 | }
|
---|
| 282 | instance.save();
|
---|
| 283 | Collections.sort(instance.layers);
|
---|
| 284 | }
|
---|
[7434] | 285 |
|
---|
[7186] | 286 | /**
|
---|
| 287 | * Get unique id for ImageryInfo.
|
---|
[7434] | 288 | *
|
---|
[7186] | 289 | * This takes care, that no id is used twice (due to a user error)
|
---|
| 290 | * @param info the ImageryInfo to look up
|
---|
| 291 | * @return null, if there is no id or the id is used twice,
|
---|
| 292 | * the corresponding id otherwise
|
---|
| 293 | */
|
---|
| 294 | public String getUniqueId(ImageryInfo info) {
|
---|
| 295 | if (info.getId() != null && layerIds.get(info.getId()) == info) {
|
---|
| 296 | return info.getId();
|
---|
| 297 | }
|
---|
| 298 | return null;
|
---|
| 299 | }
|
---|
[3715] | 300 | }
|
---|