ADD getNextNode(), getPrevNode() to net/systemeD/halcyon/connection/Way.as. These...
[potlatch2.git] / net / systemeD / halcyon / connection / Way.as
index 0e0a543..72fcc47 100644 (file)
@@ -1,27 +1,56 @@
 package net.systemeD.halcyon.connection {
     import flash.geom.Point;
-       import net.systemeD.halcyon.Globals;
+    
+    import net.systemeD.halcyon.connection.actions.*;
 
     public class Way extends Entity {
         private var nodes:Array;
+               private var edge_l:Number;
+               private var edge_r:Number;
+               private var edge_t:Number;
+               private var edge_b:Number;
                public static var entity_type:String = 'way';
 
-        public function Way(id:Number, version:uint, tags:Object, loaded:Boolean, nodes:Array) {
-            super(id, version, tags, loaded);
+        public function Way(id:Number, version:uint, tags:Object, loaded:Boolean, nodes:Array, uid:Number = NaN, timestamp:String = null) {
+            super(id, version, tags, loaded, uid, timestamp);
             this.nodes = nodes;
                        for each (var node:Node in nodes) { node.addParent(this); }
+                       calculateBbox();
         }
 
-               public function update(version:uint, tags:Object, loaded:Boolean, nodes:Array):void {
+               public function update(version:uint, tags:Object, loaded:Boolean, parentsLoaded:Boolean, nodes:Array, uid:Number = NaN, timestamp:String = null):void {
                        var node:Node;
                        for each (node in this.nodes) { node.removeParent(this); }
-                       updateEntityProperties(version,tags,loaded); this.nodes=nodes;
+                       updateEntityProperties(version,tags,loaded,parentsLoaded,uid,timestamp); this.nodes=nodes;
                        for each (node in nodes) { node.addParent(this); }
+                       calculateBbox();
                }
                
         public function get length():uint {
             return nodes.length;
         }
+
+               private function calculateBbox():void {
+                       edge_l=999999; edge_r=-999999;
+                       edge_b=999999; edge_t=-999999;
+                       for each (var node:Node in nodes) { expandBbox(node); }
+               }
+
+               public function expandBbox(node:Node):void {
+                       edge_l=Math.min(edge_l,node.lon);
+                       edge_r=Math.max(edge_r,node.lon);
+                       edge_b=Math.min(edge_b,node.lat);
+                       edge_t=Math.max(edge_t,node.lat);
+               }
+               
+               public override function within(left:Number,right:Number,top:Number,bottom:Number):Boolean {
+                       if (!edge_l ||
+                               (edge_l<left   && edge_r<left  ) ||
+                           (edge_l>right  && edge_r>right ) ||
+                           (edge_b<bottom && edge_t<bottom) ||
+                           (edge_b>top    && edge_b>top   ) || deleted) { return false; }
+                       return true;
+               }
         
         public function getNode(index:uint):Node {
             return nodes[index];
@@ -30,106 +59,96 @@ package net.systemeD.halcyon.connection {
                public function getLastNode():Node {
                        return nodes[nodes.length-1];
                }
+               
+               /** Given one node, return the next in sequence, cycling around a loop if necessary. */
+               // TODO make behave correctly for P-shaped topologies?
+               public function getNextNode(node:Node):Node {
+                       // If the last node in a loop is selected, this behaves correctly.
+                   var i:uint = indexOfNode(node);
+                   if(i < length-1)
+                   return nodes[i+1];
+               return null;
+               // What should happen for very short lengths?      
+               }
+        
+        // TODO make behave correctly for P-shaped topologies?
+        /** Given one node, return the previous, cycling around a loop if necessary. */
+        public function getPrevNode(node:Node):Node {
+            var i:uint = indexOfNode(node);
+            if(i > 0)
+                return nodes[i-1];
+            if(i == 0 && isArea() )
+                return nodes[nodes.length - 2]
+            return null;
+            // What should happen for very short lengths?      
+        }
 
-        public function insertNode(index:uint, node:Node):void {
-                       node.addParent(this);
-            nodes.splice(index, 0, node);
-            markDirty();
-            dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_ADDED, node, this, index));
+        public function insertNode(index:uint, node:Node, performAction:Function):void {
+                       performAction(new AddNodeToWayAction(this, node, nodes, index));
         }
 
-        public function appendNode(node:Node):uint {
-                       node.addParent(this);
-            nodes.push(node);
-            markDirty();
-            dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_ADDED, node, this, nodes.length - 1));
-            return nodes.length;
+        public function appendNode(node:Node, performAction:Function):uint {
+                       performAction(new AddNodeToWayAction(this, node, nodes, -1));
+            return nodes.length + 1;
         }
         
-        public function prependNode(node:Node):uint {
-                       node.addParent(this);
-            nodes.unshift(node);
-            markDirty();
-            dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_ADDED, node, this, 0));
-            return nodes.length;
+        public function prependNode(node:Node, performAction:Function):uint {
+                       performAction(new AddNodeToWayAction(this, node, nodes, 0));
+            return nodes.length + 1;
         }
         
-        public function indexOfNode(node:Node):uint {
+        // return the index of the Node, or -1 if not found
+        public function indexOfNode(node:Node):int {
             return nodes.indexOf(node);
         }
 
-               public function removeNode(node:Node):void {
-                       var i:int;
-                       while ((i=nodes.indexOf(node))>-1) {
-                               nodes.splice(i,1);
-               dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_REMOVED, node, this, i));
+               public function hasOnceOnly(node:Node):Boolean {
+                       return nodes.indexOf(node)==nodes.lastIndexOf(node);
+               }
+               
+               public function hasLockedNodes():Boolean {
+                       for each (var node:Node in nodes) {
+                               if (node.locked) { return true; }
                        }
-                       node.removeParent(this);
-                       markDirty();
+                       return false;
                }
 
-        public function removeNodeByIndex(index:uint):void {
-            var removed:Array=nodes.splice(index, 1);
-                       if (nodes.indexOf(removed[0])==-1) { removed[0].removeParent(this); }
-                       markDirty();
-            dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_REMOVED, removed[0], this, index));
+               public function removeNode(node:Node, performAction:Function):void {
+                       performAction(new RemoveNodeFromWayAction(this, node, nodes));
+               }
+
+        public function removeNodeByIndex(index:uint, performAction:Function, fireEvent:Boolean=true):void {
+            performAction(new RemoveNodeByIndexAction(this, nodes, index, fireEvent));
         }
 
                public function sliceNodes(start:int,end:int):Array {
                        return nodes.slice(start,end);
                }
 
-               public function deleteNodesFrom(start:int):void {
-                       for (var i:int=start; i<nodes.length; i++) {
-                               nodes[i].removeParent(this);
-                       }
-                       nodes.splice(start);
-                       markDirty();
-               }
-
-               public function mergeWith(way:Way,topos:int,frompos:int):void {
-                       var i:int;
-                       suspend();
-
-                       // merge relations
-                       for each (var r:Relation in way.parentRelations) {
-                               // ** needs to copy roles as well
-                               if (r.findEntityMemberIndex(this)==-1) {
-                                       r.appendMember(new RelationMember(this, ''));
-                               }
-                       }
-
-                       // merge tags
-                       var t:Object=way.getTagsHash();
-                       for (var k:String in t) {
-                               if (getTag(k) && getTag(k)!=t[k]) {
-                                       setTag(k,getTag(k)+"; "+t[k]);
-                                       // ** send a warning about tags not matching
-                               } else {
-                                       setTag(k,t[k]);
-                               }
-                       }
-
-                       // merge nodes
-                       if (frompos==0) { for (i=0; i<way.length;    i++) { addToEnd(topos,way.getNode(i)); } }
-                                          else { for (i=way.length-1; i>=0; i--) { addToEnd(topos,way.getNode(i)); } }
+        public function deleteNodesFrom(start:int, performAction:Function):void {
+            for (var i:int=nodes.length-1; i>=start; i--) {
+              performAction(new RemoveNodeByIndexAction(this, nodes, i));
+            }
+            markDirty();
+        }
 
-                       // delete way
-                       way.remove();
-                       resume();
+               public function mergeWith(way:Way,topos:int,frompos:int, performAction:Function):void {
+                       performAction(new MergeWaysAction(this, way, topos, frompos));
                }
                
-               private function addToEnd(topos:int,node:Node):void {
+               public function addToEnd(topos:int,node:Node, performAction:Function):void {
                        if (topos==0) {
                                if (nodes[0]==node) { return; }
-                               prependNode(node);
+                               prependNode(node, performAction);
                        } else {
                                if (nodes[nodes.length-1]==node) { return; }
-                               appendNode(node);
+                               appendNode(node, performAction);
                        }
                }
 
-
+        public function reverseNodes(performAction:Function):void {
+            performAction(new ReverseNodesAction(this, nodes));
+        }
 
         /**
          * Finds the 1st way segment which intersects the projected
@@ -137,7 +156,7 @@ package net.systemeD.halcyon.connection {
          * specified then the node is moved to exactly bisect the
          * segment.
          */
-        public function insertNodeAtClosestPosition(newNode:Node, isSnap:Boolean):int {
+        public function insertNodeAtClosestPosition(newNode:Node, isSnap:Boolean, performAction:Function):int {
             var closestProportion:Number = 1;
             var newIndex:uint = 0;
             var nP:Point = new Point(newNode.lon, newNode.latp);
@@ -162,10 +181,9 @@ package net.systemeD.halcyon.connection {
             
             // splice in new node
             if ( isSnap ) {
-                newNode.latp = snapped.y;
-                newNode.lon = snapped.x;
+                newNode.setLonLatp(snapped.x, snapped.y, performAction);
             }
-            insertNode(newIndex, newNode);
+            insertNode(newIndex, newNode, performAction);
             return newIndex;
         }
         
@@ -182,9 +200,24 @@ package net.systemeD.halcyon.connection {
         }
 
                public function isArea():Boolean {
+                       if (nodes.length==0) { return false; }
                        return (nodes[0].id==nodes[nodes.length-1].id && nodes.length>2);
                }
                
+               public function endsWith(node:Node):Boolean {
+                       return (nodes[0]==node || nodes[nodes.length-1]==node);
+               }
+               
+               public override function remove(performAction:Function):void {
+                       performAction(new DeleteWayAction(this, setDeletedState, nodes));
+               }
+
+               public override function nullify():void {
+                       nullifyEntity();
+                       nodes=[];
+                       edge_l=edge_r=edge_t=edge_b=NaN;
+               }
+               
                public function get clockwise():Boolean {
                        var lowest:uint=0;
                        var xmin:Number=-999999; var ymin:Number=-999999;
@@ -208,20 +241,15 @@ package net.systemeD.halcyon.connection {
                        return left;
                }
 
-               public override function remove():void {
-                       var node:Node;
-                       suspend();
-                       removeFromParents();
-                       while (nodes.length) { 
-                               node=nodes.pop();
-                               dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_REMOVED, node, this, 0));
-                               node.removeParent(this);
-                               if (!node.hasParents) { node.remove(); }
-                       }
-                       deleted=true;
-            dispatchEvent(new EntityEvent(Connection.WAY_DELETED, this));
-                       resume();
-               }
+        public function get angle():Number {
+            var dx:Number = nodes[nodes.length-1].lon - nodes[0].lon;
+            var dy:Number = nodes[nodes.length-1].latp - nodes[0].latp;
+            if (dx != 0 || dy != 0) {
+                return Math.atan2(dx,dy)*(180/Math.PI);
+            } else {
+                return 0;
+            }
+        }
 
                internal override function isEmpty():Boolean {
                        return (deleted || (nodes.length==0));