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