Bing support. Add a <scheme>microsoft</scheme> parameter to imagery.xml for this...
[potlatch2.git] / net / systemeD / halcyon / TileSet.as
1 package net.systemeD.halcyon {
2
3         import flash.display.*;
4         import flash.events.*;
5         import flash.net.*;
6         import flash.system.LoaderContext;
7         import flash.utils.Timer;
8         import net.systemeD.halcyon.MapEvent;
9
10     public class TileSet extends Sprite {
11
12                 public var tile_l:int;
13                 public var tile_r:int;
14                 public var tile_b:int;
15                 public var tile_t:int;
16
17                 private var offset_lon:Number=0;
18                 private var offset_lat:Number=0;
19
20                 private var requests:Array=[];
21                 private var tiles:Object={};            // key is "z,x,y"; value "true" (needed) or reference to sprite
22                 private var waiting:int=0;                      // number of tiles currently being downloaded
23                 private var baseurl:String;                     // e.g. http://npe.openstreetmap.org/$z/$x/$y.png
24                 private var scheme:String;                      // 900913 or microsoft
25
26                 private var map:Map;
27
28
29         public function TileSet(map:Map) {
30                         this.map=map;
31                         alpha=0.5;
32                         createSprites();
33                         map.addEventListener(MapEvent.NUDGE_BACKGROUND, nudgeHandler);
34                 }
35         
36                 public function init(params:Object, update:Boolean=false):void {
37                         baseurl=params.url;
38                         scheme =params.scheme ? params.scheme : '900913';
39                         tiles={};
40                         offset_lon=offset_lat=x=y=0;
41                         while (numChildren) { removeChildAt(0); }
42                         createSprites();
43                         if (update) { this.update(); }
44                 }
45
46                 private function createSprites():void {
47                         for (var i:uint=map.MINSCALE; i<=map.MAXSCALE; i++) {
48                                 this.addChild(new Sprite());
49                         }
50                 }
51
52                 public function setDimming(dim:Boolean):void {
53                         alpha=dim ? 0.5 : 1;
54                 }
55
56                 public function changeScale(scale:uint):void {
57                         for (var i:uint=map.MINSCALE; i<=map.MAXSCALE; i++) {
58                                 this.getChildAt(i-map.MINSCALE).visible=(scale==i);
59                         }
60                         x=map.lon2coord(map.centre_lon+offset_lon)-map.lon2coord(map.centre_lon);
61                         y=map.lat2coord(map.centre_lat+offset_lat)-map.lat2coord(map.centre_lat);
62                 }
63                         
64                 // Update bounds - called on every move
65                 
66                 public function update():void {
67                         if (!baseurl) { return; }
68                         tile_l=lon2tile(map.edge_l-offset_lon);
69                         tile_r=lon2tile(map.edge_r-offset_lon);
70                         tile_t=lat2tile(map.edge_t-offset_lat);
71                         tile_b=lat2tile(map.edge_b-offset_lat);
72                         for (var tx:int=tile_l; tx<=tile_r; tx++) {
73                                 for (var ty:int=tile_t; ty<=tile_b; ty++) {
74                                         if (!tiles[map.scale+','+tx+','+ty]) { addRequest(tx,ty); }
75                                 }
76                         }
77                 }
78
79                 // Mark that a tile needs to be loaded
80                 
81                 public function addRequest(tx:int,ty:int):void {
82                         tiles[map.scale+','+tx+','+ty]=true;
83                         requests.push([map.scale,tx,ty]);
84                 }
85
86                 // Service tile queue - called on every frame to download new tiles
87                 
88                 public function serviceQueue():void {
89                         if (waiting==4 || requests.length==0) { return; }
90                         var r:Array, tx:int, ty:int, tz:int, l:DisplayObject;
91
92                         for (var i:uint=0; i<Math.min(requests.length, 4-waiting); i++) {
93                                 r=requests.shift(); tz=r[0]; tx=r[1]; ty=r[2];
94                                 if (tx>=tile_l && tx<=tile_r && ty>=tile_t && ty<=tile_b) {
95                                         // Tile is on-screen, so load
96                                         waiting++;
97                                         var loader:Loader = new Loader();
98                                         loader.contentLoaderInfo.addEventListener(Event.INIT, doImgInit);
99                         loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, missingTileError);
100                                         loader.load(new URLRequest(tileURL(tx,ty,tz)), 
101                                                     new LoaderContext(true));
102                                         l=this.getChildAt(map.scale-map.MINSCALE);
103                                         Sprite(l).addChild(loader);
104                                         loader.x=map.lon2coord(tile2lon(tx));
105                                         loader.y=map.lat2coord(tile2lat(ty));
106 //                                      loader.alpha=0.5;
107                                 }
108                         }
109                 }
110
111         private function missingTileError(event:Event):void {
112                         waiting--;
113                         return;
114                 }
115
116                 protected function doImgInit(event:Event):void {
117                         event.target.loader.alpha=0;
118                         var t:Timer=new Timer(10,10);
119                         t.addEventListener(TimerEvent.TIMER,function():void { upFade(DisplayObject(event.target.loader)); });
120                         t.start();
121                         waiting--;
122                         return;
123                 }
124                 
125                 protected function upFade(s:DisplayObject):void {
126                         s.alpha+=0.1;
127                 }
128
129                 
130                 // Assemble tile URL
131                 
132                 private function tileURL(tx:int,ty:int,tz:uint):String {
133                         switch (scheme.toLowerCase()) {
134
135                                 case 'microsoft':
136                                         var u:String='';
137                                         for (var zoom:uint=tz; zoom>0; zoom--) {
138                                                 var byte:uint=0;
139                                                 var mask:uint=1<<(zoom-1);
140                                                 if ((tx & mask)!=0) byte++;
141                                                 if ((ty & mask)!=0) byte+=2;
142                                                 u+=String(byte);
143                                         }
144                                         return baseurl.replace('$quadkey',u);
145
146                                 default:
147                                         return baseurl.replace('$z',map.scale).replace('$x',tx).replace('$y',ty);
148                         }       
149                 }
150                 
151                 public function get url():String {
152                         return baseurl ? baseurl : '';
153                 }
154
155
156                 // Update offset
157                 
158                 public function nudgeHandler(event:MapEvent):void {
159                         if (!baseurl) { return; }
160                         this.x+=event.params.x; this.y+=event.params.y;
161                         offset_lat=map.centre_lat-map.coord2lat(map.lat2coord(map.centre_lat)-this.y);
162                         offset_lon=map.centre_lon-map.coord2lon(map.lon2coord(map.centre_lon)-this.x);
163                         update();
164                 }
165
166                 
167                 // ------------------------------------------------------------------
168                 // Co-ordinate conversion functions
169
170                 private function lon2tile(lon:Number):int {
171                         return (Math.floor((lon+180)/360*Math.pow(2,map.scale)));
172                 }
173                 private function lat2tile(lat:Number):int { 
174                         return (Math.floor((1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,map.scale)));
175                 }
176                 private function tile2lon(t:int):Number {
177                         return (t/Math.pow(2,map.scale)*360-180);
178                 }
179                 private function tile2lat(t:int):Number { 
180                         var n:Number=Math.PI-2*Math.PI*t/Math.pow(2,map.scale);
181                         return (180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n))));
182                 }
183
184         }
185 }