package net.systemeD.halcyon { import flash.display.Sprite; import flash.display.DisplayObject; import net.systemeD.halcyon.NodeUI; import net.systemeD.halcyon.WayUI; import net.systemeD.halcyon.connection.*; import net.systemeD.halcyon.connection.actions.CreatePOIAction; import net.systemeD.halcyon.styleparser.RuleSet; /** Manages the drawing of map entities, allocating their sprites etc. */ public class MapPaint extends Sprite { /** Parent Map - required for finding out bounds and scale */ public var map:Map; /** Source data for this MapPaint layer */ public var connection:Connection; /** Lowest OSM layer that can be displayed */ public var minlayer:int; /** Highest OSM layer that can be displayed */ public var maxlayer:int; /** The MapCSS rules used for drawing entities. */ public var ruleset:RuleSet; /** WayUI objects attached to Way entities that are currently visible. */ private var wayuis:Object=new Object(); /** NodeUI objects attached to POI/tagged node entities that are currently visible. */ private var nodeuis:Object=new Object(); /** MarkerUI objects attached to Marker entities that are currently visible. */ private var markeruis:Object=new Object(); /** Is this a background layer or the core paint object? */ public var isBackground:Boolean = true; /** Hash of index->position */ public var sublayerIndex:Object={}; /** The url of the style in use */ public var style:String = ''; private const VERYBIG:Number=Math.pow(2,16); private static const NO_LAYER:int=-99999; // same as NodeUI // Set up layering /** Creates paint sprites and hit sprites for all layers in range. This object ends up with a series of child sprites * as follows: p0,p1,p2..px, h0,h1,h2..hx where p are "paint sprites" and "h" are "hit sprites". There is one of each type for each layer. *

Each paint sprite has 4 child sprites (fill, casing, stroke, names). Each hit sprite has 2 child sprites (way hit tests, node hit tests).

*

Thus if layers range from -5 to +5, there will be 11 top level paint sprites followed by 11 top level hit sprites.

* * @param map The Map this is attached to. (Required for finding out bounds and scale.) * @param connection The Connection containing the data for this layer. * @param minlayer The lowest OSM layer to display. * @param maxlayer The highest OSM layer to display. * */ public function MapPaint(map:Map, connection:Connection, styleurl:String, minlayer:int, maxlayer:int) { mouseEnabled=false; this.map=map; this.connection=connection; this.minlayer=minlayer; this.maxlayer=maxlayer; sublayerIndex[1]=0; var s:Sprite, l:int; // Set up stylesheet setStyle(styleurl); // Listen for changes on this Connection connection.addEventListener(Connection.NEW_WAY, newWayCreatedListener); connection.addEventListener(Connection.NEW_POI, newPOICreatedListener); connection.addEventListener(Connection.WAY_RENUMBERED, wayRenumberedListener); connection.addEventListener(Connection.NODE_RENUMBERED, nodeRenumberedListener); connection.addEventListener(Connection.NEW_MARKER, newMarkerCreatedListener); // Add paint sprites for (l=minlayer; l<=maxlayer; l++) { // each layer (10 is +5, 0 is -5) s = getPaintSprite(); // | s.addChild(getPaintSprite()); // | 0 fill s.addChild(getPaintSprite()); // | 1 casing var t:Sprite = getPaintSprite(); // | 2 stroke t.addChild(getPaintSprite()); // | | sublayer s.addChild(t); // | | s.addChild(getPaintSprite()); // | 3 names addChild(s); // | } // Add hit sprites for (l=minlayer; l<=maxlayer; l++) { // each layer (21 is +5, 11 is -5) s = getHitSprite(); // | s.addChild(getHitSprite()); // | 0 way hit tests s.addChild(getHitSprite()); // | 1 node hit tests addChild(s); } } /** Returns the paint surface for the given layer. */ public function getPaintSpriteAt(l:int):Sprite { return getChildAt(l-minlayer) as Sprite; } /** Returns the hit sprite for the given layer. */ public function getHitSpriteAt(l:int):Sprite { return getChildAt((l-minlayer) + (maxlayer-minlayer+1)) as Sprite; } /** Is ruleset loaded? */ public function get ready():Boolean { if (!ruleset) { return false; } if (!ruleset.loaded) { return false; } return true; } public function sublayer(layer:int,sublayer:Number):Sprite { var l:DisplayObject; var o:DisplayObject; var index:String, ix:Number; if (!sublayerIndex.hasOwnProperty(sublayer)) { // work out which position to add at var lowestAbove:Number=VERYBIG; var lowestAbovePos:int=-1; var indexLength:uint=0; for (index in sublayerIndex) { ix=Number(index); if (ix>sublayer && ixsublayer) { toUpdate.push(index); } } for each (index in toUpdate) { sublayerIndex[index]++; } sublayerIndex[sublayer]=lowestAbovePos; } l=getChildAt(layer-minlayer); o=(l as Sprite).getChildAt(2); return ((o as Sprite).getChildAt(sublayerIndex[sublayer]) as Sprite); } /** * Update, and if necessary, create / remove UIs for the current viewport. * Flags control redrawing existing entities and removing UIs from entities no longer in view. * * @param redraw If true, all UIs for entities on "inside" lists will be redrawn * @param remove If true, all UIs for entites on "outside" lists will be removed. The purgable flag on UIs can override this, for example for selected objects. * fixme? add smarter behaviour for way nodes - remove NodeUIs from way nodes off screen, create them for ones * that scroll onto screen (for highlights etc) */ public function updateEntityUIs(redraw:Boolean, remove:Boolean):void { var way:Way, poi:Node, marker:Marker; var o:Object = connection.getObjectsByBbox(map.edge_l,map.edge_r,map.edge_t,map.edge_b); for each (way in o.waysInside) { if (!wayuis[way.id]) { createWayUI(way); } else if (redraw) { wayuis[way.id].recalculate(); wayuis[way.id].redraw(); } else wayuis[way.id].updateHighlights();//dubious } if (remove) { for each (way in o.waysOutside) { if (wayuis[way.id] && !wayuis[way.id].purgable) { if (redraw) { wayuis[way.id].recalculate(); wayuis[way.id].redraw(); } } else { deleteWayUI(way); } } } for each (poi in o.poisInside) { if (!nodeuis[poi.id]) { createNodeUI(poi); } else if (redraw) { nodeuis[poi.id].redraw(); } } if (remove) { for each (poi in o.poisOutside) { if (nodeuis[poi.id] && !nodeuis[poi.id].purgable) { if (redraw) { nodeuis[poi.id].redraw(); } } else { deleteNodeUI(poi); } } } for each (marker in o.markersInside) { if (!markeruis[marker.id]) { createMarkerUI(marker); } else if (redraw) { markeruis[marker.id].redraw(); } } if (remove) { for each (marker in o.markersOutside) { if (markeruis[marker.id] && !markeruis[marker.id].purgable) { if (redraw) { markeruis[marker.id].redraw(); } } else { deleteMarkerUI(marker); } } } } /** Make a UI object representing a way. */ public function createWayUI(way:Way):WayUI { if (!wayuis[way.id]) { wayuis[way.id]=new WayUI(way,this); way.addEventListener(Connection.WAY_DELETED, wayDeleted); } else { wayuis[way.id].redraw(); } return wayuis[way.id]; } /** Respond to event by removing the WayUI. */ public function wayDeleted(event:EntityEvent):void { deleteWayUI(event.entity as Way); } /** Remove a way's UI object. */ public function deleteWayUI(way:Way):void { way.removeEventListener(Connection.WAY_DELETED, wayDeleted); if (wayuis[way.id]) { wayuis[way.id].redrawMultis(); wayuis[way.id].removeSprites(); wayuis[way.id].removeEventListeners(); wayuis[way.id].removeListenSprite(); delete wayuis[way.id]; } for (var i:uint=0; i