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