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