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

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

fix #19026 - Make imagery preferences code more generic (patch by taylor.smock)

  • Property svn:eol-style set to native
File size: 32.8 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.StringReader;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.EnumMap;
12import java.util.List;
13import java.util.Locale;
14import java.util.Map;
15import java.util.Objects;
16import java.util.Optional;
17import java.util.concurrent.TimeUnit;
18import java.util.regex.Matcher;
19import java.util.regex.Pattern;
20import java.util.stream.Collectors;
21
22import javax.json.Json;
23import javax.json.JsonObject;
24import javax.json.JsonReader;
25import javax.swing.ImageIcon;
26
27import org.openstreetmap.josm.data.StructUtils.StructEntry;
28import org.openstreetmap.josm.data.sources.ISourceCategory;
29import org.openstreetmap.josm.data.sources.ISourceType;
30import org.openstreetmap.josm.data.sources.SourceBounds;
31import org.openstreetmap.josm.data.sources.SourceInfo;
32import org.openstreetmap.josm.data.sources.SourcePreferenceEntry;
33import org.openstreetmap.josm.tools.CheckParameterUtil;
34import org.openstreetmap.josm.tools.ImageProvider;
35import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
36import org.openstreetmap.josm.tools.Logging;
37import org.openstreetmap.josm.tools.MultiMap;
38import org.openstreetmap.josm.tools.StreamUtils;
39import org.openstreetmap.josm.tools.Utils;
40
41/**
42 * Class that stores info about an image background layer.
43 *
44 * @author Frederik Ramm
45 */
46public class ImageryInfo extends
47 SourceInfo<ImageryInfo.ImageryCategory, ImageryInfo.ImageryType, ImageryInfo.ImageryBounds, ImageryInfo.ImageryPreferenceEntry> {
48
49 /**
50 * Type of imagery entry.
51 */
52 public enum ImageryType implements ISourceType<ImageryType> {
53 /** A WMS (Web Map Service) entry. **/
54 WMS("wms"),
55 /** A TMS (Tile Map Service) entry. **/
56 TMS("tms"),
57 /** TMS entry for Microsoft Bing. */
58 BING("bing"),
59 /** TMS entry for Russian company <a href="https://wiki.openstreetmap.org/wiki/WikiProject_Russia/kosmosnimki">ScanEx</a>. **/
60 SCANEX("scanex"),
61 /** A WMS endpoint entry only stores the WMS server info, without layer, which are chosen later by the user. **/
62 WMS_ENDPOINT("wms_endpoint"),
63 /** WMTS stores GetCapabilities URL. Does not store any information about the layer **/
64 WMTS("wmts");
65
66 private final String typeString;
67
68 ImageryType(String typeString) {
69 this.typeString = typeString;
70 }
71
72 /**
73 * Returns the unique string identifying this type.
74 * @return the unique string identifying this type
75 * @since 6690
76 */
77 @Override
78 public final String getTypeString() {
79 return typeString;
80 }
81
82 /**
83 * Returns the imagery type from the given type string.
84 * @param s The type string
85 * @return the imagery type matching the given type string
86 */
87 public static ImageryType fromString(String s) {
88 return Arrays.stream(ImageryType.values())
89 .filter(type -> type.getTypeString().equals(s))
90 .findFirst().orElse(null);
91 }
92
93 @Override
94 public ImageryType getFromString(String s) {
95 return fromString(s);
96 }
97
98 @Override
99 public ImageryType getDefault() {
100 return WMS;
101 }
102 }
103
104 /**
105 * Category of imagery entry.
106 * @since 13792
107 */
108 public enum ImageryCategory implements ISourceCategory<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 @Override
141 public final String getCategoryString() {
142 return category;
143 }
144
145 /**
146 * Returns the description of this category.
147 * @return the description of this category
148 */
149 @Override
150 public final String getDescription() {
151 return description;
152 }
153
154 /**
155 * Returns the category icon at the given size.
156 * @param size icon wanted size
157 * @return the category icon at the given size
158 * @since 15049
159 */
160 @Override
161 public final ImageIcon getIcon(ImageSizes size) {
162 return iconCache
163 .computeIfAbsent(size, x -> Collections.synchronizedMap(new EnumMap<>(ImageryCategory.class)))
164 .computeIfAbsent(this, x -> ImageProvider.get("data/imagery", x.category, size));
165 }
166
167 /**
168 * Returns the imagery category from the given category string.
169 * @param s The category string
170 * @return the imagery category matching the given category string
171 */
172 public static ImageryCategory fromString(String s) {
173 return Arrays.stream(ImageryCategory.values())
174 .filter(category -> category.getCategoryString().equals(s))
175 .findFirst().orElse(null);
176 }
177
178 @Override
179 public ImageryCategory getDefault() {
180 return OTHER;
181 }
182
183 @Override
184 public ImageryCategory getFromString(String s) {
185 return fromString(s);
186 }
187 }
188
189 /**
190 * Multi-polygon bounds for imagery backgrounds.
191 * Used to display imagery coverage in preferences and to determine relevant imagery entries based on edit location.
192 */
193 public static class ImageryBounds extends SourceBounds {
194
195 /**
196 * Constructs a new {@code ImageryBounds} from string.
197 * @param asString The string containing the list of shapes defining this bounds
198 * @param separator The shape separator in the given string, usually a comma
199 */
200 public ImageryBounds(String asString, String separator) {
201 super(asString, separator);
202 }
203 }
204
205 private double pixelPerDegree;
206 /** maximum zoom level for TMS imagery */
207 private int defaultMaxZoom;
208 /** minimum zoom level for TMS imagery */
209 private int defaultMinZoom;
210 /** projections supported by WMS servers */
211 private List<String> serverProjections = Collections.emptyList();
212 /**
213 * marked as best in other editors
214 * @since 11575
215 */
216 private boolean bestMarked;
217 /**
218 * marked as overlay
219 * @since 13536
220 */
221 private boolean overlay;
222
223 /** mirrors of different type for this entry */
224 protected List<ImageryInfo> mirrors;
225 /**
226 * Auxiliary class to save an {@link ImageryInfo} object in the preferences.
227 */
228 /** is the geo reference correct - don't offer offset handling */
229 private boolean isGeoreferenceValid;
230 /** Should this map be transparent **/
231 private boolean transparent = true;
232 private int minimumTileExpire = (int) TimeUnit.MILLISECONDS.toSeconds(TMSCachedTileLoaderJob.MINIMUM_EXPIRES.get());
233
234 /**
235 * The ImageryPreferenceEntry class for storing data in JOSM preferences.
236 *
237 * @author Frederik Ramm, modified by Taylor Smock
238 */
239 public static class ImageryPreferenceEntry extends SourcePreferenceEntry<ImageryInfo> {
240 @StructEntry String d;
241 @StructEntry double pixel_per_eastnorth;
242 @StructEntry int max_zoom;
243 @StructEntry int min_zoom;
244 @StructEntry String projections;
245 @StructEntry MultiMap<String, String> noTileHeaders;
246 @StructEntry MultiMap<String, String> noTileChecksums;
247 @StructEntry int tileSize = -1;
248 @StructEntry Map<String, String> metadataHeaders;
249 @StructEntry boolean valid_georeference;
250 @StructEntry boolean bestMarked;
251 @StructEntry boolean modTileFeatures;
252 @StructEntry boolean overlay;
253 @StructEntry boolean transparent;
254 @StructEntry int minimumTileExpire;
255
256 /**
257 * Constructs a new empty WMS {@code ImageryPreferenceEntry}.
258 */
259 public ImageryPreferenceEntry() {
260 super();
261 }
262
263 /**
264 * Constructs a new {@code ImageryPreferenceEntry} from a given {@code ImageryInfo}.
265 * @param i The corresponding imagery info
266 */
267 public ImageryPreferenceEntry(ImageryInfo i) {
268 super(i);
269 pixel_per_eastnorth = i.pixelPerDegree;
270 bestMarked = i.bestMarked;
271 overlay = i.overlay;
272 max_zoom = i.defaultMaxZoom;
273 min_zoom = i.defaultMinZoom;
274 if (!i.serverProjections.isEmpty()) {
275 projections = String.join(",", i.serverProjections);
276 }
277 if (i.noTileHeaders != null && !i.noTileHeaders.isEmpty()) {
278 noTileHeaders = new MultiMap<>(i.noTileHeaders);
279 }
280
281 if (i.noTileChecksums != null && !i.noTileChecksums.isEmpty()) {
282 noTileChecksums = new MultiMap<>(i.noTileChecksums);
283 }
284
285 if (i.metadataHeaders != null && !i.metadataHeaders.isEmpty()) {
286 metadataHeaders = i.metadataHeaders;
287 }
288
289 tileSize = i.getTileSize();
290
291 valid_georeference = i.isGeoreferenceValid();
292 modTileFeatures = i.isModTileFeatures();
293 transparent = i.isTransparent();
294 minimumTileExpire = i.minimumTileExpire;
295 }
296
297 @Override
298 public String toString() {
299 StringBuilder s = new StringBuilder("ImageryPreferenceEntry [name=").append(name);
300 if (id != null) {
301 s.append(" id=").append(id);
302 }
303 s.append(']');
304 return s.toString();
305 }
306 }
307
308 /**
309 * Constructs a new WMS {@code ImageryInfo}.
310 */
311 public ImageryInfo() {
312 super();
313 }
314
315 /**
316 * Constructs a new WMS {@code ImageryInfo} with a given name.
317 * @param name The entry name
318 */
319 public ImageryInfo(String name) {
320 super(name);
321 }
322
323 /**
324 * Constructs a new WMS {@code ImageryInfo} with given name and extended URL.
325 * @param name The entry name
326 * @param url The entry extended URL
327 */
328 public ImageryInfo(String name, String url) {
329 this(name);
330 setExtendedUrl(url);
331 }
332
333 /**
334 * Constructs a new WMS {@code ImageryInfo} with given name, extended and EULA URLs.
335 * @param name The entry name
336 * @param url The entry URL
337 * @param eulaAcceptanceRequired The EULA URL
338 */
339 public ImageryInfo(String name, String url, String eulaAcceptanceRequired) {
340 this(name);
341 setExtendedUrl(url);
342 this.eulaAcceptanceRequired = eulaAcceptanceRequired;
343 }
344
345 /**
346 * Constructs a new {@code ImageryInfo} with given name, url, extended and EULA URLs.
347 * @param name The entry name
348 * @param url The entry URL
349 * @param type The entry imagery type. If null, WMS will be used as default
350 * @param eulaAcceptanceRequired The EULA URL
351 * @param cookies The data part of HTTP cookies header in case the service requires cookies to work
352 * @throws IllegalArgumentException if type refers to an unknown imagery type
353 */
354 public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies) {
355 this(name);
356 setExtendedUrl(url);
357 ImageryType t = ImageryType.fromString(type);
358 this.cookies = cookies;
359 this.eulaAcceptanceRequired = eulaAcceptanceRequired;
360 if (t != null) {
361 this.sourceType = t;
362 } else if (type != null && !type.isEmpty()) {
363 throw new IllegalArgumentException("unknown type: "+type);
364 }
365 }
366
367 /**
368 * Constructs a new {@code ImageryInfo} with given name, url, id, extended and EULA URLs.
369 * @param name The entry name
370 * @param url The entry URL
371 * @param type The entry imagery type. If null, WMS will be used as default
372 * @param eulaAcceptanceRequired The EULA URL
373 * @param cookies The data part of HTTP cookies header in case the service requires cookies to work
374 * @param id tile id
375 * @throws IllegalArgumentException if type refers to an unknown imagery type
376 */
377 public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies, String id) {
378 this(name, url, type, eulaAcceptanceRequired, cookies);
379 setId(id);
380 }
381
382 /**
383 * Constructs a new {@code ImageryInfo} from an imagery preference entry.
384 * @param e The imagery preference entry
385 */
386 public ImageryInfo(ImageryPreferenceEntry e) {
387 super(e.name, e.url, e.id);
388 CheckParameterUtil.ensureParameterNotNull(e.name, "name");
389 CheckParameterUtil.ensureParameterNotNull(e.url, "url");
390 description = e.description;
391 cookies = e.cookies;
392 eulaAcceptanceRequired = e.eula;
393 sourceType = ImageryType.fromString(e.type);
394 if (sourceType == null) throw new IllegalArgumentException("unknown type");
395 pixelPerDegree = e.pixel_per_eastnorth;
396 defaultMaxZoom = e.max_zoom;
397 defaultMinZoom = e.min_zoom;
398 if (e.bounds != null) {
399 bounds = new ImageryBounds(e.bounds, ",");
400 if (e.shapes != null) {
401 try {
402 for (String s : e.shapes.split(";")) {
403 bounds.addShape(new Shape(s, ","));
404 }
405 } catch (IllegalArgumentException ex) {
406 Logging.warn(ex);
407 }
408 }
409 }
410 if (e.projections != null && !e.projections.isEmpty()) {
411 // split generates null element on empty string which gives one element Array[null]
412 setServerProjections(Arrays.asList(e.projections.split(",")));
413 }
414 attributionText = Utils.intern(e.attribution_text);
415 attributionLinkURL = e.attribution_url;
416 permissionReferenceURL = e.permission_reference_url;
417 attributionImage = e.logo_image;
418 attributionImageURL = e.logo_url;
419 date = e.date;
420 bestMarked = e.bestMarked;
421 overlay = e.overlay;
422 termsOfUseText = e.terms_of_use_text;
423 termsOfUseURL = e.terms_of_use_url;
424 countryCode = Utils.intern(e.country_code);
425 icon = Utils.intern(e.icon);
426 if (e.noTileHeaders != null) {
427 noTileHeaders = e.noTileHeaders.toMap();
428 }
429 if (e.noTileChecksums != null) {
430 noTileChecksums = e.noTileChecksums.toMap();
431 }
432 setTileSize(e.tileSize);
433 metadataHeaders = e.metadataHeaders;
434 isGeoreferenceValid = e.valid_georeference;
435 modTileFeatures = e.modTileFeatures;
436 if (e.default_layers != null) {
437 try (JsonReader jsonReader = Json.createReader(new StringReader(e.default_layers))) {
438 defaultLayers = jsonReader.
439 readArray().
440 stream().
441 map(x -> DefaultLayer.fromJson((JsonObject) x, sourceType)).
442 collect(Collectors.toList());
443 }
444 }
445 setCustomHttpHeaders(e.customHttpHeaders);
446 transparent = e.transparent;
447 minimumTileExpire = e.minimumTileExpire;
448 category = ImageryCategory.fromString(e.category);
449 }
450
451 /**
452 * Constructs a new {@code ImageryInfo} from an existing one.
453 * @param i The other imagery info
454 */
455 public ImageryInfo(ImageryInfo i) {
456 super(i.name, i.url, i.id);
457 this.noTileHeaders = i.noTileHeaders;
458 this.noTileChecksums = i.noTileChecksums;
459 this.minZoom = i.minZoom;
460 this.maxZoom = i.maxZoom;
461 this.cookies = i.cookies;
462 this.tileSize = i.tileSize;
463 this.metadataHeaders = i.metadataHeaders;
464 this.modTileFeatures = i.modTileFeatures;
465
466 this.origName = i.origName;
467 this.langName = i.langName;
468 this.defaultEntry = i.defaultEntry;
469 this.eulaAcceptanceRequired = null;
470 this.sourceType = i.sourceType;
471 this.pixelPerDegree = i.pixelPerDegree;
472 this.defaultMaxZoom = i.defaultMaxZoom;
473 this.defaultMinZoom = i.defaultMinZoom;
474 this.bounds = i.bounds;
475 this.serverProjections = i.serverProjections;
476 this.description = i.description;
477 this.langDescription = i.langDescription;
478 this.attributionText = i.attributionText;
479 this.privacyPolicyURL = i.privacyPolicyURL;
480 this.permissionReferenceURL = i.permissionReferenceURL;
481 this.attributionLinkURL = i.attributionLinkURL;
482 this.attributionImage = i.attributionImage;
483 this.attributionImageURL = i.attributionImageURL;
484 this.termsOfUseText = i.termsOfUseText;
485 this.termsOfUseURL = i.termsOfUseURL;
486 this.countryCode = i.countryCode;
487 this.date = i.date;
488 this.bestMarked = i.bestMarked;
489 this.overlay = i.overlay;
490 // do not copy field {@code mirrors}
491 this.icon = Utils.intern(i.icon);
492 this.isGeoreferenceValid = i.isGeoreferenceValid;
493 setDefaultLayers(i.defaultLayers);
494 setCustomHttpHeaders(i.customHttpHeaders);
495 this.transparent = i.transparent;
496 this.minimumTileExpire = i.minimumTileExpire;
497 this.categoryOriginalString = Utils.intern(i.categoryOriginalString);
498 this.category = i.category;
499 }
500
501 /**
502 * Adds a mirror entry. Mirror entries are completed with the data from the master entry
503 * and only describe another method to access identical data.
504 *
505 * @param entry the mirror to be added
506 * @since 9658
507 */
508 public void addMirror(ImageryInfo entry) {
509 if (mirrors == null) {
510 mirrors = new ArrayList<>();
511 }
512 mirrors.add(entry);
513 }
514
515 /**
516 * Returns the mirror entries. Entries are completed with master entry data.
517 *
518 * @return the list of mirrors
519 * @since 9658
520 */
521 public List<ImageryInfo> getMirrors() {
522 List<ImageryInfo> l = new ArrayList<>();
523 if (mirrors != null) {
524 int num = 1;
525 for (ImageryInfo i : mirrors) {
526 ImageryInfo n = new ImageryInfo(this);
527 if (i.defaultMaxZoom != 0) {
528 n.defaultMaxZoom = i.defaultMaxZoom;
529 }
530 if (i.defaultMinZoom != 0) {
531 n.defaultMinZoom = i.defaultMinZoom;
532 }
533 n.setServerProjections(i.getServerProjections());
534 n.url = i.url;
535 n.sourceType = i.sourceType;
536 if (i.getTileSize() != 0) {
537 n.setTileSize(i.getTileSize());
538 }
539 if (i.getPrivacyPolicyURL() != null) {
540 n.setPrivacyPolicyURL(i.getPrivacyPolicyURL());
541 }
542 if (n.id != null) {
543 n.id = n.id + "_mirror" + num;
544 }
545 if (num > 1) {
546 n.name = tr("{0} mirror server {1}", n.name, num);
547 if (n.origName != null) {
548 n.origName += " mirror server " + num;
549 }
550 } else {
551 n.name = tr("{0} mirror server", n.name);
552 if (n.origName != null) {
553 n.origName += " mirror server";
554 }
555 }
556 l.add(n);
557 ++num;
558 }
559 }
560 return l;
561 }
562
563 /**
564 * Check if this object equals another ImageryInfo with respect to the properties
565 * that get written to the preference file.
566 *
567 * The field {@link #pixelPerDegree} is ignored.
568 *
569 * @param other the ImageryInfo object to compare to
570 * @return true if they are equal
571 */
572 @Override
573 public boolean equalsPref(SourceInfo<ImageryInfo.ImageryCategory, ImageryInfo.ImageryType,
574 ImageryInfo.ImageryBounds, ImageryInfo.ImageryPreferenceEntry> other) {
575 if (!(other instanceof ImageryInfo)) {
576 return false;
577 }
578 ImageryInfo realOther = (ImageryInfo) other;
579
580 // CHECKSTYLE.OFF: BooleanExpressionComplexity
581 return super.equalsPref(realOther) &&
582 Objects.equals(this.bestMarked, realOther.bestMarked) &&
583 Objects.equals(this.overlay, realOther.overlay) &&
584 Objects.equals(this.isGeoreferenceValid, realOther.isGeoreferenceValid) &&
585 Objects.equals(this.defaultMaxZoom, realOther.defaultMaxZoom) &&
586 Objects.equals(this.defaultMinZoom, realOther.defaultMinZoom) &&
587 Objects.equals(this.serverProjections, realOther.serverProjections) &&
588 Objects.equals(this.transparent, realOther.transparent) &&
589 Objects.equals(this.minimumTileExpire, realOther.minimumTileExpire);
590 // CHECKSTYLE.ON: BooleanExpressionComplexity
591 }
592
593 @Override
594 public int compareTo(SourceInfo<ImageryInfo.ImageryCategory, ImageryInfo.ImageryType,
595 ImageryInfo.ImageryBounds, ImageryInfo.ImageryPreferenceEntry> other) {
596 int i = super.compareTo(other);
597 if (other instanceof ImageryInfo) {
598 ImageryInfo in = (ImageryInfo) other;
599 if (i == 0) {
600 i = Double.compare(pixelPerDegree, in.pixelPerDegree);
601 }
602 }
603 return i;
604 }
605
606 /**
607 * Sets the pixel per degree value.
608 * @param ppd The ppd value
609 * @see #getPixelPerDegree()
610 */
611 public void setPixelPerDegree(double ppd) {
612 this.pixelPerDegree = ppd;
613 }
614
615 /**
616 * Sets the maximum zoom level.
617 * @param defaultMaxZoom The maximum zoom level
618 */
619 public void setDefaultMaxZoom(int defaultMaxZoom) {
620 this.defaultMaxZoom = defaultMaxZoom;
621 }
622
623 /**
624 * Sets the minimum zoom level.
625 * @param defaultMinZoom The minimum zoom level
626 */
627 public void setDefaultMinZoom(int defaultMinZoom) {
628 this.defaultMinZoom = defaultMinZoom;
629 }
630
631 /**
632 * Sets the extended URL of this entry.
633 * @param url Entry extended URL containing in addition of service URL, its type and min/max zoom info
634 */
635 public void setExtendedUrl(String url) {
636 CheckParameterUtil.ensureParameterNotNull(url);
637
638 // Default imagery type is WMS
639 this.url = url;
640 this.sourceType = ImageryType.WMS;
641
642 defaultMaxZoom = 0;
643 defaultMinZoom = 0;
644 for (ImageryType type : ImageryType.values()) {
645 Matcher m = Pattern.compile(type.getTypeString()+"(?:\\[(?:(\\d+)[,-])?(\\d+)\\])?:(.*)").matcher(url);
646 if (m.matches()) {
647 this.url = m.group(3);
648 this.sourceType = type;
649 if (m.group(2) != null) {
650 defaultMaxZoom = Integer.parseInt(m.group(2));
651 }
652 if (m.group(1) != null) {
653 defaultMinZoom = Integer.parseInt(m.group(1));
654 }
655 break;
656 }
657 }
658
659 if (serverProjections.isEmpty()) {
660 Matcher m = Pattern.compile(".*\\{PROJ\\(([^)}]+)\\)\\}.*").matcher(url.toUpperCase(Locale.ENGLISH));
661 if (m.matches()) {
662 setServerProjections(Arrays.asList(m.group(1).split(",")));
663 }
664 }
665 }
666
667 /**
668 * Gets the pixel per degree value
669 * @return The ppd value.
670 */
671 public double getPixelPerDegree() {
672 return this.pixelPerDegree;
673 }
674
675 /**
676 * Returns the maximum zoom level.
677 * @return The maximum zoom level
678 */
679 @Override
680 public int getMaxZoom() {
681 return this.defaultMaxZoom;
682 }
683
684 /**
685 * Returns the minimum zoom level.
686 * @return The minimum zoom level
687 */
688 @Override
689 public int getMinZoom() {
690 return this.defaultMinZoom;
691 }
692
693
694 /**
695 * Returns a tool tip text for display.
696 * @return The text
697 * @since 8065
698 */
699 @Override
700 public String getToolTipText() {
701 StringBuilder res = new StringBuilder(getName());
702 boolean html = false;
703 String dateStr = getDate();
704 if (dateStr != null && !dateStr.isEmpty()) {
705 res.append("<br>").append(tr("Date of imagery: {0}", dateStr));
706 html = true;
707 }
708 if (category != null && category.getDescription() != null) {
709 res.append("<br>").append(tr("Imagery category: {0}", category.getDescription()));
710 html = true;
711 }
712 if (bestMarked) {
713 res.append("<br>").append(tr("This imagery is marked as best in this region in other editors."));
714 html = true;
715 }
716 if (overlay) {
717 res.append("<br>").append(tr("This imagery is an overlay."));
718 html = true;
719 }
720 String desc = getDescription();
721 if (desc != null && !desc.isEmpty()) {
722 res.append("<br>").append(Utils.escapeReservedCharactersHTML(desc));
723 html = true;
724 }
725 if (html) {
726 res.insert(0, "<html>").append("</html>");
727 }
728 return res.toString();
729 }
730
731 /**
732 * Get the projections supported by the server. Only relevant for
733 * WMS-type ImageryInfo at the moment.
734 * @return null, if no projections have been specified; the list
735 * of supported projections otherwise.
736 */
737 public List<String> getServerProjections() {
738 return Collections.unmodifiableList(serverProjections);
739 }
740
741 /**
742 * Sets the list of collections the server supports
743 * @param serverProjections The list of supported projections
744 */
745 public void setServerProjections(Collection<String> serverProjections) {
746 CheckParameterUtil.ensureParameterNotNull(serverProjections, "serverProjections");
747 this.serverProjections = serverProjections.stream()
748 .map(String::intern)
749 .collect(StreamUtils.toUnmodifiableList());
750 }
751
752 /**
753 * Returns the extended URL, containing in addition of service URL, its type and min/max zoom info.
754 * @return The extended URL
755 */
756 public String getExtendedUrl() {
757 return sourceType.getTypeString() + (defaultMaxZoom != 0
758 ? ('['+(defaultMinZoom != 0 ? (Integer.toString(defaultMinZoom) + ',') : "")+defaultMaxZoom+']') : "") + ':' + url;
759 }
760
761 /**
762 * Gets a unique toolbar key to store this layer as toolbar item
763 * @return The kay.
764 */
765 public String getToolbarName() {
766 String res = name;
767 if (pixelPerDegree != 0) {
768 res += "#PPD="+pixelPerDegree;
769 }
770 return res;
771 }
772
773 /**
774 * Gets the name that should be displayed in the menu to add this imagery layer.
775 * @return The text.
776 */
777 public String getMenuName() {
778 String res = name;
779 if (pixelPerDegree != 0) {
780 res += " ("+pixelPerDegree+')';
781 }
782 return res;
783 }
784
785 /**
786 * Returns the imagery type.
787 * @return The imagery type
788 * @see SourceInfo#getSourceType
789 */
790 public ImageryType getImageryType() {
791 return super.getSourceType();
792 }
793
794 /**
795 * Sets the imagery type.
796 * @param imageryType The imagery type
797 * @see SourceInfo#setSourceType
798 */
799 public void setImageryType(ImageryType imageryType) {
800 super.setSourceType(imageryType);
801 }
802
803 /**
804 * Returns the imagery category.
805 * @return The imagery category
806 * @see SourceInfo#getSourceCategory
807 * @since 13792
808 */
809 public ImageryCategory getImageryCategory() {
810 return super.getSourceCategory();
811 }
812
813 /**
814 * Sets the imagery category.
815 * @param category The imagery category
816 * @see SourceInfo#setSourceCategory
817 * @since 13792
818 */
819 public void setImageryCategory(ImageryCategory category) {
820 super.setSourceCategory(category);
821 }
822
823 /**
824 * Returns the imagery category original string (don't use except for error checks).
825 * @return The imagery category original string
826 * @see SourceInfo#getSourceCategoryOriginalString
827 * @since 13792
828 */
829 public String getImageryCategoryOriginalString() {
830 return super.getSourceCategoryOriginalString();
831 }
832
833 /**
834 * Sets the imagery category original string (don't use except for error checks).
835 * @param categoryOriginalString The imagery category original string
836 * @see SourceInfo#setSourceCategoryOriginalString
837 * @since 13792
838 */
839 public void setImageryCategoryOriginalString(String categoryOriginalString) {
840 super.setSourceCategoryOriginalString(categoryOriginalString);
841 }
842
843 /**
844 * Gets the flag if the georeference is valid.
845 * @return <code>true</code> if it is valid.
846 */
847 public boolean isGeoreferenceValid() {
848 return isGeoreferenceValid;
849 }
850
851 /**
852 * Sets an indicator that the georeference is valid
853 * @param isGeoreferenceValid <code>true</code> if it is marked as valid.
854 */
855 public void setGeoreferenceValid(boolean isGeoreferenceValid) {
856 this.isGeoreferenceValid = isGeoreferenceValid;
857 }
858
859 /**
860 * Returns the status of "best" marked status in other editors.
861 * @return <code>true</code> if it is marked as best.
862 * @since 11575
863 */
864 public boolean isBestMarked() {
865 return bestMarked;
866 }
867
868 /**
869 * Returns the overlay indication.
870 * @return <code>true</code> if it is an overlay.
871 * @since 13536
872 */
873 public boolean isOverlay() {
874 return overlay;
875 }
876
877 /**
878 * Sets an indicator that in other editors it is marked as best imagery
879 * @param bestMarked <code>true</code> if it is marked as best in other editors.
880 * @since 11575
881 */
882 public void setBestMarked(boolean bestMarked) {
883 this.bestMarked = bestMarked;
884 }
885
886 /**
887 * Sets overlay indication
888 * @param overlay <code>true</code> if it is an overlay.
889 * @since 13536
890 */
891 public void setOverlay(boolean overlay) {
892 this.overlay = overlay;
893 }
894
895 /**
896 * Determines if this imagery should be transparent.
897 * @return should this imagery be transparent
898 */
899 public boolean isTransparent() {
900 return transparent;
901 }
902
903 /**
904 * Sets whether imagery should be transparent.
905 * @param transparent set to true if imagery should be transparent
906 */
907 public void setTransparent(boolean transparent) {
908 this.transparent = transparent;
909 }
910
911 /**
912 * Returns minimum tile expiration in seconds.
913 * @return minimum tile expiration in seconds
914 */
915 public int getMinimumTileExpire() {
916 return minimumTileExpire;
917 }
918
919 /**
920 * Sets minimum tile expiration in seconds.
921 * @param minimumTileExpire minimum tile expiration in seconds
922 */
923 public void setMinimumTileExpire(int minimumTileExpire) {
924 this.minimumTileExpire = minimumTileExpire;
925 }
926
927 /**
928 * Get a string representation of this imagery info suitable for the {@code source} changeset tag.
929 * @return English name, if known
930 * @since 13890
931 */
932 public String getSourceName() {
933 if (ImageryType.BING == getImageryType()) {
934 return "Bing";
935 } else {
936 if (id != null) {
937 // Retrieve english name, unfortunately not saved in preferences
938 Optional<ImageryInfo> infoEn = ImageryLayerInfo.allDefaultLayers.stream().filter(x -> id.equals(x.getId())).findAny();
939 if (infoEn.isPresent()) {
940 return infoEn.get().getOriginalName();
941 }
942 }
943 return getOriginalName();
944 }
945 }
946
947 /**
948 * Return the sorted list of activated source IDs.
949 * @return sorted list of activated source IDs
950 * @since 13536
951 */
952 public static Collection<String> getActiveIds() {
953 return getActiveIds(ImageryInfo.class);
954 }
955}
Note: See TracBrowser for help on using the repository browser.