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            if (name != null ? !name.equals(that.name) : that.name != null) return false;
279    
280            return true;
281        }
282    
283        @Override
284        public int hashCode() {
285            int result = url != null ? url.hashCode() : 0;
286            result = 31 * result + (imageryType != null ? imageryType.hashCode() : 0);
287            return result;
288        }
289    
290        @Override
291        public String toString() {
292            return "ImageryInfo{" +
293                    "name='" + name + '\'' +
294                    ", countryCode='" + countryCode + '\'' +
295                    ", url='" + url + '\'' +
296                    ", imageryType=" + imageryType +
297                    '}';
298        }
299    
300        @Override
301        public int compareTo(ImageryInfo in)
302        {
303            int i = countryCode.compareTo(in.countryCode);
304            if (i == 0) {
305                i = name.compareTo(in.name);
306            }
307            if (i == 0) {
308                i = url.compareTo(in.url);
309            }
310            if (i == 0) {
311                i = Double.compare(pixelPerDegree, in.pixelPerDegree);
312            }
313            return i;
314        }
315    
316        public boolean equalsBaseValues(ImageryInfo in)
317        {
318            return url.equals(in.url);
319        }
320    
321        public void setPixelPerDegree(double ppd) {
322            this.pixelPerDegree = ppd;
323        }
324    
325        public void setDefaultMaxZoom(int defaultMaxZoom) {
326            this.defaultMaxZoom = defaultMaxZoom;
327        }
328    
329        public void setDefaultMinZoom(int defaultMinZoom) {
330            this.defaultMinZoom = defaultMinZoom;
331        }
332    
333        public void setBounds(ImageryBounds b) {
334            this.bounds = b;
335        }
336    
337        public ImageryBounds getBounds() {
338            return bounds;
339        }
340    
341        @Override
342        public boolean requiresAttribution() {
343            return attributionText != null || attributionImage != null || termsOfUseText != null || termsOfUseURL != null;
344        }
345    
346        @Override
347        public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight) {
348            return attributionText;
349        }
350    
351        @Override
352        public String getAttributionLinkURL() {
353            return attributionLinkURL;
354        }
355    
356        @Override
357        public Image getAttributionImage() {
358            ImageIcon i = ImageProvider.getIfAvailable(attributionImage);
359            if (i != null) {
360                return i.getImage();
361            }
362            return null;
363        }
364    
365        @Override
366        public String getAttributionImageURL() {
367            return attributionImageURL;
368        }
369    
370        @Override
371        public String getTermsOfUseText() {
372            return termsOfUseText;
373        }
374    
375        @Override
376        public String getTermsOfUseURL() {
377            return termsOfUseURL;
378        }
379    
380        public void setAttributionText(String text) {
381            attributionText = text;
382        }
383    
384        public void setAttributionImageURL(String text) {
385            attributionImageURL = text;
386        }
387    
388        public void setAttributionImage(String text) {
389            attributionImage = text;
390        }
391    
392        public void setAttributionLinkURL(String text) {
393            attributionLinkURL = text;
394        }
395    
396        public void setTermsOfUseText(String text) {
397            termsOfUseText = text;
398        }
399    
400        public void setTermsOfUseURL(String text) {
401            termsOfUseURL = text;
402        }
403    
404        public void setExtendedUrl(String url) {
405            CheckParameterUtil.ensureParameterNotNull(url);
406    
407            // Default imagery type is WMS
408            this.url = url;
409            this.imageryType = ImageryType.WMS;
410    
411            defaultMaxZoom = 0;
412            defaultMinZoom = 0;
413            for (ImageryType type : ImageryType.values()) {
414                Matcher m = Pattern.compile(type.getUrlString()+"(?:\\[(?:(\\d+),)?(\\d+)\\])?:(.*)").matcher(url);
415                if(m.matches()) {
416                    this.url = m.group(3);
417                    this.imageryType = type;
418                    if(m.group(2) != null) {
419                        defaultMaxZoom = Integer.valueOf(m.group(2));
420                    }
421                    if(m.group(1) != null) {
422                        defaultMinZoom = Integer.valueOf(m.group(1));
423                    }
424                    break;
425                }
426            }
427    
428            if(serverProjections == null || serverProjections.isEmpty()) {
429                try {
430                    serverProjections = new ArrayList<String>();
431                    Matcher m = Pattern.compile(".*\\{PROJ\\(([^)}]+)\\)\\}.*").matcher(url.toUpperCase());
432                    if(m.matches()) {
433                        for(String p : m.group(1).split(","))
434                            serverProjections.add(p);
435                    }
436                } catch(Exception e) {
437                }
438            }
439        }
440    
441        public String getName() {
442            return this.name;
443        }
444    
445        public void setName(String name) {
446            this.name = name;
447        }
448    
449        public String getUrl() {
450            return this.url;
451        }
452    
453        public void setUrl(String url) {
454            this.url = url;
455        }
456    
457        public boolean isDefaultEntry() {
458            return defaultEntry;
459        }
460    
461        public void setDefaultEntry(boolean defaultEntry) {
462            this.defaultEntry = defaultEntry;
463        }
464    
465        public String getCookies() {
466            return this.cookies;
467        }
468    
469        public double getPixelPerDegree() {
470            return this.pixelPerDegree;
471        }
472    
473        public int getMaxZoom() {
474            return this.defaultMaxZoom;
475        }
476    
477        public int getMinZoom() {
478            return this.defaultMinZoom;
479        }
480    
481        public String getEulaAcceptanceRequired() {
482            return eulaAcceptanceRequired;
483        }
484    
485        public void setEulaAcceptanceRequired(String eulaAcceptanceRequired) {
486            this.eulaAcceptanceRequired = eulaAcceptanceRequired;
487        }
488    
489        public String getCountryCode() {
490            return countryCode;
491        }
492    
493        public void setCountryCode(String countryCode) {
494            this.countryCode = countryCode;
495        }
496    
497        public String getIcon() {
498            return icon;
499        }
500    
501        public void setIcon(String icon) {
502            this.icon = icon;
503        }
504    
505        /**
506         * Get the projections supported by the server. Only relevant for
507         * WMS-type ImageryInfo at the moment.
508         * @return null, if no projections have been specified; the list
509         * of supported projections otherwise.
510         */
511        public List<String> getServerProjections() {
512            if (serverProjections == null)
513                return Collections.emptyList();
514            return Collections.unmodifiableList(serverProjections);
515        }
516    
517        public void setServerProjections(Collection<String> serverProjections) {
518            this.serverProjections = new ArrayList<String>(serverProjections);
519        }
520    
521        public String getExtendedUrl() {
522            return imageryType.getUrlString() + (defaultMaxZoom != 0
523                ? "["+(defaultMinZoom != 0 ? defaultMinZoom+",":"")+defaultMaxZoom+"]" : "") + ":" + url;
524        }
525    
526        public String getToolbarName()
527        {
528            String res = name;
529            if(pixelPerDegree != 0.0) {
530                res += "#PPD="+pixelPerDegree;
531            }
532            return res;
533        }
534    
535        public String getMenuName()
536        {
537            String res = name;
538            if(pixelPerDegree != 0.0) {
539                res += " ("+pixelPerDegree+")";
540            }
541            return res;
542        }
543    
544        public boolean hasAttribution()
545        {
546            return attributionText != null;
547        }
548    
549        public void copyAttribution(ImageryInfo i)
550        {
551            this.attributionImage = i.attributionImage;
552            this.attributionImageURL = i.attributionImageURL;
553            this.attributionText = i.attributionText;
554            this.attributionLinkURL = i.attributionLinkURL;
555            this.termsOfUseText = i.termsOfUseText;
556            this.termsOfUseURL = i.termsOfUseURL;
557        }
558    
559        /**
560         * Applies the attribution from this object to a TMSTileSource.
561         */
562        public void setAttribution(AbstractTileSource s) {
563            if (attributionText != null) {
564                if (attributionText.equals("osm")) {
565                    s.setAttributionText(new Mapnik().getAttributionText(0, null, null));
566                } else {
567                    s.setAttributionText(attributionText);
568                }
569            }
570            if (attributionLinkURL != null) {
571                if (attributionLinkURL.equals("osm")) {
572                    s.setAttributionLinkURL(new Mapnik().getAttributionLinkURL());
573                } else {
574                    s.setAttributionLinkURL(attributionLinkURL);
575                }
576            }
577            if (attributionImage != null) {
578                ImageIcon i = ImageProvider.getIfAvailable(null, attributionImage);
579                if (i != null) {
580                    s.setAttributionImage(i.getImage());
581                }
582            }
583            if (attributionImageURL != null) {
584                s.setAttributionImageURL(attributionImageURL);
585            }
586            if (termsOfUseText != null) {
587                s.setTermsOfUseText(termsOfUseText);
588            }
589            if (termsOfUseURL != null) {
590                if (termsOfUseURL.equals("osm")) {
591                    s.setTermsOfUseURL(new Mapnik().getTermsOfUseURL());
592                } else {
593                    s.setTermsOfUseURL(termsOfUseURL);
594                }
595            }
596        }
597    
598        public ImageryType getImageryType() {
599            return imageryType;
600        }
601    
602        public void setImageryType(ImageryType imageryType) {
603            this.imageryType = imageryType;
604        }
605    
606        /**
607         * Returns true if this layer's URL is matched by one of the regular
608         * expressions kept by the current OsmApi instance.
609         */
610        public boolean isBlacklisted() {
611            return OsmApi.getOsmApi().getCapabilities().isOnImageryBlacklist(this.url);
612        }
613    }