PageRenderTime 66ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/com/modestmaps/core/TileGrid.as

https://github.com/jenrios/Elastic-Lists
ActionScript | 1390 lines | 940 code | 234 blank | 216 comment | 133 complexity | f2d5f55fbd30e9bb925d65c19527c171 MD5 | raw file
  1. package com.modestmaps.core
  2. {
  3. import com.modestmaps.core.painter.ITilePainter;
  4. import com.modestmaps.core.painter.ITilePainterOverride;
  5. import com.modestmaps.core.painter.TilePainter;
  6. import com.modestmaps.events.MapEvent;
  7. import com.modestmaps.mapproviders.IMapProvider;
  8. import flash.display.DisplayObject;
  9. import flash.display.Sprite;
  10. import flash.events.Event;
  11. import flash.events.MouseEvent;
  12. import flash.events.ProgressEvent;
  13. import flash.geom.Matrix;
  14. import flash.geom.Point;
  15. import flash.geom.Rectangle;
  16. import flash.text.TextField;
  17. import flash.utils.getTimer;
  18. public class TileGrid extends Sprite
  19. {
  20. // OPTIONS
  21. ///////////////////////////////
  22. // TODO: split these out into a TileGridOptions class and allow mass setting/getting?
  23. protected static const DEFAULT_MAX_PARENT_SEARCH:int = 5;
  24. protected static const DEFAULT_MAX_PARENT_LOAD:int = 0; // enable this to load lower zoom tiles first
  25. protected static const DEFAULT_MAX_CHILD_SEARCH:int = 1;
  26. protected static const DEFAULT_MAX_TILES_TO_KEEP:int = 256; // 256*256*4bytes = 0.25MB ... so 256 tiles is 64MB of memory, minimum!
  27. protected static const DEFAULT_TILE_BUFFER:int = 0;
  28. protected static const DEFAULT_ENFORCE_BOUNDS:Boolean = true;
  29. protected static const DEFAULT_ROUND_POSITIONS:Boolean = true;
  30. protected static const DEFAULT_ROUND_SCALES:Boolean = true;
  31. /** if we don't have a tile at currentZoom, onRender will look for tiles up to 5 levels out.
  32. * set this to 0 if you only want the current zoom level's tiles
  33. * WARNING: tiles will get scaled up A LOT for this, but maybe it beats blank tiles? */
  34. public var maxParentSearch:int = DEFAULT_MAX_PARENT_SEARCH;
  35. /** if we don't have a tile at currentZoom, onRender will look for tiles up to one levels further in.
  36. * set this to 0 if you only want the current zoom level's tiles
  37. * WARNING: bad, bad nasty recursion possibilities really soon if you go much above 1
  38. * - it works, but you probably don't want to change this number :) */
  39. public var maxChildSearch:int = DEFAULT_MAX_CHILD_SEARCH;
  40. /** if maxParentSearch is enabled, setting maxParentLoad to between 1 and maxParentSearch
  41. * will make requests for lower zoom levels first */
  42. public var maxParentLoad:int = DEFAULT_MAX_PARENT_LOAD;
  43. /** this is the maximum size of tileCache (visible tiles will also be kept in the cache) */
  44. public var maxTilesToKeep:int = DEFAULT_MAX_TILES_TO_KEEP;
  45. // 0 or 1, really: 2 will load *lots* of extra tiles
  46. public var tileBuffer:int = DEFAULT_TILE_BUFFER;
  47. /** set this to true to enable enforcing of map bounds from the map provider's limits */
  48. public var enforceBoundsEnabled:Boolean = DEFAULT_ENFORCE_BOUNDS;
  49. /** set this to false, along with roundScalesEnabled, if you need a map to stay 'fixed' in place as it changes size */
  50. public var roundPositionsEnabled:Boolean = DEFAULT_ROUND_POSITIONS;
  51. /** set this to false, along with roundPositionsEnabled, if you need a map to stay 'fixed' in place as it changes size */
  52. public var roundScalesEnabled:Boolean = DEFAULT_ROUND_SCALES;
  53. ///////////////////////////////
  54. // END OPTIONS
  55. // TILE_WIDTH and TILE_HEIGHT are now tileWidth and tileHeight
  56. // this was needed for the NASA DailyPlanetProvider which has 512x512px tiles
  57. // public static const TILE_WIDTH:Number = 256;
  58. // public static const TILE_HEIGHT:Number = 256;
  59. // read-only, kept up to date by calculateBounds()
  60. protected var _minZoom:Number;
  61. protected var _maxZoom:Number;
  62. protected var minTx:Number, maxTx:Number, minTy:Number, maxTy:Number;
  63. // read-only, convenience for tileWidth/Height
  64. protected var _tileWidth:Number;
  65. protected var _tileHeight:Number;
  66. // pan and zoom etc are stored in here
  67. // NB: this matrix is never applied to a DisplayObject's transform
  68. // because it would require scaling tile positions to compensate.
  69. // Instead, we adapt its values such that the current zoom level
  70. // is approximately scale 1, and positions make sense in screen pixels
  71. protected var worldMatrix:Matrix;
  72. // this turns screen points into coordinates
  73. protected var _invertedMatrix:Matrix; // use lazy getter for this
  74. // the corners and center of the screen, in map coordinates
  75. // (these also have lazy getters)
  76. protected var _topLeftCoordinate:Coordinate;
  77. protected var _bottomRightCoordinate:Coordinate;
  78. protected var _topRightCoordinate:Coordinate;
  79. protected var _bottomLeftCoordinate:Coordinate;
  80. protected var _centerCoordinate:Coordinate;
  81. // where the tiles live:
  82. protected var well:Sprite;
  83. //protected var provider:IMapProvider;
  84. protected var tilePainter:ITilePainter;
  85. // coordinate bounds derived from IMapProviders
  86. protected var limits:Array;
  87. // keys we've recently seen
  88. protected var recentlySeen:Array = [];
  89. // currently visible tiles
  90. protected var visibleTiles:Array = [];
  91. // number of tiles we're failing to show
  92. protected var blankCount:int = 0;
  93. // a textfield with lots of stats
  94. public var debugField:DebugField;
  95. // what zoom level of tiles is 'correct'?
  96. protected var _currentTileZoom:int;
  97. // so we know if we're going in or out
  98. protected var previousTileZoom:int;
  99. // for sorting the queue:
  100. protected var centerRow:Number;
  101. protected var centerColumn:Number;
  102. // for pan events
  103. protected var startPan:Coordinate;
  104. public var panning:Boolean;
  105. // previous mouse position when dragging
  106. protected var pmouse:Point;
  107. // for zoom events
  108. protected var startZoom:Number = -1;
  109. public var zooming:Boolean;
  110. protected var mapWidth:Number;
  111. protected var mapHeight:Number;
  112. protected var draggable:Boolean;
  113. // setting this.dirty = true will request an Event.RENDER
  114. protected var _dirty:Boolean;
  115. // setting to true will dispatch a CHANGE event which Map will convert to an EXTENT_CHANGED for us
  116. protected var matrixChanged:Boolean = false;
  117. public function TileGrid(w:Number, h:Number, draggable:Boolean, provider:IMapProvider)
  118. {
  119. doubleClickEnabled = true;
  120. //this.map = map;
  121. this.draggable = draggable;
  122. // don't call set map provider here, because it triggers a redraw and we're not ready for that
  123. //this.provider = provider;
  124. if (provider is ITilePainterOverride) {
  125. this.tilePainter = ITilePainterOverride(provider).getTilePainter();
  126. }
  127. else {
  128. this.tilePainter = new TilePainter(this, provider, maxParentLoad == 0 ? centerDistanceCompare : zoomThenCenterCompare);
  129. }
  130. tilePainter.addEventListener(ProgressEvent.PROGRESS, onProgress, false, 0, true);
  131. tilePainter.addEventListener(MapEvent.ALL_TILES_LOADED, onAllTilesLoaded, false, 0, true);
  132. tilePainter.addEventListener(MapEvent.BEGIN_TILE_LOADING, onBeginTileLoading, false, 0, true);
  133. this.limits = provider.outerLimits();
  134. // but do grab tile dimensions:
  135. _tileWidth = provider.tileWidth;
  136. _tileHeight = provider.tileHeight;
  137. // and calculate bounds from provider
  138. calculateBounds();
  139. this.mapWidth = w;
  140. this.mapHeight = h;
  141. scrollRect = new Rectangle(0, 0, mapWidth, mapHeight);
  142. debugField = new DebugField();
  143. debugField.x = mapWidth - debugField.width - 15;
  144. debugField.y = mapHeight - debugField.height - 15;
  145. well = new Sprite();
  146. well.name = 'well';
  147. well.doubleClickEnabled = true;
  148. well.mouseEnabled = true;
  149. well.mouseChildren = false;
  150. addChild(well);
  151. worldMatrix = new Matrix();
  152. addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
  153. }
  154. /**
  155. * Get the Tile instance that corresponds to a given coordinate.
  156. */
  157. public function getCoordTile(coord:Coordinate):Tile
  158. {
  159. // these get floored when they're cast as ints in tileKey()
  160. var key:String = tileKey(coord.column, coord.row, coord.zoom);
  161. return well.getChildByName(key) as Tile;
  162. }
  163. private function onAddedToStage(event:Event):void
  164. {
  165. if (draggable) {
  166. addEventListener(MouseEvent.MOUSE_DOWN, mousePressed, true);
  167. }
  168. addEventListener(Event.RENDER, onRender);
  169. addEventListener(Event.ENTER_FRAME, onEnterFrame);
  170. addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
  171. removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
  172. dirty = true;
  173. // force an on-render in case we were added in a render handler
  174. onRender();
  175. }
  176. private function onRemovedFromStage(event:Event):void
  177. {
  178. if (hasEventListener(MouseEvent.MOUSE_DOWN)) {
  179. removeEventListener(MouseEvent.MOUSE_DOWN, mousePressed, true);
  180. }
  181. removeEventListener(Event.RENDER, onRender);
  182. removeEventListener(Event.ENTER_FRAME, onEnterFrame);
  183. // FIXME: should we still do this, in TilePainter?
  184. //queueTimer.stop();
  185. removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
  186. addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
  187. }
  188. /** The classes themselves serve as factories!
  189. *
  190. * @param tileClass e.g. Tile, TweenTile, etc.
  191. *
  192. * @see http://norvig.com/design-patterns/img013.gif
  193. */
  194. public function setTileClass(tileClass:Class):void
  195. {
  196. // first get rid of everything, which passes tiles back to the pool
  197. clearEverything();
  198. // then assign the new class, which creates a new pool array
  199. tilePainter.setTileClass(tileClass);
  200. }
  201. /** processes the tileQueue and optionally outputs stats into debugField */
  202. protected function onEnterFrame(event:Event=null):void
  203. {
  204. if (debugField.parent) {
  205. debugField.update(this, blankCount, recentlySeen.length, tilePainter);
  206. debugField.x = mapWidth - debugField.width - 15;
  207. debugField.y = mapHeight - debugField.height - 15;
  208. }
  209. }
  210. protected function onRendered():void
  211. {
  212. // listen out for this if you want to be sure map is in its final state before reprojecting markers etc.
  213. dispatchEvent(new MapEvent(MapEvent.RENDERED));
  214. }
  215. protected function onPanned():void
  216. {
  217. var pt:Point = coordinatePoint(startPan);
  218. dispatchEvent(new MapEvent(MapEvent.PANNED, pt.subtract(new Point(mapWidth/2, mapHeight/2))));
  219. }
  220. protected function onZoomed():void
  221. {
  222. var zoomEvent:MapEvent = new MapEvent(MapEvent.ZOOMED_BY, zoomLevel-startZoom);
  223. // this might also be useful
  224. zoomEvent.zoomLevel = zoomLevel;
  225. dispatchEvent(zoomEvent);
  226. }
  227. protected function onChanged():void
  228. {
  229. // doesn't bubble, unlike MapEvent
  230. // Map will pick this up and dispatch MapEvent.EXTENT_CHANGED for us
  231. dispatchEvent(new Event(Event.CHANGE, false, false));
  232. }
  233. protected function onBeginTileLoading(event:MapEvent):void
  234. {
  235. dispatchEvent(event);
  236. }
  237. protected function onProgress(event:ProgressEvent):void
  238. {
  239. // dispatch tile load progress
  240. dispatchEvent(event);
  241. }
  242. protected function onAllTilesLoaded(event:MapEvent):void
  243. {
  244. dispatchEvent(event);
  245. // request redraw to take parent and child tiles off the stage if we haven't already
  246. dirty = true;
  247. }
  248. /**
  249. * figures out from worldMatrix which tiles we should be showing, adds them to the stage, adds them to the tileQueue if needed, etc.
  250. *
  251. * from my recent testing, TileGrid.onRender takes < 5ms most of the time, and rarely >10ms
  252. * (Flash Player 9, Firefox, Macbook Pro)
  253. *
  254. */
  255. protected function onRender(event:Event=null):void
  256. {
  257. var t:Number = getTimer();
  258. if (!dirty || !stage) {
  259. //trace(getTimer() - t, "ms in", provider);
  260. onRendered();
  261. return;
  262. }
  263. var boundsEnforced:Boolean = enforceBounds();
  264. if (zooming || panning) {
  265. if (panning) {
  266. onPanned();
  267. }
  268. if (zooming) {
  269. onZoomed();
  270. }
  271. }
  272. else if (boundsEnforced) {
  273. onChanged();
  274. }
  275. else if (matrixChanged) {
  276. matrixChanged = false;
  277. onChanged();
  278. }
  279. // what zoom level of tiles should we be loading, taking into account min/max zoom?
  280. // (0 when scale == 1, 1 when scale == 2, 2 when scale == 4, etc.)
  281. var newZoom:int = Math.min(maxZoom, Math.max(minZoom, Math.round(zoomLevel)));
  282. // see if the newZoom is different to currentZoom
  283. // so we know which way we're zooming, if any:
  284. if (currentTileZoom != newZoom) {
  285. previousTileZoom = currentTileZoom;
  286. }
  287. // this is the level of tiles we'll be loading:
  288. _currentTileZoom = newZoom;
  289. // find start and end columns for the visible tiles, at current tile zoom
  290. // we project all four corners to take account of potential rotation in worldMatrix
  291. var tlC:Coordinate = topLeftCoordinate.zoomTo(currentTileZoom);
  292. var brC:Coordinate = bottomRightCoordinate.zoomTo(currentTileZoom);
  293. var trC:Coordinate = topRightCoordinate.zoomTo(currentTileZoom);
  294. var blC:Coordinate = bottomLeftCoordinate.zoomTo(currentTileZoom);
  295. // optionally pad it out a little bit more with a tile buffer
  296. // TODO: investigate giving a directional bias to TILE_BUFFER when panning quickly
  297. // NB:- I'm pretty sure these calculations are accurate enough that using
  298. // Math.ceil for the maxCols will load one column too many -- Tom
  299. var minCol:int = Math.floor(Math.min(tlC.column,brC.column,trC.column,blC.column)) - tileBuffer;
  300. var maxCol:int = Math.floor(Math.max(tlC.column,brC.column,trC.column,blC.column)) + tileBuffer;
  301. var minRow:int = Math.floor(Math.min(tlC.row,brC.row,trC.row,blC.row)) - tileBuffer;
  302. var maxRow:int = Math.floor(Math.max(tlC.row,brC.row,trC.row,blC.row)) + tileBuffer;
  303. // loop over all tiles and find parent or child tiles from cache to compensate for unloaded tiles:
  304. repopulateVisibleTiles(minCol, maxCol, minRow, maxRow);
  305. // move visible tiles to the end of recentlySeen if we're done loading them
  306. // the 'least recently seen' tiles will be removed from the tileCache below
  307. for each (var visibleTile:Tile in visibleTiles) {
  308. if (tilePainter.isPainted(visibleTile)) {
  309. var ri:int = recentlySeen.indexOf(visibleTile.name);
  310. if (ri >= 0) {
  311. recentlySeen.splice(ri, 1);
  312. }
  313. recentlySeen.push(visibleTile.name);
  314. }
  315. }
  316. // prune tiles from the well if they shouldn't be there (not currently in visibleTiles)
  317. // TODO: unless they're fading in or out?
  318. // (loop backwards so removal doesn't change i)
  319. for (var i:int = well.numChildren-1; i >= 0; i--) {
  320. var wellTile:Tile = well.getChildAt(i) as Tile;
  321. if (visibleTiles.indexOf(wellTile) < 0) {
  322. well.removeChild(wellTile);
  323. wellTile.hide();
  324. tilePainter.cancelPainting(wellTile);
  325. }
  326. }
  327. // position tiles such that currentZoom is approximately scale 1
  328. // and x and y make sense in pixels relative to tlC.column and tlC.row (topleft)
  329. positionTiles(tlC.column, tlC.row);
  330. // all the visible tiles will be at the end of recentlySeen
  331. // let's make sure we keep them around:
  332. var maxRecentlySeen:int = Math.max(visibleTiles.length, maxTilesToKeep);
  333. // prune cache of already seen tiles if it's getting too big:
  334. if (recentlySeen.length > maxRecentlySeen) {
  335. // can we sort so that biggest zoom levels get removed first, without removing currently visible tiles?
  336. /* var visibleKeys:Array = recentlySeen.slice(recentlySeen.length - visibleTiles.length, recentlySeen.length);
  337. // take a look at everything else
  338. recentlySeen = recentlySeen.slice(0, recentlySeen.length - visibleTiles.length);
  339. recentlySeen = recentlySeen.sort(Array.DESCENDING);
  340. recentlySeen = recentlySeen.concat(visibleKeys); */
  341. // throw away keys at the beginning of recentlySeen
  342. recentlySeen = recentlySeen.slice(recentlySeen.length - maxRecentlySeen, recentlySeen.length);
  343. // loop over our internal tile cache
  344. // and throw out tiles not in recentlySeen
  345. tilePainter.retainKeysInCache(recentlySeen);
  346. }
  347. // update centerRow and centerCol for sorting the tileQueue in processQueue()
  348. var center:Coordinate = centerCoordinate.zoomTo(currentTileZoom);
  349. centerRow = center.row;
  350. centerColumn = center.column;
  351. onRendered();
  352. dirty = false;
  353. //trace(getTimer() - t, "ms in", provider);
  354. }
  355. /**
  356. * loops over given cols and rows and adds tiles to visibleTiles array and the well
  357. * using child or parent tiles to compensate for tiles not yet available in the tileCache
  358. */
  359. private function repopulateVisibleTiles(minCol:int, maxCol:int, minRow:int, maxRow:int):void
  360. {
  361. visibleTiles = [];
  362. blankCount = 0; // keep count of how many tiles we missed?
  363. // for use in loops etc.
  364. var coord:Coordinate = new Coordinate(0,0,0);
  365. var searchedParentKeys:Object = {};
  366. // loop over currently visible tiles
  367. for (var col:int = minCol; col <= maxCol; col++) {
  368. for (var row:int = minRow; row <= maxRow; row++) {
  369. // create a string key for this tile
  370. var key:String = tileKey(col, row, currentTileZoom);
  371. // see if we already have this tile
  372. var tile:Tile = well.getChildByName(key) as Tile;
  373. // create it if not, and add it to the load queue
  374. if (!tile) {
  375. tile = tilePainter.getTileFromCache(key);
  376. if (!tile) {
  377. coord.row = row;
  378. coord.column = col;
  379. coord.zoom = currentTileZoom;
  380. tile = tilePainter.createAndPopulateTile(coord, key);
  381. }
  382. else {
  383. tile.show();
  384. }
  385. well.addChild(tile);
  386. }
  387. visibleTiles.push(tile);
  388. var tileReady:Boolean = tile.isShowing() && !tilePainter.isPainting(tile);
  389. //
  390. // if the tile isn't ready yet, we're going to reuse a parent tile
  391. // if there isn't a parent tile, and we're zooming out, we'll reuse child tiles
  392. // if we don't get all 4 child tiles, we'll look at more parent levels
  393. //
  394. // yes, this is quite involved, but it should be fast enough because most of the loops
  395. // don't get hit most of the time
  396. //
  397. if (!tileReady) {
  398. var foundParent:Boolean = false;
  399. var foundChildren:int = 0;
  400. if (currentTileZoom > previousTileZoom) {
  401. // if it still doesn't have enough images yet, or it's fading in, try a double size parent instead
  402. if (maxParentSearch > 0 && currentTileZoom > minZoom) {
  403. var firstParentKey:String = parentKey(col, row, currentTileZoom, currentTileZoom-1);
  404. if (!searchedParentKeys[firstParentKey]) {
  405. searchedParentKeys[firstParentKey] = true;
  406. if (ensureVisible(firstParentKey)) {
  407. foundParent = true;
  408. }
  409. if (!foundParent && (currentTileZoom - 1 < maxParentLoad)) {
  410. //trace("requesting parent tile at zoom", pzoom);
  411. var firstParentCoord:Array = parentCoord(col, row, currentTileZoom, currentTileZoom-1);
  412. visibleTiles.push(requestLoad(firstParentCoord[0], firstParentCoord[1], currentTileZoom-1));
  413. }
  414. }
  415. }
  416. }
  417. else {
  418. // currentZoom <= previousZoom, so we're zooming out
  419. // and therefore we might want to reuse 'smaller' tiles
  420. // if it doesn't have an image yet, see if we can make it from smaller images
  421. if (!foundParent && maxChildSearch > 0 && currentTileZoom < maxZoom) {
  422. for (var czoom:int = currentTileZoom+1; czoom <= Math.min(maxZoom, currentTileZoom+maxChildSearch); czoom++) {
  423. var ckeys:Array = childKeys(col, row, currentTileZoom, czoom);
  424. for each (var ckey:String in ckeys) {
  425. if (ensureVisible(ckey)) {
  426. foundChildren++;
  427. }
  428. } // ckeys
  429. if (foundChildren == ckeys.length) {
  430. break;
  431. }
  432. } // czoom
  433. }
  434. }
  435. var stillNeedsAnImage:Boolean = !foundParent && foundChildren < 4;
  436. // if it still doesn't have an image yet, try more parent zooms
  437. if (stillNeedsAnImage && maxParentSearch > 1 && currentTileZoom > minZoom) {
  438. var startZoomSearch:int = currentTileZoom - 1;
  439. if (currentTileZoom > previousTileZoom) {
  440. // we already looked for parent level 1, and didn't find it, so:
  441. startZoomSearch -= 1;
  442. }
  443. var endZoomSearch:int = Math.max(minZoom, currentTileZoom-maxParentSearch);
  444. for (var pzoom:int = startZoomSearch; pzoom >= endZoomSearch; pzoom--) {
  445. var pkey:String = parentKey(col, row, currentTileZoom, pzoom);
  446. if (!searchedParentKeys[pkey]) {
  447. searchedParentKeys[pkey] = true;
  448. if (ensureVisible(pkey)) {
  449. stillNeedsAnImage = false;
  450. break;
  451. }
  452. if (currentTileZoom - pzoom < maxParentLoad) {
  453. //trace("requesting parent tile at zoom", pzoom);
  454. var pcoord:Array = parentCoord(col, row, currentTileZoom, pzoom);
  455. visibleTiles.push(requestLoad(pcoord[0], pcoord[1], pzoom));
  456. }
  457. }
  458. else {
  459. break;
  460. }
  461. }
  462. }
  463. if (stillNeedsAnImage) {
  464. blankCount++;
  465. }
  466. } // if !tileReady
  467. } // for row
  468. } // for col
  469. // trace("zoomLevel", zoomLevel, "currentTileZoom", currentTileZoom, "blankCount", blankCount);
  470. } // repopulateVisibleTiles
  471. // TODO: do this with events instead?
  472. public function tilePainted(tile:Tile):void
  473. {
  474. if (currentTileZoom-tile.zoom <= maxParentLoad) {
  475. tile.show();
  476. }
  477. else {
  478. tile.showNow();
  479. }
  480. }
  481. /**
  482. * returns an array of all the tiles that are on the screen
  483. * (including parent and child tiles currently visible until
  484. * the current zoom level finishes loading)
  485. * */
  486. public function getVisibleTiles():Array
  487. {
  488. return visibleTiles;
  489. }
  490. private function positionTiles(realMinCol:Number, realMinRow:Number):void
  491. {
  492. // sort children by difference from current zoom level
  493. // this means current is on top, +1 and -1 are next, then +2 and -2, etc.
  494. visibleTiles.sort(distanceFromCurrentZoomCompare, Array.DESCENDING);
  495. // for positioning tile according to current transform, based on current tile zoom
  496. var scaleFactors:Array = new Array(maxZoom+1);
  497. // scales to compensate for zoom differences between current grid zoom level
  498. var tileScales:Array = new Array(maxZoom+1);
  499. for (var z:int = 0; z <= maxZoom; z++) {
  500. scaleFactors[z] = Math.pow(2.0, currentTileZoom-z)
  501. // round up to the nearest pixel to avoid seams between zoom levels
  502. if (roundScalesEnabled) {
  503. tileScales[z] = Math.ceil(Math.pow(2, zoomLevel-z) * tileWidth) / tileWidth;
  504. }
  505. else {
  506. tileScales[z] = Math.pow(2, zoomLevel-z);
  507. }
  508. }
  509. // hugs http://www.senocular.com/flash/tutorials/transformmatrix/
  510. var px:Point = worldMatrix.deltaTransformPoint(new Point(0, 1));
  511. var tileAngleDegrees:Number = ((180/Math.PI) * Math.atan2(px.y, px.x) - 90);
  512. // apply the sorted depths, position all the tiles and also keep recentlySeen updated:
  513. for each (var tile:Tile in visibleTiles) {
  514. // if we set them all to numChildren-1, descending, they should end up correctly sorted
  515. well.setChildIndex(tile, well.numChildren-1);
  516. tile.scaleX = tile.scaleY = tileScales[tile.zoom];
  517. var pt:Point = coordinatePoint(new Coordinate(tile.row, tile.column, tile.zoom));
  518. tile.x = pt.x;
  519. tile.y = pt.y;
  520. tile.rotation = tileAngleDegrees;
  521. }
  522. }
  523. private function zoomThenCenterCompare(t1:Tile, t2:Tile):int
  524. {
  525. if (t1.zoom == t2.zoom) {
  526. return centerDistanceCompare(t1, t2);
  527. }
  528. return t1.zoom < t2.zoom ? -1 : t1.zoom > t2.zoom ? 1 : 0;
  529. }
  530. // for sorting arrays of tiles by distance from center Coordinate
  531. private function centerDistanceCompare(t1:Tile, t2:Tile):int
  532. {
  533. if (t1.zoom == t2.zoom && t1.zoom == currentTileZoom && t2.zoom == currentTileZoom) {
  534. var d1:int = Math.pow(t1.row+0.5-centerRow,2) + Math.pow(t1.column+0.5-centerColumn,2);
  535. var d2:int = Math.pow(t2.row+0.5-centerRow,2) + Math.pow(t2.column+0.5-centerColumn,2);
  536. return d1 < d2 ? -1 : d1 > d2 ? 1 : 0;
  537. }
  538. return Math.abs(t1.zoom-currentTileZoom) < Math.abs(t2.zoom-currentTileZoom) ? -1 : 1;
  539. }
  540. // for sorting arrays of tiles by distance from currentZoom
  541. private function distanceFromCurrentZoomCompare(t1:Tile, t2:Tile):int
  542. {
  543. var d1:int = Math.abs(t1.zoom-currentTileZoom);
  544. var d2:int = Math.abs(t2.zoom-currentTileZoom);
  545. return d1 < d2 ? -1 : d1 > d2 ? 1 : zoomCompare(t2, t1); // t2, t1 so that big tiles are on top of small
  546. }
  547. // for when tiles have same difference in zoom in distanceFromCurrentZoomCompare
  548. private static function zoomCompare(t1:Tile, t2:Tile):int
  549. {
  550. return t1.zoom == t2.zoom ? 0 : t1.zoom > t2.zoom ? 1 : -1;
  551. }
  552. // makes sure that if a tile with the given key exists in the cache
  553. // that it is added to the well and added to visibleTiles
  554. // returns null if tile does not exist in cache
  555. private function ensureVisible(key:String):Tile
  556. {
  557. var tile:Tile = tilePainter.getTileFromCache(key);
  558. if (tile) {
  559. if (!well.contains(tile)) {
  560. well.addChildAt(tile,0);
  561. tilePainted(tile);
  562. }
  563. if (visibleTiles.indexOf(tile) < 0) {
  564. visibleTiles.push(tile); // don't get rid of it yet!
  565. }
  566. return tile;
  567. }
  568. return null;
  569. }
  570. // for use in requestLoad
  571. private var tempCoord:Coordinate = new Coordinate(0,0,0);
  572. /** create a tile and add it to the queue - WARNING: this is buggy for the current zoom level, it's only used for parent zooms when maxParentLoad is > 0 */
  573. private function requestLoad(col:int, row:int, zoom:int):Tile
  574. {
  575. var key:String = tileKey(col, row, zoom);
  576. var tile:Tile = well.getChildByName(key) as Tile;
  577. if (!tile) {
  578. tempCoord.row = row;
  579. tempCoord.column = col;
  580. tempCoord.zoom = zoom;
  581. tile = tilePainter.createAndPopulateTile(tempCoord, key);
  582. well.addChild(tile);
  583. }
  584. return tile;
  585. }
  586. private static const zoomLetter:Array = "abcdefghijklmnopqrstuvwxyz".split('');
  587. /** zoom is translated into a letter so that keys can easily be sorted (alphanumerically) by zoom level */
  588. private function tileKey(col:int, row:int, zoom:int):String
  589. {
  590. return zoomLetter[zoom]+":"+col+":"+row;
  591. }
  592. // TODO: check that this does the right thing with negative row/col?
  593. private function parentKey(col:int, row:int, zoom:int, parentZoom:int):String
  594. {
  595. var scaleFactor:Number = Math.pow(2.0, zoom-parentZoom);
  596. var pcol:int = Math.floor(Number(col) / scaleFactor);
  597. var prow:int = Math.floor(Number(row) / scaleFactor);
  598. return tileKey(pcol,prow,parentZoom);
  599. }
  600. // used when maxParentLoad is > 0
  601. // TODO: check that this does the right thing with negative row/col?
  602. private function parentCoord(col:int, row:int, zoom:int, parentZoom:int):Array
  603. {
  604. var scaleFactor:Number = Math.pow(2.0, zoom-parentZoom);
  605. var pcol:int = Math.floor(Number(col) / scaleFactor);
  606. var prow:int = Math.floor(Number(row) / scaleFactor);
  607. return [ pcol, prow ];
  608. }
  609. // TODO: check that this does the right thing with negative row/col?
  610. private function childKeys(col:int, row:int, zoom:int, childZoom:int):Array
  611. {
  612. var scaleFactor:Number = Math.pow(2, zoom-childZoom); // one zoom in = 0.5
  613. var rowColSpan:int = Math.pow(2, childZoom-zoom); // one zoom in = 2, two = 4
  614. var keys:Array = [];
  615. for (var ccol:int = col/scaleFactor; ccol < (col/scaleFactor)+rowColSpan; ccol++) {
  616. for (var crow:int = row/scaleFactor; crow < (row/scaleFactor)+rowColSpan; crow++) {
  617. keys.push(tileKey(ccol, crow, childZoom));
  618. }
  619. }
  620. return keys;
  621. }
  622. public function mousePressed(event:MouseEvent):void
  623. {
  624. prepareForPanning(true);
  625. pmouse = new Point(event.stageX, event.stageY);
  626. stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseDragged);
  627. stage.addEventListener(MouseEvent.MOUSE_UP, mouseReleased);
  628. stage.addEventListener(Event.MOUSE_LEAVE, mouseReleased);
  629. }
  630. public function mouseReleased(event:Event):void
  631. {
  632. stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseDragged);
  633. stage.removeEventListener(MouseEvent.MOUSE_UP, mouseReleased);
  634. stage.removeEventListener(Event.MOUSE_LEAVE, mouseReleased);
  635. donePanning();
  636. dirty = true;
  637. if (event is MouseEvent) {
  638. MouseEvent(event).updateAfterEvent();
  639. }
  640. else if (event.type == Event.MOUSE_LEAVE) {
  641. onRender();
  642. }
  643. }
  644. public function mouseDragged(event:MouseEvent):void
  645. {
  646. var mousePoint:Point = new Point(event.stageX, event.stageY);
  647. tx += mousePoint.x - pmouse.x;
  648. ty += mousePoint.y - pmouse.y;
  649. pmouse = mousePoint;
  650. dirty = true;
  651. event.updateAfterEvent();
  652. }
  653. // today is all about lazy evaluation
  654. // this gets set to null by 'dirty = true'
  655. // and only calculated again if you need it
  656. protected function get invertedMatrix():Matrix
  657. {
  658. if (!_invertedMatrix) {
  659. _invertedMatrix = worldMatrix.clone();
  660. _invertedMatrix.invert();
  661. _invertedMatrix.scale(scale/tileWidth, scale/tileHeight);
  662. }
  663. return _invertedMatrix;
  664. }
  665. /** derived from map provider by calculateBounds(), read-only here for convenience */
  666. public function get minZoom():Number
  667. {
  668. return _minZoom;
  669. }
  670. /** derived from map provider by calculateBounds(), read-only here for convenience */
  671. public function get maxZoom():Number
  672. {
  673. return _maxZoom;
  674. }
  675. /** convenience method for tileWidth */
  676. public function get tileWidth():Number
  677. {
  678. return _tileWidth;
  679. }
  680. /** convenience method for tileHeight */
  681. public function get tileHeight():Number
  682. {
  683. return _tileHeight;
  684. }
  685. /** read-only, this is the level of tiles we'll be loading first */
  686. public function get currentTileZoom():Number
  687. {
  688. return _currentTileZoom;
  689. }
  690. public function get topLeftCoordinate():Coordinate
  691. {
  692. if (!_topLeftCoordinate) {
  693. var tl:Point = invertedMatrix.transformPoint(new Point());
  694. _topLeftCoordinate = new Coordinate(tl.y, tl.x, zoomLevel);
  695. }
  696. return _topLeftCoordinate;
  697. }
  698. public function get bottomRightCoordinate():Coordinate
  699. {
  700. if (!_bottomRightCoordinate) {
  701. var br:Point = invertedMatrix.transformPoint(new Point(mapWidth, mapHeight));
  702. _bottomRightCoordinate = new Coordinate(br.y, br.x, zoomLevel);
  703. }
  704. return _bottomRightCoordinate;
  705. }
  706. public function get topRightCoordinate():Coordinate
  707. {
  708. if (!_topRightCoordinate) {
  709. var tr:Point = invertedMatrix.transformPoint(new Point(mapWidth,0));
  710. _topRightCoordinate = new Coordinate(tr.y, tr.x, zoomLevel);
  711. }
  712. return _topRightCoordinate;
  713. }
  714. public function get bottomLeftCoordinate():Coordinate
  715. {
  716. if (!_bottomLeftCoordinate) {
  717. var bl:Point = invertedMatrix.transformPoint(new Point(0, mapHeight));
  718. _bottomLeftCoordinate = new Coordinate(bl.y, bl.x, zoomLevel);
  719. }
  720. return _bottomLeftCoordinate;
  721. }
  722. public function get centerCoordinate():Coordinate
  723. {
  724. if (!_centerCoordinate) {
  725. var c:Point = invertedMatrix.transformPoint(new Point(mapWidth/2, mapHeight/2));
  726. _centerCoordinate = new Coordinate(c.y, c.x, zoomLevel);
  727. }
  728. return _centerCoordinate;
  729. }
  730. public function coordinatePoint(coord:Coordinate, context:DisplayObject=null):Point
  731. {
  732. // this is basically the same as coord.zoomTo, but doesn't make a new Coordinate:
  733. var zoomFactor:Number = Math.pow(2, 8-coord.zoom);
  734. var zoomedColumn:Number = coord.column * zoomFactor;
  735. var zoomedRow:Number = coord.row * zoomFactor;
  736. var screenPoint:Point = worldMatrix.transformPoint(new Point(zoomedColumn, zoomedRow));
  737. if (context && context != this)
  738. {
  739. screenPoint = this.parent.localToGlobal(screenPoint);
  740. screenPoint = context.globalToLocal(screenPoint);
  741. }
  742. return screenPoint;
  743. }
  744. public function pointCoordinate(point:Point, context:DisplayObject=null):Coordinate
  745. {
  746. if (context && context != this)
  747. {
  748. point = context.localToGlobal(point);
  749. point = this.globalToLocal(point);
  750. }
  751. var p:Point = invertedMatrix.transformPoint(point);
  752. return new Coordinate(p.y, p.x, zoomLevel);
  753. }
  754. public function prepareForPanning(dragging:Boolean=false):void
  755. {
  756. if (panning) {
  757. donePanning();
  758. }
  759. if (!dragging && draggable) {
  760. if (hasEventListener(MouseEvent.MOUSE_DOWN)) {
  761. removeEventListener(MouseEvent.MOUSE_DOWN, mousePressed, true);
  762. }
  763. }
  764. startPan = centerCoordinate.copy();
  765. panning = true;
  766. onStartPanning();
  767. }
  768. protected function onStartPanning():void
  769. {
  770. dispatchEvent(new MapEvent(MapEvent.START_PANNING));
  771. }
  772. public function donePanning():void
  773. {
  774. if (draggable) {
  775. if (!hasEventListener(MouseEvent.MOUSE_DOWN)) {
  776. addEventListener(MouseEvent.MOUSE_DOWN, mousePressed, true);
  777. }
  778. }
  779. startPan = null;
  780. panning = false;
  781. onStopPanning();
  782. }
  783. protected function onStopPanning():void
  784. {
  785. dispatchEvent(new MapEvent(MapEvent.STOP_PANNING));
  786. }
  787. public function prepareForZooming():void
  788. {
  789. if (startZoom >= 0) {
  790. doneZooming();
  791. }
  792. startZoom = zoomLevel;
  793. zooming = true;
  794. onStartZooming();
  795. }
  796. protected function onStartZooming():void
  797. {
  798. dispatchEvent(new MapEvent(MapEvent.START_ZOOMING, startZoom));
  799. }
  800. public function doneZooming():void
  801. {
  802. onStopZooming();
  803. startZoom = -1;
  804. zooming = false;
  805. }
  806. protected function onStopZooming():void
  807. {
  808. var event:MapEvent = new MapEvent(MapEvent.STOP_ZOOMING, zoomLevel);
  809. event.zoomDelta = zoomLevel - startZoom;
  810. dispatchEvent(event);
  811. }
  812. public function resetTiles(coord:Coordinate):void
  813. {
  814. var sc:Number = Math.pow(2, coord.zoom);
  815. worldMatrix.identity();
  816. worldMatrix.scale(sc, sc);
  817. worldMatrix.translate(mapWidth/2, mapHeight/2 );
  818. worldMatrix.translate(-tileWidth*coord.column, -tileHeight*coord.row);
  819. // reset the inverted matrix, request a redraw, etc.
  820. dirty = true;
  821. }
  822. public function get zoomLevel():Number
  823. {
  824. return Math.log(scale) / Math.LN2;
  825. }
  826. public function set zoomLevel(n:Number):void
  827. {
  828. if (zoomLevel != n)
  829. {
  830. scale = Math.pow(2, n);
  831. }
  832. }
  833. public function get scale():Number
  834. {
  835. return Math.sqrt(worldMatrix.a * worldMatrix.a + worldMatrix.b * worldMatrix.b);
  836. }
  837. public function set scale(n:Number):void
  838. {
  839. if (scale != n)
  840. {
  841. var needsStop:Boolean = false;
  842. if (!zooming) {
  843. prepareForZooming();
  844. needsStop = true;
  845. }
  846. var sc:Number = n / scale;
  847. worldMatrix.translate(-mapWidth/2, -mapHeight/2);
  848. worldMatrix.scale(sc, sc);
  849. worldMatrix.translate(mapWidth/2, mapHeight/2);
  850. dirty = true;
  851. if (needsStop) {
  852. doneZooming();
  853. }
  854. }
  855. }
  856. public function resizeTo(p:Point):void
  857. {
  858. if (mapWidth != p.x || mapHeight != p.y)
  859. {
  860. var dx:Number = p.x - mapWidth;
  861. var dy:Number = p.y - mapHeight;
  862. // maintain the center point:
  863. tx += dx/2;
  864. ty += dy/2;
  865. mapWidth = p.x;
  866. mapHeight = p.y;
  867. scrollRect = new Rectangle(0, 0, mapWidth, mapHeight);
  868. debugField.x = mapWidth - debugField.width - 15;
  869. debugField.y = mapHeight - debugField.height - 15;
  870. dirty = true;
  871. // force this but only for onResize
  872. onRender();
  873. }
  874. // this makes sure the well is clickable even without tiles
  875. well.graphics.clear();
  876. well.graphics.beginFill(0x000000, 0);
  877. well.graphics.drawRect(0, 0, mapWidth, mapHeight);
  878. well.graphics.endFill();
  879. }
  880. public function setMapProvider(provider:IMapProvider):void
  881. {
  882. if (provider is ITilePainterOverride) {
  883. this.tilePainter = ITilePainterOverride(provider).getTilePainter();
  884. }
  885. else {
  886. this.tilePainter = new TilePainter(this, provider, maxParentLoad == 0 ? centerDistanceCompare : zoomThenCenterCompare);
  887. }
  888. tilePainter.addEventListener(ProgressEvent.PROGRESS, onProgress, false, 0, true);
  889. tilePainter.addEventListener(MapEvent.ALL_TILES_LOADED, onAllTilesLoaded, false, 0, true);
  890. tilePainter.addEventListener(MapEvent.BEGIN_TILE_LOADING, onBeginTileLoading, false, 0, true);
  891. // TODO: set limits independently of provider
  892. this.limits = provider.outerLimits();
  893. _tileWidth = provider.tileWidth;
  894. _tileHeight = provider.tileHeight;
  895. calculateBounds();
  896. clearEverything();
  897. }
  898. protected function clearEverything(event:Event=null):void
  899. {
  900. while (well.numChildren > 0) {
  901. well.removeChildAt(0);
  902. }
  903. tilePainter.reset();
  904. recentlySeen = [];
  905. dirty = true;
  906. }
  907. protected function calculateBounds():void
  908. {
  909. var tl:Coordinate = limits[0] as Coordinate;
  910. var br:Coordinate = limits[1] as Coordinate;
  911. _maxZoom = Math.max(tl.zoom, br.zoom);
  912. _minZoom = Math.min(tl.zoom, br.zoom);
  913. tl = tl.zoomTo(0);
  914. br = br.zoomTo(0);
  915. minTx = tl.column * tileWidth;
  916. maxTx = br.column * tileWidth;
  917. minTy = tl.row * tileHeight;
  918. maxTy = br.row * tileHeight;
  919. }
  920. /** this may seem like a heavy function, but it only gets called once per render
  921. * and it doesn't have any loops, so it flies by, really
  922. * TODO: fix this for rotations */
  923. public function enforceBoundsOnMatrix(matrix:Matrix):Boolean
  924. {
  925. var touched:Boolean = false;
  926. // first check that we're not zoomed in too close...
  927. var matrixScale:Number = Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b);
  928. var matrixZoomLevel:Number = Math.log(matrixScale) / Math.LN2;
  929. if (matrixZoomLevel < minZoom || matrixZoomLevel > maxZoom) {
  930. var oldScale:Number = matrixScale;
  931. matrixZoomLevel = Math.max(minZoom, Math.min(matrixZoomLevel, maxZoom));
  932. matrixScale = Math.pow(2, matrixZoomLevel);
  933. var scaleFactor:Number = matrixScale / oldScale;
  934. matrix.scale(scaleFactor, scaleFactor);
  935. touched = true;
  936. }
  937. // then make sure we haven't gone too far...
  938. var inverse:Matrix = matrix.clone();
  939. inverse.invert();
  940. inverse.scale(matrixScale/tileWidth, matrixScale/tileHeight);
  941. // zoom topLeft and bottomRight coords to 0
  942. // so that they can be compared against minTx etc.
  943. var topLeftPoint:Point = inverse.transformPoint(new Point());
  944. var topLeft:Coordinate = new Coordinate(topLeftPoint.y, topLeftPoint.x, matrixZoomLevel).zoomTo(0);
  945. var bottomRightPoint:Point = inverse.transformPoint(new Point(mapWidth, mapHeight));
  946. var bottomRight:Coordinate = new Coordinate(bottomRightPoint.y, bottomRightPoint.x, matrixZoomLevel).zoomTo(0);
  947. // apply horizontal constraints
  948. var leftX:Number = topLeft.column * tileWidth;
  949. var rightX:Number = bottomRight.column * tileWidth;
  950. if (rightX-leftX > maxTx-minTx) {
  951. // if we're wider than the map, center align
  952. matrix.tx = (mapWidth-(minTx+maxTx)*matrixScale)/2;
  953. touched = true;
  954. }
  955. else if (leftX < minTx) {
  956. matrix.tx += (leftX-minTx)*matrixScale;
  957. touched = true;
  958. }
  959. else if (rightX > maxTx) {
  960. matrix.tx += (rightX-maxTx)*matrixScale;
  961. touched = true;
  962. }
  963. // apply vertical constraints
  964. var upY:Number = topLeft.row * tileHeight;
  965. var downY:Number = bottomRight.row * tileHeight;
  966. if (downY-upY > maxTy-minTy) {
  967. // if we're taller than the map, center align
  968. matrix.ty = (mapHeight-(minTy+maxTy)*matrixScale)/2;
  969. touched = true;
  970. }
  971. else if (upY < minTy) {
  972. matrix.ty += (upY-minTy)*matrixScale;
  973. touched = true;
  974. }
  975. else if (downY > maxTy) {
  976. matrix.ty += (downY-maxTy)*matrixScale;
  977. touched = true;
  978. }
  979. return touched;
  980. }
  981. /** called inside of onRender before events are fired
  982. * enforceBoundsOnMatrix modifies worldMatrix directly
  983. * doesn't use scale/zoomLevel setters to correct values otherwise we'd get stuck in a loop! */
  984. protected function enforceBounds():Boolean
  985. {
  986. if (!enforceBoundsEnabled) {
  987. return false;
  988. }
  989. var touched:Boolean = enforceBoundsOnMatrix(worldMatrix);
  990. /* this is potentially the way to wrap the x position
  991. but all the tiles flash and the values aren't quite right
  992. so wrapping the matrix needs more work :(
  993. var wrapTx:Number = 256 * scale;
  994. if (worldMatrix.tx > 0) {
  995. worldMatrix.tx = worldMatrix.tx - wrapTx;
  996. }
  997. else if (worldMatrix.tx < -wrapTx) {
  998. worldMatrix.tx += wrapTx;
  999. } */
  1000. // to make sure we haven't gone too far
  1001. // zoom topLeft and bottomRight coords to 0
  1002. // so that they can be compared against minTx etc.
  1003. if (touched) {
  1004. _invertedMatrix = null;
  1005. _topLeftCoordinate = null;
  1006. _bottomRightCoordinate = null;
  1007. _topRightCoordinate = null;
  1008. _bottomLeftCoordinate = null;
  1009. _centerCoordinate = null;
  1010. }
  1011. return touched;
  1012. }
  1013. protected function set dirty(d:Boolean):void
  1014. {
  1015. _dirty = d;
  1016. if (d) {
  1017. if (stage) stage.invalidate();
  1018. _invertedMatrix = null;
  1019. _topLeftCoordinate = null;
  1020. _bottomRightCoordinate = null;
  1021. _topRightCoordinate = null;
  1022. _bottomLeftCoordinate = null;
  1023. _centerCoordinate = null;
  1024. }
  1025. }
  1026. protected function get dirty():Boolean
  1027. {
  1028. return _dirty;
  1029. }
  1030. public function getMatrix():Matrix
  1031. {
  1032. return worldMatrix.clone();
  1033. }
  1034. public function setMatrix(m:Matrix):void
  1035. {
  1036. worldMatrix = m;
  1037. matrixChanged = true;
  1038. dirty = true;
  1039. }
  1040. public function get a():Number
  1041. {
  1042. return worldMatrix.a;
  1043. }
  1044. public function get b():Number
  1045. {
  1046. return worldMatrix.b;
  1047. }
  1048. public function get c():Number
  1049. {
  1050. return worldMatrix.c;
  1051. }
  1052. public function get d():Number
  1053. {
  1054. return worldMatrix.d;
  1055. }
  1056. public function get tx():Number
  1057. {
  1058. return worldMatrix.tx;
  1059. }
  1060. public function get ty():Number
  1061. {
  1062. return worldMatrix.ty;
  1063. }
  1064. public function set a(n:Number):void
  1065. {
  1066. worldMatrix.a = n;
  1067. dirty = true;
  1068. }
  1069. public function set b(n:Number):void
  1070. {
  1071. worldMatrix.b = n;
  1072. dirty = true;
  1073. }
  1074. public function set c(n:Number):void
  1075. {
  1076. worldMatrix.c = n;
  1077. dirty = true;
  1078. }
  1079. public function set d(n:Number):void
  1080. {
  1081. worldMatrix.d = n;
  1082. dirty = true;
  1083. }
  1084. public function set tx(n:Number):void
  1085. {
  1086. worldMatrix.tx = n;
  1087. dirty = true;
  1088. }
  1089. public function set ty(n:Number):void
  1090. {
  1091. worldMatrix.ty = n;
  1092. dirty = true;
  1093. }
  1094. }
  1095. }
  1096. import flash.text.TextField;
  1097. import com.modestmaps.core.TileGrid;
  1098. import com.modestmaps.core.Tile;
  1099. import flash.text.TextFormat;
  1100. import flash.utils.getTimer;
  1101. import flash.display.Sprite;
  1102. import flash.system.System;
  1103. import com.modestmaps.core.painter.TilePainter;
  1104. import com.modestmaps.core.painter.ITilePainter;
  1105. class DebugField extends TextField
  1106. {
  1107. // for stats:
  1108. protected var lastFrameTime:Number;
  1109. protected var fps:Number = 30;
  1110. public function DebugField():void
  1111. {
  1112. defaultTextFormat = new TextFormat(null, 12, 0x000000, false);
  1113. backgroundColor = 0xffffff;
  1114. background = true;
  1115. text = "messages";
  1116. name = 'debugField';
  1117. mouseEnabled = false;
  1118. selectable = false;
  1119. multiline = true;
  1120. wordWrap = false;
  1121. lastFrameTime = getTimer();
  1122. }
  1123. public function update(grid:TileGrid, blankCount:int, recentCount:int, tilePainter:ITilePainter):void
  1124. {
  1125. // for stats...
  1126. var frameDuration:Number = getTimer() - lastFrameTime;
  1127. lastFrameTime = getTimer();
  1128. fps = (0.9 * fps) + (0.1 * (1000.0/frameDuration));
  1129. var well:Sprite = grid.getChildByName('well') as Sprite;
  1130. // report stats:
  1131. var tileChildren:int = 0;
  1132. for (var i:int = 0; i < well.numChildren; i++) {
  1133. tileChildren += Tile(well.getChildAt(i)).numChildren;
  1134. }
  1135. this.text = "tx: " + grid.tx.toFixed(3)
  1136. + "\nty: " + grid.ty.toFixed(3)
  1137. + "\nsc: " + grid.scale.toFixed(4)
  1138. + "\nfps: " + fps.toFixed(0)
  1139. + "\ncurrent child count: " + well.numChildren
  1140. + "\ncurrent child of tile count: " + tileChildren
  1141. + "\nvisible tile count: " + grid.getVisibleTiles().length
  1142. + "\nblank count: " + blankCount
  1143. + "\nrecently used tiles: " + recentCount
  1144. + "\ntiles created: " + Tile.count
  1145. + "\nqueue length: " + tilePainter.getQueueCount()
  1146. + "\nrequests: " + tilePainter.getRequestCount()
  1147. + "\nfinished (cached) tiles: " + tilePainter.getCacheSize()
  1148. + "\ncachedLoaders: " + tilePainter.getLoaderCacheCount()
  1149. + "\nmemory: " + (System.totalMemory/1048576).toFixed(1) + "MB";
  1150. width = textWidth+8;
  1151. height = textHeight+4;
  1152. }
  1153. }