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

Last change on this file since 11848 was 11848, checked in by Don-vip, 2 years ago

fix #14613 - Special HTML characters not escaped in GUI error messages

  • Property svn:eol-style set to native
File size: 42.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.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.List;
12import java.util.Locale;
13import java.util.Map;
14import java.util.Objects;
15import java.util.Set;
16import java.util.TreeSet;
17import java.util.regex.Matcher;
18import java.util.regex.Pattern;
19import java.util.stream.Collectors;
20
21import javax.swing.ImageIcon;
22
23import org.openstreetmap.gui.jmapviewer.interfaces.Attributed;
24import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
25import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTileSource;
26import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.Mapnik;
27import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo;
28import org.openstreetmap.josm.Main;
29import org.openstreetmap.josm.data.Bounds;
30import org.openstreetmap.josm.data.Preferences.pref;
31import org.openstreetmap.josm.io.Capabilities;
32import org.openstreetmap.josm.io.OsmApi;
33import org.openstreetmap.josm.tools.CheckParameterUtil;
34import org.openstreetmap.josm.tools.ImageProvider;
35import org.openstreetmap.josm.tools.LanguageInfo;
36import org.openstreetmap.josm.tools.MultiMap;
37import org.openstreetmap.josm.tools.Utils;
38
39/**
40 * Class that stores info about an image background layer.
41 *
42 * @author Frederik Ramm
43 */
44public class ImageryInfo extends TileSourceInfo implements Comparable<ImageryInfo>, Attributed {
45
46    /**
47     * Type of imagery entry.
48     */
49    public enum ImageryType {
50        /** A WMS (Web Map Service) entry. **/
51        WMS("wms"),
52        /** A TMS (Tile Map Service) entry. **/
53        TMS("tms"),
54        /** TMS entry for Microsoft Bing. */
55        BING("bing"),
56        /** TMS entry for Russian company <a href="https://wiki.openstreetmap.org/wiki/WikiProject_Russia/kosmosnimki">ScanEx</a>. **/
57        SCANEX("scanex"),
58        /** A WMS endpoint entry only stores the WMS server info, without layer, which are chosen later by the user. **/
59        WMS_ENDPOINT("wms_endpoint"),
60        /** WMTS stores GetCapabilities URL. Does not store any information about the layer **/
61        WMTS("wmts");
62
63
64        private final String typeString;
65
66        ImageryType(String urlString) {
67            this.typeString = urlString;
68        }
69
70        /**
71         * Returns the unique string identifying this type.
72         * @return the unique string identifying this type
73         * @since 6690
74         */
75        public final String getTypeString() {
76            return typeString;
77        }
78
79        /**
80         * Returns the imagery type from the given type string.
81         * @param s The type string
82         * @return the imagery type matching the given type string
83         */
84        public static ImageryType fromString(String s) {
85            for (ImageryType type : ImageryType.values()) {
86                if (type.getTypeString().equals(s)) {
87                    return type;
88                }
89            }
90            return null;
91        }
92    }
93
94    /**
95     * Multi-polygon bounds for imagery backgrounds.
96     * Used to display imagery coverage in preferences and to determine relevant imagery entries based on edit location.
97     */
98    public static class ImageryBounds extends Bounds {
99
100        /**
101         * Constructs a new {@code ImageryBounds} from string.
102         * @param asString The string containing the list of shapes defining this bounds
103         * @param separator The shape separator in the given string, usually a comma
104         */
105        public ImageryBounds(String asString, String separator) {
106            super(asString, separator);
107        }
108
109        private List<Shape> shapes = new ArrayList<>();
110
111        /**
112         * Adds a new shape to this bounds.
113         * @param shape The shape to add
114         */
115        public final void addShape(Shape shape) {
116            this.shapes.add(shape);
117        }
118
119        /**
120         * Sets the list of shapes defining this bounds.
121         * @param shapes The list of shapes defining this bounds.
122         */
123        public final void setShapes(List<Shape> shapes) {
124            this.shapes = shapes;
125        }
126
127        /**
128         * Returns the list of shapes defining this bounds.
129         * @return The list of shapes defining this bounds
130         */
131        public final List<Shape> getShapes() {
132            return shapes;
133        }
134
135        @Override
136        public int hashCode() {
137            return Objects.hash(super.hashCode(), shapes);
138        }
139
140        @Override
141        public boolean equals(Object o) {
142            if (this == o) return true;
143            if (o == null || getClass() != o.getClass()) return false;
144            if (!super.equals(o)) return false;
145            ImageryBounds that = (ImageryBounds) o;
146            return Objects.equals(shapes, that.shapes);
147        }
148    }
149
150    /** original name of the imagery entry in case of translation call, for multiple languages English when possible */
151    private String origName;
152    /** (original) language of the translated name entry */
153    private String langName;
154    /** whether this is a entry activated by default or not */
155    private boolean defaultEntry;
156    /** The data part of HTTP cookies header in case the service requires cookies to work */
157    private String cookies;
158    /** Whether this service requires a explicit EULA acceptance before it can be activated */
159    private String eulaAcceptanceRequired;
160    /** type of the imagery servics - WMS, TMS, ... */
161    private ImageryType imageryType = ImageryType.WMS;
162    private double pixelPerDegree;
163    /** maximum zoom level for TMS imagery */
164    private int defaultMaxZoom;
165    /** minimum zoom level for TMS imagery */
166    private int defaultMinZoom;
167    /** display bounds of imagery, displayed in prefs and used for automatic imagery selection */
168    private ImageryBounds bounds;
169    /** projections supported by WMS servers */
170    private List<String> serverProjections = Collections.emptyList();
171    /** description of the imagery entry, should contain notes what type of data it is */
172    private String description;
173    /** language of the description entry */
174    private String langDescription;
175    /** Text of a text attribution displayed when using the imagery */
176    private String attributionText;
177    /** Link behing the text attribution displayed when using the imagery */
178    private String attributionLinkURL;
179    /** Image of a graphical attribution displayed when using the imagery */
180    private String attributionImage;
181    /** Link behind the graphical attribution displayed when using the imagery */
182    private String attributionImageURL;
183    /** Text with usage terms displayed when using the imagery */
184    private String termsOfUseText;
185    /** Link behind the text with usage terms displayed when using the imagery */
186    private String termsOfUseURL;
187    /** country code of the imagery (for country specific imagery) */
188    private String countryCode = "";
189    /**
190      * creation date of the imagery (in the form YYYY-MM-DD;YYYY-MM-DD, where
191      * DD and MM as well as a second date are optional)
192      * @since 11570
193      */
194    private String date;
195    /**
196      * marked as best in other editors
197      * @since 11575
198      */
199    private boolean bestMarked;
200    /** mirrors of different type for this entry */
201    private List<ImageryInfo> mirrors;
202    /** icon used in menu */
203    private String icon;
204    /** is the geo reference correct - don't offer offset handling */
205    private boolean isGeoreferenceValid;
206    /** does the EPSG:4326 to mercator woraround work as expected */
207    private boolean isEpsg4326To3857Supported;
208    /** which layers should be activated by default on layer addition. **/
209    private Collection<DefaultLayer> defaultLayers = Collections.emptyList();
210    // when adding a field, also adapt the ImageryInfo(ImageryInfo)
211    // and ImageryInfo(ImageryPreferenceEntry) constructor, equals method, and ImageryPreferenceEntry
212
213    /**
214     * Auxiliary class to save an {@link ImageryInfo} object in the preferences.
215     */
216    public static class ImageryPreferenceEntry {
217        @pref String name;
218        @pref String d;
219        @pref String id;
220        @pref String type;
221        @pref String url;
222        @pref double pixel_per_eastnorth;
223        @pref String eula;
224        @pref String attribution_text;
225        @pref String attribution_url;
226        @pref String logo_image;
227        @pref String logo_url;
228        @pref String terms_of_use_text;
229        @pref String terms_of_use_url;
230        @pref String country_code = "";
231        @pref String date;
232        @pref int max_zoom;
233        @pref int min_zoom;
234        @pref String cookies;
235        @pref String bounds;
236        @pref String shapes;
237        @pref String projections;
238        @pref String icon;
239        @pref String description;
240        @pref MultiMap<String, String> noTileHeaders;
241        @pref MultiMap<String, String> noTileChecksums;
242        @pref int tileSize = -1;
243        @pref Map<String, String> metadataHeaders;
244        @pref boolean valid_georeference;
245        @pref boolean bestMarked;
246        @pref boolean supports_epsg_4326_to_3857_conversion;
247        // TODO: disabled until change of layers is implemented
248        // @pref String default_layers;
249
250        /**
251         * Constructs a new empty WMS {@code ImageryPreferenceEntry}.
252         */
253        public ImageryPreferenceEntry() {
254            // Do nothing
255        }
256
257        /**
258         * Constructs a new {@code ImageryPreferenceEntry} from a given {@code ImageryInfo}.
259         * @param i The corresponding imagery info
260         */
261        public ImageryPreferenceEntry(ImageryInfo i) {
262            name = i.name;
263            id = i.id;
264            type = i.imageryType.getTypeString();
265            url = i.url;
266            pixel_per_eastnorth = i.pixelPerDegree;
267            eula = i.eulaAcceptanceRequired;
268            attribution_text = i.attributionText;
269            attribution_url = i.attributionLinkURL;
270            date = i.date;
271            bestMarked = i.bestMarked;
272            logo_image = i.attributionImage;
273            logo_url = i.attributionImageURL;
274            terms_of_use_text = i.termsOfUseText;
275            terms_of_use_url = i.termsOfUseURL;
276            country_code = i.countryCode;
277            max_zoom = i.defaultMaxZoom;
278            min_zoom = i.defaultMinZoom;
279            cookies = i.cookies;
280            icon = i.icon;
281            description = i.description;
282            if (i.bounds != null) {
283                bounds = i.bounds.encodeAsString(",");
284                StringBuilder shapesString = new StringBuilder();
285                for (Shape s : i.bounds.getShapes()) {
286                    if (shapesString.length() > 0) {
287                        shapesString.append(';');
288                    }
289                    shapesString.append(s.encodeAsString(","));
290                }
291                if (shapesString.length() > 0) {
292                    shapes = shapesString.toString();
293                }
294            }
295            projections = i.serverProjections.stream().collect(Collectors.joining(","));
296            if (i.noTileHeaders != null && !i.noTileHeaders.isEmpty()) {
297                noTileHeaders = new MultiMap<>(i.noTileHeaders);
298            }
299
300            if (i.noTileChecksums != null && !i.noTileChecksums.isEmpty()) {
301                noTileChecksums = new MultiMap<>(i.noTileChecksums);
302            }
303
304            if (i.metadataHeaders != null && !i.metadataHeaders.isEmpty()) {
305                metadataHeaders = i.metadataHeaders;
306            }
307
308            tileSize = i.getTileSize();
309
310            valid_georeference = i.isGeoreferenceValid();
311            supports_epsg_4326_to_3857_conversion = i.isEpsg4326To3857Supported();
312            // TODO disabled until change of layers is implemented
313            // default_layers = i.defaultLayers.stream().collect(Collectors.joining(","));
314        }
315
316        @Override
317        public String toString() {
318            StringBuilder s = new StringBuilder("ImageryPreferenceEntry [name=").append(name);
319            if (id != null) {
320                s.append(" id=").append(id);
321            }
322            s.append(']');
323            return s.toString();
324        }
325    }
326
327    /**
328     * Constructs a new WMS {@code ImageryInfo}.
329     */
330    public ImageryInfo() {
331        super();
332    }
333
334    /**
335     * Constructs a new WMS {@code ImageryInfo} with a given name.
336     * @param name The entry name
337     */
338    public ImageryInfo(String name) {
339        super(name);
340    }
341
342    /**
343     * Constructs a new WMS {@code ImageryInfo} with given name and extended URL.
344     * @param name The entry name
345     * @param url The entry extended URL
346     */
347    public ImageryInfo(String name, String url) {
348        this(name);
349        setExtendedUrl(url);
350    }
351
352    /**
353     * Constructs a new WMS {@code ImageryInfo} with given name, extended and EULA URLs.
354     * @param name The entry name
355     * @param url The entry URL
356     * @param eulaAcceptanceRequired The EULA URL
357     */
358    public ImageryInfo(String name, String url, String eulaAcceptanceRequired) {
359        this(name);
360        setExtendedUrl(url);
361        this.eulaAcceptanceRequired = eulaAcceptanceRequired;
362    }
363
364    /**
365     * Constructs a new {@code ImageryInfo} with given name, url, extended and EULA URLs.
366     * @param name The entry name
367     * @param url The entry URL
368     * @param type The entry imagery type. If null, WMS will be used as default
369     * @param eulaAcceptanceRequired The EULA URL
370     * @param cookies The data part of HTTP cookies header in case the service requires cookies to work
371     * @throws IllegalArgumentException if type refers to an unknown imagery type
372     */
373    public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies) {
374        this(name);
375        setExtendedUrl(url);
376        ImageryType t = ImageryType.fromString(type);
377        this.cookies = cookies;
378        this.eulaAcceptanceRequired = eulaAcceptanceRequired;
379        if (t != null) {
380            this.imageryType = t;
381        } else if (type != null && !type.trim().isEmpty()) {
382            throw new IllegalArgumentException("unknown type: "+type);
383        }
384    }
385
386    public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies, String id) {
387        this(name, url, type, eulaAcceptanceRequired, cookies);
388        setId(id);
389    }
390
391    /**
392     * Constructs a new {@code ImageryInfo} from an imagery preference entry.
393     * @param e The imagery preference entry
394     */
395    public ImageryInfo(ImageryPreferenceEntry e) {
396        super(e.name, e.url, e.id);
397        CheckParameterUtil.ensureParameterNotNull(e.name, "name");
398        CheckParameterUtil.ensureParameterNotNull(e.url, "url");
399        description = e.description;
400        cookies = e.cookies;
401        eulaAcceptanceRequired = e.eula;
402        imageryType = ImageryType.fromString(e.type);
403        if (imageryType == null) throw new IllegalArgumentException("unknown type");
404        pixelPerDegree = e.pixel_per_eastnorth;
405        defaultMaxZoom = e.max_zoom;
406        defaultMinZoom = e.min_zoom;
407        if (e.bounds != null) {
408            bounds = new ImageryBounds(e.bounds, ",");
409            if (e.shapes != null) {
410                try {
411                    for (String s : e.shapes.split(";")) {
412                        bounds.addShape(new Shape(s, ","));
413                    }
414                } catch (IllegalArgumentException ex) {
415                    Main.warn(ex);
416                }
417            }
418        }
419        if (e.projections != null && !e.projections.isEmpty()) {
420            // split generates null element on empty string which gives one element Array[null]
421            serverProjections = Arrays.asList(e.projections.split(","));
422        }
423        attributionText = e.attribution_text;
424        attributionLinkURL = e.attribution_url;
425        attributionImage = e.logo_image;
426        attributionImageURL = e.logo_url;
427        date = e.date;
428        bestMarked = e.bestMarked;
429        termsOfUseText = e.terms_of_use_text;
430        termsOfUseURL = e.terms_of_use_url;
431        countryCode = e.country_code;
432        icon = e.icon;
433        if (e.noTileHeaders != null) {
434            noTileHeaders = e.noTileHeaders.toMap();
435        }
436        if (e.noTileChecksums != null) {
437            noTileChecksums = e.noTileChecksums.toMap();
438        }
439        setTileSize(e.tileSize);
440        metadataHeaders = e.metadataHeaders;
441        isEpsg4326To3857Supported = e.supports_epsg_4326_to_3857_conversion;
442        isGeoreferenceValid = e.valid_georeference;
443        // TODO disabled until change of layers is implemented
444        // defaultLayers = Arrays.asList(e.default_layers.split(","));
445    }
446
447    /**
448     * Constructs a new {@code ImageryInfo} from an existing one.
449     * @param i The other imagery info
450     */
451    public ImageryInfo(ImageryInfo i) {
452        super(i.name, i.url, i.id);
453        this.origName = i.origName;
454        this.langName = i.langName;
455        this.bestMarked = i.bestMarked;
456        this.defaultEntry = i.defaultEntry;
457        this.cookies = i.cookies;
458        this.eulaAcceptanceRequired = null;
459        this.imageryType = i.imageryType;
460        this.pixelPerDegree = i.pixelPerDegree;
461        this.defaultMaxZoom = i.defaultMaxZoom;
462        this.defaultMinZoom = i.defaultMinZoom;
463        this.bounds = i.bounds;
464        this.serverProjections = i.serverProjections;
465        this.attributionText = i.attributionText;
466        this.attributionLinkURL = i.attributionLinkURL;
467        this.attributionImage = i.attributionImage;
468        this.attributionImageURL = i.attributionImageURL;
469        this.termsOfUseText = i.termsOfUseText;
470        this.termsOfUseURL = i.termsOfUseURL;
471        this.countryCode = i.countryCode;
472        this.date = i.date;
473        this.icon = i.icon;
474        this.description = i.description;
475        this.noTileHeaders = i.noTileHeaders;
476        this.noTileChecksums = i.noTileChecksums;
477        this.metadataHeaders = i.metadataHeaders;
478        this.isEpsg4326To3857Supported = i.isEpsg4326To3857Supported;
479        this.isGeoreferenceValid = i.isGeoreferenceValid;
480        this.defaultLayers = i.defaultLayers;
481    }
482
483    @Override
484    public int hashCode() {
485        return Objects.hash(url, imageryType);
486    }
487
488    /**
489     * Check if this object equals another ImageryInfo with respect to the properties
490     * that get written to the preference file.
491     *
492     * The field {@link #pixelPerDegree} is ignored.
493     *
494     * @param other the ImageryInfo object to compare to
495     * @return true if they are equal
496     */
497    public boolean equalsPref(ImageryInfo other) {
498        if (other == null) {
499            return false;
500        }
501
502        // CHECKSTYLE.OFF: BooleanExpressionComplexity
503        return
504                Objects.equals(this.name, other.name) &&
505                Objects.equals(this.id, other.id) &&
506                Objects.equals(this.url, other.url) &&
507                Objects.equals(this.bestMarked, other.bestMarked) &&
508                Objects.equals(this.isEpsg4326To3857Supported, other.isEpsg4326To3857Supported) &&
509                Objects.equals(this.isGeoreferenceValid, other.isGeoreferenceValid) &&
510                Objects.equals(this.cookies, other.cookies) &&
511                Objects.equals(this.eulaAcceptanceRequired, other.eulaAcceptanceRequired) &&
512                Objects.equals(this.imageryType, other.imageryType) &&
513                Objects.equals(this.defaultMaxZoom, other.defaultMaxZoom) &&
514                Objects.equals(this.defaultMinZoom, other.defaultMinZoom) &&
515                Objects.equals(this.bounds, other.bounds) &&
516                Objects.equals(this.serverProjections, other.serverProjections) &&
517                Objects.equals(this.attributionText, other.attributionText) &&
518                Objects.equals(this.attributionLinkURL, other.attributionLinkURL) &&
519                Objects.equals(this.attributionImageURL, other.attributionImageURL) &&
520                Objects.equals(this.attributionImage, other.attributionImage) &&
521                Objects.equals(this.termsOfUseText, other.termsOfUseText) &&
522                Objects.equals(this.termsOfUseURL, other.termsOfUseURL) &&
523                Objects.equals(this.countryCode, other.countryCode) &&
524                Objects.equals(this.date, other.date) &&
525                Objects.equals(this.icon, other.icon) &&
526                Objects.equals(this.description, other.description) &&
527                Objects.equals(this.noTileHeaders, other.noTileHeaders) &&
528                Objects.equals(this.noTileChecksums, other.noTileChecksums) &&
529                Objects.equals(this.metadataHeaders, other.metadataHeaders) &&
530                Objects.equals(this.defaultLayers, other.defaultLayers);
531        // CHECKSTYLE.ON: BooleanExpressionComplexity
532    }
533
534    @Override
535    public boolean equals(Object o) {
536        if (this == o) return true;
537        if (o == null || getClass() != o.getClass()) return false;
538        ImageryInfo that = (ImageryInfo) o;
539        return imageryType == that.imageryType && Objects.equals(url, that.url);
540    }
541
542    @Override
543    public String toString() {
544        return "ImageryInfo{" +
545                "name='" + name + '\'' +
546                ", countryCode='" + countryCode + '\'' +
547                ", url='" + url + '\'' +
548                ", imageryType=" + imageryType +
549                '}';
550    }
551
552    @Override
553    public int compareTo(ImageryInfo in) {
554        int i = countryCode.compareTo(in.countryCode);
555        if (i == 0) {
556            i = name.toLowerCase(Locale.ENGLISH).compareTo(in.name.toLowerCase(Locale.ENGLISH));
557        }
558        if (i == 0) {
559            i = url.compareTo(in.url);
560        }
561        if (i == 0) {
562            i = Double.compare(pixelPerDegree, in.pixelPerDegree);
563        }
564        return i;
565    }
566
567    public boolean equalsBaseValues(ImageryInfo in) {
568        return url.equals(in.url);
569    }
570
571    /**
572     * Sets the pixel per degree value.
573     * @param ppd The ppd value
574     * @see #getPixelPerDegree()
575     */
576    public void setPixelPerDegree(double ppd) {
577        this.pixelPerDegree = ppd;
578    }
579
580    /**
581     * Sets the maximum zoom level.
582     * @param defaultMaxZoom The maximum zoom level
583     */
584    public void setDefaultMaxZoom(int defaultMaxZoom) {
585        this.defaultMaxZoom = defaultMaxZoom;
586    }
587
588    /**
589     * Sets the minimum zoom level.
590     * @param defaultMinZoom The minimum zoom level
591     */
592    public void setDefaultMinZoom(int defaultMinZoom) {
593        this.defaultMinZoom = defaultMinZoom;
594    }
595
596    /**
597     * Sets the imagery polygonial bounds.
598     * @param b The imagery bounds (non-rectangular)
599     */
600    public void setBounds(ImageryBounds b) {
601        this.bounds = b;
602    }
603
604    /**
605     * Returns the imagery polygonial bounds.
606     * @return The imagery bounds (non-rectangular)
607     */
608    public ImageryBounds getBounds() {
609        return bounds;
610    }
611
612    @Override
613    public boolean requiresAttribution() {
614        return attributionText != null || attributionImage != null || termsOfUseText != null || termsOfUseURL != null;
615    }
616
617    @Override
618    public String getAttributionText(int zoom, ICoordinate topLeft, ICoordinate botRight) {
619        return attributionText;
620    }
621
622    @Override
623    public String getAttributionLinkURL() {
624        return attributionLinkURL;
625    }
626
627    @Override
628    public Image getAttributionImage() {
629        ImageIcon i = ImageProvider.getIfAvailable(attributionImage);
630        if (i != null) {
631            return i.getImage();
632        }
633        return null;
634    }
635
636    @Override
637    public String getAttributionImageURL() {
638        return attributionImageURL;
639    }
640
641    @Override
642    public String getTermsOfUseText() {
643        return termsOfUseText;
644    }
645
646    @Override
647    public String getTermsOfUseURL() {
648        return termsOfUseURL;
649    }
650
651    /**
652     * Set the attribution text
653     * @param text The text
654     * @see #getAttributionText(int, ICoordinate, ICoordinate)
655     */
656    public void setAttributionText(String text) {
657        attributionText = text;
658    }
659
660    /**
661     * Set the attribution image
662     * @param url The url of the image.
663     * @see #getAttributionImageURL()
664     */
665    public void setAttributionImageURL(String url) {
666        attributionImageURL = url;
667    }
668
669    /**
670     * Set the image for the attribution
671     * @param res The image resource
672     * @see #getAttributionImage()
673     */
674    public void setAttributionImage(String res) {
675        attributionImage = res;
676    }
677
678    /**
679     * Sets the URL the attribution should link to.
680     * @param url The url.
681     * @see #getAttributionLinkURL()
682     */
683    public void setAttributionLinkURL(String url) {
684        attributionLinkURL = url;
685    }
686
687    /**
688     * Sets the text to display to the user as terms of use.
689     * @param text The text
690     * @see #getTermsOfUseText()
691     */
692    public void setTermsOfUseText(String text) {
693        termsOfUseText = text;
694    }
695
696    /**
697     * Sets a url that links to the terms of use text.
698     * @param text The url.
699     * @see #getTermsOfUseURL()
700     */
701    public void setTermsOfUseURL(String text) {
702        termsOfUseURL = text;
703    }
704
705    /**
706     * Sets the extended URL of this entry.
707     * @param url Entry extended URL containing in addition of service URL, its type and min/max zoom info
708     */
709    public void setExtendedUrl(String url) {
710        CheckParameterUtil.ensureParameterNotNull(url);
711
712        // Default imagery type is WMS
713        this.url = url;
714        this.imageryType = ImageryType.WMS;
715
716        defaultMaxZoom = 0;
717        defaultMinZoom = 0;
718        for (ImageryType type : ImageryType.values()) {
719            Matcher m = Pattern.compile(type.getTypeString()+"(?:\\[(?:(\\d+)[,-])?(\\d+)\\])?:(.*)").matcher(url);
720            if (m.matches()) {
721                this.url = m.group(3);
722                this.imageryType = type;
723                if (m.group(2) != null) {
724                    defaultMaxZoom = Integer.parseInt(m.group(2));
725                }
726                if (m.group(1) != null) {
727                    defaultMinZoom = Integer.parseInt(m.group(1));
728                }
729                break;
730            }
731        }
732
733        if (serverProjections.isEmpty()) {
734            serverProjections = new ArrayList<>();
735            Matcher m = Pattern.compile(".*\\{PROJ\\(([^)}]+)\\)\\}.*").matcher(url.toUpperCase(Locale.ENGLISH));
736            if (m.matches()) {
737                for (String p : m.group(1).split(",")) {
738                    serverProjections.add(p);
739                }
740            }
741        }
742    }
743
744    /**
745     * Returns the entry name.
746     * @return The entry name
747     * @since 6968
748     */
749    public String getOriginalName() {
750        return this.origName != null ? this.origName : this.name;
751    }
752
753    /**
754     * Sets the entry name and handle translation.
755     * @param language The used language
756     * @param name The entry name
757     * @since 8091
758     */
759    public void setName(String language, String name) {
760        boolean isdefault = LanguageInfo.getJOSMLocaleCode(null).equals(language);
761        if (LanguageInfo.isBetterLanguage(langName, language)) {
762            this.name = isdefault ? tr(name) : name;
763            this.langName = language;
764        }
765        if (origName == null || isdefault) {
766            this.origName = name;
767        }
768    }
769
770    /**
771     * Store the id of this info to the preferences and clear it afterwards.
772     */
773    public void clearId() {
774        if (this.id != null) {
775            Collection<String> newAddedIds = new TreeSet<>(Main.pref.getCollection("imagery.layers.addedIds"));
776            newAddedIds.add(this.id);
777            Main.pref.putCollection("imagery.layers.addedIds", newAddedIds);
778        }
779        setId(null);
780    }
781
782    /**
783     * Determines if this entry is enabled by default.
784     * @return {@code true} if this entry is enabled by default, {@code false} otherwise
785     */
786    public boolean isDefaultEntry() {
787        return defaultEntry;
788    }
789
790    /**
791     * Sets the default state of this entry.
792     * @param defaultEntry {@code true} if this entry has to be enabled by default, {@code false} otherwise
793     */
794    public void setDefaultEntry(boolean defaultEntry) {
795        this.defaultEntry = defaultEntry;
796    }
797
798    /**
799     * Return the data part of HTTP cookies header in case the service requires cookies to work
800     * @return the cookie data part
801     */
802    @Override
803    public String getCookies() {
804        return this.cookies;
805    }
806
807    /**
808     * Gets the pixel per degree value
809     * @return The ppd value.
810     */
811    public double getPixelPerDegree() {
812        return this.pixelPerDegree;
813    }
814
815    /**
816     * Returns the maximum zoom level.
817     * @return The maximum zoom level
818     */
819    @Override
820    public int getMaxZoom() {
821        return this.defaultMaxZoom;
822    }
823
824    /**
825     * Returns the minimum zoom level.
826     * @return The minimum zoom level
827     */
828    @Override
829    public int getMinZoom() {
830        return this.defaultMinZoom;
831    }
832
833    /**
834     * Returns the description text when existing.
835     * @return The description
836     * @since 8065
837     */
838    public String getDescription() {
839        return this.description;
840    }
841
842    /**
843     * Sets the description text when existing.
844     * @param language The used language
845     * @param description the imagery description text
846     * @since 8091
847     */
848    public void setDescription(String language, String description) {
849        boolean isdefault = LanguageInfo.getJOSMLocaleCode(null).equals(language);
850        if (LanguageInfo.isBetterLanguage(langDescription, language)) {
851            this.description = isdefault ? tr(description) : description;
852            this.langDescription = language;
853        }
854    }
855
856    /**
857     * Returns a tool tip text for display.
858     * @return The text
859     * @since 8065
860     */
861    public String getToolTipText() {
862        StringBuilder res = new StringBuilder(getName());
863        boolean html = false;
864        String dateStr = getDate();
865        if (dateStr != null && !dateStr.isEmpty()) {
866            res.append("<br>").append(tr("Date of imagery: {0}", dateStr));
867            html = true;
868        }
869        if (bestMarked) {
870            res.append("<br>").append(tr("This imagery is marked as best in this region in other editors."));
871            html = true;
872        }
873        String desc = getDescription();
874        if (desc != null && !desc.isEmpty()) {
875            res.append("<br>").append(Utils.escapeReservedCharactersHTML(desc));
876            html = true;
877        }
878        if (html) {
879            res.insert(0, "<html>").append("</html>");
880        }
881        return res.toString();
882    }
883
884    /**
885     * Returns the EULA acceptance URL, if any.
886     * @return The URL to an EULA text that has to be accepted before use, or {@code null}
887     */
888    public String getEulaAcceptanceRequired() {
889        return eulaAcceptanceRequired;
890    }
891
892    /**
893     * Sets the EULA acceptance URL.
894     * @param eulaAcceptanceRequired The URL to an EULA text that has to be accepted before use
895     */
896    public void setEulaAcceptanceRequired(String eulaAcceptanceRequired) {
897        this.eulaAcceptanceRequired = eulaAcceptanceRequired;
898    }
899
900    /**
901     * Returns the ISO 3166-1-alpha-2 country code.
902     * @return The country code (2 letters)
903     */
904    public String getCountryCode() {
905        return countryCode;
906    }
907
908    /**
909     * Sets the ISO 3166-1-alpha-2 country code.
910     * @param countryCode The country code (2 letters)
911     */
912    public void setCountryCode(String countryCode) {
913        this.countryCode = countryCode;
914    }
915
916    /**
917     * Returns the date information.
918     * @return The date (in the form YYYY-MM-DD;YYYY-MM-DD, where
919     * DD and MM as well as a second date are optional)
920     * @since 11570
921     */
922    public String getDate() {
923        return date;
924    }
925
926    /**
927     * Sets the date information.
928     * @param date The date information
929     * @since 11570
930     */
931    public void setDate(String date) {
932        this.date = date;
933    }
934
935    /**
936     * Returns the entry icon.
937     * @return The entry icon
938     */
939    public String getIcon() {
940        return icon;
941    }
942
943    /**
944     * Sets the entry icon.
945     * @param icon The entry icon
946     */
947    public void setIcon(String icon) {
948        this.icon = icon;
949    }
950
951    /**
952     * Get the projections supported by the server. Only relevant for
953     * WMS-type ImageryInfo at the moment.
954     * @return null, if no projections have been specified; the list
955     * of supported projections otherwise.
956     */
957    public List<String> getServerProjections() {
958        return Collections.unmodifiableList(serverProjections);
959    }
960
961    /**
962     * Sets the list of collections the server supports
963     * @param serverProjections The list of supported projections
964     */
965    public void setServerProjections(Collection<String> serverProjections) {
966        CheckParameterUtil.ensureParameterNotNull(serverProjections, "serverProjections");
967        this.serverProjections = new ArrayList<>(serverProjections);
968    }
969
970    /**
971     * Returns the extended URL, containing in addition of service URL, its type and min/max zoom info.
972     * @return The extended URL
973     */
974    public String getExtendedUrl() {
975        return imageryType.getTypeString() + (defaultMaxZoom != 0
976            ? ('['+(defaultMinZoom != 0 ? (Integer.toString(defaultMinZoom) + ',') : "")+defaultMaxZoom+']') : "") + ':' + url;
977    }
978
979    /**
980     * Gets a unique toolbar key to store this layer as toolbar item
981     * @return The kay.
982     */
983    public String getToolbarName() {
984        String res = name;
985        if (pixelPerDegree != 0) {
986            res += "#PPD="+pixelPerDegree;
987        }
988        return res;
989    }
990
991    /**
992     * Gets the name that should be displayed in the menu to add this imagery layer.
993     * @return The text.
994     */
995    public String getMenuName() {
996        String res = name;
997        if (pixelPerDegree != 0) {
998            res += " ("+pixelPerDegree+')';
999        }
1000        return res;
1001    }
1002
1003    /**
1004     * Determines if this entry requires attribution.
1005     * @return {@code true} if some attribution text has to be displayed, {@code false} otherwise
1006     */
1007    public boolean hasAttribution() {
1008        return attributionText != null;
1009    }
1010
1011    /**
1012     * Copies attribution from another {@code ImageryInfo}.
1013     * @param i The other imagery info to get attribution from
1014     */
1015    public void copyAttribution(ImageryInfo i) {
1016        this.attributionImage = i.attributionImage;
1017        this.attributionImageURL = i.attributionImageURL;
1018        this.attributionText = i.attributionText;
1019        this.attributionLinkURL = i.attributionLinkURL;
1020        this.termsOfUseText = i.termsOfUseText;
1021        this.termsOfUseURL = i.termsOfUseURL;
1022    }
1023
1024    /**
1025     * Applies the attribution from this object to a tile source.
1026     * @param s The tile source
1027     */
1028    public void setAttribution(AbstractTileSource s) {
1029        if (attributionText != null) {
1030            if ("osm".equals(attributionText)) {
1031                s.setAttributionText(new Mapnik().getAttributionText(0, null, null));
1032            } else {
1033                s.setAttributionText(attributionText);
1034            }
1035        }
1036        if (attributionLinkURL != null) {
1037            if ("osm".equals(attributionLinkURL)) {
1038                s.setAttributionLinkURL(new Mapnik().getAttributionLinkURL());
1039            } else {
1040                s.setAttributionLinkURL(attributionLinkURL);
1041            }
1042        }
1043        if (attributionImage != null) {
1044            ImageIcon i = ImageProvider.getIfAvailable(null, attributionImage);
1045            if (i != null) {
1046                s.setAttributionImage(i.getImage());
1047            }
1048        }
1049        if (attributionImageURL != null) {
1050            s.setAttributionImageURL(attributionImageURL);
1051        }
1052        if (termsOfUseText != null) {
1053            s.setTermsOfUseText(termsOfUseText);
1054        }
1055        if (termsOfUseURL != null) {
1056            if ("osm".equals(termsOfUseURL)) {
1057                s.setTermsOfUseURL(new Mapnik().getTermsOfUseURL());
1058            } else {
1059                s.setTermsOfUseURL(termsOfUseURL);
1060            }
1061        }
1062    }
1063
1064    /**
1065     * Returns the imagery type.
1066     * @return The imagery type
1067     */
1068    public ImageryType getImageryType() {
1069        return imageryType;
1070    }
1071
1072    /**
1073     * Sets the imagery type.
1074     * @param imageryType The imagery type
1075     */
1076    public void setImageryType(ImageryType imageryType) {
1077        this.imageryType = imageryType;
1078    }
1079
1080    /**
1081     * Returns true if this layer's URL is matched by one of the regular
1082     * expressions kept by the current OsmApi instance.
1083     * @return {@code true} is this entry is blacklisted, {@code false} otherwise
1084     */
1085    public boolean isBlacklisted() {
1086        Capabilities capabilities = OsmApi.getOsmApi().getCapabilities();
1087        return capabilities != null && capabilities.isOnImageryBlacklist(this.url);
1088    }
1089
1090    /**
1091     * Sets the map of &lt;header name, header value&gt; that if any of this header
1092     * will be returned, then this tile will be treated as "no tile at this zoom level"
1093     *
1094     * @param noTileHeaders Map of &lt;header name, header value&gt; which will be treated as "no tile at this zoom level"
1095     * @since 9613
1096     */
1097    public void setNoTileHeaders(MultiMap<String, String> noTileHeaders) {
1098       if (noTileHeaders == null || noTileHeaders.isEmpty()) {
1099           this.noTileHeaders = null;
1100       } else {
1101            this.noTileHeaders = noTileHeaders.toMap();
1102       }
1103    }
1104
1105    @Override
1106    public Map<String, Set<String>> getNoTileHeaders() {
1107        return noTileHeaders;
1108    }
1109
1110    /**
1111     * Sets the map of &lt;checksum type, checksum value&gt; that if any tile with that checksum
1112     * will be returned, then this tile will be treated as "no tile at this zoom level"
1113     *
1114     * @param noTileChecksums Map of &lt;checksum type, checksum value&gt; which will be treated as "no tile at this zoom level"
1115     * @since 9613
1116     */
1117    public void setNoTileChecksums(MultiMap<String, String> noTileChecksums) {
1118        if (noTileChecksums == null || noTileChecksums.isEmpty()) {
1119            this.noTileChecksums = null;
1120        } else {
1121            this.noTileChecksums = noTileChecksums.toMap();
1122        }
1123    }
1124
1125    @Override
1126    public Map<String, Set<String>> getNoTileChecksums() {
1127        return noTileChecksums;
1128    }
1129
1130    /**
1131     * Returns the map of &lt;header name, metadata key&gt; indicating, which HTTP headers should
1132     * be moved to metadata
1133     *
1134     * @param metadataHeaders map of &lt;header name, metadata key&gt; indicating, which HTTP headers should be moved to metadata
1135     * @since 8418
1136     */
1137    public void setMetadataHeaders(Map<String, String> metadataHeaders) {
1138        if (metadataHeaders == null || metadataHeaders.isEmpty()) {
1139            this.metadataHeaders = null;
1140        } else {
1141            this.metadataHeaders = metadataHeaders;
1142        }
1143    }
1144
1145    /**
1146     * Gets the flag if epsg 4326 to 3857 is supported
1147     * @return The flag.
1148     */
1149    public boolean isEpsg4326To3857Supported() {
1150        return isEpsg4326To3857Supported;
1151    }
1152
1153    /**
1154     * Sets the flag that epsg 4326 to 3857 is supported
1155     * @param isEpsg4326To3857Supported The flag.
1156     */
1157    public void setEpsg4326To3857Supported(boolean isEpsg4326To3857Supported) {
1158        this.isEpsg4326To3857Supported = isEpsg4326To3857Supported;
1159    }
1160
1161    /**
1162     * Gets the flag if the georeference is valid.
1163     * @return <code>true</code> if it is valid.
1164     */
1165    public boolean isGeoreferenceValid() {
1166        return isGeoreferenceValid;
1167    }
1168
1169    /**
1170     * Sets an indicator that the georeference is valid
1171     * @param isGeoreferenceValid <code>true</code> if it is marked as valid.
1172     */
1173    public void setGeoreferenceValid(boolean isGeoreferenceValid) {
1174        this.isGeoreferenceValid = isGeoreferenceValid;
1175    }
1176
1177    /**
1178     * Returns the status of "best" marked status in other editors.
1179     * @return <code>true</code> if it is marked as best.
1180     * @since 11575
1181     */
1182    public boolean isBestMarked() {
1183        return bestMarked;
1184    }
1185
1186    /**
1187     * Sets an indicator that in other editors it is marked as best imagery
1188     * @param bestMarked <code>true</code> if it is marked as best in other editors.
1189     * @since 11575
1190     */
1191    public void setBestMarked(boolean bestMarked) {
1192        this.bestMarked = bestMarked;
1193    }
1194
1195    /**
1196     * Adds a mirror entry. Mirror entries are completed with the data from the master entry
1197     * and only describe another method to access identical data.
1198     *
1199     * @param entry the mirror to be added
1200     * @since 9658
1201     */
1202    public void addMirror(ImageryInfo entry) {
1203       if (mirrors == null) {
1204           mirrors = new ArrayList<>();
1205       }
1206       mirrors.add(entry);
1207    }
1208
1209    /**
1210     * Returns the mirror entries. Entries are completed with master entry data.
1211     *
1212     * @return the list of mirrors
1213     * @since 9658
1214     */
1215    public List<ImageryInfo> getMirrors() {
1216       List<ImageryInfo> l = new ArrayList<>();
1217       if (mirrors != null) {
1218           int num = 1;
1219           for (ImageryInfo i : mirrors) {
1220               ImageryInfo n = new ImageryInfo(this);
1221               if (i.defaultMaxZoom != 0) {
1222                   n.defaultMaxZoom = i.defaultMaxZoom;
1223               }
1224               if (i.defaultMinZoom != 0) {
1225                   n.defaultMinZoom = i.defaultMinZoom;
1226               }
1227               n.setServerProjections(i.getServerProjections());
1228               n.url = i.url;
1229               n.imageryType = i.imageryType;
1230               if (i.getTileSize() != 0) {
1231                   n.setTileSize(i.getTileSize());
1232               }
1233               if (n.id != null) {
1234                   n.id = n.id + "_mirror"+num;
1235               }
1236               if (num > 1) {
1237                   n.name = tr("{0} mirror server {1}", n.name, num);
1238                   if (n.origName != null) {
1239                       n.origName += " mirror server " + num;
1240                   }
1241               } else {
1242                   n.name = tr("{0} mirror server", n.name);
1243                   if (n.origName != null) {
1244                       n.origName += " mirror server";
1245                   }
1246               }
1247               l.add(n);
1248               ++num;
1249           }
1250       }
1251       return l;
1252    }
1253
1254    /**
1255     * Returns default layers that should be shown for this Imagery (if at all supported by imagery provider)
1256     * If no layer is set to default and there is more than one imagery available, then user will be asked to choose the layer
1257     * to work on
1258     * @return Collection of the layer names
1259     */
1260    public Collection<DefaultLayer> getDefaultLayers() {
1261        return defaultLayers;
1262    }
1263
1264    /**
1265     * Sets the default layers that user will work with
1266     * @param layers set the list of default layers
1267     */
1268    public void setDefaultLayers(Collection<DefaultLayer> layers) {
1269        if (ImageryType.WMTS.equals(this.imageryType)) {
1270            CheckParameterUtil.ensureThat(layers == null ||
1271                    layers.isEmpty() ||
1272                    layers.iterator().next() instanceof WMTSDefaultLayer, "Incorrect default layer");
1273        }
1274        this.defaultLayers = layers;
1275    }
1276}
Note: See TracBrowser for help on using the repository browser.