1 package net.systemeD.halcyon {
3 import flash.display.*;
6 import flash.system.LoaderContext;
7 import flash.utils.Timer;
8 import flash.filters.*;
9 import net.systemeD.halcyon.MapEvent;
11 public class TileSet extends Sprite {
13 public var tile_l:int;
14 public var tile_r:int;
15 public var tile_b:int;
16 public var tile_t:int;
18 private var offset_lon:Number=0;
19 private var offset_lat:Number=0;
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
30 private var sharpenFilter:BitmapFilter = new ConvolutionFilter(3, 3,
34 private var sharpening:Boolean = false;
35 // http://flylib.com/books/en/2.701.1.170/1/
37 public function TileSet(map:Map) {
40 map.addEventListener(MapEvent.NUDGE_BACKGROUND, nudgeHandler);
43 public function init(params:Object, update:Boolean=false, dim:Boolean=true, sharpen:Boolean=false):void {
47 scheme =params.scheme ? params.scheme : '900913';
49 offset_lon=offset_lat=x=y=0;
50 while (numChildren) { removeChildAt(0); }
52 if (update) { this.update(); }
55 private function createSprites():void {
56 for (var i:uint=map.MINSCALE; i<=map.MAXSCALE; i++) {
57 this.addChild(new Sprite());
61 public function setDimming(dim:Boolean):void {
64 public function getDimming():Boolean {
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;
78 public function getSharpen():Boolean {
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);
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);
90 // Update bounds - called on every move
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); }
105 // Mark that a tile needs to be loaded
107 public function addRequest(tx:int,ty:int):void {
108 tiles[map.scale+','+tx+','+ty]=true;
109 requests.push([map.scale,tx,ty]);
112 // Service tile queue - called on every frame to download new tiles
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;
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
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]; }
138 private function missingTileError(event:Event):void {
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)); });
152 protected function upFade(s:DisplayObject):void {
159 private function tileURL(tx:int,ty:int,tz:uint):String {
161 switch (scheme.toLowerCase()) {
165 for (var zoom:uint=tz; zoom>0; zoom--) {
167 var mask:uint=1<<(zoom-1);
168 if ((tx & mask)!=0) byte++;
169 if ((ty & mask)!=0) byte+=2;
172 t=baseurl.replace('$quadkey',u); break;
175 if (baseurl.indexOf('$x')>-1) {
176 t=baseurl.replace('$z',map.scale).replace('$x',tx).replace('$y',ty);
178 t=baseurl.replace('!',map.scale).replace('!',tx).replace('!',ty);
183 for each (var block:* in blocks) { if (t.match(block)) return ''; }
187 public function get url():String {
188 return baseurl ? baseurl : '';
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);
202 // ------------------------------------------------------------------
203 // Co-ordinate conversion functions
205 private function lon2tile(lon:Number):int {
206 return (Math.floor((lon+180)/360*Math.pow(2,map.scale)));
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)));
211 private function tile2lon(t:int):Number {
212 return (t/Math.pow(2,map.scale)*360-180);
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))));