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