001// License: BSD or GPL. For details, see Readme.txt file.
002package org.openstreetmap.gui.jmapviewer.tilesources;
003
004import java.util.Random;
005
006import org.openstreetmap.gui.jmapviewer.OsmMercator;
007
008/*
009 * This tilesource uses different to OsmMercator projection.
010 *
011 * Earth is assumed an ellipsoid in this projection, unlike
012 * sphere in OsmMercator, so latitude calculation differs
013 * a lot.
014 *
015 * The longitude calculation is the same as in OsmMercator,
016 * we inherit it from AbstractTMSTileSource.
017 *
018 * TODO: correct getDistance() method.
019 */
020
021public class ScanexTileSource extends TMSTileSource {
022    private static final String DEFAULT_URL = "http://maps.kosmosnimki.ru";
023    private static final int DEFAULT_MAXZOOM = 14;
024    private static String API_KEY = "4018C5A9AECAD8868ED5DEB2E41D09F7";
025
026    private enum ScanexLayer {
027        IRS("irs", "/TileSender.ashx?ModeKey=tile&MapName=F7B8CF651682420FA1749D894C8AD0F6&LayerName=BAC78D764F0443BD9AF93E7A998C9F5B"),
028        SPOT("spot", "/TileSender.ashx?ModeKey=tile&MapName=F7B8CF651682420FA1749D894C8AD0F6&LayerName=F51CE95441284AF6B2FC319B609C7DEC");
029
030        private String name;
031        private String uri;
032
033        ScanexLayer(String name, String uri) {
034            this.name = name;
035            this.uri = uri;
036        }
037        public String getName() {
038            return name;
039        }
040        public String getUri() {
041            return uri;
042        }
043    }
044
045    /* IRS by default */
046    private ScanexLayer Layer = ScanexLayer.IRS;
047
048    public ScanexTileSource(String name, String url, int maxZoom) {
049        super(name, url, maxZoom);
050
051        for (ScanexLayer layer : ScanexLayer.values()) {
052            if (url.equalsIgnoreCase(layer.getName())) {
053                this.Layer = layer;
054                /*
055                 * Override baseUrl and maxZoom in base class.
056                 */
057                this.baseUrl = DEFAULT_URL;
058                if (maxZoom == 0)
059                    this.maxZoom = DEFAULT_MAXZOOM;
060                break;
061            }
062        }
063    }
064
065    @Override
066    public String getExtension() {
067        return("jpeg");
068    }
069
070    @Override
071    public String getTilePath(int zoom, int tilex, int tiley) {
072        int tmp = (int)Math.pow(2.0, zoom - 1);
073
074        tilex = tilex - tmp;
075        tiley = tmp - tiley - 1;
076
077        return this.Layer.getUri() + "&apikey=" + API_KEY + "&x=" + tilex + "&y=" + tiley + "&z=" + zoom;
078    }
079
080    public TileUpdate getTileUpdate() {
081        return TileUpdate.IfNoneMatch;
082    }
083
084
085    /*
086     * Latitude to Y and back calculations.
087     */
088    private static double RADIUS_E = 6378137;   /* radius of Earth at equator, m */
089    private static double EQUATOR = 40075016.68557849; /* equator length, m */
090    private static double E = 0.0818191908426;  /* eccentricity of Earth's ellipsoid */
091
092    @Override
093    public int LatToY(double lat, int zoom) {
094        return (int )(latToTileY(lat, zoom) * OsmMercator.TILE_SIZE);
095    }
096
097    @Override
098    public double YToLat(int y, int zoom) {
099        return tileYToLat((double )y / OsmMercator.TILE_SIZE, zoom);
100    }
101
102    @Override
103    public double latToTileY(double lat, int zoom) {
104        double tmp = Math.tan(Math.PI/4 * (1 + lat/90));
105        double pow = Math.pow(Math.tan(Math.PI/4 + Math.asin(E * Math.sin(Math.toRadians(lat)))/2), E);
106
107        return (EQUATOR/2 - (RADIUS_E * Math.log(tmp/pow))) * Math.pow(2.0, zoom) / EQUATOR;
108    }
109
110    @Override
111    public double tileYToLat(int y, int zoom) {
112        return tileYToLat((double )y, zoom);
113    }
114
115    /*
116     * To solve inverse formula latitude = f(y) we use
117     * Newton's method. We cache previous calculated latitude,
118     * because new one is usually close to the old one. In case
119     * if solution gets out of bounds, we reset to a new random
120     * value.
121     */
122    private double cached_lat = 0;
123    private double tileYToLat(double y, int zoom) {
124        double lat0, lat;
125
126        lat = cached_lat;
127        do {
128            lat0 = lat;
129            lat = lat - Math.toDegrees(NextTerm(Math.toRadians(lat), y, zoom));
130            if (lat > OsmMercator.MAX_LAT || lat < OsmMercator.MIN_LAT) {
131                Random r = new Random();
132                lat = OsmMercator.MIN_LAT +
133                  r.nextInt((int )(OsmMercator.MAX_LAT - OsmMercator.MIN_LAT));
134            }
135        } while ((Math.abs(lat0 - lat) > 0.000001));
136
137        cached_lat = lat;
138
139        return (lat);
140    }
141
142    /* Next term in Newton's polynomial */
143    private double NextTerm(double lat, double y, int zoom) {
144        double sinl=Math.sin(lat);
145        double cosl=Math.cos(lat);
146        double ec, f, df;
147
148        zoom = (int )Math.pow(2.0, zoom - 1);
149        ec = Math.exp((1 - y/zoom)*Math.PI);
150
151        f = (Math.tan(Math.PI/4+lat/2) -
152            ec * Math.pow(Math.tan(Math.PI/4 + Math.asin(E * sinl)/2), E));
153        df = 1/(1 - sinl) - ec * E * cosl/((1 - E * sinl) *
154            (Math.sqrt (1 - E * E * sinl * sinl)));
155
156        return (f/df);
157    }
158}