// License: GPL. For details, see Readme.txt file. package org.openstreetmap.gui.jmapviewer.tilesources; import java.awt.Point; import java.util.Random; import org.openstreetmap.gui.jmapviewer.Coordinate; import org.openstreetmap.gui.jmapviewer.OsmMercator; import org.openstreetmap.gui.jmapviewer.TileXY; import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate; /** * This tilesource uses different to OsmMercator projection. * * Earth is assumed an ellipsoid in this projection, unlike * sphere in OsmMercator, so latitude calculation differs a lot. * * The longitude calculation is the same as in OsmMercator, * we inherit it from AbstractTMSTileSource. * * TODO: correct getDistance() method. */ public class ScanexTileSource extends TMSTileSource { private static final String DEFAULT_URL = "http://maps.kosmosnimki.ru"; private static final int DEFAULT_MAXZOOM = 14; private static final String API_KEY = "4018C5A9AECAD8868ED5DEB2E41D09F7"; private enum ScanexLayer { IRS("irs", "/TileSender.ashx?ModeKey=tile&MapName=F7B8CF651682420FA1749D894C8AD0F6&LayerName=BAC78D764F0443BD9AF93E7A998C9F5B"), SPOT("spot", "/TileSender.ashx?ModeKey=tile&MapName=F7B8CF651682420FA1749D894C8AD0F6&LayerName=F51CE95441284AF6B2FC319B609C7DEC"); private final String name; private final String uri; ScanexLayer(String name, String uri) { this.name = name; this.uri = uri; } public String getName() { return name; } public String getUri() { return uri; } } /** IRS by default */ private ScanexLayer layer = ScanexLayer.IRS; private TemplatedTMSTileSource TemplateSource = null; /** cached latitude used in {@link #tileYToLat(double, int)} */ private double cachedLat; /** * Constructs a new {@code ScanexTileSource}. * @param info tile source info */ public ScanexTileSource(TileSourceInfo info) { super(info); String url = info.getUrl(); /** * The formulae in tileYToLat() and latToTileY() have 2^8 * hardcoded in them, so explicitly state that. For now * the assignment matches OsmMercator.DEFAUL_TILE_SIZE, and * thus is extraneous. But let it be there just in case if * OsmMercator changes. */ this.tileSize = 256; for (ScanexLayer slayer : ScanexLayer.values()) { if (url.equalsIgnoreCase(slayer.getName())) { this.layer = slayer; // Override baseUrl and maxZoom in base class. this.baseUrl = DEFAULT_URL; if (maxZoom == 0) this.maxZoom = DEFAULT_MAXZOOM; return; } } /** If not "irs" or "spot" keyword, then a custom URL. */ TemplatedTMSTileSource.checkUrl(info.getUrl()); this.TemplateSource = new TemplatedTMSTileSource(info); } @Override public String getExtension() { return "jpeg"; } @Override public String getTileUrl(int zoom, int tilex, int tiley) { if (this.TemplateSource != null) return this.TemplateSource.getTileUrl(zoom, tilex, tiley); else return this.getBaseUrl() + getTilePath(zoom, tilex, tiley); } @Override public String getTilePath(int zoom, int tilex, int tiley) { int tmp = (int) Math.pow(2.0, zoom - 1); tilex = tilex - tmp; tiley = tmp - tiley - 1; return this.layer.getUri() + "&apikey=" + API_KEY + "&x=" + tilex + "&y=" + tiley + "&z=" + zoom; } // Latitude to Y and back calculations. private static double RADIUS_E = 6378137; /* radius of Earth at equator, m */ private static double EQUATOR = 40075016.68557849; /* equator length, m */ private static double E = 0.0818191908426; /* eccentricity of Earth's ellipsoid */ @Override public Point latLonToXY(double lat, double lon, int zoom) { return new Point( (int) osmMercator.lonToX(lon, zoom), (int) latToTileY(lat, zoom) ); } @Override public ICoordinate xyToLatLon(int x, int y, int zoom) { return new Coordinate( tileYToLat(y, zoom), osmMercator.xToLon(x, zoom) ); } @Override public TileXY latLonToTileXY(double lat, double lon, int zoom) { return new TileXY( osmMercator.lonToX(lon, zoom) / getTileSize(), latToTileY(lat, zoom) ); } @Override public ICoordinate tileXYToLatLon(int x, int y, int zoom) { return new Coordinate( tileYToLat(y, zoom), osmMercator.xToLon(x * getTileSize(), zoom) ); } private double latToTileY(double lat, int zoom) { double tmp = Math.tan(Math.PI/4 * (1 + lat/90)); double pow = Math.pow(Math.tan(Math.PI/4 + Math.asin(E * Math.sin(Math.toRadians(lat)))/2), E); return (EQUATOR/2 - (RADIUS_E * Math.log(tmp/pow))) * Math.pow(2.0, zoom) / EQUATOR; } /* * To solve inverse formula latitude = f(y) we use * Newton's method. We cache previous calculated latitude, * because new one is usually close to the old one. In case * if solution gets out of bounds, we reset to a new random value. */ private double tileYToLat(double y, int zoom) { double lat0; double lat = cachedLat; do { lat0 = lat; lat = lat - Math.toDegrees(nextTerm(Math.toRadians(lat), y, zoom)); if (lat > OsmMercator.MAX_LAT || lat < OsmMercator.MIN_LAT) { Random r = new Random(); lat = OsmMercator.MIN_LAT + r.nextInt((int) (OsmMercator.MAX_LAT - OsmMercator.MIN_LAT)); } } while (Math.abs(lat0 - lat) > 0.000001); cachedLat = lat; return lat; } /* Next term in Newton's polynomial */ private static double nextTerm(double lat, double y, int zoom) { double sinl = Math.sin(lat); double cosl = Math.cos(lat); zoom = (int) Math.pow(2.0, zoom - 1); double ec = Math.exp((1 - y/zoom)*Math.PI); double f = Math.tan(Math.PI/4+lat/2) - ec * Math.pow(Math.tan(Math.PI/4 + Math.asin(E * sinl)/2), E); double df = 1/(1 - sinl) - ec * E * cosl/((1 - E * sinl) * (Math.sqrt(1 - E * E * sinl * sinl))); return f/df; } }