Add support for TMS tile-numbering scheme
[potlatch2.git] / net / systemeD / halcyon / TileSet.as
old mode 100755 (executable)
new mode 100644 (file)
index 2b51bc8..b546955
@@ -2,11 +2,11 @@ package net.systemeD.halcyon {
 
        import flash.display.*;
        import flash.events.*;
+       import flash.filters.*;
        import flash.net.*;
-       
-       import net.systemeD.halcyon.ImageURLLoader;
        import flash.system.LoaderContext;
-       
+       import flash.utils.Timer;
+
     public class TileSet extends Sprite {
 
                public var tile_l:int;
@@ -14,29 +14,49 @@ package net.systemeD.halcyon {
                public var tile_b:int;
                public var tile_t:int;
 
-               public var xoffset:Number=0;
-               public var yoffset:Number=0;
+               private var offset_lon:Number=0;
+               private var offset_lat:Number=0;
 
                private var requests:Array=[];
-               private var tiles:Object={};            // key is "z,x,y"; value "true" (needed) or reference to sprite
+               private var tiles:Object={};            // key is "z,x,y"; value "true" if queued, or reference to loader object if requested
                private var waiting:int=0;                      // number of tiles currently being downloaded
+               private var loadcount:int=0;            // number of tiles fully downloaded
                private var baseurl:String;                     // e.g. http://npe.openstreetmap.org/$z/$x/$y.png
+               private var scheme:String;                      // 900913 or microsoft
+               public var blocks:Array;                        // array of regexes which are verboten
 
                private var map:Map;
+               private const MAXTILEREQUESTS:uint= 4;
+               private const MAXTILESLOADED:uint=30;
 
+               private var sharpenFilter:BitmapFilter = new ConvolutionFilter(3, 3, 
+                       [0, -1, 0,
+            -1, 5, -1,
+             0, -1, 0], 0);
+               private var sharpening:Boolean = false;
+               // http://flylib.com/books/en/2.701.1.170/1/
 
         public function TileSet(map:Map) {
                        this.map=map;
                        createSprites();
+                       map.addEventListener(MapEvent.NUDGE_BACKGROUND, nudgeHandler);
                }
        
-               public function init(url:String=null):void {
-                       baseurl=url;
+               /** @param params Currently includes "url" and "scheme"
+                * @param update Trigger update now?
+                * @param dim Start with imagery faded?
+                * @param sharpen Start with sharpen filter applied?
+                */
+               public function init(params:Object, update:Boolean=false, dim:Boolean=true, sharpen:Boolean=false):void {
+                       setDimming(dim);
+                       sharpening=sharpen;
+                       baseurl=params.url;
+                       scheme =params.scheme ? params.scheme : '900913';
                        tiles={};
-                       if (!url) { 
-                               while (this.numChildren) { this.removeChildAt(0); }
-                               createSprites();
-                       }
+                       offset_lon=offset_lat=x=y=0;
+                       while (numChildren) { removeChildAt(0); }
+                       createSprites();
+                       if (update) { this.update(); }
                }
 
                private function createSprites():void {
@@ -45,21 +65,49 @@ package net.systemeD.halcyon {
                        }
                }
 
+               /** Toggle fading of imagery. */
+               public function setDimming(dim:Boolean):void {
+                       alpha=dim ? 0.5 : 1;
+               }
+               /** Is imagery currently set faded? */
+               public function getDimming():Boolean {
+                       return (alpha<1);
+               }
+
+        /** Toggle sharpen filter. */
+               public function setSharpen(sharpen:Boolean):void {
+                       var f:Array=[]; if (sharpen) { f=[sharpenFilter]; }
+                       for (var i:uint=0; i<numChildren; i++) {
+                               var s:Sprite=Sprite(getChildAt(i));
+                               for (var j:uint=0; j<s.numChildren; j++) {
+                                       s.getChildAt(j).filters=f;
+                               }
+                       }
+                       sharpening=sharpen;
+               }
+               
+               /** Is sharpen filter applied? */
+               public function getSharpen():Boolean {
+                       return sharpening;
+               }
+
+               /** Set zoom scale (no update triggerd). */
                public function changeScale(scale:uint):void {
                        for (var i:uint=map.MINSCALE; i<=map.MAXSCALE; i++) {
                                this.getChildAt(i-map.MINSCALE).visible=(scale==i);
                        }
-                       // ** Should also double or halve xoffset/yoffset accordingly
+                       x=map.lon2coord(map.centre_lon+offset_lon)-map.lon2coord(map.centre_lon);
+                       y=map.lat2coord(map.centre_lat+offset_lat)-map.lat2coord(map.centre_lat);
                }
                        
-               // Update bounds - called on every move
+               /** Update bounds of tile area, and request new tiles if needed.  */
                
                public function update():void {
                        if (!baseurl) { return; }
-                       tile_l=lon2tile(map.edge_l+xoffset);
-                       tile_r=lon2tile(map.edge_r+xoffset);
-                       tile_t=lat2tile(map.edge_t+yoffset);
-                       tile_b=lat2tile(map.edge_b+yoffset);
+                       tile_l=lon2tile(map.edge_l-offset_lon);
+                       tile_r=lon2tile(map.edge_r-offset_lon);
+                       tile_t=lat2tile(map.edge_t-offset_lat);
+                       tile_b=lat2tile(map.edge_b-offset_lat);
                        for (var tx:int=tile_l; tx<=tile_r; tx++) {
                                for (var ty:int=tile_t; ty<=tile_b; ty++) {
                                        if (!tiles[map.scale+','+tx+','+ty]) { addRequest(tx,ty); }
@@ -67,55 +115,145 @@ package net.systemeD.halcyon {
                        }
                }
 
-               // Mark that a tile needs to be loaded
+               /** Mark that a tile needs to be loaded*/
                
                public function addRequest(tx:int,ty:int):void {
                        tiles[map.scale+','+tx+','+ty]=true;
                        requests.push([map.scale,tx,ty]);
                }
 
-               // Service tile queue - called on every frame to download new tiles
+               /** Service tile queue - called on every frame to download new tiles */
                
                public function serviceQueue():void {
-                       if (waiting==4 || requests.length==0) { return; }
+                       if (waiting==MAXTILEREQUESTS || requests.length==0) { return; } //SB
                        var r:Array, tx:int, ty:int, tz:int, l:DisplayObject;
 
-                       for (var i:uint=0; i<Math.min(requests.length, 4-waiting); i++) {
+                       for (var i:uint=0; i<Math.min(requests.length, MAXTILEREQUESTS-waiting); i++) {
                                r=requests.shift(); tz=r[0]; tx=r[1]; ty=r[2];
                                if (tx>=tile_l && tx<=tile_r && ty>=tile_t && ty<=tile_b) {
                                        // Tile is on-screen, so load
                                        waiting++;
                                        var loader:Loader = new Loader();
+                                       tiles[map.scale+','+tx+','+ty]=loader;
                                        loader.contentLoaderInfo.addEventListener(Event.INIT, doImgInit);
                        loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, missingTileError);
-                                       loader.load(new URLRequest(tileURL(tx,ty)), 
+                                       loader.load(new URLRequest(tileURL(tx,ty,tz)), 
                                                    new LoaderContext(true));
                                        l=this.getChildAt(map.scale-map.MINSCALE);
                                        Sprite(l).addChild(loader);
                                        loader.x=map.lon2coord(tile2lon(tx));
                                        loader.y=map.lat2coord(tile2lat(ty));
-                                       loader.alpha=0.5;
+                                       if (sharpening) { loader.filters=[sharpenFilter]; }
+                    /*
+                    var timer:Timer = new Timer(5000, 1);
+                    timer.addEventListener(TimerEvent.TIMER, function(){checkTileLoaded(map.scale,tx,ty);});
+                    timer.start();
+                                       */
+
+                               } else {
+                                       tiles[tz+','+tx+','+ty]=false; // Map has moved between the time we wanted this tile and now, so make 
+                                                                      //it available for a future request
                                }
                        }
                }
 
+/* We may need something like this in the future, not sure. Trouble is if a tile doesn't get loaded on first go,
+   it will never get loaded. 
+        private function checkTileLoaded(z,x,y) {
+            if (tiles[z+','+x+','+y]==true){
+               trace("Didn't even start getting tile: " + z+','+x+','+y);
+               requests.push([z,x,y]);
+               return; 
+            }  
+               var l:Loader = tiles[z+','+x+','+y];
+               if (l.alpha < 0.1) { 
+                       trace('Broken tile:' + z+','+x+','+y); 
+               }
+
+        }
+        */
         private function missingTileError(event:Event):void {
                        waiting--;
                        return;
                }
 
+               /** Tile image has been downloaded, so start displaying it. */
                protected function doImgInit(event:Event):void {
+                       event.target.loader.alpha=0;
+                       var t:Timer=new Timer(10,10);
+                       t.addEventListener(TimerEvent.TIMER,function():void { upFade(DisplayObject(event.target.loader)); });
+                       t.start();
                        waiting--;
+                       loadcount++;
+                       if (loadcount>MAXTILESLOADED) purgeTiles();
                        return;
                }
+               
+               protected function upFade(s:DisplayObject):void {
+                       s.alpha+=0.1;
+               }
+               
+               protected function purgeTiles():void {
+                       for (var tile:String in tiles) {
+                               if (tiles[tile] is Sprite) {
+                                       var coords:Array=tile.split(','); var tz:uint=coords[0]; var tx:uint=coords[1]; var ty:uint=coords[1];
+                                       if (tz!=map.scale || tx<tile_l || tx>tile_r || ty<tile_t || ty<tile_b) {
+                                               if (tiles[tile].parent) tiles[tile].parent.removeChild(tiles[tile]);
+                                               delete tiles[tile];
+                                               loadcount--;
+                                       }
+                               }
+                       }
+               }
 
                
                // Assemble tile URL
                
-               private function tileURL(tx:int,ty:int):String {
-                       return baseurl.replace('$z',map.scale).replace('$x',tx).replace('$y',ty);
+               private function tileURL(tx:int,ty:int,tz:uint):String {
+                       var t:String='';
+                       switch (scheme.toLowerCase()) {
+
+                               case 'microsoft':
+                                       var u:String='';
+                                       for (var zoom:uint=tz; zoom>0; zoom--) {
+                                               var byte:uint=0;
+                                               var mask:uint=1<<(zoom-1);
+                                               if ((tx & mask)!=0) byte++;
+                                               if ((ty & mask)!=0) byte+=2;
+                                               u+=String(byte);
+                                       }
+                                       t=baseurl.replace('$quadkey',u); break;
+
+                               case 'tms':
+                                       ty=Math.pow(2,tz)-1-ty;
+                                       t=baseurl.replace('$z',map.scale).replace('$x',tx).replace('$y',ty);
+                                       break;
+
+                               default:
+                                       if (baseurl.indexOf('$x')>-1) {
+                                               t=baseurl.replace('$z',map.scale).replace('$x',tx).replace('$y',ty);
+                                       } else {
+                                               t=baseurl.replace('!',map.scale).replace('!',tx).replace('!',ty);
+                                       }
+                                       break;
+
+                       }
+                       for each (var block:* in blocks) { if (t.match(block)) return ''; }
+                       return t;
+               }
+               
+               public function get url():String {
+                       return baseurl ? baseurl : '';
                }
 
+               /** Respond to nudge event by updating offset between imagery and map. */
+               public function nudgeHandler(event:MapEvent):void {
+                       if (!baseurl) { return; }
+                       this.x+=event.params.x; this.y+=event.params.y;
+                       offset_lat=map.centre_lat-map.coord2lat(map.lat2coord(map.centre_lat)-this.y);
+                       offset_lon=map.centre_lon-map.coord2lon(map.lon2coord(map.centre_lon)-this.x);
+                       update();
+               }
 
                
                // ------------------------------------------------------------------