1 package net.systemeD.halcyon {
3 import flash.display.Sprite;
4 import flash.display.DisplayObject;
5 import net.systemeD.halcyon.NodeUI;
6 import net.systemeD.halcyon.WayUI;
7 import net.systemeD.halcyon.connection.*;
8 import net.systemeD.halcyon.styleparser.RuleSet;
10 /** Manages the drawing of map entities, allocating their sprites etc. */
11 public class MapPaint extends Sprite {
13 /** Parent Map - required for finding out bounds and scale */
16 /** Source data for this MapPaint layer */
17 public var connection:Connection;
19 /** Lowest OSM layer that can be displayed */
20 public var minlayer:int;
21 /** Highest OSM layer that can be displayed */
22 public var maxlayer:int;
23 /** The MapCSS rules used for drawing entities. */
24 public var ruleset:RuleSet;
25 /** WayUI objects attached to Way entities that are currently visible. */
26 public var wayuis:Object=new Object(); // sprites for ways and (POI/tagged) nodes
27 /** NodeUI objects attached to POI/tagged node entities that are currently visible. */
28 // confirm this - it seems to me all nodes in ways get nodeuis? --Steve B
29 public var nodeuis:Object=new Object();
30 /** MarkerUI objects attached to Marker entities that are currently visible. */
31 public var markeruis:Object=new Object();
32 /** Is this a background layer or the core paint object? */
33 public var isBackground:Boolean = true;
34 /** Hash of index->position */
35 public var sublayerIndex:Object={};
37 private const VERYBIG:Number=Math.pow(2,16);
38 private static const NO_LAYER:int=-99999; // same as NodeUI
42 /** Creates paint sprites and hit sprites for all layers in range. This object ends up with a series of child sprites
43 * 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.
44 * <p>Each paint sprite has 4 child sprites (fill, casing, stroke, names). Each hit sprite has 2 child sprites (way hit tests, node hit tests).</p>
45 * <p>Thus if layers range from -5 to +5, there will be 11 top level paint sprites followed by 11 top level hit sprites.</p>
47 * @param map The Map this is attached to. (Required for finding out bounds and scale.)
48 * @param connection The Connection containing the data for this layer.
49 * @param minlayer The lowest OSM layer to display.
50 * @param maxlayer The highest OSM layer to display.
52 public function MapPaint(map:Map, connection:Connection, styleurl:String, minlayer:int, maxlayer:int) {
56 this.connection=connection;
57 this.minlayer=minlayer;
58 this.maxlayer=maxlayer;
65 // Listen for changes on this Connection
66 connection.addEventListener(Connection.NEW_WAY, newWayCreatedListener);
67 connection.addEventListener(Connection.NEW_POI, newPOICreatedListener);
68 connection.addEventListener(Connection.WAY_RENUMBERED, wayRenumberedListener);
69 connection.addEventListener(Connection.NODE_RENUMBERED, nodeRenumberedListener);
70 connection.addEventListener(Connection.NEW_MARKER, newMarkerCreatedListener);
73 for (l=minlayer; l<=maxlayer; l++) { // each layer (10 is +5, 0 is -5)
74 s = getPaintSprite(); // |
75 s.addChild(getPaintSprite()); // | 0 fill
76 s.addChild(getPaintSprite()); // | 1 casing
77 var t:Sprite = getPaintSprite(); // | 2 stroke
78 t.addChild(getPaintSprite()); // | | sublayer
80 s.addChild(getPaintSprite()); // | 3 names
85 for (l=minlayer; l<=maxlayer; l++) { // each layer (21 is +5, 11 is -5)
86 s = getHitSprite(); // |
87 s.addChild(getHitSprite()); // | 0 way hit tests
88 s.addChild(getHitSprite()); // | 1 node hit tests
93 /** Returns the paint surface for the given layer. */
94 public function getPaintSpriteAt(l:int):Sprite {
95 return getChildAt(l-minlayer) as Sprite;
98 /** Returns the hit sprite for the given layer. */
99 public function getHitSpriteAt(l:int):Sprite {
100 return getChildAt((l-minlayer) + (maxlayer-minlayer+1)) as Sprite;
103 /** Is ruleset loaded? */
104 public function get ready():Boolean {
105 if (!ruleset) { return false; }
106 if (!ruleset.loaded) { return false; }
110 public function sublayer(layer:int,sublayer:Number):Sprite {
113 var index:String, ix:Number;
114 if (!sublayerIndex.hasOwnProperty(sublayer)) {
115 // work out which position to add at
116 var lowestAbove:Number=VERYBIG;
117 var lowestAbovePos:int=-1;
118 var indexLength:uint=0;
119 for (index in sublayerIndex) {
121 if (ix>sublayer && ix<lowestAbove) {
123 lowestAbovePos=sublayerIndex[index];
127 if (lowestAbovePos==-1) { lowestAbovePos=indexLength; }
130 for (var i:int=minlayer; i<=maxlayer; i++) {
131 l=getChildAt(i-minlayer);
132 o=(l as Sprite).getChildAt(2);
133 (o as Sprite).addChildAt(getPaintSprite(),lowestAbovePos);
137 // (we do it in this rather indirect way because if you alter sublayerIndex directly
138 // within the loop, it confuses the iterator)
139 var toUpdate:Array=[];
140 for (index in sublayerIndex) {
142 if (ix>sublayer) { toUpdate.push(index); }
144 for each (index in toUpdate) { sublayerIndex[index]++; }
145 sublayerIndex[sublayer]=lowestAbovePos;
148 l=getChildAt(layer-minlayer);
149 o=(l as Sprite).getChildAt(2);
150 return ((o as Sprite).getChildAt(sublayerIndex[sublayer]) as Sprite);
154 * Update, and if necessary, create / remove UIs for the current viewport.
155 * Flags control redrawing existing entities and removing UIs from entities no longer in view.
157 * @param redraw If true, all UIs for entities on "inside" lists will be redrawn
158 * @param remove If true, all UIs for entites on "outside" lists will be removed. The purgable flag on UIs
159 can override this, for example for selected objects.
160 * fixme? add smarter behaviour for way nodes - remove NodeUIs from way nodes off screen, create them for ones
161 * that scroll onto screen (for highlights etc)
163 public function updateEntityUIs(redraw:Boolean, remove:Boolean):void {
164 var way:Way, poi:Node, marker:Marker;
165 var o:Object = connection.getObjectsByBbox(map.edge_l,map.edge_r,map.edge_t,map.edge_b);
167 for each (way in o.waysInside) {
168 if (!wayuis[way.id]) { createWayUI(way); }
169 else if (redraw) { wayuis[way.id].recalculate(); wayuis[way.id].redraw(); }
170 else wayuis[way.id].updateHighlights();//dubious
174 for each (way in o.waysOutside) {
175 if (wayuis[way.id] && !wayuis[way.id].purgable) {
176 if (redraw) { wayuis[way.id].recalculate(); wayuis[way.id].redraw(); }
183 for each (poi in o.poisInside) {
184 if (!nodeuis[poi.id]) { createNodeUI(poi); }
185 else if (redraw) { nodeuis[poi.id].redraw(); }
189 for each (poi in o.poisOutside) {
190 if (nodeuis[poi.id] && !nodeuis[poi.id].purgable) {
191 if (redraw) { nodeuis[poi.id].redraw(); }
198 for each (marker in o.markersInside) {
199 if (!markeruis[marker.id]) { createMarkerUI(marker); }
200 else if (redraw) { markeruis[marker.id].redraw(); }
204 for each (marker in o.markersOutside) {
205 if (markeruis[marker.id] && !markeruis[marker.id].purgable) {
206 if (redraw) { markeruis[marker.id].redraw(); }
208 deleteMarkerUI(marker);
214 /** Make a UI object representing a way. */
215 public function createWayUI(way:Way):WayUI {
216 if (!wayuis[way.id]) {
217 wayuis[way.id]=new WayUI(way,this);
218 way.addEventListener(Connection.WAY_DELETED, wayDeleted);
220 wayuis[way.id].redraw();
222 return wayuis[way.id];
225 /** Respond to event by removing the WayUI. */
226 public function wayDeleted(event:EntityEvent):void {
227 deleteWayUI(event.entity as Way);
230 /** Remove a way's UI object. */
231 public function deleteWayUI(way:Way):void {
232 way.removeEventListener(Connection.WAY_DELETED, wayDeleted);
233 if (wayuis[way.id]) {
234 wayuis[way.id].redrawMultis();
235 wayuis[way.id].removeSprites();
236 wayuis[way.id].removeEventListeners();
237 wayuis[way.id].removeListenSprite();
238 delete wayuis[way.id];
240 for (var i:uint=0; i<way.length; i++) {
241 var node:Node=way.getNode(i);
242 if (nodeuis[node.id]) { deleteNodeUI(node); }
246 /** Make a UI object representing a node. */
247 public function createNodeUI(node:Node,rotation:Number=0,layer:int=NO_LAYER,stateClasses:Object=null):NodeUI {
248 if (!nodeuis[node.id]) {
249 nodeuis[node.id]=new NodeUI(node,this,rotation,layer,stateClasses);
250 node.addEventListener(Connection.NODE_DELETED, nodeDeleted);
252 for (var state:String in stateClasses) {
253 nodeuis[node.id].setStateClass(state,stateClasses[state]);
255 nodeuis[node.id].redraw();
257 return nodeuis[node.id];
260 /** Respond to event by deleting NodeUI. */
261 public function nodeDeleted(event:EntityEvent):void {
262 deleteNodeUI(event.entity as Node);
265 /** Remove a node's UI object. */
266 public function deleteNodeUI(node:Node):void {
267 node.removeEventListener(Connection.NODE_DELETED, nodeDeleted);
268 if (!nodeuis[node.id]) { return; }
269 nodeuis[node.id].removeSprites();
270 nodeuis[node.id].removeEventListeners();
271 nodeuis[node.id].removeListenSprite();
272 delete nodeuis[node.id];
275 /** Make a UI object representing a marker. */
276 public function createMarkerUI(marker:Marker,rotation:Number=0,layer:int=NO_LAYER,stateClasses:Object=null):MarkerUI {
277 if (!markeruis[marker.id]) {
278 markeruis[marker.id]=new MarkerUI(marker,this,rotation,layer,stateClasses);
279 marker.addEventListener(Connection.NODE_DELETED, markerDeleted);
281 for (var state:String in stateClasses) {
282 markeruis[marker.id].setStateClass(state,stateClasses[state]);
284 markeruis[marker.id].redraw();
286 return markeruis[marker.id];
289 /** Respond to event by deleting MarkerUI. */
290 public function markerDeleted(event:EntityEvent):void {
291 deleteMarkerUI(event.entity as Marker);
294 /** Remove a marker's UI object. */
295 public function deleteMarkerUI(marker:Marker):void {
296 marker.removeEventListener(Connection.NODE_DELETED, markerDeleted);
297 if (!markeruis[marker.id]) { return; }
298 markeruis[marker.id].removeSprites();
299 markeruis[marker.id].removeEventListeners();
300 markeruis[marker.id].removeListenSprite();
301 delete markeruis[marker.id];
304 public function renumberWayUI(way:Way,oldID:Number):void {
305 if (!wayuis[oldID]) { return; }
306 wayuis[way.id]=wayuis[oldID];
307 delete wayuis[oldID];
310 public function renumberNodeUI(node:Node,oldID:Number):void {
311 if (!nodeuis[oldID]) { return; }
312 nodeuis[node.id]=nodeuis[oldID];
313 delete nodeuis[oldID];
316 /** Make a new sprite for painting on */
317 private function getPaintSprite():Sprite {
318 var s:Sprite = new Sprite();
319 s.mouseEnabled = false;
320 s.mouseChildren = false;
324 private function getHitSprite():Sprite {
325 var s:Sprite = new Sprite();
329 public function redraw():void {
330 for each (var w:WayUI in wayuis) { w.recalculate(); w.invalidateStyleList(); w.redraw(); }
331 /* sometimes (e.g. in Map.setStyle) Mappaint.redrawPOIs() is called immediately afterwards anyway. FIXME? */
332 for each (var p:NodeUI in nodeuis) { p.invalidateStyleList(); p.redraw(); }
333 for each (var m:MarkerUI in markeruis) { m.invalidateStyleList(); m.redraw(); }
336 /** Redraw nodes and markers */
337 public function redrawPOIs():void {
338 for each (var p:NodeUI in nodeuis) { p.invalidateStyleList(); p.redraw(); }
339 for each (var m:MarkerUI in markeruis) { m.invalidateStyleList(); m.redraw(); }
342 /** Switch to new MapCSS. */
343 public function setStyle(url:String):void {
344 ruleset=new RuleSet(map.MINSCALE,map.MAXSCALE,redraw,redrawPOIs);
345 ruleset.loadFromCSS(url);
348 // >>>> REFACTOR: remove this
349 public function findSource():VectorLayer {
350 // var v:VectorLayer;
351 // for each (v in map.vectorlayers) {
352 // if (v.paint==this) { return v; }
357 // ==================== Start of code moved from Map.as
359 // Listeners for Connection events
361 private function newWayCreatedListener(event:EntityEvent):void {
362 var way:Way = event.entity as Way;
363 if (!way.loaded || !way.within(map.edge_l, map.edge_r, map.edge_t, map.edge_b)) { return; }
367 private function newPOICreatedListener(event:EntityEvent):void {
368 var node:Node = event.entity as Node;
369 if (!node.within(map.edge_l, map.edge_r, map.edge_t, map.edge_b)) { return; }
373 private function newMarkerCreatedListener(event:EntityEvent):void {
374 var marker:Marker = event.entity as Marker;
375 if (!marker.within(map.edge_l, map.edge_r, map.edge_t, map.edge_b)) { return; }
376 createMarkerUI(marker);
379 private function wayRenumberedListener(event:EntityRenumberedEvent):void {
380 var way:Way = event.entity as Way;
381 renumberWayUI(way,event.oldID);
384 private function nodeRenumberedListener(event:EntityRenumberedEvent):void {
385 var node:Node = event.entity as Node;
386 renumberNodeUI(node,event.oldID);
389 /** Visually mark an entity as highlighted. */
390 public function setHighlight(entity:Entity, settings:Object):void {
391 if ( entity is Way && wayuis[entity.id] ) { wayuis[entity.id].setHighlight(settings); }
392 else if ( entity is Node && nodeuis[entity.id]) { nodeuis[entity.id].setHighlight(settings); }
395 public function setHighlightOnNodes(way:Way, settings:Object):void {
396 if (wayuis[way.id]) wayuis[way.id].setHighlightOnNodes(settings);
399 public function protectWay(way:Way):void {
400 if (wayuis[way.id]) wayuis[way.id].protectSprites();
403 public function unprotectWay(way:Way):void {
404 if (wayuis[way.id]) wayuis[way.id].unprotectSprites();
407 public function limitWayDrawing(way:Way,except:Number=NaN,only:Number=NaN):void {
408 if (!wayuis[way.id]) return;
409 wayuis[way.id].drawExcept=except;
410 wayuis[way.id].drawOnly =only;
411 wayuis[way.id].redraw();
414 /** Protect Entities and EntityUIs against purging. This prevents the currently selected items
415 from being purged even though they're off-screen. */
417 public function setPurgable(entities:Array, purgable:Boolean):void {
418 for each (var entity:Entity in entities) {
419 entity.locked=!purgable;
420 if ( entity is Way ) {
421 var way:Way=entity as Way;
422 if (wayuis[way.id]) { wayuis[way.id].purgable=purgable; }
423 for (var i:uint=0; i<way.length; i++) {
424 var node:Node=way.getNode(i)
425 node.locked=!purgable;
426 if (nodeuis[node.id]) { nodeuis[node.id].purgable=purgable; }
428 } else if ( entity is Node && nodeuis[entity.id]) {
429 nodeuis[entity.id].purgable=purgable;
434 // ==================== End of code moved from Map.as