1 package net.systemeD.halcyon {
3 import flash.display.*;
6 import flash.system.LoaderContext;
7 import flash.utils.Timer;
8 import net.systemeD.halcyon.MapEvent;
10 public class TileSet extends Sprite {
12 public var tile_l:int;
13 public var tile_r:int;
14 public var tile_b:int;
15 public var tile_t:int;
17 private var offset_lon:Number=0;
18 private var offset_lat:Number=0;
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
28 public function TileSet(map:Map) {
32 map.addEventListener(MapEvent.NUDGE_BACKGROUND, nudgeHandler);
35 public function init(url:String=null, update:Boolean=false):void {
38 offset_lon=offset_lat=x=y=0;
39 while (numChildren) { removeChildAt(0); }
41 if (update) { this.update(); }
44 private function createSprites():void {
45 for (var i:uint=map.MINSCALE; i<=map.MAXSCALE; i++) {
46 this.addChild(new Sprite());
50 public function setDimming(dim:Boolean):void {
54 public function changeScale(scale:uint):void {
55 for (var i:uint=map.MINSCALE; i<=map.MAXSCALE; i++) {
56 this.getChildAt(i-map.MINSCALE).visible=(scale==i);
58 x=map.lon2coord(map.centre_lon+offset_lon)-map.lon2coord(map.centre_lon);
59 y=map.lat2coord(map.centre_lat+offset_lat)-map.lat2coord(map.centre_lat);
62 // Update bounds - called on every move
64 public function update():void {
65 if (!baseurl) { return; }
66 tile_l=lon2tile(map.edge_l-offset_lon);
67 tile_r=lon2tile(map.edge_r-offset_lon);
68 tile_t=lat2tile(map.edge_t-offset_lat);
69 tile_b=lat2tile(map.edge_b-offset_lat);
70 for (var tx:int=tile_l; tx<=tile_r; tx++) {
71 for (var ty:int=tile_t; ty<=tile_b; ty++) {
72 if (!tiles[map.scale+','+tx+','+ty]) { addRequest(tx,ty); }
77 // Mark that a tile needs to be loaded
79 public function addRequest(tx:int,ty:int):void {
80 tiles[map.scale+','+tx+','+ty]=true;
81 requests.push([map.scale,tx,ty]);
84 // Service tile queue - called on every frame to download new tiles
86 public function serviceQueue():void {
87 if (waiting==4 || requests.length==0) { return; }
88 var r:Array, tx:int, ty:int, tz:int, l:DisplayObject;
90 for (var i:uint=0; i<Math.min(requests.length, 4-waiting); i++) {
91 r=requests.shift(); tz=r[0]; tx=r[1]; ty=r[2];
92 if (tx>=tile_l && tx<=tile_r && ty>=tile_t && ty<=tile_b) {
93 // Tile is on-screen, so load
95 var loader:Loader = new Loader();
96 loader.contentLoaderInfo.addEventListener(Event.INIT, doImgInit);
97 loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, missingTileError);
98 loader.load(new URLRequest(tileURL(tx,ty)),
99 new LoaderContext(true));
100 l=this.getChildAt(map.scale-map.MINSCALE);
101 Sprite(l).addChild(loader);
102 loader.x=map.lon2coord(tile2lon(tx));
103 loader.y=map.lat2coord(tile2lat(ty));
109 private function missingTileError(event:Event):void {
114 protected function doImgInit(event:Event):void {
115 event.target.loader.alpha=0;
116 var t:Timer=new Timer(10,10);
117 t.addEventListener(TimerEvent.TIMER,function():void { upFade(DisplayObject(event.target.loader)); });
123 protected function upFade(s:DisplayObject):void {
130 private function tileURL(tx:int,ty:int):String {
131 return baseurl.replace('$z',map.scale).replace('$x',tx).replace('$y',ty);
134 public function get url():String {
135 return baseurl ? baseurl : '';
141 public function nudgeHandler(event:MapEvent):void {
142 if (!baseurl) { return; }
143 this.x+=event.params.x; this.y+=event.params.y;
144 offset_lat=map.centre_lat-map.coord2lat(map.lat2coord(map.centre_lat)-this.y);
145 offset_lon=map.centre_lon-map.coord2lon(map.lon2coord(map.centre_lon)-this.x);
150 // ------------------------------------------------------------------
151 // Co-ordinate conversion functions
153 private function lon2tile(lon:Number):int {
154 return (Math.floor((lon+180)/360*Math.pow(2,map.scale)));
156 private function lat2tile(lat:Number):int {
157 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)));
159 private function tile2lon(t:int):Number {
160 return (t/Math.pow(2,map.scale)*360-180);
162 private function tile2lat(t:int):Number {
163 var n:Number=Math.PI-2*Math.PI*t/Math.pow(2,map.scale);
164 return (180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n))));