001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.imagery;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Image;
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.Collection;
010import java.util.Collections;
011import java.util.List;
012import java.util.Locale;
013import java.util.Map;
014import java.util.Objects;
015import java.util.Set;
016import java.util.TreeSet;
017import java.util.regex.Matcher;
018import java.util.regex.Pattern;
019
020import javax.swing.ImageIcon;
021
022import org.openstreetmap.gui.jmapviewer.interfaces.Attributed;
023import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
024import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTileSource;
025import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.Mapnik;
026import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo;
027import org.openstreetmap.josm.Main;
028import org.openstreetmap.josm.data.Bounds;
029import org.openstreetmap.josm.data.Preferences.pref;
030import org.openstreetmap.josm.io.Capabilities;
031import org.openstreetmap.josm.io.OsmApi;
032import org.openstreetmap.josm.tools.CheckParameterUtil;
033import org.openstreetmap.josm.tools.ImageProvider;
034import org.openstreetmap.josm.tools.LanguageInfo;
035import org.openstreetmap.josm.tools.MultiMap;
036
037/**
038 * Class that stores info about an image background layer.
039 *
040 * @author Frederik Ramm
041 */
042public class ImageryInfo extends TileSourceInfo implements Comparable<ImageryInfo>, Attributed {
043
044    /**
045     * Type of imagery entry.
046     */
047    public enum ImageryType {
048        /** A WMS (Web Map Service) entry. **/
049        WMS("wms"),
050        /** A TMS (Tile Map Service) entry. **/
051        TMS("tms"),
052        /** An HTML proxy (previously used for Yahoo imagery) entry. **/
053        HTML("html"),
054        /** TMS entry for Microsoft Bing. */
055        BING("bing"),
056        /** TMS entry for Russian company <a href="https://wiki.openstreetmap.org/wiki/WikiProject_Russia/kosmosnimki">ScanEx</a>. **/
057        SCANEX("scanex"),
058        /** A WMS endpoint entry only stores the WMS server info, without layer, which are chosen later by the user. **/
059        WMS_ENDPOINT("wms_endpoint"),
060        /** WMTS stores GetCapabilities URL. Does not store any information about the layer **/
061        WMTS("wmts");
062
063
064        private final String typeString;
065
066        ImageryType(String urlString) {
067            this.typeString = urlString;
068        }
069
070        /**
071         * Returns the unique string identifying this type.
072         * @return the unique string identifying this type
073         * @since 6690
074         */
075        public final String getTypeString() {
076            return typeString;
077        }
078
079        /**
080         * Returns the imagery type from the given type string.
081         * @param s The type string
082         * @return the imagery type matching the given type string
083         */
084        public static ImageryType fromString(String s) {
085            for (ImageryType type : ImageryType.values()) {
086                if (type.getTypeString().equals(s)) {
087                    return type;
088                }
089            }
090            return null;
091        }
092    }
093
094    /**
095     * Multi-polygon bounds for imagery backgrounds.
096     * Used to display imagery coverage in preferences and to determine relevant imagery entries based on edit location.
097     */
098    public static class ImageryBounds extends Bounds {
099
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;
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    /** mirrors of different type for this entry */
190    private List<ImageryInfo> mirrors;
191    /** icon used in menu */
192    private String icon;
193    private boolean isGeoreferenceValid;
194    private boolean isEpsg4326To3857Supported;
195    // when adding a field, also adapt the ImageryInfo(ImageryInfo)
196    // and ImageryInfo(ImageryPreferenceEntry) constructor, equals method, and ImageryPreferenceEntry
197
198    /**
199     * Auxiliary class to save an {@link ImageryInfo} object in the preferences.
200     */
201    public static class ImageryPreferenceEntry {
202        @pref String name;
203        @pref String id;
204        @pref String type;
205        @pref String url;
206        @pref double pixel_per_eastnorth;
207        @pref String eula;
208        @pref String attribution_text;
209        @pref String attribution_url;
210        @pref String logo_image;
211        @pref String logo_url;
212        @pref String terms_of_use_text;
213        @pref String terms_of_use_url;
214        @pref String country_code = "";
215        @pref int max_zoom;
216        @pref int min_zoom;
217        @pref String cookies;
218        @pref String bounds;
219        @pref String shapes;
220        @pref String projections;
221        @pref String icon;
222        @pref String description;
223        @pref MultiMap<String, String> noTileHeaders;
224        @pref MultiMap<String, String> noTileChecksums;
225        @pref int tileSize = -1;
226        @pref Map<String, String> metadataHeaders;
227        @pref boolean valid_georeference;
228        @pref boolean supports_epsg_4326_to_3857_conversion;
229
230        /**
231         * Constructs a new empty WMS {@code ImageryPreferenceEntry}.
232         */
233        public ImageryPreferenceEntry() {
234            // Do nothing
235        }
236
237        /**
238         * Constructs a new {@code ImageryPreferenceEntry} from a given {@code ImageryInfo}.
239         * @param i The corresponding imagery info
240         */
241        public ImageryPreferenceEntry(ImageryInfo i) {
242            name = i.name;
243            id = i.id;
244            type = i.imageryType.getTypeString();
245            url = i.url;
246            pixel_per_eastnorth = i.pixelPerDegree;
247            eula = i.eulaAcceptanceRequired;
248            attribution_text = i.attributionText;
249            attribution_url = i.attributionLinkURL;
250            logo_image = i.attributionImage;
251            logo_url = i.attributionImageURL;
252            terms_of_use_text = i.termsOfUseText;
253            terms_of_use_url = i.termsOfUseURL;
254            country_code = i.countryCode;
255            max_zoom = i.defaultMaxZoom;
256            min_zoom = i.defaultMinZoom;
257            cookies = i.cookies;
258            icon = i.icon;
259            description = i.description;
260            if (i.bounds != null) {
261                bounds = i.bounds.encodeAsString(",");
262                StringBuilder shapesString = new StringBuilder();
263                for (Shape s : i.bounds.getShapes()) {
264                    if (shapesString.length() > 0) {
265                        shapesString.append(';');
266                    }
267                    shapesString.append(s.encodeAsString(","));
268                }
269                if (shapesString.length() > 0) {
270                    shapes = shapesString.toString();
271                }
272            }
273            if (i.serverProjections != null && !i.serverProjections.isEmpty()) {
274                StringBuilder val = new StringBuilder();
275                for (String p : i.serverProjections) {
276                    if (val.length() > 0) {
277                        val.append(',');
278                    }
279                    val.append(p);
280                }
281                projections = val.toString();
282            }
283            if (i.noTileHeaders != null && !i.noTileHeaders.isEmpty()) {
284                noTileHeaders = new MultiMap<>(i.noTileHeaders);
285            }
286
287            if (i.noTileChecksums != null && !i.noTileChecksums.isEmpty()) {
288                noTileChecksums = new MultiMap<>(i.noTileChecksums);
289            }
290
291            if (i.metadataHeaders != null && !i.metadataHeaders.isEmpty()) {
292                metadataHeaders = i.metadataHeaders;
293            }
294
295            tileSize = i.getTileSize();
296
297            valid_georeference = i.isGeoreferenceValid();
298            supports_epsg_4326_to_3857_conversion = i.isEpsg4326To3857Supported();
299        }
300
301        @Override
302        public String toString() {
303            StringBuilder s = new StringBuilder("ImageryPreferenceEntry [name=").append(name);
304            if (id != null) {
305                s.append(" id=").append(id);
306            }
307            s.append(']');
308            return s.toString();
309        }
310    }
311
312    /**
313     * Constructs a new WMS {@code ImageryInfo}.
314     */
315    public ImageryInfo() {
316        super();
317    }
318
319    /**
320     * Constructs a new WMS {@code ImageryInfo} with a given name.
321     * @param name The entry name
322     */
323    public ImageryInfo(String name) {
324        super(name);
325    }
326
327    /**
328     * Constructs a new WMS {@code ImageryInfo} with given name and extended URL.
329     * @param name The entry name
330     * @param url The entry extended URL
331     */
332    public ImageryInfo(String name, String url) {
333        this(name);
334        setExtendedUrl(url);
335    }
336
337    /**
338     * Constructs a new WMS {@code ImageryInfo} with given name, extended and EULA URLs.
339     * @param name The entry name
340     * @param url The entry URL
341     * @param eulaAcceptanceRequired The EULA URL
342     */
343    public ImageryInfo(String name, String url, String eulaAcceptanceRequired) {
344        this(name);
345        setExtendedUrl(url);
346        this.eulaAcceptanceRequired = eulaAcceptanceRequired;
347    }
348
349    /**
350     * Constructs a new {@code ImageryInfo} with given name, url, extended and EULA URLs.
351     * @param name The entry name
352     * @param url The entry URL
353     * @param type The entry imagery type. If null, WMS will be used as default
354     * @param eulaAcceptanceRequired The EULA URL
355     * @param cookies The data part of HTTP cookies header in case the service requires cookies to work
356     * @throws IllegalArgumentException if type refers to an unknown imagery type
357     */
358    public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies) {
359        this(name);
360        setExtendedUrl(url);
361        ImageryType t = ImageryType.fromString(type);
362        this.cookies = cookies;
363        this.eulaAcceptanceRequired = eulaAcceptanceRequired;
364        if (t != null) {
365            this.imageryType = t;
366        } else if (type != null && !type.trim().isEmpty()) {
367            throw new IllegalArgumentException("unknown type: "+type);
368        }
369    }
370
371    public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies, String id) {
372        this(name, url, type, eulaAcceptanceRequired, cookies);
373        setId(id);
374    }
375
376    /**
377     * Constructs a new {@code ImageryInfo} from an imagery preference entry.
378     * @param e The imagery preference entry
379     */
380    public ImageryInfo(ImageryPreferenceEntry e) {
381        super(e.name, e.url, e.id);
382        CheckParameterUtil.ensureParameterNotNull(e.name, "name");
383        CheckParameterUtil.ensureParameterNotNull(e.url, "url");
384        description = e.description;
385        cookies = e.cookies;
386        eulaAcceptanceRequired = e.eula;
387        imageryType = ImageryType.fromString(e.type);
388        if (imageryType == null) throw new IllegalArgumentException("unknown type");
389        pixelPerDegree = e.pixel_per_eastnorth;
390        defaultMaxZoom = e.max_zoom;
391        defaultMinZoom = e.min_zoom;
392        if (e.bounds != null) {
393            bounds = new ImageryBounds(e.bounds, ",");
394            if (e.shapes != null) {
395                try {
396                    for (String s : e.shapes.split(";")) {
397                        bounds.addShape(new Shape(s, ","));
398                    }
399                } catch (IllegalArgumentException ex) {
400                    Main.warn(ex);
401                }
402            }
403        }
404        if (e.projections != null) {
405            serverProjections = Arrays.asList(e.projections.split(","));
406        }
407        attributionText = e.attribution_text;
408        attributionLinkURL = e.attribution_url;
409        attributionImage = e.logo_image;
410        attributionImageURL = e.logo_url;
411        termsOfUseText = e.terms_of_use_text;
412        termsOfUseURL = e.terms_of_use_url;
413        countryCode = e.country_code;
414        icon = e.icon;
415        if (e.noTileHeaders != null) {
416            noTileHeaders = e.noTileHeaders.toMap();
417        }
418        if (e.noTileChecksums != null) {
419            noTileChecksums = e.noTileChecksums.toMap();
420        }
421        setTileSize(e.tileSize);
422        metadataHeaders = e.metadataHeaders;
423        isEpsg4326To3857Supported = e.supports_epsg_4326_to_3857_conversion;
424        isGeoreferenceValid = e.valid_georeference;
425    }
426
427    /**
428     * Constructs a new {@code ImageryInfo} from an existing one.
429     * @param i The other imagery info
430     */
431    public ImageryInfo(ImageryInfo i) {
432        super(i.name, i.url, i.id);
433        this.defaultEntry = i.defaultEntry;
434        this.cookies = i.cookies;
435        this.eulaAcceptanceRequired = null;
436        this.imageryType = i.imageryType;
437        this.pixelPerDegree = i.pixelPerDegree;
438        this.defaultMaxZoom = i.defaultMaxZoom;
439        this.defaultMinZoom = i.defaultMinZoom;
440        this.bounds = i.bounds;
441        this.serverProjections = i.serverProjections;
442        this.attributionText = i.attributionText;
443        this.attributionLinkURL = i.attributionLinkURL;
444        this.attributionImage = i.attributionImage;
445        this.attributionImageURL = i.attributionImageURL;
446        this.termsOfUseText = i.termsOfUseText;
447        this.termsOfUseURL = i.termsOfUseURL;
448        this.countryCode = i.countryCode;
449        this.icon = i.icon;
450        this.description = i.description;
451        this.noTileHeaders = i.noTileHeaders;
452        this.noTileChecksums = i.noTileChecksums;
453        this.metadataHeaders = i.metadataHeaders;
454        this.isEpsg4326To3857Supported = i.isEpsg4326To3857Supported;
455        this.isGeoreferenceValid = i.isGeoreferenceValid;
456    }
457
458    @Override
459    public int hashCode() {
460        return Objects.hash(url, imageryType);
461    }
462
463    /**
464     * Check if this object equals another ImageryInfo with respect to the properties
465     * that get written to the preference file.
466     *
467     * The field {@link #pixelPerDegree} is ignored.
468     *
469     * @param other the ImageryInfo object to compare to
470     * @return true if they are equal
471     */
472    public boolean equalsPref(ImageryInfo other) {
473        if (other == null) {
474            return false;
475        }
476
477        return
478                Objects.equals(this.name, other.name) &&
479                Objects.equals(this.id, other.id) &&
480                Objects.equals(this.url, other.url) &&
481                Objects.equals(this.cookies, other.cookies) &&
482                Objects.equals(this.eulaAcceptanceRequired, other.eulaAcceptanceRequired) &&
483                Objects.equals(this.imageryType, other.imageryType) &&
484                Objects.equals(this.defaultMaxZoom, other.defaultMaxZoom) &&
485                Objects.equals(this.defaultMinZoom, other.defaultMinZoom) &&
486                Objects.equals(this.bounds, other.bounds) &&
487                Objects.equals(this.serverProjections, other.serverProjections) &&
488                Objects.equals(this.attributionText, other.attributionText) &&
489                Objects.equals(this.attributionLinkURL, other.attributionLinkURL) &&
490                Objects.equals(this.attributionImageURL, other.attributionImageURL) &&
491                Objects.equals(this.attributionImage, other.attributionImage) &&
492                Objects.equals(this.termsOfUseText, other.termsOfUseText) &&
493                Objects.equals(this.termsOfUseURL, other.termsOfUseURL) &&
494                Objects.equals(this.countryCode, other.countryCode) &&
495                Objects.equals(this.icon, other.icon) &&
496                Objects.equals(this.description, other.description) &&
497                Objects.equals(this.noTileHeaders, other.noTileHeaders) &&
498                Objects.equals(this.noTileChecksums, other.noTileChecksums) &&
499                Objects.equals(this.metadataHeaders, other.metadataHeaders);
500    }
501
502    @Override
503    public boolean equals(Object o) {
504        if (this == o) return true;
505        if (o == null || getClass() != o.getClass()) return false;
506        ImageryInfo that = (ImageryInfo) o;
507        return imageryType == that.imageryType && Objects.equals(url, that.url);
508    }
509
510    @Override
511    public String toString() {
512        return "ImageryInfo{" +
513                "name='" + name + '\'' +
514                ", countryCode='" + countryCode + '\'' +
515                ", url='" + url + '\'' +
516                ", imageryType=" + imageryType +
517                '}';
518    }
519
520    @Override
521    public int compareTo(ImageryInfo in) {
522        int i = countryCode.compareTo(in.countryCode);
523        if (i == 0) {
524            i = name.toLowerCase(Locale.ENGLISH).compareTo(in.name.toLowerCase(Locale.ENGLISH));
525        }
526        if (i == 0) {
527            i = url.compareTo(in.url);
528        }
529        if (i == 0) {
530            i = Double.compare(pixelPerDegree, in.pixelPerDegree);
531        }
532        return i;
533    }
534
535    public boolean equalsBaseValues(ImageryInfo in) {
536        return url.equals(in.url);
537    }
538
539    public void setPixelPerDegree(double ppd) {
540        this.pixelPerDegree = ppd;
541    }
542
543    /**
544     * Sets the maximum zoom level.
545     * @param defaultMaxZoom The maximum zoom level
546     */
547    public void setDefaultMaxZoom(int defaultMaxZoom) {
548        this.defaultMaxZoom = defaultMaxZoom;
549    }
550
551    /**
552     * Sets the minimum zoom level.
553     * @param defaultMinZoom The minimum zoom level
554     */
555    public void setDefaultMinZoom(int defaultMinZoom) {
556        this.defaultMinZoom = defaultMinZoom;
557    }
558
559    /**
560     * Sets the imagery polygonial bounds.
561     * @param b The imagery bounds (non-rectangular)
562     */
563    public void setBounds(ImageryBounds b) {
564        this.bounds = b;
565    }
566
567    /**
568     * Returns the imagery polygonial bounds.
569     * @return The imagery bounds (non-rectangular)
570     */
571    public ImageryBounds getBounds() {
572        return bounds;
573    }
574
575    @Override
576    public boolean requiresAttribution() {
577        return attributionText != null || attributionImage != null || termsOfUseText != null || termsOfUseURL != null;
578    }
579
580    @Override
581    public String getAttributionText(int zoom, ICoordinate topLeft, ICoordinate botRight) {
582        return attributionText;
583    }
584
585    @Override
586    public String getAttributionLinkURL() {
587        return attributionLinkURL;
588    }
589
590    @Override
591    public Image getAttributionImage() {
592        ImageIcon i = ImageProvider.getIfAvailable(attributionImage);
593        if (i != null) {
594            return i.getImage();
595        }
596        return null;
597    }
598
599    @Override
600    public String getAttributionImageURL() {
601        return attributionImageURL;
602    }
603
604    @Override
605    public String getTermsOfUseText() {
606        return termsOfUseText;
607    }
608
609    @Override
610    public String getTermsOfUseURL() {
611        return termsOfUseURL;
612    }
613
614    public void setAttributionText(String text) {
615        attributionText = text;
616    }
617
618    public void setAttributionImageURL(String text) {
619        attributionImageURL = text;
620    }
621
622    public void setAttributionImage(String text) {
623        attributionImage = text;
624    }
625
626    public void setAttributionLinkURL(String text) {
627        attributionLinkURL = text;
628    }
629
630    public void setTermsOfUseText(String text) {
631        termsOfUseText = text;
632    }
633
634    public void setTermsOfUseURL(String text) {
635        termsOfUseURL = text;
636    }
637
638    /**
639     * Sets the extended URL of this entry.
640     * @param url Entry extended URL containing in addition of service URL, its type and min/max zoom info
641     */
642    public void setExtendedUrl(String url) {
643        CheckParameterUtil.ensureParameterNotNull(url);
644
645        // Default imagery type is WMS
646        this.url = url;
647        this.imageryType = ImageryType.WMS;
648
649        defaultMaxZoom = 0;
650        defaultMinZoom = 0;
651        for (ImageryType type : ImageryType.values()) {
652            Matcher m = Pattern.compile(type.getTypeString()+"(?:\\[(?:(\\d+),)?(\\d+)\\])?:(.*)").matcher(url);
653            if (m.matches()) {
654                this.url = m.group(3);
655                this.imageryType = type;
656                if (m.group(2) != null) {
657                    defaultMaxZoom = Integer.parseInt(m.group(2));
658                }
659                if (m.group(1) != null) {
660                    defaultMinZoom = Integer.parseInt(m.group(1));
661                }
662                break;
663            }
664        }
665
666        if (serverProjections == null || serverProjections.isEmpty()) {
667            serverProjections = new ArrayList<>();
668            Matcher m = Pattern.compile(".*\\{PROJ\\(([^)}]+)\\)\\}.*").matcher(url.toUpperCase(Locale.ENGLISH));
669            if (m.matches()) {
670                for (String p : m.group(1).split(",")) {
671                    serverProjections.add(p);
672                }
673            }
674        }
675    }
676
677    /**
678     * Returns the entry name.
679     * @return The entry name
680     * @since 6968
681     */
682    public String getOriginalName() {
683        return this.origName != null ? this.origName : this.name;
684    }
685
686    /**
687     * Sets the entry name and handle translation.
688     * @param language The used language
689     * @param name The entry name
690     * @since 8091
691     */
692    public void setName(String language, String name) {
693        boolean isdefault = LanguageInfo.getJOSMLocaleCode(null).equals(language);
694        if (LanguageInfo.isBetterLanguage(langName, language)) {
695            this.name = isdefault ? tr(name) : name;
696            this.langName = language;
697        }
698        if (origName == null || isdefault) {
699            this.origName = name;
700        }
701    }
702
703    public void clearId() {
704        if (this.id != null) {
705            Collection<String> newAddedIds = new TreeSet<>(Main.pref.getCollection("imagery.layers.addedIds"));
706            newAddedIds.add(this.id);
707            Main.pref.putCollection("imagery.layers.addedIds", newAddedIds);
708        }
709        setId(null);
710    }
711
712    /**
713     * Determines if this entry is enabled by default.
714     * @return {@code true} if this entry is enabled by default, {@code false} otherwise
715     */
716    public boolean isDefaultEntry() {
717        return defaultEntry;
718    }
719
720    /**
721     * Sets the default state of this entry.
722     * @param defaultEntry {@code true} if this entry has to be enabled by default, {@code false} otherwise
723     */
724    public void setDefaultEntry(boolean defaultEntry) {
725        this.defaultEntry = defaultEntry;
726    }
727
728    /**
729     * Return the data part of HTTP cookies header in case the service requires cookies to work
730     * @return the cookie data part
731     */
732    @Override
733    public String getCookies() {
734        return this.cookies;
735    }
736
737    public double getPixelPerDegree() {
738        return this.pixelPerDegree;
739    }
740
741    /**
742     * Returns the maximum zoom level.
743     * @return The maximum zoom level
744     */
745    @Override
746    public int getMaxZoom() {
747        return this.defaultMaxZoom;
748    }
749
750    /**
751     * Returns the minimum zoom level.
752     * @return The minimum zoom level
753     */
754    @Override
755    public int getMinZoom() {
756        return this.defaultMinZoom;
757    }
758
759    /**
760     * Returns the description text when existing.
761     * @return The description
762     * @since 8065
763     */
764    public String getDescription() {
765        return this.description;
766    }
767
768    /**
769     * Sets the description text when existing.
770     * @param language The used language
771     * @param description the imagery description text
772     * @since 8091
773     */
774    public void setDescription(String language, String description) {
775        boolean isdefault = LanguageInfo.getJOSMLocaleCode(null).equals(language);
776        if (LanguageInfo.isBetterLanguage(langDescription, language)) {
777            this.description = isdefault ? tr(description) : description;
778            this.langDescription = language;
779        }
780    }
781
782    /**
783     * Returns a tool tip text for display.
784     * @return The text
785     * @since 8065
786     */
787    public String getToolTipText() {
788        String desc = getDescription();
789        if (desc != null && !desc.isEmpty()) {
790            return "<html>" + getName() + "<br>" + desc + "</html>";
791        }
792        return getName();
793    }
794
795    /**
796     * Returns the EULA acceptance URL, if any.
797     * @return The URL to an EULA text that has to be accepted before use, or {@code null}
798     */
799    public String getEulaAcceptanceRequired() {
800        return eulaAcceptanceRequired;
801    }
802
803    /**
804     * Sets the EULA acceptance URL.
805     * @param eulaAcceptanceRequired The URL to an EULA text that has to be accepted before use
806     */
807    public void setEulaAcceptanceRequired(String eulaAcceptanceRequired) {
808        this.eulaAcceptanceRequired = eulaAcceptanceRequired;
809    }
810
811    /**
812     * Returns the ISO 3166-1-alpha-2 country code.
813     * @return The country code (2 letters)
814     */
815    public String getCountryCode() {
816        return countryCode;
817    }
818
819    /**
820     * Sets the ISO 3166-1-alpha-2 country code.
821     * @param countryCode The country code (2 letters)
822     */
823    public void setCountryCode(String countryCode) {
824        this.countryCode = countryCode;
825    }
826
827    /**
828     * Returns the entry icon.
829     * @return The entry icon
830     */
831    public String getIcon() {
832        return icon;
833    }
834
835    /**
836     * Sets the entry icon.
837     * @param icon The entry icon
838     */
839    public void setIcon(String icon) {
840        this.icon = icon;
841    }
842
843    /**
844     * Get the projections supported by the server. Only relevant for
845     * WMS-type ImageryInfo at the moment.
846     * @return null, if no projections have been specified; the list
847     * of supported projections otherwise.
848     */
849    public List<String> getServerProjections() {
850        if (serverProjections == null)
851            return Collections.emptyList();
852        return Collections.unmodifiableList(serverProjections);
853    }
854
855    public void setServerProjections(Collection<String> serverProjections) {
856        this.serverProjections = new ArrayList<>(serverProjections);
857    }
858
859    /**
860     * Returns the extended URL, containing in addition of service URL, its type and min/max zoom info.
861     * @return The extended URL
862     */
863    public String getExtendedUrl() {
864        return imageryType.getTypeString() + (defaultMaxZoom != 0
865            ? ('['+(defaultMinZoom != 0 ? (Integer.toString(defaultMinZoom) + ',') : "")+defaultMaxZoom+"]") : "") + ':' + url;
866    }
867
868    public String getToolbarName() {
869        String res = name;
870        if (pixelPerDegree != 0) {
871            res += "#PPD="+pixelPerDegree;
872        }
873        return res;
874    }
875
876    public String getMenuName() {
877        String res = name;
878        if (pixelPerDegree != 0) {
879            res += " ("+pixelPerDegree+')';
880        }
881        return res;
882    }
883
884    /**
885     * Determines if this entry requires attribution.
886     * @return {@code true} if some attribution text has to be displayed, {@code false} otherwise
887     */
888    public boolean hasAttribution() {
889        return attributionText != null;
890    }
891
892    /**
893     * Copies attribution from another {@code ImageryInfo}.
894     * @param i The other imagery info to get attribution from
895     */
896    public void copyAttribution(ImageryInfo i) {
897        this.attributionImage = i.attributionImage;
898        this.attributionImageURL = i.attributionImageURL;
899        this.attributionText = i.attributionText;
900        this.attributionLinkURL = i.attributionLinkURL;
901        this.termsOfUseText = i.termsOfUseText;
902        this.termsOfUseURL = i.termsOfUseURL;
903    }
904
905    /**
906     * Applies the attribution from this object to a tile source.
907     * @param s The tile source
908     */
909    public void setAttribution(AbstractTileSource s) {
910        if (attributionText != null) {
911            if ("osm".equals(attributionText)) {
912                s.setAttributionText(new Mapnik().getAttributionText(0, null, null));
913            } else {
914                s.setAttributionText(attributionText);
915            }
916        }
917        if (attributionLinkURL != null) {
918            if ("osm".equals(attributionLinkURL)) {
919                s.setAttributionLinkURL(new Mapnik().getAttributionLinkURL());
920            } else {
921                s.setAttributionLinkURL(attributionLinkURL);
922            }
923        }
924        if (attributionImage != null) {
925            ImageIcon i = ImageProvider.getIfAvailable(null, attributionImage);
926            if (i != null) {
927                s.setAttributionImage(i.getImage());
928            }
929        }
930        if (attributionImageURL != null) {
931            s.setAttributionImageURL(attributionImageURL);
932        }
933        if (termsOfUseText != null) {
934            s.setTermsOfUseText(termsOfUseText);
935        }
936        if (termsOfUseURL != null) {
937            if ("osm".equals(termsOfUseURL)) {
938                s.setTermsOfUseURL(new Mapnik().getTermsOfUseURL());
939            } else {
940                s.setTermsOfUseURL(termsOfUseURL);
941            }
942        }
943    }
944
945    /**
946     * Returns the imagery type.
947     * @return The imagery type
948     */
949    public ImageryType getImageryType() {
950        return imageryType;
951    }
952
953    /**
954     * Sets the imagery type.
955     * @param imageryType The imagery type
956     */
957    public void setImageryType(ImageryType imageryType) {
958        this.imageryType = imageryType;
959    }
960
961    /**
962     * Returns true if this layer's URL is matched by one of the regular
963     * expressions kept by the current OsmApi instance.
964     * @return {@code true} is this entry is blacklisted, {@code false} otherwise
965     */
966    public boolean isBlacklisted() {
967        Capabilities capabilities = OsmApi.getOsmApi().getCapabilities();
968        return capabilities != null && capabilities.isOnImageryBlacklist(this.url);
969    }
970
971    /**
972     * Sets the map of &lt;header name, header value&gt; that if any of this header
973     * will be returned, then this tile will be treated as "no tile at this zoom level"
974     *
975     * @param noTileHeaders Map of &lt;header name, header value&gt; which will be treated as "no tile at this zoom level"
976     * @since 9613
977     */
978    public void setNoTileHeaders(MultiMap<String, String> noTileHeaders) {
979       if (noTileHeaders == null) {
980           this.noTileHeaders = null;
981       } else {
982            this.noTileHeaders = noTileHeaders.toMap();
983       }
984    }
985
986    @Override
987    public Map<String, Set<String>> getNoTileHeaders() {
988        return noTileHeaders;
989    }
990
991    /**
992     * Sets the map of &lt;checksum type, checksum value&gt; that if any tile with that checksum
993     * will be returned, then this tile will be treated as "no tile at this zoom level"
994     *
995     * @param noTileChecksums Map of &lt;checksum type, checksum value&gt; which will be treated as "no tile at this zoom level"
996     * @since 9613
997     */
998    public void setNoTileChecksums(MultiMap<String, String> noTileChecksums) {
999        if (noTileChecksums == null) {
1000            this.noTileChecksums = null;
1001        } else {
1002            this.noTileChecksums = noTileChecksums.toMap();
1003        }
1004    }
1005
1006    @Override
1007    public Map<String, Set<String>> getNoTileChecksums() {
1008        return noTileChecksums;
1009    }
1010
1011    /**
1012     * Returns the map of &lt;header name, metadata key&gt; indicating, which HTTP headers should
1013     * be moved to metadata
1014     *
1015     * @param metadataHeaders map of &lt;header name, metadata key&gt; indicating, which HTTP headers should be moved to metadata
1016     * @since 8418
1017     */
1018    public void setMetadataHeaders(Map<String, String> metadataHeaders) {
1019        this.metadataHeaders = metadataHeaders;
1020    }
1021
1022    public boolean isEpsg4326To3857Supported() {
1023        return isEpsg4326To3857Supported;
1024    }
1025
1026    public void setEpsg4326To3857Supported(boolean isEpsg4326To3857Supported) {
1027        this.isEpsg4326To3857Supported = isEpsg4326To3857Supported;
1028    }
1029
1030    public boolean isGeoreferenceValid() {
1031        return isGeoreferenceValid;
1032    }
1033
1034    public void setGeoreferenceValid(boolean isGeoreferenceValid) {
1035        this.isGeoreferenceValid = isGeoreferenceValid;
1036    }
1037
1038    /**
1039     * Adds a mirror entry. Mirror entries are completed with the data from the master entry
1040     * and only describe another method to access identical data.
1041     *
1042     * @param entry the mirror to be added
1043     * @since 9658
1044     */
1045    public void addMirror(ImageryInfo entry) {
1046       if (mirrors == null) {
1047           mirrors = new ArrayList<>();
1048       }
1049       mirrors.add(entry);
1050    }
1051
1052    /**
1053     * Returns the mirror entries. Entries are completed with master entry data.
1054     *
1055     * @return the list of mirrors
1056     * @since 9658
1057     */
1058    public List<ImageryInfo> getMirrors() {
1059       List<ImageryInfo> l = new ArrayList<>();
1060       if (mirrors != null) {
1061           for (ImageryInfo i : mirrors) {
1062               ImageryInfo n = new ImageryInfo(this);
1063               if (i.defaultMaxZoom != 0) {
1064                   n.defaultMaxZoom = i.defaultMaxZoom;
1065               }
1066               if (i.defaultMinZoom != 0) {
1067                   n.defaultMinZoom = i.defaultMinZoom;
1068               }
1069               if (i.serverProjections != null) {
1070                   n.serverProjections = i.serverProjections;
1071               }
1072               n.url = i.url;
1073               n.imageryType = i.imageryType;
1074               if (i.getTileSize() != 0) {
1075                   n.setTileSize(i.getTileSize());
1076               }
1077               l.add(n);
1078           }
1079       }
1080       return l;
1081    }
1082}