001// License: BSD or GPL. For details, see Readme.txt file. 002// Authors of this file, namely Gleb Smirnoff and Andrey Boltenkov, allow 003// to reuse the code under BSD license. 004package org.openstreetmap.gui.jmapviewer.tilesources; 005 006import java.awt.Point; 007import java.util.Random; 008 009import org.openstreetmap.gui.jmapviewer.Coordinate; 010import org.openstreetmap.gui.jmapviewer.OsmMercator; 011import org.openstreetmap.gui.jmapviewer.TileXY; 012import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate; 013 014/** 015 * This tilesource uses different to OsmMercator projection. 016 * 017 * Earth is assumed an ellipsoid in this projection, unlike 018 * sphere in OsmMercator, so latitude calculation differs a lot. 019 * 020 * The longitude calculation is the same as in OsmMercator, 021 * we inherit it from AbstractTMSTileSource. 022 * 023 * TODO: correct getDistance() method. 024 */ 025public class ScanexTileSource extends TMSTileSource { 026 private static final String DEFAULT_URL = "http://maps.kosmosnimki.ru"; 027 private static final int DEFAULT_MAXZOOM = 14; 028 private static final String API_KEY = "4018C5A9AECAD8868ED5DEB2E41D09F7"; 029 030 private enum ScanexLayer { 031 IRS("irs", "/TileSender.ashx?ModeKey=tile&MapName=F7B8CF651682420FA1749D894C8AD0F6&LayerName=BAC78D764F0443BD9AF93E7A998C9F5B"), 032 SPOT("spot", "/TileSender.ashx?ModeKey=tile&MapName=F7B8CF651682420FA1749D894C8AD0F6&LayerName=F51CE95441284AF6B2FC319B609C7DEC"); 033 034 private final String name; 035 private final String uri; 036 037 ScanexLayer(String name, String uri) { 038 this.name = name; 039 this.uri = uri; 040 } 041 042 public String getName() { 043 return name; 044 } 045 046 public String getUri() { 047 return uri; 048 } 049 } 050 051 /** IRS by default */ 052 private ScanexLayer layer = ScanexLayer.IRS; 053 private TemplatedTMSTileSource TemplateSource = null; 054 055 /** cached latitude used in {@link #tileYToLat(double, int)} */ 056 private double cachedLat; 057 058 /** 059 * Constructs a new {@code ScanexTileSource}. 060 * @param info tile source info 061 */ 062 public ScanexTileSource(TileSourceInfo info) { 063 super(info); 064 String url = info.getUrl(); 065 066 /** 067 * The formulae in tileYToLat() and latToTileY() have 2^8 068 * hardcoded in them, so explicitly state that. For now 069 * the assignment matches OsmMercator.DEFAUL_TILE_SIZE, and 070 * thus is extraneous. But let it be there just in case if 071 * OsmMercator changes. 072 */ 073 this.tileSize = 256; 074 075 for (ScanexLayer slayer : ScanexLayer.values()) { 076 if (url.equalsIgnoreCase(slayer.getName())) { 077 this.layer = slayer; 078 // Override baseUrl and maxZoom in base class. 079 this.baseUrl = DEFAULT_URL; 080 if (maxZoom == 0) 081 this.maxZoom = DEFAULT_MAXZOOM; 082 return; 083 } 084 } 085 /** If not "irs" or "spot" keyword, then a custom URL. */ 086 TemplatedTMSTileSource.checkUrl(info.getUrl()); 087 this.TemplateSource = new TemplatedTMSTileSource(info); 088 } 089 090 @Override 091 public String getExtension() { 092 return "jpeg"; 093 } 094 095 @Override 096 public String getTileUrl(int zoom, int tilex, int tiley) { 097 if (this.TemplateSource != null) 098 return this.TemplateSource.getTileUrl(zoom, tilex, tiley); 099 else 100 return this.getBaseUrl() + getTilePath(zoom, tilex, tiley); 101 } 102 103 @Override 104 public String getTilePath(int zoom, int tilex, int tiley) { 105 int tmp = (int) Math.pow(2.0, zoom - 1); 106 107 tilex = tilex - tmp; 108 tiley = tmp - tiley - 1; 109 110 return this.layer.getUri() + "&apikey=" + API_KEY + "&x=" + tilex + "&y=" + tiley + "&z=" + zoom; 111 } 112 113 // Latitude to Y and back calculations. 114 private static final double RADIUS_E = 6378137; /* radius of Earth at equator, m */ 115 private static final double EQUATOR = 40075016.68557849; /* equator length, m */ 116 private static final double E = 0.0818191908426; /* eccentricity of Earth's ellipsoid */ 117 118 @Override 119 public Point latLonToXY(double lat, double lon, int zoom) { 120 return new Point( 121 (int) Math.round(osmMercator.lonToX(lon, zoom)), 122 (int) Math.round(latToTileY(lat, zoom)) 123 ); 124 } 125 126 @Override 127 public ICoordinate xyToLatLon(int x, int y, int zoom) { 128 return new Coordinate( 129 tileYToLat(y, zoom), 130 osmMercator.xToLon(x, zoom) 131 ); 132 } 133 134 @Override 135 public TileXY latLonToTileXY(double lat, double lon, int zoom) { 136 return new TileXY( 137 osmMercator.lonToX(lon, zoom) / getTileSize(), 138 latToTileY(lat, zoom) 139 ); 140 } 141 142 @Override 143 public ICoordinate tileXYToLatLon(int x, int y, int zoom) { 144 return new Coordinate( 145 tileYToLat(y, zoom), 146 osmMercator.xToLon(x * getTileSize(), zoom) 147 ); 148 } 149 150 private double latToTileY(double lat, int zoom) { 151 double tmp = Math.tan(Math.PI/4 * (1 + lat/90)); 152 double pow = Math.pow(Math.tan(Math.PI/4 + Math.asin(E * Math.sin(Math.toRadians(lat)))/2), E); 153 154 return (EQUATOR/2 - (RADIUS_E * Math.log(tmp/pow))) * Math.pow(2.0, zoom) / EQUATOR; 155 } 156 157 /* 158 * To solve inverse formula latitude = f(y) we use 159 * Newton's method. We cache previous calculated latitude, 160 * because new one is usually close to the old one. In case 161 * if solution gets out of bounds, we reset to a new random value. 162 */ 163 private double tileYToLat(double y, int zoom) { 164 double lat0; 165 double lat = cachedLat; 166 do { 167 lat0 = lat; 168 lat = lat - Math.toDegrees(nextTerm(Math.toRadians(lat), y, zoom)); 169 if (lat > OsmMercator.MAX_LAT || lat < OsmMercator.MIN_LAT) { 170 Random r = new Random(); 171 lat = OsmMercator.MIN_LAT + 172 r.nextInt((int) (OsmMercator.MAX_LAT - OsmMercator.MIN_LAT)); 173 } 174 } while (Math.abs(lat0 - lat) > 0.000001); 175 176 cachedLat = lat; 177 178 return lat; 179 } 180 181 /* Next term in Newton's polynomial */ 182 private static double nextTerm(double lat, double y, int zoom) { 183 double sinl = Math.sin(lat); 184 double cosl = Math.cos(lat); 185 186 zoom = (int) Math.pow(2.0, zoom - 1); 187 double ec = Math.exp((1 - y/zoom)*Math.PI); 188 189 double f = Math.tan(Math.PI/4+lat/2) - 190 ec * Math.pow(Math.tan(Math.PI/4 + Math.asin(E * sinl)/2), E); 191 double df = 1/(1 - sinl) - ec * E * cosl/((1 - E * sinl) * 192 (Math.sqrt(1 - E * E * sinl * sinl))); 193 194 return f/df; 195 } 196}