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