001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.data.imagery;
003    
004    import java.awt.Image;
005    import java.util.ArrayList;
006    import java.util.Arrays;
007    import java.util.Collection;
008    import java.util.Collections;
009    import java.util.List;
010    import java.util.regex.Matcher;
011    import java.util.regex.Pattern;
012    
013    import javax.swing.ImageIcon;
014    
015    import org.openstreetmap.gui.jmapviewer.Coordinate;
016    import org.openstreetmap.gui.jmapviewer.interfaces.Attributed;
017    import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTileSource;
018    import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.Mapnik;
019    import org.openstreetmap.josm.Main;
020    import org.openstreetmap.josm.data.Bounds;
021    import org.openstreetmap.josm.data.Preferences.pref;
022    import org.openstreetmap.josm.io.OsmApi;
023    import org.openstreetmap.josm.tools.CheckParameterUtil;
024    import org.openstreetmap.josm.tools.ImageProvider;
025    
026    /**
027     * Class that stores info about an image background layer.
028     *
029     * @author Frederik Ramm <frederik@remote.org>
030     */
031    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
032    
033        public enum ImageryType {
034            WMS("wms"),
035            TMS("tms"),
036            HTML("html"),
037            BING("bing"),
038            SCANEX("scanex");
039    
040            private String urlString;
041    
042            ImageryType(String urlString) {
043                this.urlString = urlString;
044            }
045    
046            public String getUrlString() {
047                return urlString;
048            }
049            
050            public static ImageryType fromUrlString(String s) {
051                for (ImageryType type : ImageryType.values()) {
052                    if (type.getUrlString().equals(s)) {
053                        return type;
054                    }
055                }
056                return null;
057            }
058        }
059    
060        public static class ImageryBounds extends Bounds {
061            public ImageryBounds(String asString, String separator) {
062                super(asString, separator);
063            }
064    
065            private List<Shape> shapes = new ArrayList<Shape>();
066    
067            public void addShape(Shape shape) {
068                this.shapes.add(shape);
069            }
070    
071            public void setShapes(List<Shape> shapes) {
072                this.shapes = shapes;
073            }
074    
075            public List<Shape> getShapes() {
076                return shapes;
077            }
078        }
079    
080        private String name;
081        private String url = null;
082        private boolean defaultEntry = false;
083        private String cookies = null;
084        private String eulaAcceptanceRequired= null;
085        private ImageryType imageryType = ImageryType.WMS;
086        private double pixelPerDegree = 0.0;
087        private int defaultMaxZoom = 0;
088        private int defaultMinZoom = 0;
089        private ImageryBounds bounds = null;
090        private List<String> serverProjections;
091        private String attributionText;
092        private String attributionLinkURL;
093        private String attributionImage;
094        private String attributionImageURL;
095        private String termsOfUseText;
096        private String termsOfUseURL;
097        private String countryCode = "";
098        private String icon;
099        // when adding a field, also adapt the ImageryInfo(ImageryInfo) constructor
100    
101        /** auxiliary class to save an ImageryInfo object in the preferences */
102        public static class ImageryPreferenceEntry {
103            @pref String name;
104            @pref String type;
105            @pref String url;
106            @pref double pixel_per_eastnorth;
107            @pref String eula;
108            @pref String attribution_text;
109            @pref String attribution_url;
110            @pref String logo_image;
111            @pref String logo_url;
112            @pref String terms_of_use_text;
113            @pref String terms_of_use_url;
114            @pref String country_code = "";
115            @pref int max_zoom;
116            @pref int min_zoom;
117            @pref String cookies;
118            @pref String bounds;
119            @pref String shapes;
120            @pref String projections;
121            @pref String icon;
122    
123            public ImageryPreferenceEntry() {
124            }
125    
126            public ImageryPreferenceEntry(ImageryInfo i) {
127                name = i.name;
128                type = i.imageryType.getUrlString();
129                url = i.url;
130                pixel_per_eastnorth = i.pixelPerDegree;
131                eula = i.eulaAcceptanceRequired;
132                attribution_text = i.attributionText;
133                attribution_url = i.attributionLinkURL;
134                logo_image = i.attributionImage;
135                logo_url = i.attributionImageURL;
136                terms_of_use_text = i.termsOfUseText;
137                terms_of_use_url = i.termsOfUseURL;
138                country_code = i.countryCode;
139                max_zoom = i.defaultMaxZoom;
140                min_zoom = i.defaultMinZoom;
141                cookies = i.cookies;
142                icon = i.icon;
143                if (i.bounds != null) {
144                    bounds = i.bounds.encodeAsString(",");
145                    String shapesString = "";
146                    for (Shape s : i.bounds.getShapes()) {
147                        if (!shapesString.isEmpty()) {
148                            shapesString += ";";
149                        }
150                        shapesString += s.encodeAsString(",");
151                    }
152                    if (!shapesString.isEmpty()) {
153                        shapes = shapesString;
154                    }
155                }
156                if (i.serverProjections != null && !i.serverProjections.isEmpty()) {
157                    String val = "";
158                    for (String p : i.serverProjections) {
159                        if (!val.isEmpty())
160                            val += ",";
161                        val += p;
162                    }
163                    projections = val;
164                }
165            }
166        }
167    
168        public ImageryInfo() {
169        }
170    
171        public ImageryInfo(String name) {
172            this.name=name;
173        }
174    
175        public ImageryInfo(String name, String url) {
176            this.name=name;
177            setExtendedUrl(url);
178        }
179    
180        public ImageryInfo(String name, String url, String eulaAcceptanceRequired) {
181            this.name=name;
182            setExtendedUrl(url);
183            this.eulaAcceptanceRequired = eulaAcceptanceRequired;
184        }
185    
186        public ImageryInfo(String name, String url, String eulaAcceptanceRequired, String cookies) {
187            this.name=name;
188            setExtendedUrl(url);
189            this.cookies=cookies;
190            this.eulaAcceptanceRequired = eulaAcceptanceRequired;
191        }
192    
193        public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies) {
194            this.name=name;
195            setExtendedUrl(url);
196            ImageryType t = ImageryType.fromUrlString(type);
197            this.cookies=cookies;
198            if (t != null) {
199                this.imageryType = t;
200            }
201        }
202    
203        public ImageryInfo(String name, String url, String cookies, double pixelPerDegree) {
204            this.name=name;
205            setExtendedUrl(url);
206            this.cookies=cookies;
207            this.pixelPerDegree=pixelPerDegree;
208        }
209    
210        public ImageryInfo(ImageryPreferenceEntry e) {
211            CheckParameterUtil.ensureParameterNotNull(e.name, "name");
212            CheckParameterUtil.ensureParameterNotNull(e.url, "url");
213            name = e.name;
214            url = e.url;
215            cookies = e.cookies;
216            eulaAcceptanceRequired = e.eula;
217            imageryType = ImageryType.fromUrlString(e.type);
218            if (imageryType == null) throw new IllegalArgumentException("unknown type");
219            pixelPerDegree = e.pixel_per_eastnorth;
220            defaultMaxZoom = e.max_zoom;
221            defaultMinZoom = e.min_zoom;
222            if (e.bounds != null) {
223                bounds = new ImageryBounds(e.bounds, ",");
224                if (e.shapes != null) {
225                    try {
226                        for (String s : e.shapes.split(";")) {
227                            bounds.addShape(new Shape(s, ","));
228                        }
229                    } catch (IllegalArgumentException ex) {
230                        Main.warn(ex.toString());
231                    }
232                }
233            }
234            if (e.projections != null) {
235                serverProjections = Arrays.asList(e.projections.split(","));
236            }
237            attributionText = e.attribution_text;
238            attributionLinkURL = e.attribution_url;
239            attributionImage = e.logo_image;
240            attributionImageURL = e.logo_url;
241            termsOfUseText = e.terms_of_use_text;
242            termsOfUseURL = e.terms_of_use_url;
243            countryCode = e.country_code;
244            icon = e.icon;
245        }
246    
247        public ImageryInfo(ImageryInfo i) {
248            this.name = i.name;
249            this.url = i.url;
250            this.defaultEntry = i.defaultEntry;
251            this.cookies = i.cookies;
252            this.eulaAcceptanceRequired = null;
253            this.imageryType = i.imageryType;
254            this.pixelPerDegree = i.pixelPerDegree;
255            this.defaultMaxZoom = i.defaultMaxZoom;
256            this.defaultMinZoom = i.defaultMinZoom;
257            this.bounds = i.bounds;
258            this.serverProjections = i.serverProjections;
259            this.attributionText = i.attributionText;
260            this.attributionLinkURL = i.attributionLinkURL;
261            this.attributionImage = i.attributionImage;
262            this.attributionImageURL = i.attributionImageURL;
263            this.termsOfUseText = i.termsOfUseText;
264            this.termsOfUseURL = i.termsOfUseURL;
265            this.countryCode = i.countryCode;
266            this.icon = i.icon;
267        }
268    
269        @Override
270        public boolean equals(Object o) {
271            if (this == o) return true;
272            if (o == null || getClass() != o.getClass()) return false;
273    
274            ImageryInfo that = (ImageryInfo) o;
275    
276            if (imageryType != that.imageryType) return false;
277            if (url != null ? !url.equals(that.url) : that.url != null) return false;
278    
279            return true;
280        }
281    
282        @Override
283        public int hashCode() {
284            int result = url != null ? url.hashCode() : 0;
285            result = 31 * result + (imageryType != null ? imageryType.hashCode() : 0);
286            return result;
287        }
288    
289        @Override
290        public String toString() {
291            return "ImageryInfo{" +
292                    "name='" + name + '\'' +
293                    ", countryCode='" + countryCode + '\'' +
294                    ", url='" + url + '\'' +
295                    ", imageryType=" + imageryType +
296                    '}';
297        }
298    
299        @Override
300        public int compareTo(ImageryInfo in)
301        {
302            int i = countryCode.compareTo(in.countryCode);
303            if (i == 0) {
304                i = name.compareTo(in.name);
305            }
306            if (i == 0) {
307                i = url.compareTo(in.url);
308            }
309            if (i == 0) {
310                i = Double.compare(pixelPerDegree, in.pixelPerDegree);
311            }
312            return i;
313        }
314    
315        public boolean equalsBaseValues(ImageryInfo in)
316        {
317            return url.equals(in.url);
318        }
319    
320        public void setPixelPerDegree(double ppd) {
321            this.pixelPerDegree = ppd;
322        }
323    
324        public void setDefaultMaxZoom(int defaultMaxZoom) {
325            this.defaultMaxZoom = defaultMaxZoom;
326        }
327    
328        public void setDefaultMinZoom(int defaultMinZoom) {
329            this.defaultMinZoom = defaultMinZoom;
330        }
331    
332        public void setBounds(ImageryBounds b) {
333            this.bounds = b;
334        }
335    
336        public ImageryBounds getBounds() {
337            return bounds;
338        }
339    
340        @Override
341        public boolean requiresAttribution() {
342            return attributionText != null || attributionImage != null || termsOfUseText != null || termsOfUseURL != null;
343        }
344    
345        @Override
346        public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight) {
347            return attributionText;
348        }
349    
350        @Override
351        public String getAttributionLinkURL() {
352            return attributionLinkURL;
353        }
354    
355        @Override
356        public Image getAttributionImage() {
357            ImageIcon i = ImageProvider.getIfAvailable(attributionImage);
358            if (i != null) {
359                return i.getImage();
360            }
361            return null;
362        }
363    
364        @Override
365        public String getAttributionImageURL() {
366            return attributionImageURL;
367        }
368    
369        @Override
370        public String getTermsOfUseText() {
371            return termsOfUseText;
372        }
373    
374        @Override
375        public String getTermsOfUseURL() {
376            return termsOfUseURL;
377        }
378    
379        public void setAttributionText(String text) {
380            attributionText = text;
381        }
382    
383        public void setAttributionImageURL(String text) {
384            attributionImageURL = text;
385        }
386    
387        public void setAttributionImage(String text) {
388            attributionImage = text;
389        }
390    
391        public void setAttributionLinkURL(String text) {
392            attributionLinkURL = text;
393        }
394    
395        public void setTermsOfUseText(String text) {
396            termsOfUseText = text;
397        }
398    
399        public void setTermsOfUseURL(String text) {
400            termsOfUseURL = text;
401        }
402    
403        public void setExtendedUrl(String url) {
404            CheckParameterUtil.ensureParameterNotNull(url);
405    
406            // Default imagery type is WMS
407            this.url = url;
408            this.imageryType = ImageryType.WMS;
409    
410            defaultMaxZoom = 0;
411            defaultMinZoom = 0;
412            for (ImageryType type : ImageryType.values()) {
413                Matcher m = Pattern.compile(type.getUrlString()+"(?:\\[(?:(\\d+),)?(\\d+)\\])?:(.*)").matcher(url);
414                if(m.matches()) {
415                    this.url = m.group(3);
416                    this.imageryType = type;
417                    if(m.group(2) != null) {
418                        defaultMaxZoom = Integer.valueOf(m.group(2));
419                    }
420                    if(m.group(1) != null) {
421                        defaultMinZoom = Integer.valueOf(m.group(1));
422                    }
423                    break;
424                }
425            }
426    
427            if(serverProjections == null || serverProjections.isEmpty()) {
428                try {
429                    serverProjections = new ArrayList<String>();
430                    Matcher m = Pattern.compile(".*\\{PROJ\\(([^)}]+)\\)\\}.*").matcher(url.toUpperCase());
431                    if(m.matches()) {
432                        for(String p : m.group(1).split(","))
433                            serverProjections.add(p);
434                    }
435                } catch(Exception e) {
436                }
437            }
438        }
439    
440        public String getName() {
441            return this.name;
442        }
443    
444        public void setName(String name) {
445            this.name = name;
446        }
447    
448        public String getUrl() {
449            return this.url;
450        }
451    
452        public void setUrl(String url) {
453            this.url = url;
454        }
455    
456        public boolean isDefaultEntry() {
457            return defaultEntry;
458        }
459    
460        public void setDefaultEntry(boolean defaultEntry) {
461            this.defaultEntry = defaultEntry;
462        }
463    
464        public String getCookies() {
465            return this.cookies;
466        }
467    
468        public double getPixelPerDegree() {
469            return this.pixelPerDegree;
470        }
471    
472        public int getMaxZoom() {
473            return this.defaultMaxZoom;
474        }
475    
476        public int getMinZoom() {
477            return this.defaultMinZoom;
478        }
479    
480        public String getEulaAcceptanceRequired() {
481            return eulaAcceptanceRequired;
482        }
483    
484        public void setEulaAcceptanceRequired(String eulaAcceptanceRequired) {
485            this.eulaAcceptanceRequired = eulaAcceptanceRequired;
486        }
487    
488        public String getCountryCode() {
489            return countryCode;
490        }
491    
492        public void setCountryCode(String countryCode) {
493            this.countryCode = countryCode;
494        }
495    
496        public String getIcon() {
497            return icon;
498        }
499    
500        public void setIcon(String icon) {
501            this.icon = icon;
502        }
503    
504        /**
505         * Get the projections supported by the server. Only relevant for
506         * WMS-type ImageryInfo at the moment.
507         * @return null, if no projections have been specified; the list
508         * of supported projections otherwise.
509         */
510        public List<String> getServerProjections() {
511            if (serverProjections == null)
512                return Collections.emptyList();
513            return Collections.unmodifiableList(serverProjections);
514        }
515    
516        public void setServerProjections(Collection<String> serverProjections) {
517            this.serverProjections = new ArrayList<String>(serverProjections);
518        }
519    
520        public String getExtendedUrl() {
521            return imageryType.getUrlString() + (defaultMaxZoom != 0
522                ? "["+(defaultMinZoom != 0 ? defaultMinZoom+",":"")+defaultMaxZoom+"]" : "") + ":" + url;
523        }
524    
525        public String getToolbarName()
526        {
527            String res = name;
528            if(pixelPerDegree != 0.0) {
529                res += "#PPD="+pixelPerDegree;
530            }
531            return res;
532        }
533    
534        public String getMenuName()
535        {
536            String res = name;
537            if(pixelPerDegree != 0.0) {
538                res += " ("+pixelPerDegree+")";
539            }
540            return res;
541        }
542    
543        public boolean hasAttribution()
544        {
545            return attributionText != null;
546        }
547    
548        public void copyAttribution(ImageryInfo i)
549        {
550            this.attributionImage = i.attributionImage;
551            this.attributionImageURL = i.attributionImageURL;
552            this.attributionText = i.attributionText;
553            this.attributionLinkURL = i.attributionLinkURL;
554            this.termsOfUseText = i.termsOfUseText;
555            this.termsOfUseURL = i.termsOfUseURL;
556        }
557    
558        /**
559         * Applies the attribution from this object to a TMSTileSource.
560         */
561        public void setAttribution(AbstractTileSource s) {
562            if (attributionText != null) {
563                if (attributionText.equals("osm")) {
564                    s.setAttributionText(new Mapnik().getAttributionText(0, null, null));
565                } else {
566                    s.setAttributionText(attributionText);
567                }
568            }
569            if (attributionLinkURL != null) {
570                if (attributionLinkURL.equals("osm")) {
571                    s.setAttributionLinkURL(new Mapnik().getAttributionLinkURL());
572                } else {
573                    s.setAttributionLinkURL(attributionLinkURL);
574                }
575            }
576            if (attributionImage != null) {
577                ImageIcon i = ImageProvider.getIfAvailable(null, attributionImage);
578                if (i != null) {
579                    s.setAttributionImage(i.getImage());
580                }
581            }
582            if (attributionImageURL != null) {
583                s.setAttributionImageURL(attributionImageURL);
584            }
585            if (termsOfUseText != null) {
586                s.setTermsOfUseText(termsOfUseText);
587            }
588            if (termsOfUseURL != null) {
589                if (termsOfUseURL.equals("osm")) {
590                    s.setTermsOfUseURL(new Mapnik().getTermsOfUseURL());
591                } else {
592                    s.setTermsOfUseURL(termsOfUseURL);
593                }
594            }
595        }
596    
597        public ImageryType getImageryType() {
598            return imageryType;
599        }
600    
601        public void setImageryType(ImageryType imageryType) {
602            this.imageryType = imageryType;
603        }
604    
605        /**
606         * Returns true if this layer's URL is matched by one of the regular
607         * expressions kept by the current OsmApi instance.
608         */
609        public boolean isBlacklisted() {
610            return OsmApi.getOsmApi().getCapabilities().isOnImageryBlacklist(this.url);
611        }
612    }