001// License: 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 a lot.
013 *
014 * The longitude calculation is the same as in OsmMercator,
015 * we inherit it from AbstractTMSTileSource.
016 *
017 * TODO: correct getDistance() method.
018 */
019public class ScanexTileSource extends TMSTileSource {
020    private static final String DEFAULT_URL = "http://maps.kosmosnimki.ru";
021    private static final int DEFAULT_MAXZOOM = 14;
022    private static final String API_KEY = "4018C5A9AECAD8868ED5DEB2E41D09F7";
023
024    // Latitude to Y and back calculations.
025
026    /** eccentricity of Earth's ellipsoid */
027    private static double E = 0.0818191908426;
028
029    private enum ScanexLayer {
030        IRS("irs", "/TileSender.ashx?ModeKey=tile&MapName=F7B8CF651682420FA1749D894C8AD0F6&LayerName=BAC78D764F0443BD9AF93E7A998C9F5B"),
031        SPOT("spot", "/TileSender.ashx?ModeKey=tile&MapName=F7B8CF651682420FA1749D894C8AD0F6&LayerName=F51CE95441284AF6B2FC319B609C7DEC");
032
033        private String name;
034        private String uri;
035
036        ScanexLayer(String name, String uri) {
037            this.name = name;
038            this.uri = uri;
039        }
040
041        public String getName() {
042            return name;
043        }
044
045        public String getUri() {
046            return uri;
047        }
048    }
049
050    /** IRS by default */
051    private ScanexLayer layer = ScanexLayer.IRS;
052
053    /** cached latitude used in {@link #tileYToLat(double, int)} */
054    private double cachedLat;
055
056    /**
057     * Constructs a new {@code ScanexTileSource}.
058     * @param info tile source info
059     */
060    public ScanexTileSource(TileSourceInfo info) {
061        super(info);
062        String url = info.getUrl();
063
064        for (ScanexLayer slayer : ScanexLayer.values()) {
065            if (url.equalsIgnoreCase(slayer.getName())) {
066                this.layer = slayer;
067                // Override baseUrl and maxZoom in base class.
068                this.baseUrl = DEFAULT_URL;
069                if (maxZoom == 0)
070                    this.maxZoom = DEFAULT_MAXZOOM;
071                break;
072            }
073        }
074    }
075
076    @Override
077    public String getExtension() {
078        return "jpeg";
079    }
080
081    @Override
082    public String getTilePath(int zoom, int tilex, int tiley) {
083        int tmp = (int) Math.pow(2.0, zoom - 1);
084
085        tilex = tilex - tmp;
086        tiley = tmp - tiley - 1;
087
088        return this.layer.getUri() + "&apikey=" + API_KEY + "&x=" + tilex + "&y=" + tiley + "&z=" + zoom;
089    }
090
091    /*
092     * To solve inverse formula latitude = f(y) we use
093     * Newton's method. We cache previous calculated latitude,
094     * because new one is usually close to the old one. In case
095     * if solution gets out of bounds, we reset to a new random value.
096     */
097    private double tileYToLat(double y, int zoom) {
098        double lat0;
099        double lat = cachedLat;
100        do {
101            lat0 = lat;
102            lat = lat - Math.toDegrees(nextTerm(Math.toRadians(lat), y, zoom));
103            if (lat > OsmMercator.MAX_LAT || lat < OsmMercator.MIN_LAT) {
104                Random r = new Random();
105                lat = OsmMercator.MIN_LAT +
106                  r.nextInt((int) (OsmMercator.MAX_LAT - OsmMercator.MIN_LAT));
107            }
108        } while (Math.abs(lat0 - lat) > 0.000001);
109
110        cachedLat = lat;
111
112        return lat;
113    }
114
115    /* Next term in Newton's polynomial */
116    private static double nextTerm(double lat, double y, int zoom) {
117        double sinl = Math.sin(lat);
118        double cosl = Math.cos(lat);
119
120        zoom = (int) Math.pow(2.0, zoom - 1);
121        double ec = Math.exp((1 - y/zoom)*Math.PI);
122
123        double f = Math.tan(Math.PI/4+lat/2) -
124            ec * Math.pow(Math.tan(Math.PI/4 + Math.asin(E * sinl)/2), E);
125        double df = 1/(1 - sinl) - ec * E * cosl/((1 - E * sinl) *
126            (Math.sqrt(1 - E * E * sinl * sinl)));
127
128        return f/df;
129    }
130}