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    // when adding a field, also adapt the ImageryInfo(ImageryInfo) constructor, equals method, and ImageryPreferenceEntry
203
204    /**
205     * Auxiliary class to save an {@link ImageryInfo} object in the preferences.
206     */
207    public static class ImageryPreferenceEntry {
208        @pref String name;
209        @pref String id;
210        @pref String type;
211        @pref String url;
212        @pref double pixel_per_eastnorth;
213        @pref String eula;
214        @pref String attribution_text;
215        @pref String attribution_url;
216        @pref String logo_image;
217        @pref String logo_url;
218        @pref String terms_of_use_text;
219        @pref String terms_of_use_url;
220        @pref String country_code = "";
221        @pref int max_zoom;
222        @pref int min_zoom;
223        @pref String cookies;
224        @pref String bounds;
225        @pref String shapes;
226        @pref String projections;
227        @pref String icon;
228        @pref String description;
229        @pref Map<String, String> noTileHeaders;
230        @pref int tileSize = OsmMercator.DEFAUL_TILE_SIZE;
231        @pref Map<String, String> metadataHeaders;
232
233        /**
234         * Constructs a new empty WMS {@code ImageryPreferenceEntry}.
235         */
236        public ImageryPreferenceEntry() {
237        }
238
239        /**
240         * Constructs a new {@code ImageryPreferenceEntry} from a given {@code ImageryInfo}.
241         * @param i The corresponding imagery info
242         */
243        public ImageryPreferenceEntry(ImageryInfo i) {
244            name = i.name;
245            id = i.id;
246            type = i.imageryType.getTypeString();
247            url = i.url;
248            pixel_per_eastnorth = i.pixelPerDegree;
249            eula = i.eulaAcceptanceRequired;
250            attribution_text = i.attributionText;
251            attribution_url = i.attributionLinkURL;
252            logo_image = i.attributionImage;
253            logo_url = i.attributionImageURL;
254            terms_of_use_text = i.termsOfUseText;
255            terms_of_use_url = i.termsOfUseURL;
256            country_code = i.countryCode;
257            max_zoom = i.defaultMaxZoom;
258            min_zoom = i.defaultMinZoom;
259            cookies = i.cookies;
260            icon = i.icon;
261            description = i.description;
262            if (i.bounds != null) {
263                bounds = i.bounds.encodeAsString(",");
264                StringBuilder shapesString = new StringBuilder();
265                for (Shape s : i.bounds.getShapes()) {
266                    if (shapesString.length() > 0) {
267                        shapesString.append(';');
268                    }
269                    shapesString.append(s.encodeAsString(","));
270                }
271                if (shapesString.length() > 0) {
272                    shapes = shapesString.toString();
273                }
274            }
275            if (i.serverProjections != null && !i.serverProjections.isEmpty()) {
276                StringBuilder val = new StringBuilder();
277                for (String p : i.serverProjections) {
278                    if (val.length() > 0) {
279                        val.append(',');
280                    }
281                    val.append(p);
282                }
283                projections = val.toString();
284            }
285            if (i.noTileHeaders != null && !i.noTileHeaders.isEmpty()) {
286                noTileHeaders = i.noTileHeaders;
287            }
288
289            if (i.metadataHeaders != null && !i.metadataHeaders.isEmpty()) {
290                metadataHeaders = i.metadataHeaders;
291            }
292
293            tileSize = i.getTileSize();
294        }
295
296        @Override
297        public String toString() {
298            StringBuilder s = new StringBuilder("ImageryPreferenceEntry [name=").append(name);
299            if (id != null) {
300                s.append(" id=").append(id);
301            }
302            s.append(']');
303            return s.toString();
304        }
305    }
306
307    /**
308     * Constructs a new WMS {@code ImageryInfo}.
309     */
310    public ImageryInfo() {
311        super();
312    }
313
314    /**
315     * Constructs a new WMS {@code ImageryInfo} with a given name.
316     * @param name The entry name
317     */
318    public ImageryInfo(String name) {
319        super(name);
320    }
321
322    /**
323     * Constructs a new WMS {@code ImageryInfo} with given name and extended URL.
324     * @param name The entry name
325     * @param url The entry extended URL
326     */
327    public ImageryInfo(String name, String url) {
328        this(name);
329        setExtendedUrl(url);
330    }
331
332    /**
333     * Constructs a new WMS {@code ImageryInfo} with given name, extended and EULA URLs.
334     * @param name The entry name
335     * @param url The entry URL
336     * @param eulaAcceptanceRequired The EULA URL
337     */
338    public ImageryInfo(String name, String url, String eulaAcceptanceRequired) {
339        this(name);
340        setExtendedUrl(url);
341        this.eulaAcceptanceRequired = eulaAcceptanceRequired;
342    }
343
344    /**
345     * Constructs a new {@code ImageryInfo} with given name, url, extended and EULA URLs.
346     * @param name The entry name
347     * @param url The entry URL
348     * @param type The entry imagery type. If null, WMS will be used as default
349     * @param eulaAcceptanceRequired The EULA URL
350     * @param cookies The data part of HTTP cookies header in case the service requires cookies to work
351     * @throws IllegalArgumentException if type refers to an unknown imagery type
352     */
353    public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies) {
354        this(name);
355        setExtendedUrl(url);
356        ImageryType t = ImageryType.fromString(type);
357        this.cookies = cookies;
358        this.eulaAcceptanceRequired = eulaAcceptanceRequired;
359        if (t != null) {
360            this.imageryType = t;
361        } else if (type != null && !type.trim().isEmpty()) {
362            throw new IllegalArgumentException("unknown type: "+type);
363        }
364    }
365
366    public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies, String id) {
367        this(name, url, type, eulaAcceptanceRequired, cookies);
368        setId(id);
369    }
370
371    /**
372     * Constructs a new {@code ImageryInfo} from an imagery preference entry.
373     * @param e The imagery preference entry
374     */
375    public ImageryInfo(ImageryPreferenceEntry e) {
376        super(e.name, e.url, e.id);
377        CheckParameterUtil.ensureParameterNotNull(e.name, "name");
378        CheckParameterUtil.ensureParameterNotNull(e.url, "url");
379        description = e.description;
380        cookies = e.cookies;
381        eulaAcceptanceRequired = e.eula;
382        imageryType = ImageryType.fromString(e.type);
383        if (imageryType == null) throw new IllegalArgumentException("unknown type");
384        pixelPerDegree = e.pixel_per_eastnorth;
385        defaultMaxZoom = e.max_zoom;
386        defaultMinZoom = e.min_zoom;
387        if (e.bounds != null) {
388            bounds = new ImageryBounds(e.bounds, ",");
389            if (e.shapes != null) {
390                try {
391                    for (String s : e.shapes.split(";")) {
392                        bounds.addShape(new Shape(s, ","));
393                    }
394                } catch (IllegalArgumentException ex) {
395                    Main.warn(ex);
396                }
397            }
398        }
399        if (e.projections != null) {
400            serverProjections = Arrays.asList(e.projections.split(","));
401        }
402        attributionText = e.attribution_text;
403        attributionLinkURL = e.attribution_url;
404        attributionImage = e.logo_image;
405        attributionImageURL = e.logo_url;
406        termsOfUseText = e.terms_of_use_text;
407        termsOfUseURL = e.terms_of_use_url;
408        countryCode = e.country_code;
409        icon = e.icon;
410        if (e.noTileHeaders != null) {
411            noTileHeaders = e.noTileHeaders;
412        }
413        setTileSize(e.tileSize);
414        metadataHeaders = e.metadataHeaders;
415    }
416
417    /**
418     * Constructs a new {@code ImageryInfo} from an existing one.
419     * @param i The other imagery info
420     */
421    public ImageryInfo(ImageryInfo i) {
422        super(i.name, i.url, i.id);
423        this.defaultEntry = i.defaultEntry;
424        this.cookies = i.cookies;
425        this.eulaAcceptanceRequired = null;
426        this.imageryType = i.imageryType;
427        this.pixelPerDegree = i.pixelPerDegree;
428        this.defaultMaxZoom = i.defaultMaxZoom;
429        this.defaultMinZoom = i.defaultMinZoom;
430        this.bounds = i.bounds;
431        this.serverProjections = i.serverProjections;
432        this.attributionText = i.attributionText;
433        this.attributionLinkURL = i.attributionLinkURL;
434        this.attributionImage = i.attributionImage;
435        this.attributionImageURL = i.attributionImageURL;
436        this.termsOfUseText = i.termsOfUseText;
437        this.termsOfUseURL = i.termsOfUseURL;
438        this.countryCode = i.countryCode;
439        this.icon = i.icon;
440        this.description = i.description;
441        this.noTileHeaders = i.noTileHeaders;
442        this.metadataHeaders = i.metadataHeaders;
443    }
444
445    @Override
446    public boolean equals(Object o) {
447        if (this == o) return true;
448        if (o == null || getClass() != o.getClass()) return false;
449
450        ImageryInfo that = (ImageryInfo) o;
451
452        if (imageryType != that.imageryType) return false;
453        if (url != null ? !url.equals(that.url) : that.url != null) return false;
454        if (name != null ? !name.equals(that.name) : that.name != null) return false;
455
456        return true;
457    }
458
459    /**
460     * Check if this object equals another ImageryInfo with respect to the properties
461     * that get written to the preference file.
462     *
463     * The field {@link #pixelPerDegree} is ignored.
464     *
465     * @param other the ImageryInfo object to compare to
466     * @return true if they are equal
467     */
468    public boolean equalsPref(ImageryInfo other) {
469        if (other == null) {
470            return false;
471        }
472
473        return
474                Objects.equals(this.name, other.name) &&
475                Objects.equals(this.id, other.id) &&
476                Objects.equals(this.url, other.url) &&
477                Objects.equals(this.cookies, other.cookies) &&
478                Objects.equals(this.eulaAcceptanceRequired, other.eulaAcceptanceRequired) &&
479                Objects.equals(this.imageryType, other.imageryType) &&
480                Objects.equals(this.defaultMaxZoom, other.defaultMaxZoom) &&
481                Objects.equals(this.defaultMinZoom, other.defaultMinZoom) &&
482                Objects.equals(this.bounds, other.bounds) &&
483                Objects.equals(this.serverProjections, other.serverProjections) &&
484                Objects.equals(this.attributionText, other.attributionText) &&
485                Objects.equals(this.attributionLinkURL, other.attributionLinkURL) &&
486                Objects.equals(this.attributionImageURL, other.attributionImageURL) &&
487                Objects.equals(this.attributionImage, other.attributionImage) &&
488                Objects.equals(this.termsOfUseText, other.termsOfUseText) &&
489                Objects.equals(this.termsOfUseURL, other.termsOfUseURL) &&
490                Objects.equals(this.countryCode, other.countryCode) &&
491                Objects.equals(this.icon, other.icon) &&
492                Objects.equals(this.description, other.description) &&
493                Objects.equals(this.noTileHeaders, other.noTileHeaders) &&
494                Objects.equals(this.metadataHeaders, other.metadataHeaders);
495    }
496
497    @Override
498    public int hashCode() {
499        int result = url != null ? url.hashCode() : 0;
500        result = 31 * result + (imageryType != null ? imageryType.hashCode() : 0);
501        return result;
502    }
503
504    @Override
505    public String toString() {
506        return "ImageryInfo{" +
507                "name='" + name + '\'' +
508                ", countryCode='" + countryCode + '\'' +
509                ", url='" + url + '\'' +
510                ", imageryType=" + imageryType +
511                '}';
512    }
513
514    @Override
515    public int compareTo(ImageryInfo in) {
516        int i = countryCode.compareTo(in.countryCode);
517        if (i == 0) {
518            i = name.toLowerCase(Locale.ENGLISH).compareTo(in.name.toLowerCase(Locale.ENGLISH));
519        }
520        if (i == 0) {
521            i = url.compareTo(in.url);
522        }
523        if (i == 0) {
524            i = Double.compare(pixelPerDegree, in.pixelPerDegree);
525        }
526        return i;
527    }
528
529    public boolean equalsBaseValues(ImageryInfo in) {
530        return url.equals(in.url);
531    }
532
533    public void setPixelPerDegree(double ppd) {
534        this.pixelPerDegree = ppd;
535    }
536
537    /**
538     * Sets the maximum zoom level.
539     * @param defaultMaxZoom The maximum zoom level
540     */
541    public void setDefaultMaxZoom(int defaultMaxZoom) {
542        this.defaultMaxZoom = defaultMaxZoom;
543    }
544
545    /**
546     * Sets the minimum zoom level.
547     * @param defaultMinZoom The minimum zoom level
548     */
549    public void setDefaultMinZoom(int defaultMinZoom) {
550        this.defaultMinZoom = defaultMinZoom;
551    }
552
553    /**
554     * Sets the imagery polygonial bounds.
555     * @param b The imagery bounds (non-rectangular)
556     */
557    public void setBounds(ImageryBounds b) {
558        this.bounds = b;
559    }
560
561    /**
562     * Returns the imagery polygonial bounds.
563     * @return The imagery bounds (non-rectangular)
564     */
565    public ImageryBounds getBounds() {
566        return bounds;
567    }
568
569    @Override
570    public boolean requiresAttribution() {
571        return attributionText != null || attributionImage != null || termsOfUseText != null || termsOfUseURL != null;
572    }
573
574    @Override
575    public String getAttributionText(int zoom, ICoordinate topLeft, ICoordinate botRight) {
576        return attributionText;
577    }
578
579    @Override
580    public String getAttributionLinkURL() {
581        return attributionLinkURL;
582    }
583
584    @Override
585    public Image getAttributionImage() {
586        ImageIcon i = ImageProvider.getIfAvailable(attributionImage);
587        if (i != null) {
588            return i.getImage();
589        }
590        return null;
591    }
592
593    @Override
594    public String getAttributionImageURL() {
595        return attributionImageURL;
596    }
597
598    @Override
599    public String getTermsOfUseText() {
600        return termsOfUseText;
601    }
602
603    @Override
604    public String getTermsOfUseURL() {
605        return termsOfUseURL;
606    }
607
608    public void setAttributionText(String text) {
609        attributionText = text;
610    }
611
612    public void setAttributionImageURL(String text) {
613        attributionImageURL = text;
614    }
615
616    public void setAttributionImage(String text) {
617        attributionImage = text;
618    }
619
620    public void setAttributionLinkURL(String text) {
621        attributionLinkURL = text;
622    }
623
624    public void setTermsOfUseText(String text) {
625        termsOfUseText = text;
626    }
627
628    public void setTermsOfUseURL(String text) {
629        termsOfUseURL = text;
630    }
631
632    /**
633     * Sets the extended URL of this entry.
634     * @param url Entry extended URL containing in addition of service URL, its type and min/max zoom info
635     */
636    public void setExtendedUrl(String url) {
637        CheckParameterUtil.ensureParameterNotNull(url);
638
639        // Default imagery type is WMS
640        this.url = url;
641        this.imageryType = ImageryType.WMS;
642
643        defaultMaxZoom = 0;
644        defaultMinZoom = 0;
645        for (ImageryType type : ImageryType.values()) {
646            Matcher m = Pattern.compile(type.getTypeString()+"(?:\\[(?:(\\d+),)?(\\d+)\\])?:(.*)").matcher(url);
647            if (m.matches()) {
648                this.url = m.group(3);
649                this.imageryType = type;
650                if (m.group(2) != null) {
651                    defaultMaxZoom = Integer.parseInt(m.group(2));
652                }
653                if (m.group(1) != null) {
654                    defaultMinZoom = Integer.parseInt(m.group(1));
655                }
656                break;
657            }
658        }
659
660        if (serverProjections == null || serverProjections.isEmpty()) {
661            try {
662                serverProjections = new ArrayList<>();
663                Matcher m = Pattern.compile(".*\\{PROJ\\(([^)}]+)\\)\\}.*").matcher(url.toUpperCase(Locale.ENGLISH));
664                if (m.matches()) {
665                    for (String p : m.group(1).split(",")) {
666                        serverProjections.add(p);
667                    }
668                }
669            } catch (Exception e) {
670                Main.warn(e);
671            }
672        }
673    }
674
675    /**
676     * Returns the entry name.
677     * @return The entry name
678     * @since 6968
679     */
680    public String getOriginalName() {
681        return this.origName != null ? this.origName : this.name;
682    }
683
684    /**
685     * Sets the entry name and handle translation.
686     * @param language The used language
687     * @param name The entry name
688     * @since 8091
689     */
690    public void setName(String language, String name) {
691        boolean isdefault = LanguageInfo.getJOSMLocaleCode(null).equals(language);
692        if (LanguageInfo.isBetterLanguage(langName, language)) {
693            this.name = isdefault ? tr(name) : name;
694            this.langName = language;
695        }
696        if (origName == null || isdefault) {
697            this.origName = name;
698        }
699    }
700
701    public void clearId() {
702        if (this.id != null) {
703            Collection<String> newAddedIds = new TreeSet<>(Main.pref.getCollection("imagery.layers.addedIds"));
704            newAddedIds.add(this.id);
705            Main.pref.putCollection("imagery.layers.addedIds", newAddedIds);
706        }
707        setId(null);
708    }
709
710    /**
711     * Determines if this entry is enabled by default.
712     * @return {@code true} if this entry is enabled by default, {@code false} otherwise
713     */
714    public boolean isDefaultEntry() {
715        return defaultEntry;
716    }
717
718    /**
719     * Sets the default state of this entry.
720     * @param defaultEntry {@code true} if this entry has to be enabled by default, {@code false} otherwise
721     */
722    public void setDefaultEntry(boolean defaultEntry) {
723        this.defaultEntry = defaultEntry;
724    }
725
726    /**
727     * Return the data part of HTTP cookies header in case the service requires cookies to work
728     * @return the cookie data part
729     */
730    @Override
731    public String getCookies() {
732        return this.cookies;
733    }
734
735    public double getPixelPerDegree() {
736        return this.pixelPerDegree;
737    }
738
739    /**
740     * Returns the maximum zoom level.
741     * @return The maximum zoom level
742     */
743    @Override
744    public int getMaxZoom() {
745        return this.defaultMaxZoom;
746    }
747
748    /**
749     * Returns the minimum zoom level.
750     * @return The minimum zoom level
751     */
752    @Override
753    public int getMinZoom() {
754        return this.defaultMinZoom;
755    }
756
757    /**
758     * Returns the description text when existing.
759     * @return The description
760     * @since 8065
761     */
762    public String getDescription() {
763        return this.description;
764    }
765
766    /**
767     * Sets the description text when existing.
768     * @param language The used language
769     * @param description the imagery description text
770     * @since 8091
771     */
772    public void setDescription(String language, String description) {
773        boolean isdefault = LanguageInfo.getJOSMLocaleCode(null).equals(language);
774        if (LanguageInfo.isBetterLanguage(langDescription, language)) {
775            this.description = isdefault ? tr(description) : description;
776            this.langDescription = language;
777        }
778    }
779
780    /**
781     * Returns a tool tip text for display.
782     * @return The text
783     * @since 8065
784     */
785    public String getToolTipText() {
786        String desc = getDescription();
787        if (desc != null && !desc.isEmpty()) {
788            return "<html>" + getName() + "<br>" + desc + "</html>";
789        }
790        return getName();
791    }
792
793    /**
794     * Returns the EULA acceptance URL, if any.
795     * @return The URL to an EULA text that has to be accepted before use, or {@code null}
796     */
797    public String getEulaAcceptanceRequired() {
798        return eulaAcceptanceRequired;
799    }
800
801    /**
802     * Sets the EULA acceptance URL.
803     * @param eulaAcceptanceRequired The URL to an EULA text that has to be accepted before use
804     */
805    public void setEulaAcceptanceRequired(String eulaAcceptanceRequired) {
806        this.eulaAcceptanceRequired = eulaAcceptanceRequired;
807    }
808
809    /**
810     * Returns the ISO 3166-1-alpha-2 country code.
811     * @return The country code (2 letters)
812     */
813    public String getCountryCode() {
814        return countryCode;
815    }
816
817    /**
818     * Sets the ISO 3166-1-alpha-2 country code.
819     * @param countryCode The country code (2 letters)
820     */
821    public void setCountryCode(String countryCode) {
822        this.countryCode = countryCode;
823    }
824
825    /**
826     * Returns the entry icon.
827     * @return The entry icon
828     */
829    public String getIcon() {
830        return icon;
831    }
832
833    /**
834     * Sets the entry icon.
835     * @param icon The entry icon
836     */
837    public void setIcon(String icon) {
838        this.icon = icon;
839    }
840
841    /**
842     * Get the projections supported by the server. Only relevant for
843     * WMS-type ImageryInfo at the moment.
844     * @return null, if no projections have been specified; the list
845     * of supported projections otherwise.
846     */
847    public List<String> getServerProjections() {
848        if (serverProjections == null)
849            return Collections.emptyList();
850        return Collections.unmodifiableList(serverProjections);
851    }
852
853    public void setServerProjections(Collection<String> serverProjections) {
854        this.serverProjections = new ArrayList<>(serverProjections);
855    }
856
857    /**
858     * Returns the extended URL, containing in addition of service URL, its type and min/max zoom info.
859     * @return The extended URL
860     */
861    public String getExtendedUrl() {
862        return imageryType.getTypeString() + (defaultMaxZoom != 0
863            ? "["+(defaultMinZoom != 0 ? Integer.toString(defaultMinZoom) + ',' : "")+defaultMaxZoom+"]" : "") + ':' + url;
864    }
865
866    public String getToolbarName() {
867        String res = name;
868        if (pixelPerDegree != 0) {
869            res += "#PPD="+pixelPerDegree;
870        }
871        return res;
872    }
873
874    public String getMenuName() {
875        String res = name;
876        if (pixelPerDegree != 0) {
877            res += " ("+pixelPerDegree+')';
878        }
879        return res;
880    }
881
882    /**
883     * Determines if this entry requires attribution.
884     * @return {@code true} if some attribution text has to be displayed, {@code false} otherwise
885     */
886    public boolean hasAttribution() {
887        return attributionText != null;
888    }
889
890    /**
891     * Copies attribution from another {@code ImageryInfo}.
892     * @param i The other imagery info to get attribution from
893     */
894    public void copyAttribution(ImageryInfo i) {
895        this.attributionImage = i.attributionImage;
896        this.attributionImageURL = i.attributionImageURL;
897        this.attributionText = i.attributionText;
898        this.attributionLinkURL = i.attributionLinkURL;
899        this.termsOfUseText = i.termsOfUseText;
900        this.termsOfUseURL = i.termsOfUseURL;
901    }
902
903    /**
904     * Applies the attribution from this object to a tile source.
905     * @param s The tile source
906     */
907    public void setAttribution(AbstractTileSource s) {
908        if (attributionText != null) {
909            if ("osm".equals(attributionText)) {
910                s.setAttributionText(new Mapnik().getAttributionText(0, null, null));
911            } else {
912                s.setAttributionText(attributionText);
913            }
914        }
915        if (attributionLinkURL != null) {
916            if ("osm".equals(attributionLinkURL)) {
917                s.setAttributionLinkURL(new Mapnik().getAttributionLinkURL());
918            } else {
919                s.setAttributionLinkURL(attributionLinkURL);
920            }
921        }
922        if (attributionImage != null) {
923            ImageIcon i = ImageProvider.getIfAvailable(null, attributionImage);
924            if (i != null) {
925                s.setAttributionImage(i.getImage());
926            }
927        }
928        if (attributionImageURL != null) {
929            s.setAttributionImageURL(attributionImageURL);
930        }
931        if (termsOfUseText != null) {
932            s.setTermsOfUseText(termsOfUseText);
933        }
934        if (termsOfUseURL != null) {
935            if ("osm".equals(termsOfUseURL)) {
936                s.setTermsOfUseURL(new Mapnik().getTermsOfUseURL());
937            } else {
938                s.setTermsOfUseURL(termsOfUseURL);
939            }
940        }
941    }
942
943    /**
944     * Returns the imagery type.
945     * @return The imagery type
946     */
947    public ImageryType getImageryType() {
948        return imageryType;
949    }
950
951    /**
952     * Sets the imagery type.
953     * @param imageryType The imagery type
954     */
955    public void setImageryType(ImageryType imageryType) {
956        this.imageryType = imageryType;
957    }
958
959    /**
960     * Returns true if this layer's URL is matched by one of the regular
961     * expressions kept by the current OsmApi instance.
962     * @return {@code true} is this entry is blacklisted, {@code false} otherwise
963     */
964    public boolean isBlacklisted() {
965        Capabilities capabilities = OsmApi.getOsmApi().getCapabilities();
966        return capabilities != null && capabilities.isOnImageryBlacklist(this.url);
967    }
968
969    /**
970     * Sets the map of &lt;header name, header value&gt; that if any of this header
971     * will be returned, then this tile will be treated as "no tile at this zoom level"
972     *
973     * @param noTileHeaders Map of &lt;header name, header value&gt; which will be treated as "no tile at this zoom level"
974     * @since 8344
975     */
976    public void setNoTileHeaders(Map<String, String> noTileHeaders) {
977       this.noTileHeaders = noTileHeaders;
978    }
979
980    @Override
981    public Map<String, String> getNoTileHeaders() {
982        return noTileHeaders;
983    }
984
985    /**
986     * Returns the map of &lt;header name, metadata key&gt; indicating, which HTTP headers should
987     * be moved to metadata
988     *
989     * @param metadataHeaders map of &lt;header name, metadata key&gt; indicating, which HTTP headers should be moved to metadata
990     * @since 8418
991     */
992    public void setMetadataHeaders(Map<String, String> metadataHeaders) {
993        this.metadataHeaders = metadataHeaders;
994    }
995}