source: josm/trunk/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java@ 16436

Last change on this file since 16436 was 16436, checked in by simon04, 4 years ago

see #19251 - Java 8: use Stream

  • Property svn:eol-style set to native
File size: 56.6 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.awt.Image;
7import java.io.StringReader;
8import java.util.ArrayList;
9import java.util.Arrays;
10import java.util.Collection;
11import java.util.Collections;
12import java.util.EnumMap;
13import java.util.HashMap;
14import java.util.List;
15import java.util.Locale;
16import java.util.Map;
17import java.util.Objects;
18import java.util.Optional;
19import java.util.Set;
20import java.util.TreeSet;
21import java.util.concurrent.TimeUnit;
22import java.util.regex.Matcher;
23import java.util.regex.Pattern;
24import java.util.stream.Collectors;
25
26import javax.json.Json;
27import javax.json.JsonObject;
28import javax.json.JsonReader;
29import javax.json.stream.JsonCollectors;
30import javax.swing.ImageIcon;
31
32import org.openstreetmap.gui.jmapviewer.interfaces.Attributed;
33import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
34import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTileSource;
35import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.Mapnik;
36import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo;
37import org.openstreetmap.josm.data.Bounds;
38import org.openstreetmap.josm.data.StructUtils;
39import org.openstreetmap.josm.data.StructUtils.StructEntry;
40import org.openstreetmap.josm.io.Capabilities;
41import org.openstreetmap.josm.io.OsmApi;
42import org.openstreetmap.josm.spi.preferences.Config;
43import org.openstreetmap.josm.spi.preferences.IPreferences;
44import org.openstreetmap.josm.tools.CheckParameterUtil;
45import org.openstreetmap.josm.tools.ImageProvider;
46import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
47import org.openstreetmap.josm.tools.LanguageInfo;
48import org.openstreetmap.josm.tools.Logging;
49import org.openstreetmap.josm.tools.MultiMap;
50import org.openstreetmap.josm.tools.StreamUtils;
51import org.openstreetmap.josm.tools.Utils;
52
53/**
54 * Class that stores info about an image background layer.
55 *
56 * @author Frederik Ramm
57 */
58public class ImageryInfo extends TileSourceInfo implements Comparable<ImageryInfo>, Attributed {
59
60 /**
61 * Type of imagery entry.
62 */
63 public enum ImageryType {
64 /** A WMS (Web Map Service) entry. **/
65 WMS("wms"),
66 /** A TMS (Tile Map Service) entry. **/
67 TMS("tms"),
68 /** TMS entry for Microsoft Bing. */
69 BING("bing"),
70 /** TMS entry for Russian company <a href="https://wiki.openstreetmap.org/wiki/WikiProject_Russia/kosmosnimki">ScanEx</a>. **/
71 SCANEX("scanex"),
72 /** A WMS endpoint entry only stores the WMS server info, without layer, which are chosen later by the user. **/
73 WMS_ENDPOINT("wms_endpoint"),
74 /** WMTS stores GetCapabilities URL. Does not store any information about the layer **/
75 WMTS("wmts");
76
77 private final String typeString;
78
79 ImageryType(String typeString) {
80 this.typeString = typeString;
81 }
82
83 /**
84 * Returns the unique string identifying this type.
85 * @return the unique string identifying this type
86 * @since 6690
87 */
88 public final String getTypeString() {
89 return typeString;
90 }
91
92 /**
93 * Returns the imagery type from the given type string.
94 * @param s The type string
95 * @return the imagery type matching the given type string
96 */
97 public static ImageryType fromString(String s) {
98 return Arrays.stream(ImageryType.values())
99 .filter(type -> type.getTypeString().equals(s))
100 .findFirst().orElse(null);
101 }
102 }
103
104 /**
105 * Category of imagery entry.
106 * @since 13792
107 */
108 public enum ImageryCategory {
109 /** A aerial or satellite photo. **/
110 PHOTO(/* ICON(data/imagery/) */ "photo", tr("Aerial or satellite photo")),
111 /** A map of digital terrain model, digital surface model or contour lines. **/
112 ELEVATION(/* ICON(data/imagery/) */ "elevation", tr("Elevation map")),
113 /** A map. **/
114 MAP(/* ICON(data/imagery/) */ "map", tr("Map")),
115 /** A historic or otherwise outdated map. */
116 HISTORICMAP(/* ICON(data/imagery/) */ "historicmap", tr("Historic or otherwise outdated map")),
117 /** A map based on OSM data. **/
118 OSMBASEDMAP(/* ICON(data/imagery/) */ "osmbasedmap", tr("Map based on OSM data")),
119 /** A historic or otherwise outdated aerial or satellite photo. **/
120 HISTORICPHOTO(/* ICON(data/imagery/) */ "historicphoto", tr("Historic or otherwise outdated aerial or satellite photo")),
121 /** A map for quality assurance **/
122 QUALITY_ASSURANCE(/* ICON(data/imagery/) */ "qa", tr("Map for quality assurance")),
123 /** Any other type of imagery **/
124 OTHER(/* ICON(data/imagery/) */ "other", tr("Imagery not matching any other category"));
125
126 private final String category;
127 private final String description;
128 private static final Map<ImageSizes, Map<ImageryCategory, ImageIcon>> iconCache =
129 Collections.synchronizedMap(new EnumMap<>(ImageSizes.class));
130
131 ImageryCategory(String category, String description) {
132 this.category = category;
133 this.description = description;
134 }
135
136 /**
137 * Returns the unique string identifying this category.
138 * @return the unique string identifying this category
139 */
140 public final String getCategoryString() {
141 return category;
142 }
143
144 /**
145 * Returns the description of this category.
146 * @return the description of this category
147 */
148 public final String getDescription() {
149 return description;
150 }
151
152 /**
153 * Returns the category icon at the given size.
154 * @param size icon wanted size
155 * @return the category icon at the given size
156 * @since 15049
157 */
158 public final ImageIcon getIcon(ImageSizes size) {
159 return iconCache
160 .computeIfAbsent(size, x -> Collections.synchronizedMap(new EnumMap<>(ImageryCategory.class)))
161 .computeIfAbsent(this, x -> ImageProvider.get("data/imagery", x.category, size));
162 }
163
164 /**
165 * Returns the imagery category from the given category string.
166 * @param s The category string
167 * @return the imagery category matching the given category string
168 */
169 public static ImageryCategory fromString(String s) {
170 return Arrays.stream(ImageryCategory.values())
171 .filter(category -> category.getCategoryString().equals(s))
172 .findFirst().orElse(null);
173 }
174 }
175
176 /**
177 * Multi-polygon bounds for imagery backgrounds.
178 * Used to display imagery coverage in preferences and to determine relevant imagery entries based on edit location.
179 */
180 public static class ImageryBounds extends Bounds {
181
182 /**
183 * Constructs a new {@code ImageryBounds} from string.
184 * @param asString The string containing the list of shapes defining this bounds
185 * @param separator The shape separator in the given string, usually a comma
186 */
187 public ImageryBounds(String asString, String separator) {
188 super(asString, separator);
189 }
190
191 private List<Shape> shapes = new ArrayList<>();
192
193 /**
194 * Adds a new shape to this bounds.
195 * @param shape The shape to add
196 */
197 public final void addShape(Shape shape) {
198 this.shapes.add(shape);
199 }
200
201 /**
202 * Sets the list of shapes defining this bounds.
203 * @param shapes The list of shapes defining this bounds.
204 */
205 public final void setShapes(List<Shape> shapes) {
206 this.shapes = shapes;
207 }
208
209 /**
210 * Returns the list of shapes defining this bounds.
211 * @return The list of shapes defining this bounds
212 */
213 public final List<Shape> getShapes() {
214 return shapes;
215 }
216
217 @Override
218 public int hashCode() {
219 return Objects.hash(super.hashCode(), shapes);
220 }
221
222 @Override
223 public boolean equals(Object o) {
224 if (this == o) return true;
225 if (o == null || getClass() != o.getClass()) return false;
226 if (!super.equals(o)) return false;
227 ImageryBounds that = (ImageryBounds) o;
228 return Objects.equals(shapes, that.shapes);
229 }
230 }
231
232 /** original name of the imagery entry in case of translation call, for multiple languages English when possible */
233 private String origName;
234 /** (original) language of the translated name entry */
235 private String langName;
236 /** whether this is a entry activated by default or not */
237 private boolean defaultEntry;
238 /** Whether this service requires a explicit EULA acceptance before it can be activated */
239 private String eulaAcceptanceRequired;
240 /** type of the imagery servics - WMS, TMS, ... */
241 private ImageryType imageryType = ImageryType.WMS;
242 private double pixelPerDegree;
243 /** maximum zoom level for TMS imagery */
244 private int defaultMaxZoom;
245 /** minimum zoom level for TMS imagery */
246 private int defaultMinZoom;
247 /** display bounds of imagery, displayed in prefs and used for automatic imagery selection */
248 private ImageryBounds bounds;
249 /** projections supported by WMS servers */
250 private List<String> serverProjections = Collections.emptyList();
251 /** description of the imagery entry, should contain notes what type of data it is */
252 private String description;
253 /** language of the description entry */
254 private String langDescription;
255 /** Text of a text attribution displayed when using the imagery */
256 private String attributionText;
257 /** Link to the privacy policy of the operator */
258 private String privacyPolicyURL;
259 /** Link to a reference stating the permission for OSM usage */
260 private String permissionReferenceURL;
261 /** Link behind the text attribution displayed when using the imagery */
262 private String attributionLinkURL;
263 /** Image of a graphical attribution displayed when using the imagery */
264 private String attributionImage;
265 /** Link behind the graphical attribution displayed when using the imagery */
266 private String attributionImageURL;
267 /** Text with usage terms displayed when using the imagery */
268 private String termsOfUseText;
269 /** Link behind the text with usage terms displayed when using the imagery */
270 private String termsOfUseURL;
271 /** country code of the imagery (for country specific imagery) */
272 private String countryCode = "";
273 /**
274 * creation date of the imagery (in the form YYYY-MM-DD;YYYY-MM-DD, where
275 * DD and MM as well as a second date are optional).
276 *
277 * Also used as time filter for WMS time={time} parameter (such as Sentinel-2)
278 * @since 11570
279 */
280 private String date;
281 /**
282 * marked as best in other editors
283 * @since 11575
284 */
285 private boolean bestMarked;
286 /**
287 * marked as overlay
288 * @since 13536
289 */
290 private boolean overlay;
291 /**
292 * list of old IDs, only for loading, not handled anywhere else
293 * @since 13536
294 */
295 private Collection<String> oldIds;
296 /** mirrors of different type for this entry */
297 private List<ImageryInfo> mirrors;
298 /** icon used in menu */
299 private String icon;
300 /** is the geo reference correct - don't offer offset handling */
301 private boolean isGeoreferenceValid;
302 /** which layers should be activated by default on layer addition. **/
303 private List<DefaultLayer> defaultLayers = Collections.emptyList();
304 /** HTTP headers **/
305 private Map<String, String> customHttpHeaders = Collections.emptyMap();
306 /** Should this map be transparent **/
307 private boolean transparent = true;
308 private int minimumTileExpire = (int) TimeUnit.MILLISECONDS.toSeconds(TMSCachedTileLoaderJob.MINIMUM_EXPIRES.get());
309 /** category of the imagery */
310 private ImageryCategory category;
311 /** category of the imagery (input string, not saved, copied or used otherwise except for error checks) */
312 private String categoryOriginalString;
313 /** when adding a field, also adapt the:
314 * {@link #ImageryPreferenceEntry ImageryPreferenceEntry object}
315 * {@link #ImageryPreferenceEntry#ImageryPreferenceEntry(ImageryInfo) ImageryPreferenceEntry constructor}
316 * {@link #ImageryInfo(ImageryPreferenceEntry) ImageryInfo constructor}
317 * {@link #ImageryInfo(ImageryInfo) ImageryInfo constructor}
318 * {@link #equalsPref(ImageryPreferenceEntry) equalsPref method}
319 **/
320
321 /**
322 * Auxiliary class to save an {@link ImageryInfo} object in the preferences.
323 */
324 public static class ImageryPreferenceEntry {
325 @StructEntry String name;
326 @StructEntry String d;
327 @StructEntry String id;
328 @StructEntry String type;
329 @StructEntry String url;
330 @StructEntry double pixel_per_eastnorth;
331 @StructEntry String eula;
332 @StructEntry String attribution_text;
333 @StructEntry String attribution_url;
334 @StructEntry String permission_reference_url;
335 @StructEntry String logo_image;
336 @StructEntry String logo_url;
337 @StructEntry String terms_of_use_text;
338 @StructEntry String terms_of_use_url;
339 @StructEntry String country_code = "";
340 @StructEntry String date;
341 @StructEntry int max_zoom;
342 @StructEntry int min_zoom;
343 @StructEntry String cookies;
344 @StructEntry String bounds;
345 @StructEntry String shapes;
346 @StructEntry String projections;
347 @StructEntry String icon;
348 @StructEntry String description;
349 @StructEntry MultiMap<String, String> noTileHeaders;
350 @StructEntry MultiMap<String, String> noTileChecksums;
351 @StructEntry int tileSize = -1;
352 @StructEntry Map<String, String> metadataHeaders;
353 @StructEntry boolean valid_georeference;
354 @StructEntry boolean bestMarked;
355 @StructEntry boolean modTileFeatures;
356 @StructEntry boolean overlay;
357 @StructEntry String default_layers;
358 @StructEntry Map<String, String> customHttpHeaders;
359 @StructEntry boolean transparent;
360 @StructEntry int minimumTileExpire;
361 @StructEntry String category;
362
363 /**
364 * Constructs a new empty WMS {@code ImageryPreferenceEntry}.
365 */
366 public ImageryPreferenceEntry() {
367 // Do nothing
368 }
369
370 /**
371 * Constructs a new {@code ImageryPreferenceEntry} from a given {@code ImageryInfo}.
372 * @param i The corresponding imagery info
373 */
374 public ImageryPreferenceEntry(ImageryInfo i) {
375 name = i.name;
376 id = i.id;
377 type = i.imageryType.getTypeString();
378 url = i.url;
379 pixel_per_eastnorth = i.pixelPerDegree;
380 eula = i.eulaAcceptanceRequired;
381 attribution_text = i.attributionText;
382 attribution_url = i.attributionLinkURL;
383 permission_reference_url = i.permissionReferenceURL;
384 date = i.date;
385 bestMarked = i.bestMarked;
386 overlay = i.overlay;
387 logo_image = i.attributionImage;
388 logo_url = i.attributionImageURL;
389 terms_of_use_text = i.termsOfUseText;
390 terms_of_use_url = i.termsOfUseURL;
391 country_code = i.countryCode;
392 max_zoom = i.defaultMaxZoom;
393 min_zoom = i.defaultMinZoom;
394 cookies = i.cookies;
395 icon = intern(i.icon);
396 description = i.description;
397 category = i.category != null ? i.category.getCategoryString() : null;
398 if (i.bounds != null) {
399 bounds = i.bounds.encodeAsString(",");
400 String shapesString = Shape.encodeAsString(i.bounds.getShapes());
401 if (!shapesString.isEmpty()) {
402 shapes = shapesString;
403 }
404 }
405 if (!i.serverProjections.isEmpty()) {
406 projections = String.join(",", i.serverProjections);
407 }
408 if (i.noTileHeaders != null && !i.noTileHeaders.isEmpty()) {
409 noTileHeaders = new MultiMap<>(i.noTileHeaders);
410 }
411
412 if (i.noTileChecksums != null && !i.noTileChecksums.isEmpty()) {
413 noTileChecksums = new MultiMap<>(i.noTileChecksums);
414 }
415
416 if (i.metadataHeaders != null && !i.metadataHeaders.isEmpty()) {
417 metadataHeaders = i.metadataHeaders;
418 }
419
420 tileSize = i.getTileSize();
421
422 valid_georeference = i.isGeoreferenceValid();
423 modTileFeatures = i.isModTileFeatures();
424 if (!i.defaultLayers.isEmpty()) {
425 default_layers = i.defaultLayers.stream().map(DefaultLayer::toJson).collect(JsonCollectors.toJsonArray()).toString();
426 }
427 customHttpHeaders = i.customHttpHeaders;
428 transparent = i.isTransparent();
429 minimumTileExpire = i.minimumTileExpire;
430 }
431
432 @Override
433 public String toString() {
434 StringBuilder s = new StringBuilder("ImageryPreferenceEntry [name=").append(name);
435 if (id != null) {
436 s.append(" id=").append(id);
437 }
438 s.append(']');
439 return s.toString();
440 }
441 }
442
443 /**
444 * Constructs a new WMS {@code ImageryInfo}.
445 */
446 public ImageryInfo() {
447 super();
448 }
449
450 /**
451 * Constructs a new WMS {@code ImageryInfo} with a given name.
452 * @param name The entry name
453 */
454 public ImageryInfo(String name) {
455 super(name);
456 }
457
458 /**
459 * Constructs a new WMS {@code ImageryInfo} with given name and extended URL.
460 * @param name The entry name
461 * @param url The entry extended URL
462 */
463 public ImageryInfo(String name, String url) {
464 this(name);
465 setExtendedUrl(url);
466 }
467
468 /**
469 * Constructs a new WMS {@code ImageryInfo} with given name, extended and EULA URLs.
470 * @param name The entry name
471 * @param url The entry URL
472 * @param eulaAcceptanceRequired The EULA URL
473 */
474 public ImageryInfo(String name, String url, String eulaAcceptanceRequired) {
475 this(name);
476 setExtendedUrl(url);
477 this.eulaAcceptanceRequired = eulaAcceptanceRequired;
478 }
479
480 /**
481 * Constructs a new {@code ImageryInfo} with given name, url, extended and EULA URLs.
482 * @param name The entry name
483 * @param url The entry URL
484 * @param type The entry imagery type. If null, WMS will be used as default
485 * @param eulaAcceptanceRequired The EULA URL
486 * @param cookies The data part of HTTP cookies header in case the service requires cookies to work
487 * @throws IllegalArgumentException if type refers to an unknown imagery type
488 */
489 public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies) {
490 this(name);
491 setExtendedUrl(url);
492 ImageryType t = ImageryType.fromString(type);
493 this.cookies = cookies;
494 this.eulaAcceptanceRequired = eulaAcceptanceRequired;
495 if (t != null) {
496 this.imageryType = t;
497 } else if (type != null && !type.isEmpty()) {
498 throw new IllegalArgumentException("unknown type: "+type);
499 }
500 }
501
502 /**
503 * Constructs a new {@code ImageryInfo} with given name, url, id, extended and EULA URLs.
504 * @param name The entry name
505 * @param url The entry URL
506 * @param type The entry imagery type. If null, WMS will be used as default
507 * @param eulaAcceptanceRequired The EULA URL
508 * @param cookies The data part of HTTP cookies header in case the service requires cookies to work
509 * @param id tile id
510 * @throws IllegalArgumentException if type refers to an unknown imagery type
511 */
512 public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies, String id) {
513 this(name, url, type, eulaAcceptanceRequired, cookies);
514 setId(id);
515 }
516
517 /**
518 * Constructs a new {@code ImageryInfo} from an imagery preference entry.
519 * @param e The imagery preference entry
520 */
521 public ImageryInfo(ImageryPreferenceEntry e) {
522 super(e.name, e.url, e.id);
523 CheckParameterUtil.ensureParameterNotNull(e.name, "name");
524 CheckParameterUtil.ensureParameterNotNull(e.url, "url");
525 description = e.description;
526 cookies = e.cookies;
527 eulaAcceptanceRequired = e.eula;
528 imageryType = ImageryType.fromString(e.type);
529 if (imageryType == null) throw new IllegalArgumentException("unknown type");
530 pixelPerDegree = e.pixel_per_eastnorth;
531 defaultMaxZoom = e.max_zoom;
532 defaultMinZoom = e.min_zoom;
533 if (e.bounds != null) {
534 bounds = new ImageryBounds(e.bounds, ",");
535 if (e.shapes != null) {
536 try {
537 for (String s : e.shapes.split(";")) {
538 bounds.addShape(new Shape(s, ","));
539 }
540 } catch (IllegalArgumentException ex) {
541 Logging.warn(ex);
542 }
543 }
544 }
545 if (e.projections != null && !e.projections.isEmpty()) {
546 // split generates null element on empty string which gives one element Array[null]
547 setServerProjections(Arrays.asList(e.projections.split(",")));
548 }
549 attributionText = intern(e.attribution_text);
550 attributionLinkURL = e.attribution_url;
551 permissionReferenceURL = e.permission_reference_url;
552 attributionImage = e.logo_image;
553 attributionImageURL = e.logo_url;
554 date = e.date;
555 bestMarked = e.bestMarked;
556 overlay = e.overlay;
557 termsOfUseText = e.terms_of_use_text;
558 termsOfUseURL = e.terms_of_use_url;
559 countryCode = intern(e.country_code);
560 icon = intern(e.icon);
561 if (e.noTileHeaders != null) {
562 noTileHeaders = e.noTileHeaders.toMap();
563 }
564 if (e.noTileChecksums != null) {
565 noTileChecksums = e.noTileChecksums.toMap();
566 }
567 setTileSize(e.tileSize);
568 metadataHeaders = e.metadataHeaders;
569 isGeoreferenceValid = e.valid_georeference;
570 modTileFeatures = e.modTileFeatures;
571 if (e.default_layers != null) {
572 try (JsonReader jsonReader = Json.createReader(new StringReader(e.default_layers))) {
573 defaultLayers = jsonReader.
574 readArray().
575 stream().
576 map(x -> DefaultLayer.fromJson((JsonObject) x, imageryType)).
577 collect(Collectors.toList());
578 }
579 }
580 setCustomHttpHeaders(e.customHttpHeaders);
581 transparent = e.transparent;
582 minimumTileExpire = e.minimumTileExpire;
583 category = ImageryCategory.fromString(e.category);
584 }
585
586 /**
587 * Constructs a new {@code ImageryInfo} from an existing one.
588 * @param i The other imagery info
589 */
590 public ImageryInfo(ImageryInfo i) {
591 super(i.name, i.url, i.id);
592 this.noTileHeaders = i.noTileHeaders;
593 this.noTileChecksums = i.noTileChecksums;
594 this.minZoom = i.minZoom;
595 this.maxZoom = i.maxZoom;
596 this.cookies = i.cookies;
597 this.tileSize = i.tileSize;
598 this.metadataHeaders = i.metadataHeaders;
599 this.modTileFeatures = i.modTileFeatures;
600
601 this.origName = i.origName;
602 this.langName = i.langName;
603 this.defaultEntry = i.defaultEntry;
604 this.eulaAcceptanceRequired = null;
605 this.imageryType = i.imageryType;
606 this.pixelPerDegree = i.pixelPerDegree;
607 this.defaultMaxZoom = i.defaultMaxZoom;
608 this.defaultMinZoom = i.defaultMinZoom;
609 this.bounds = i.bounds;
610 this.serverProjections = i.serverProjections;
611 this.description = i.description;
612 this.langDescription = i.langDescription;
613 this.attributionText = i.attributionText;
614 this.privacyPolicyURL = i.privacyPolicyURL;
615 this.permissionReferenceURL = i.permissionReferenceURL;
616 this.attributionLinkURL = i.attributionLinkURL;
617 this.attributionImage = i.attributionImage;
618 this.attributionImageURL = i.attributionImageURL;
619 this.termsOfUseText = i.termsOfUseText;
620 this.termsOfUseURL = i.termsOfUseURL;
621 this.countryCode = i.countryCode;
622 this.date = i.date;
623 this.bestMarked = i.bestMarked;
624 this.overlay = i.overlay;
625 // do not copy field {@code mirrors}
626 this.icon = intern(i.icon);
627 this.isGeoreferenceValid = i.isGeoreferenceValid;
628 setDefaultLayers(i.defaultLayers);
629 setCustomHttpHeaders(i.customHttpHeaders);
630 this.transparent = i.transparent;
631 this.minimumTileExpire = i.minimumTileExpire;
632 this.categoryOriginalString = intern(i.categoryOriginalString);
633 this.category = i.category;
634 }
635
636 @Override
637 public int hashCode() {
638 return Objects.hash(url, imageryType);
639 }
640
641 /**
642 * Check if this object equals another ImageryInfo with respect to the properties
643 * that get written to the preference file.
644 *
645 * The field {@link #pixelPerDegree} is ignored.
646 *
647 * @param other the ImageryInfo object to compare to
648 * @return true if they are equal
649 */
650 public boolean equalsPref(ImageryInfo other) {
651 if (other == null) {
652 return false;
653 }
654
655 // CHECKSTYLE.OFF: BooleanExpressionComplexity
656 return
657 Objects.equals(this.name, other.name) &&
658 Objects.equals(this.id, other.id) &&
659 Objects.equals(this.url, other.url) &&
660 Objects.equals(this.modTileFeatures, other.modTileFeatures) &&
661 Objects.equals(this.bestMarked, other.bestMarked) &&
662 Objects.equals(this.overlay, other.overlay) &&
663 Objects.equals(this.isGeoreferenceValid, other.isGeoreferenceValid) &&
664 Objects.equals(this.cookies, other.cookies) &&
665 Objects.equals(this.eulaAcceptanceRequired, other.eulaAcceptanceRequired) &&
666 Objects.equals(this.imageryType, other.imageryType) &&
667 Objects.equals(this.defaultMaxZoom, other.defaultMaxZoom) &&
668 Objects.equals(this.defaultMinZoom, other.defaultMinZoom) &&
669 Objects.equals(this.bounds, other.bounds) &&
670 Objects.equals(this.serverProjections, other.serverProjections) &&
671 Objects.equals(this.attributionText, other.attributionText) &&
672 Objects.equals(this.attributionLinkURL, other.attributionLinkURL) &&
673 Objects.equals(this.permissionReferenceURL, other.permissionReferenceURL) &&
674 Objects.equals(this.attributionImageURL, other.attributionImageURL) &&
675 Objects.equals(this.attributionImage, other.attributionImage) &&
676 Objects.equals(this.termsOfUseText, other.termsOfUseText) &&
677 Objects.equals(this.termsOfUseURL, other.termsOfUseURL) &&
678 Objects.equals(this.countryCode, other.countryCode) &&
679 Objects.equals(this.date, other.date) &&
680 Objects.equals(this.icon, other.icon) &&
681 Objects.equals(this.description, other.description) &&
682 Objects.equals(this.noTileHeaders, other.noTileHeaders) &&
683 Objects.equals(this.noTileChecksums, other.noTileChecksums) &&
684 Objects.equals(this.metadataHeaders, other.metadataHeaders) &&
685 Objects.equals(this.defaultLayers, other.defaultLayers) &&
686 Objects.equals(this.customHttpHeaders, other.customHttpHeaders) &&
687 Objects.equals(this.transparent, other.transparent) &&
688 Objects.equals(this.minimumTileExpire, other.minimumTileExpire) &&
689 Objects.equals(this.category, other.category);
690 // CHECKSTYLE.ON: BooleanExpressionComplexity
691 }
692
693 @Override
694 public boolean equals(Object o) {
695 if (this == o) return true;
696 if (o == null || getClass() != o.getClass()) return false;
697 ImageryInfo that = (ImageryInfo) o;
698 return imageryType == that.imageryType && Objects.equals(url, that.url);
699 }
700
701 private static final Map<String, String> localizedCountriesCache = new HashMap<>();
702 static {
703 localizedCountriesCache.put("", tr("Worldwide"));
704 }
705
706 /**
707 * Returns a localized name for the given country code, or "Worldwide" if empty.
708 * This function falls back on the English name, and uses the ISO code as a last-resortvalue.
709 *
710 * @param countryCode An ISO 3166 alpha-2 country code or a UN M.49 numeric-3 area code
711 * @return The name of the country appropriate to the current locale.
712 * @see Locale#getDisplayCountry
713 * @since 15158
714 */
715 public static String getLocalizedCountry(String countryCode) {
716 return localizedCountriesCache.computeIfAbsent(countryCode, code -> new Locale("en", code).getDisplayCountry());
717 }
718
719 @Override
720 public String toString() {
721 // Used in imagery preferences filtering, so must be efficient
722 return new StringBuilder(name)
723 .append('[').append(countryCode)
724 // appending the localized country in toString() allows us to filter imagery preferences table with it!
725 .append("] ('").append(getLocalizedCountry(countryCode)).append(')')
726 .append(" - ").append(url)
727 .append(" - ").append(imageryType)
728 .toString();
729 }
730
731 @Override
732 public int compareTo(ImageryInfo in) {
733 int i = countryCode.compareTo(in.countryCode);
734 if (i == 0) {
735 i = name.toLowerCase(Locale.ENGLISH).compareTo(in.name.toLowerCase(Locale.ENGLISH));
736 }
737 if (i == 0) {
738 i = url.compareTo(in.url);
739 }
740 if (i == 0) {
741 i = Double.compare(pixelPerDegree, in.pixelPerDegree);
742 }
743 return i;
744 }
745
746 /**
747 * Determines if URL is equal to given imagery info.
748 * @param in imagery info
749 * @return {@code true} if URL is equal to given imagery info
750 */
751 public boolean equalsBaseValues(ImageryInfo in) {
752 return url.equals(in.url);
753 }
754
755 /**
756 * Sets the pixel per degree value.
757 * @param ppd The ppd value
758 * @see #getPixelPerDegree()
759 */
760 public void setPixelPerDegree(double ppd) {
761 this.pixelPerDegree = ppd;
762 }
763
764 /**
765 * Sets the maximum zoom level.
766 * @param defaultMaxZoom The maximum zoom level
767 */
768 public void setDefaultMaxZoom(int defaultMaxZoom) {
769 this.defaultMaxZoom = defaultMaxZoom;
770 }
771
772 /**
773 * Sets the minimum zoom level.
774 * @param defaultMinZoom The minimum zoom level
775 */
776 public void setDefaultMinZoom(int defaultMinZoom) {
777 this.defaultMinZoom = defaultMinZoom;
778 }
779
780 /**
781 * Sets the imagery polygonial bounds.
782 * @param b The imagery bounds (non-rectangular)
783 */
784 public void setBounds(ImageryBounds b) {
785 this.bounds = b;
786 }
787
788 /**
789 * Returns the imagery polygonial bounds.
790 * @return The imagery bounds (non-rectangular)
791 */
792 public ImageryBounds getBounds() {
793 return bounds;
794 }
795
796 @Override
797 public boolean requiresAttribution() {
798 return attributionText != null || attributionLinkURL != null || attributionImage != null
799 || termsOfUseText != null || termsOfUseURL != null;
800 }
801
802 @Override
803 public String getAttributionText(int zoom, ICoordinate topLeft, ICoordinate botRight) {
804 return attributionText;
805 }
806
807 @Override
808 public String getAttributionLinkURL() {
809 return attributionLinkURL;
810 }
811
812 /**
813 * Return the permission reference URL.
814 * @return The url
815 * @see #setPermissionReferenceURL
816 * @since 11975
817 */
818 public String getPermissionReferenceURL() {
819 return permissionReferenceURL;
820 }
821
822 /**
823 * Return the privacy policy URL.
824 * @return The url
825 * @see #setPrivacyPolicyURL
826 * @since 16127
827 */
828 public String getPrivacyPolicyURL() {
829 return privacyPolicyURL;
830 }
831
832 @Override
833 public Image getAttributionImage() {
834 ImageIcon i = ImageProvider.getIfAvailable(attributionImage);
835 if (i != null) {
836 return i.getImage();
837 }
838 return null;
839 }
840
841 /**
842 * Return the raw attribution logo information (an URL to the image).
843 * @return The url text
844 * @since 12257
845 */
846 public String getAttributionImageRaw() {
847 return attributionImage;
848 }
849
850 @Override
851 public String getAttributionImageURL() {
852 return attributionImageURL;
853 }
854
855 @Override
856 public String getTermsOfUseText() {
857 return termsOfUseText;
858 }
859
860 @Override
861 public String getTermsOfUseURL() {
862 return termsOfUseURL;
863 }
864
865 /**
866 * Set the attribution text
867 * @param text The text
868 * @see #getAttributionText(int, ICoordinate, ICoordinate)
869 */
870 public void setAttributionText(String text) {
871 attributionText = intern(text);
872 }
873
874 /**
875 * Set the attribution image
876 * @param url The url of the image.
877 * @see #getAttributionImageURL()
878 */
879 public void setAttributionImageURL(String url) {
880 attributionImageURL = url;
881 }
882
883 /**
884 * Set the image for the attribution
885 * @param res The image resource
886 * @see #getAttributionImage()
887 */
888 public void setAttributionImage(String res) {
889 attributionImage = res;
890 }
891
892 /**
893 * Sets the URL the attribution should link to.
894 * @param url The url.
895 * @see #getAttributionLinkURL()
896 */
897 public void setAttributionLinkURL(String url) {
898 attributionLinkURL = url;
899 }
900
901 /**
902 * Sets the permission reference URL.
903 * @param url The url.
904 * @see #getPermissionReferenceURL()
905 * @since 11975
906 */
907 public void setPermissionReferenceURL(String url) {
908 permissionReferenceURL = url;
909 }
910
911 /**
912 * Sets the privacy policy URL.
913 * @param url The url.
914 * @see #getPrivacyPolicyURL()
915 * @since 16127
916 */
917 public void setPrivacyPolicyURL(String url) {
918 privacyPolicyURL = url;
919 }
920
921 /**
922 * Sets the text to display to the user as terms of use.
923 * @param text The text
924 * @see #getTermsOfUseText()
925 */
926 public void setTermsOfUseText(String text) {
927 termsOfUseText = text;
928 }
929
930 /**
931 * Sets a url that links to the terms of use text.
932 * @param text The url.
933 * @see #getTermsOfUseURL()
934 */
935 public void setTermsOfUseURL(String text) {
936 termsOfUseURL = text;
937 }
938
939 /**
940 * Sets the extended URL of this entry.
941 * @param url Entry extended URL containing in addition of service URL, its type and min/max zoom info
942 */
943 public void setExtendedUrl(String url) {
944 CheckParameterUtil.ensureParameterNotNull(url);
945
946 // Default imagery type is WMS
947 this.url = url;
948 this.imageryType = ImageryType.WMS;
949
950 defaultMaxZoom = 0;
951 defaultMinZoom = 0;
952 for (ImageryType type : ImageryType.values()) {
953 Matcher m = Pattern.compile(type.getTypeString()+"(?:\\[(?:(\\d+)[,-])?(\\d+)\\])?:(.*)").matcher(url);
954 if (m.matches()) {
955 this.url = m.group(3);
956 this.imageryType = type;
957 if (m.group(2) != null) {
958 defaultMaxZoom = Integer.parseInt(m.group(2));
959 }
960 if (m.group(1) != null) {
961 defaultMinZoom = Integer.parseInt(m.group(1));
962 }
963 break;
964 }
965 }
966
967 if (serverProjections.isEmpty()) {
968 Matcher m = Pattern.compile(".*\\{PROJ\\(([^)}]+)\\)\\}.*").matcher(url.toUpperCase(Locale.ENGLISH));
969 if (m.matches()) {
970 setServerProjections(Arrays.asList(m.group(1).split(",")));
971 }
972 }
973 }
974
975 /**
976 * Returns the entry name.
977 * @return The entry name
978 * @since 6968
979 */
980 public String getOriginalName() {
981 return this.origName != null ? this.origName : this.name;
982 }
983
984 /**
985 * Sets the entry name and handle translation.
986 * @param language The used language
987 * @param name The entry name
988 * @since 8091
989 */
990 public void setName(String language, String name) {
991 boolean isdefault = LanguageInfo.getJOSMLocaleCode(null).equals(language);
992 if (LanguageInfo.isBetterLanguage(langName, language)) {
993 this.name = isdefault ? tr(name) : name;
994 this.langName = language;
995 }
996 if (origName == null || isdefault) {
997 this.origName = name;
998 }
999 }
1000
1001 /**
1002 * Store the id of this info to the preferences and clear it afterwards.
1003 */
1004 public void clearId() {
1005 if (this.id != null) {
1006 Collection<String> newAddedIds = new TreeSet<>(Config.getPref().getList("imagery.layers.addedIds"));
1007 newAddedIds.add(this.id);
1008 Config.getPref().putList("imagery.layers.addedIds", new ArrayList<>(newAddedIds));
1009 }
1010 setId(null);
1011 }
1012
1013 /**
1014 * Determines if this entry is enabled by default.
1015 * @return {@code true} if this entry is enabled by default, {@code false} otherwise
1016 */
1017 public boolean isDefaultEntry() {
1018 return defaultEntry;
1019 }
1020
1021 /**
1022 * Sets the default state of this entry.
1023 * @param defaultEntry {@code true} if this entry has to be enabled by default, {@code false} otherwise
1024 */
1025 public void setDefaultEntry(boolean defaultEntry) {
1026 this.defaultEntry = defaultEntry;
1027 }
1028
1029 /**
1030 * Gets the pixel per degree value
1031 * @return The ppd value.
1032 */
1033 public double getPixelPerDegree() {
1034 return this.pixelPerDegree;
1035 }
1036
1037 /**
1038 * Returns the maximum zoom level.
1039 * @return The maximum zoom level
1040 */
1041 @Override
1042 public int getMaxZoom() {
1043 return this.defaultMaxZoom;
1044 }
1045
1046 /**
1047 * Returns the minimum zoom level.
1048 * @return The minimum zoom level
1049 */
1050 @Override
1051 public int getMinZoom() {
1052 return this.defaultMinZoom;
1053 }
1054
1055 /**
1056 * Returns the description text when existing.
1057 * @return The description
1058 * @since 8065
1059 */
1060 public String getDescription() {
1061 return this.description;
1062 }
1063
1064 /**
1065 * Sets the description text when existing.
1066 * @param language The used language
1067 * @param description the imagery description text
1068 * @since 8091
1069 */
1070 public void setDescription(String language, String description) {
1071 boolean isdefault = LanguageInfo.getJOSMLocaleCode(null).equals(language);
1072 if (LanguageInfo.isBetterLanguage(langDescription, language)) {
1073 this.description = isdefault ? tr(description) : description;
1074 this.langDescription = intern(language);
1075 }
1076 }
1077
1078 /**
1079 * Return the sorted list of activated Imagery IDs.
1080 * @return sorted list of activated Imagery IDs
1081 * @since 13536
1082 */
1083 public static Collection<String> getActiveIds() {
1084 IPreferences pref = Config.getPref();
1085 if (pref == null) {
1086 return Collections.emptyList();
1087 }
1088 List<ImageryPreferenceEntry> entries = StructUtils.getListOfStructs(pref, "imagery.entries", null, ImageryPreferenceEntry.class);
1089 if (entries == null) {
1090 return Collections.emptyList();
1091 }
1092 return entries.stream()
1093 .filter(prefEntry -> prefEntry.id != null && !prefEntry.id.isEmpty())
1094 .map(prefEntry -> prefEntry.id)
1095 .sorted()
1096 .collect(Collectors.toList());
1097 }
1098
1099 /**
1100 * Returns a tool tip text for display.
1101 * @return The text
1102 * @since 8065
1103 */
1104 public String getToolTipText() {
1105 StringBuilder res = new StringBuilder(getName());
1106 boolean html = false;
1107 String dateStr = getDate();
1108 if (dateStr != null && !dateStr.isEmpty()) {
1109 res.append("<br>").append(tr("Date of imagery: {0}", dateStr));
1110 html = true;
1111 }
1112 if (category != null && category.getDescription() != null) {
1113 res.append("<br>").append(tr("Imagery category: {0}", category.getDescription()));
1114 html = true;
1115 }
1116 if (bestMarked) {
1117 res.append("<br>").append(tr("This imagery is marked as best in this region in other editors."));
1118 html = true;
1119 }
1120 if (overlay) {
1121 res.append("<br>").append(tr("This imagery is an overlay."));
1122 html = true;
1123 }
1124 String desc = getDescription();
1125 if (desc != null && !desc.isEmpty()) {
1126 res.append("<br>").append(Utils.escapeReservedCharactersHTML(desc));
1127 html = true;
1128 }
1129 if (html) {
1130 res.insert(0, "<html>").append("</html>");
1131 }
1132 return res.toString();
1133 }
1134
1135 /**
1136 * Returns the EULA acceptance URL, if any.
1137 * @return The URL to an EULA text that has to be accepted before use, or {@code null}
1138 */
1139 public String getEulaAcceptanceRequired() {
1140 return eulaAcceptanceRequired;
1141 }
1142
1143 /**
1144 * Sets the EULA acceptance URL.
1145 * @param eulaAcceptanceRequired The URL to an EULA text that has to be accepted before use
1146 */
1147 public void setEulaAcceptanceRequired(String eulaAcceptanceRequired) {
1148 this.eulaAcceptanceRequired = eulaAcceptanceRequired;
1149 }
1150
1151 /**
1152 * Returns the ISO 3166-1-alpha-2 country code.
1153 * @return The country code (2 letters)
1154 */
1155 public String getCountryCode() {
1156 return countryCode;
1157 }
1158
1159 /**
1160 * Sets the ISO 3166-1-alpha-2 country code.
1161 * @param countryCode The country code (2 letters)
1162 */
1163 public void setCountryCode(String countryCode) {
1164 this.countryCode = intern(countryCode);
1165 }
1166
1167 /**
1168 * Returns the date information.
1169 * @return The date (in the form YYYY-MM-DD;YYYY-MM-DD, where
1170 * DD and MM as well as a second date are optional)
1171 * @since 11570
1172 */
1173 public String getDate() {
1174 return date;
1175 }
1176
1177 /**
1178 * Sets the date information.
1179 * @param date The date information
1180 * @since 11570
1181 */
1182 public void setDate(String date) {
1183 this.date = date;
1184 }
1185
1186 /**
1187 * Returns the entry icon.
1188 * @return The entry icon
1189 */
1190 public String getIcon() {
1191 return icon;
1192 }
1193
1194 /**
1195 * Sets the entry icon.
1196 * @param icon The entry icon
1197 */
1198 public void setIcon(String icon) {
1199 this.icon = intern(icon);
1200 }
1201
1202 /**
1203 * Get the projections supported by the server. Only relevant for
1204 * WMS-type ImageryInfo at the moment.
1205 * @return null, if no projections have been specified; the list
1206 * of supported projections otherwise.
1207 */
1208 public List<String> getServerProjections() {
1209 return Collections.unmodifiableList(serverProjections);
1210 }
1211
1212 /**
1213 * Sets the list of collections the server supports
1214 * @param serverProjections The list of supported projections
1215 */
1216 public void setServerProjections(Collection<String> serverProjections) {
1217 CheckParameterUtil.ensureParameterNotNull(serverProjections, "serverProjections");
1218 this.serverProjections = serverProjections.stream()
1219 .map(String::intern)
1220 .collect(StreamUtils.toUnmodifiableList());
1221 }
1222
1223 /**
1224 * Returns the extended URL, containing in addition of service URL, its type and min/max zoom info.
1225 * @return The extended URL
1226 */
1227 public String getExtendedUrl() {
1228 return imageryType.getTypeString() + (defaultMaxZoom != 0
1229 ? ('['+(defaultMinZoom != 0 ? (Integer.toString(defaultMinZoom) + ',') : "")+defaultMaxZoom+']') : "") + ':' + url;
1230 }
1231
1232 /**
1233 * Gets a unique toolbar key to store this layer as toolbar item
1234 * @return The kay.
1235 */
1236 public String getToolbarName() {
1237 String res = name;
1238 if (pixelPerDegree != 0) {
1239 res += "#PPD="+pixelPerDegree;
1240 }
1241 return res;
1242 }
1243
1244 /**
1245 * Gets the name that should be displayed in the menu to add this imagery layer.
1246 * @return The text.
1247 */
1248 public String getMenuName() {
1249 String res = name;
1250 if (pixelPerDegree != 0) {
1251 res += " ("+pixelPerDegree+')';
1252 }
1253 return res;
1254 }
1255
1256 /**
1257 * Determines if this entry requires attribution.
1258 * @return {@code true} if some attribution text has to be displayed, {@code false} otherwise
1259 */
1260 public boolean hasAttribution() {
1261 return attributionText != null;
1262 }
1263
1264 /**
1265 * Copies attribution from another {@code ImageryInfo}.
1266 * @param i The other imagery info to get attribution from
1267 */
1268 public void copyAttribution(ImageryInfo i) {
1269 this.attributionImage = i.attributionImage;
1270 this.attributionImageURL = i.attributionImageURL;
1271 this.attributionText = i.attributionText;
1272 this.attributionLinkURL = i.attributionLinkURL;
1273 this.termsOfUseText = i.termsOfUseText;
1274 this.termsOfUseURL = i.termsOfUseURL;
1275 }
1276
1277 /**
1278 * Applies the attribution from this object to a tile source.
1279 * @param s The tile source
1280 */
1281 public void setAttribution(AbstractTileSource s) {
1282 if (attributionText != null) {
1283 if ("osm".equals(attributionText)) {
1284 s.setAttributionText(new Mapnik().getAttributionText(0, null, null));
1285 } else {
1286 s.setAttributionText(attributionText);
1287 }
1288 }
1289 if (attributionLinkURL != null) {
1290 if ("osm".equals(attributionLinkURL)) {
1291 s.setAttributionLinkURL(new Mapnik().getAttributionLinkURL());
1292 } else {
1293 s.setAttributionLinkURL(attributionLinkURL);
1294 }
1295 }
1296 if (attributionImage != null) {
1297 ImageIcon i = ImageProvider.getIfAvailable(null, attributionImage);
1298 if (i != null) {
1299 s.setAttributionImage(i.getImage());
1300 }
1301 }
1302 if (attributionImageURL != null) {
1303 s.setAttributionImageURL(attributionImageURL);
1304 }
1305 if (termsOfUseText != null) {
1306 s.setTermsOfUseText(termsOfUseText);
1307 }
1308 if (termsOfUseURL != null) {
1309 if ("osm".equals(termsOfUseURL)) {
1310 s.setTermsOfUseURL(new Mapnik().getTermsOfUseURL());
1311 } else {
1312 s.setTermsOfUseURL(termsOfUseURL);
1313 }
1314 }
1315 }
1316
1317 /**
1318 * Returns the imagery type.
1319 * @return The imagery type
1320 */
1321 public ImageryType getImageryType() {
1322 return imageryType;
1323 }
1324
1325 /**
1326 * Sets the imagery type.
1327 * @param imageryType The imagery type
1328 */
1329 public void setImageryType(ImageryType imageryType) {
1330 this.imageryType = imageryType;
1331 }
1332
1333 /**
1334 * Returns the imagery category.
1335 * @return The imagery category
1336 * @since 13792
1337 */
1338 public ImageryCategory getImageryCategory() {
1339 return category;
1340 }
1341
1342 /**
1343 * Sets the imagery category.
1344 * @param category The imagery category
1345 * @since 13792
1346 */
1347 public void setImageryCategory(ImageryCategory category) {
1348 this.category = category;
1349 }
1350
1351 /**
1352 * Returns the imagery category original string (don't use except for error checks).
1353 * @return The imagery category original string
1354 * @since 13792
1355 */
1356 public String getImageryCategoryOriginalString() {
1357 return categoryOriginalString;
1358 }
1359
1360 /**
1361 * Sets the imagery category original string (don't use except for error checks).
1362 * @param categoryOriginalString The imagery category original string
1363 * @since 13792
1364 */
1365 public void setImageryCategoryOriginalString(String categoryOriginalString) {
1366 this.categoryOriginalString = intern(categoryOriginalString);
1367 }
1368
1369 /**
1370 * Returns true if this layer's URL is matched by one of the regular
1371 * expressions kept by the current OsmApi instance.
1372 * @return {@code true} is this entry is blacklisted, {@code false} otherwise
1373 */
1374 public boolean isBlacklisted() {
1375 Capabilities capabilities = OsmApi.getOsmApi().getCapabilities();
1376 return capabilities != null && capabilities.isOnImageryBlacklist(this.url);
1377 }
1378
1379 /**
1380 * Sets the map of &lt;header name, header value&gt; that if any of this header
1381 * will be returned, then this tile will be treated as "no tile at this zoom level"
1382 *
1383 * @param noTileHeaders Map of &lt;header name, header value&gt; which will be treated as "no tile at this zoom level"
1384 * @since 9613
1385 */
1386 public void setNoTileHeaders(MultiMap<String, String> noTileHeaders) {
1387 if (noTileHeaders == null || noTileHeaders.isEmpty()) {
1388 this.noTileHeaders = null;
1389 } else {
1390 this.noTileHeaders = noTileHeaders.toMap();
1391 }
1392 }
1393
1394 @Override
1395 public Map<String, Set<String>> getNoTileHeaders() {
1396 return noTileHeaders;
1397 }
1398
1399 /**
1400 * Sets the map of &lt;checksum type, checksum value&gt; that if any tile with that checksum
1401 * will be returned, then this tile will be treated as "no tile at this zoom level"
1402 *
1403 * @param noTileChecksums Map of &lt;checksum type, checksum value&gt; which will be treated as "no tile at this zoom level"
1404 * @since 9613
1405 */
1406 public void setNoTileChecksums(MultiMap<String, String> noTileChecksums) {
1407 if (noTileChecksums == null || noTileChecksums.isEmpty()) {
1408 this.noTileChecksums = null;
1409 } else {
1410 this.noTileChecksums = noTileChecksums.toMap();
1411 }
1412 }
1413
1414 @Override
1415 public Map<String, Set<String>> getNoTileChecksums() {
1416 return noTileChecksums;
1417 }
1418
1419 /**
1420 * Returns the map of &lt;header name, metadata key&gt; indicating, which HTTP headers should
1421 * be moved to metadata
1422 *
1423 * @param metadataHeaders map of &lt;header name, metadata key&gt; indicating, which HTTP headers should be moved to metadata
1424 * @since 8418
1425 */
1426 public void setMetadataHeaders(Map<String, String> metadataHeaders) {
1427 if (metadataHeaders == null || metadataHeaders.isEmpty()) {
1428 this.metadataHeaders = null;
1429 } else {
1430 this.metadataHeaders = metadataHeaders;
1431 }
1432 }
1433
1434 /**
1435 * Gets the flag if the georeference is valid.
1436 * @return <code>true</code> if it is valid.
1437 */
1438 public boolean isGeoreferenceValid() {
1439 return isGeoreferenceValid;
1440 }
1441
1442 /**
1443 * Sets an indicator that the georeference is valid
1444 * @param isGeoreferenceValid <code>true</code> if it is marked as valid.
1445 */
1446 public void setGeoreferenceValid(boolean isGeoreferenceValid) {
1447 this.isGeoreferenceValid = isGeoreferenceValid;
1448 }
1449
1450 /**
1451 * Returns the status of "best" marked status in other editors.
1452 * @return <code>true</code> if it is marked as best.
1453 * @since 11575
1454 */
1455 public boolean isBestMarked() {
1456 return bestMarked;
1457 }
1458
1459 /**
1460 * Returns the overlay indication.
1461 * @return <code>true</code> if it is an overlay.
1462 * @since 13536
1463 */
1464 public boolean isOverlay() {
1465 return overlay;
1466 }
1467
1468 /**
1469 * Sets an indicator that in other editors it is marked as best imagery
1470 * @param bestMarked <code>true</code> if it is marked as best in other editors.
1471 * @since 11575
1472 */
1473 public void setBestMarked(boolean bestMarked) {
1474 this.bestMarked = bestMarked;
1475 }
1476
1477 /**
1478 * Sets overlay indication
1479 * @param overlay <code>true</code> if it is an overlay.
1480 * @since 13536
1481 */
1482 public void setOverlay(boolean overlay) {
1483 this.overlay = overlay;
1484 }
1485
1486 /**
1487 * Adds an old Id.
1488 *
1489 * @param id the Id to be added
1490 * @since 13536
1491 */
1492 public void addOldId(String id) {
1493 if (oldIds == null) {
1494 oldIds = new ArrayList<>();
1495 }
1496 oldIds.add(id);
1497 }
1498
1499 /**
1500 * Get old Ids.
1501 *
1502 * @return collection of ids
1503 * @since 13536
1504 */
1505 public Collection<String> getOldIds() {
1506 return oldIds;
1507 }
1508
1509 /**
1510 * Adds a mirror entry. Mirror entries are completed with the data from the master entry
1511 * and only describe another method to access identical data.
1512 *
1513 * @param entry the mirror to be added
1514 * @since 9658
1515 */
1516 public void addMirror(ImageryInfo entry) {
1517 if (mirrors == null) {
1518 mirrors = new ArrayList<>();
1519 }
1520 mirrors.add(entry);
1521 }
1522
1523 /**
1524 * Returns the mirror entries. Entries are completed with master entry data.
1525 *
1526 * @return the list of mirrors
1527 * @since 9658
1528 */
1529 public List<ImageryInfo> getMirrors() {
1530 List<ImageryInfo> l = new ArrayList<>();
1531 if (mirrors != null) {
1532 int num = 1;
1533 for (ImageryInfo i : mirrors) {
1534 ImageryInfo n = new ImageryInfo(this);
1535 if (i.defaultMaxZoom != 0) {
1536 n.defaultMaxZoom = i.defaultMaxZoom;
1537 }
1538 if (i.defaultMinZoom != 0) {
1539 n.defaultMinZoom = i.defaultMinZoom;
1540 }
1541 n.setServerProjections(i.getServerProjections());
1542 n.url = i.url;
1543 n.imageryType = i.imageryType;
1544 if (i.getTileSize() != 0) {
1545 n.setTileSize(i.getTileSize());
1546 }
1547 if (i.getPrivacyPolicyURL() != null) {
1548 n.setPrivacyPolicyURL(i.getPrivacyPolicyURL());
1549 }
1550 if (n.id != null) {
1551 n.id = n.id + "_mirror"+num;
1552 }
1553 if (num > 1) {
1554 n.name = tr("{0} mirror server {1}", n.name, num);
1555 if (n.origName != null) {
1556 n.origName += " mirror server " + num;
1557 }
1558 } else {
1559 n.name = tr("{0} mirror server", n.name);
1560 if (n.origName != null) {
1561 n.origName += " mirror server";
1562 }
1563 }
1564 l.add(n);
1565 ++num;
1566 }
1567 }
1568 return l;
1569 }
1570
1571 /**
1572 * Returns default layers that should be shown for this Imagery (if at all supported by imagery provider)
1573 * If no layer is set to default and there is more than one imagery available, then user will be asked to choose the layer
1574 * to work on
1575 * @return Collection of the layer names
1576 */
1577 public List<DefaultLayer> getDefaultLayers() {
1578 return defaultLayers;
1579 }
1580
1581 /**
1582 * Sets the default layers that user will work with
1583 * @param layers set the list of default layers
1584 */
1585 public void setDefaultLayers(List<DefaultLayer> layers) {
1586 this.defaultLayers = Utils.toUnmodifiableList(layers);
1587 }
1588
1589 /**
1590 * Returns custom HTTP headers that should be sent with request towards imagery provider
1591 * @return headers
1592 */
1593 public Map<String, String> getCustomHttpHeaders() {
1594 return customHttpHeaders;
1595 }
1596
1597 /**
1598 * Sets custom HTTP headers that should be sent with request towards imagery provider
1599 * @param customHttpHeaders http headers
1600 */
1601 public void setCustomHttpHeaders(Map<String, String> customHttpHeaders) {
1602 this.customHttpHeaders = Utils.toUnmodifiableMap(customHttpHeaders);
1603 }
1604
1605 /**
1606 * Determines if this imagery should be transparent.
1607 * @return should this imagery be transparent
1608 */
1609 public boolean isTransparent() {
1610 return transparent;
1611 }
1612
1613 /**
1614 * Sets whether imagery should be transparent.
1615 * @param transparent set to true if imagery should be transparent
1616 */
1617 public void setTransparent(boolean transparent) {
1618 this.transparent = transparent;
1619 }
1620
1621 /**
1622 * Returns minimum tile expiration in seconds.
1623 * @return minimum tile expiration in seconds
1624 */
1625 public int getMinimumTileExpire() {
1626 return minimumTileExpire;
1627 }
1628
1629 /**
1630 * Sets minimum tile expiration in seconds.
1631 * @param minimumTileExpire minimum tile expiration in seconds
1632 */
1633 public void setMinimumTileExpire(int minimumTileExpire) {
1634 this.minimumTileExpire = minimumTileExpire;
1635 }
1636
1637 /**
1638 * Get a string representation of this imagery info suitable for the {@code source} changeset tag.
1639 * @return English name, if known
1640 * @since 13890
1641 */
1642 public String getSourceName() {
1643 if (ImageryType.BING == getImageryType()) {
1644 return "Bing";
1645 } else {
1646 if (id != null) {
1647 // Retrieve english name, unfortunately not saved in preferences
1648 Optional<ImageryInfo> infoEn = ImageryLayerInfo.allDefaultLayers.stream().filter(x -> id.equals(x.getId())).findAny();
1649 if (infoEn.isPresent()) {
1650 return infoEn.get().getOriginalName();
1651 }
1652 }
1653 return getOriginalName();
1654 }
1655 }
1656
1657 private static String intern(String string) {
1658 return string == null ? null : string.intern();
1659 }
1660}
Note: See TracBrowser for help on using the repository browser.