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