1 /*************************************** 2 * Copyright 2011, 2012 GlobWeb contributors. 3 * 4 * This file is part of GlobWeb. 5 * 6 * GlobWeb is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU Lesser General Public License as published by 8 * the Free Software Foundation, version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * GlobWeb is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with GlobWeb. If not, see <http://www.gnu.org/licenses/>. 18 ***************************************/ 19 20 define(['./Utils','./RasterLayer','./MercatorTiling'], function(Utils,RasterLayer,MercatorTiling) { 21 22 /**************************************************************************************************************/ 23 24 var BingTileSystem = (function() 25 { 26 var EarthRadius = 6378137; 27 var MinLatitude = -85.05112878; 28 var MaxLatitude = 85.05112878; 29 var MinLongitude = -180; 30 var MaxLongitude = 180; 31 32 33 // <summary> 34 // Clips a number to the specified minimum and maximum values. 35 // </summary> 36 // <param name="n">The number to clip.</param> 37 // <param name="minValue">Minimum allowable value.</param> 38 // <param name="maxValue">Maximum allowable value.</param> 39 // <returns>The clipped value.</returns> 40 function Clip( n, minValue, maxValue) 41 { 42 return Math.min(Math.max(n, minValue), maxValue); 43 } 44 45 46 // <summary> 47 // Determines the map width and height (in pixels) at a specified level 48 // of detail. 49 // </summary> 50 // <param name="levelOfDetail">Level of detail, from 1 (lowest detail) 51 // to 23 (highest detail).</param> 52 // <returns>The map width and height in pixels.</returns> 53 function MapSize(levelOfDetail) 54 { 55 return 256 << levelOfDetail; 56 } 57 58 59 // <summary> 60 // Determines the ground resolution (in meters per pixel) at a specified 61 // latitude and level of detail. 62 // </summary> 63 // <param name="latitude">Latitude (in degrees) at which to measure the 64 // ground resolution.</param> 65 // <param name="levelOfDetail">Level of detail, from 1 (lowest detail) 66 // to 23 (highest detail).</param> 67 // <returns>The ground resolution, in meters per pixel.</returns> 68 function GroundResolution(latitude, levelOfDetail) 69 { 70 latitude = Clip(latitude, MinLatitude, MaxLatitude); 71 return Math.cos(latitude * Math.PI / 180.0) * 2.0 * Math.PI * EarthRadius / MapSize(levelOfDetail); 72 } 73 74 75 76 // <summary> 77 // Determines the map scale at a specified latitude, level of detail, 78 // and screen resolution. 79 // </summary> 80 // <param name="latitude">Latitude (in degrees) at which to measure the 81 // map scale.</param> 82 // <param name="levelOfDetail">Level of detail, from 1 (lowest detail) 83 // to 23 (highest detail).</param> 84 // <param name="screenDpi">Resolution of the screen, in dots per inch.</param> 85 // <returns>The map scale, expressed as the denominator N of the ratio 1 : N.</returns> 86 function MapScale( latitude, levelOfDetail, screenDpi) 87 { 88 return GroundResolution(latitude, levelOfDetail) * screenDpi / 0.0254; 89 } 90 91 // <summary> 92 // Converts a point from latitude/longitude WGS-84 coordinates (in degrees) 93 // into pixel XY coordinates at a specified level of detail. 94 // </summary> 95 // <param name="latitude">Latitude of the point, in degrees.</param> 96 // <param name="longitude">Longitude of the point, in degrees.</param> 97 // <param name="levelOfDetail">Level of detail, from 1 (lowest detail) 98 // to 23 (highest detail).</param> 99 // <param name="pixelX">Output parameter receiving the X coordinate in pixels.</param> 100 // <param name="pixelY">Output parameter receiving the Y coordinate in pixels.</param> 101 function LatLongToPixelXY(latitude, longitude, levelOfDetail) 102 { 103 latitude = Clip(latitude, MinLatitude, MaxLatitude); 104 longitude = Clip(longitude, MinLongitude, MaxLongitude); 105 106 var x = (longitude + 180) / 360; 107 var sinLatitude = Math.sin(latitude * Math.PI / 180); 108 var y = 0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI); 109 110 var mapSize = MapSize(levelOfDetail); 111 var pixelX = Clip(x * mapSize + 0.5, 0, mapSize - 1); 112 var pixelY = Clip(y * mapSize + 0.5, 0, mapSize - 1); 113 114 return [ Math.floor(pixelX), Math.floor(pixelY) ]; 115 } 116 117 118 119 // <summary> 120 // Converts a pixel from pixel XY coordinates at a specified level of detail 121 // into latitude/longitude WGS-84 coordinates (in degrees). 122 // </summary> 123 // <param name="pixelX">X coordinate of the point, in pixels.</param> 124 // <param name="pixelY">Y coordinates of the point, in pixels.</param> 125 // <param name="levelOfDetail">Level of detail, from 1 (lowest detail) 126 // to 23 (highest detail).</param> 127 // <param name="latitude">Output parameter receiving the latitude in degrees.</param> 128 // <param name="longitude">Output parameter receiving the longitude in degrees.</param> 129 function PixelXYToLatLong( pixelX, pixelY, levelOfDetail) 130 { 131 var mapSize = MapSize(levelOfDetail); 132 var x = (Clip(pixelX, 0, mapSize - 1) / mapSize) - 0.5; 133 var y = 0.5 - (Clip(pixelY, 0, mapSize - 1) / mapSize); 134 135 var latitude = 90 - 360 * Math.atan(Math.exp(-y * 2 * Math.PI)) / Math.PI; 136 var longitude = 360 * x; 137 138 return [ latitude, longitude ]; 139 } 140 141 142 143 // <summary> 144 // Converts pixel XY coordinates into tile XY coordinates of the tile containing 145 // the specified pixel. 146 // </summary> 147 // <param name="pixelX">Pixel X coordinate.</param> 148 // <param name="pixelY">Pixel Y coordinate.</param> 149 // <param name="tileX">Output parameter receiving the tile X coordinate.</param> 150 // <param name="tileY">Output parameter receiving the tile Y coordinate.</param> 151 function PixelXYToTileXY( pixelXY ) 152 { 153 return [ pixelXY[0] / 256, pixelXY[1] / 256 ]; 154 } 155 156 157 158 // <summary> 159 // Converts tile XY coordinates into pixel XY coordinates of the upper-left pixel 160 // of the specified tile. 161 // </summary> 162 // <param name="tileX">Tile X coordinate.</param> 163 // <param name="tileY">Tile Y coordinate.</param> 164 // <param name="pixelX">Output parameter receiving the pixel X coordinate.</param> 165 // <param name="pixelY">Output parameter receiving the pixel Y coordinate.</param> 166 function TileXYToPixelXY( tileXY ) 167 { 168 return [ tileXY[0] * 256, tileXY[1] * 256 ]; 169 } 170 171 172 173 // <summary> 174 // Converts tile XY coordinates into a QuadKey at a specified level of detail. 175 // </summary> 176 // <param name="tileX">Tile X coordinate.</param> 177 // <param name="tileY">Tile Y coordinate.</param> 178 // <param name="levelOfDetail">Level of detail, from 1 (lowest detail) 179 // to 23 (highest detail).</param> 180 // <returns>A string containing the QuadKey.</returns> 181 function TileXYToQuadKey( tileX, tileY, levelOfDetail) 182 { 183 var quadKey = ""; 184 for ( var i = levelOfDetail; i > 0; i--) 185 { 186 var digit = '0'; 187 var mask = 1 << (i - 1); 188 if ((tileX & mask) != 0) 189 { 190 digit++; 191 } 192 if ((tileY & mask) != 0) 193 { 194 digit++; 195 digit++; 196 } 197 quadKey += digit; 198 } 199 return quadKey; 200 } 201 202 203 204 // <summary> 205 // Converts a QuadKey into tile XY coordinates. 206 // </summary> 207 // <param name="quadKey">QuadKey of the tile.</param> 208 // <param name="tileX">Output parameter receiving the tile X coordinate.</param> 209 // <param name="tileY">Output parameter receiving the tile Y coordinate.</param> 210 // <param name="levelOfDetail">Output parameter receiving the level of detail.</param> 211 function QuadKeyToTileXY( quadKey) 212 { 213 var tileX = 0, tileY = 0; 214 var levelOfDetail = quadKey.length(); 215 for (var i = levelOfDetail; i > 0; i--) 216 { 217 var mask = 1 << (i - 1); 218 switch (quadKey[levelOfDetail - i]) 219 { 220 case '0': 221 break; 222 223 case '1': 224 tileX |= mask; 225 break; 226 227 case '2': 228 tileY |= mask; 229 break; 230 231 case '3': 232 tileX |= mask; 233 tileY |= mask; 234 break; 235 236 default: 237 throw new ArgumentException("Invalid QuadKey digit sequence."); 238 } 239 } 240 } 241 242 return { 243 tileXYToQuadKey: TileXYToQuadKey, 244 latLongToPixelXY : LatLongToPixelXY 245 } 246 })(); 247 248 /**************************************************************************************************************/ 249 250 251 /** @name BingLayer 252 @class 253 A layer to display Bing imagery data. 254 @augments RasterLayer 255 @param options Configuration properties. See {@link RasterLayer} for base properties : 256 <ul> 257 <li>imageSet : the image set to use, can be Aerial, Road</li> 258 <li>key : the bing key to use</li> 259 </ul> 260 */ 261 var BingLayer = function( options ) 262 { 263 // Call ancestor 264 RasterLayer.prototype.constructor.call( this, options ); 265 266 this.tilePixelSize = 256; 267 this.tiling = new MercatorTiling( options.baseLevel || 2 ); 268 this.numberOfLevels = 18; 269 this.baseUrl = ""; 270 this.baseUrlSubDomains = []; 271 this._ready = false; 272 273 var self = this; 274 275 // Need to provide a global callback for JSONP 276 window["_bingTileProviderCallback"] = function(result) { 277 278 self.baseUrl = result.resourceSets[0].resources[0].imageUrl; 279 self.baseUrlSubDomains = result.resourceSets[0].resources[0].imageUrlSubdomains; 280 self._ready = true; 281 282 // Call callback if set 283 if (options.onready && options.onready instanceof Function) 284 { 285 options.onready(self); 286 } 287 288 // Request a frame 289 if ( self.globe ) 290 { 291 self.globe.renderContext.requestFrame(); 292 } 293 }; 294 295 // JSONP Call : needed because of cross-site origin policy 296 var script = document.createElement("script"); 297 script.type = "text/javascript"; 298 script.src = "http://dev.virtualearth.net/REST/V1/Imagery/Metadata/" + options.imageSet + "?jsonp=_bingTileProviderCallback&key=" + options.key; 299 script.id = "_bingTileProviderCallback"; 300 document.getElementsByTagName("head")[0].appendChild(script); 301 } 302 303 Utils.inherits(RasterLayer,BingLayer); 304 305 /**************************************************************************************************************/ 306 307 /** 308 Get an url for the given tile 309 */ 310 BingLayer.prototype.getUrl = function(tile) 311 { 312 var url = this.baseUrl.replace( "{quadkey}", BingTileSystem.tileXYToQuadKey(tile.x,tile.y,tile.level) ); 313 return url.replace( "{subdomain}", this.baseUrlSubDomains[ Math.floor( Math.random() * this.baseUrlSubDomains.length ) ] ); 314 } 315 316 /**************************************************************************************************************/ 317 318 return BingLayer; 319 320 }); 321