purge ways when there are too many in memory
[potlatch2.git] / net / systemeD / halcyon / connection / Way.as
1 package net.systemeD.halcyon.connection {
2     import flash.geom.Point;
3         import net.systemeD.halcyon.Globals;
4         import net.systemeD.halcyon.connection.actions.*;
5
6     public class Way extends Entity {
7         private var nodes:Array;
8                 private var edge_l:Number;
9                 private var edge_r:Number;
10                 private var edge_t:Number;
11                 private var edge_b:Number;
12                 public static var entity_type:String = 'way';
13
14         public function Way(id:Number, version:uint, tags:Object, loaded:Boolean, nodes:Array) {
15             super(id, version, tags, loaded);
16             this.nodes = nodes;
17                         for each (var node:Node in nodes) { node.addParent(this); }
18                         calculateBbox();
19         }
20
21                 public function update(version:uint, tags:Object, loaded:Boolean, nodes:Array):void {
22                         var node:Node;
23                         for each (node in this.nodes) { node.removeParent(this); }
24                         updateEntityProperties(version,tags,loaded); this.nodes=nodes;
25                         for each (node in nodes) { node.addParent(this); }
26                         calculateBbox();
27                 }
28                 
29         public function get length():uint {
30             return nodes.length;
31         }
32
33                 private function calculateBbox():void {
34                         edge_l=999999; edge_r=-999999;
35                         edge_b=999999; edge_t=-999999;
36                         for each (var node:Node in nodes) { expandBbox(node); }
37                 }
38
39                 public function expandBbox(node:Node):void {
40                         edge_l=Math.min(edge_l,node.lon);
41                         edge_r=Math.max(edge_r,node.lon);
42                         edge_b=Math.min(edge_b,node.lat);
43                         edge_t=Math.max(edge_t,node.lat);
44                 }
45                 
46                 public function intersectsBbox(left:Number,right:Number,top:Number,bottom:Number):Boolean {
47                         if ((edge_l<left   && edge_r<left  ) ||
48                             (edge_l>right  && edge_r>right ) ||
49                             (edge_b<bottom && edge_t<bottom) ||
50                             (edge_b>top    && edge_b>top   )) { return false; }
51                         return true;
52                 }
53         
54         public function getNode(index:uint):Node {
55             return nodes[index];
56         }
57
58                 public function getLastNode():Node {
59                         return nodes[nodes.length-1];
60                 }
61
62         public function insertNode(index:uint, node:Node, performAction:Function):void {
63                         performAction(new AddNodeToWayAction(this, node, nodes, index));
64         }
65
66         public function appendNode(node:Node, performAction:Function):uint {
67                         performAction(new AddNodeToWayAction(this, node, nodes, -1));
68             return nodes.length + 1;
69         }
70         
71         public function prependNode(node:Node, performAction:Function):uint {
72                         performAction(new AddNodeToWayAction(this, node, nodes, 0));
73             return nodes.length + 1;
74         }
75         
76         public function indexOfNode(node:Node):uint {
77             return nodes.indexOf(node);
78         }
79
80                 public function removeNode(node:Node, performAction:Function):void {
81                         performAction(new RemoveNodeFromWayAction(this, node, nodes));
82                 }
83
84         public function removeNodeByIndex(index:uint, performAction:Function, fireEvent:Boolean=true):void {
85             performAction(new RemoveNodeByIndexAction(this, nodes, index, fireEvent));
86         }
87
88                 public function sliceNodes(start:int,end:int):Array {
89                         return nodes.slice(start,end);
90                 }
91
92         public function deleteNodesFrom(start:int, performAction:Function):void {
93             for (var i:int=nodes.length-1; i>=start; i--) {
94               performAction(new RemoveNodeByIndexAction(this, nodes, i));
95             }
96             markDirty();
97         }
98
99                 public function mergeWith(way:Way,topos:int,frompos:int, performAction:Function):void {
100                         performAction(new MergeWaysAction(this, way, topos, frompos));
101                 }
102                 
103                 public function addToEnd(topos:int,node:Node, performAction:Function):void {
104                         if (topos==0) {
105                                 if (nodes[0]==node) { return; }
106                                 prependNode(node, performAction);
107                         } else {
108                                 if (nodes[nodes.length-1]==node) { return; }
109                                 appendNode(node, performAction);
110                         }
111                 }
112
113         public function reverseNodes(performAction:Function):void {
114             performAction(new ReverseNodesAction(this, nodes));
115         }
116
117         /**
118          * Finds the 1st way segment which intersects the projected
119          * coordinate and adds the node to that segment. If snap is
120          * specified then the node is moved to exactly bisect the
121          * segment.
122          */
123         public function insertNodeAtClosestPosition(newNode:Node, isSnap:Boolean, performAction:Function):int {
124             var closestProportion:Number = 1;
125             var newIndex:uint = 0;
126             var nP:Point = new Point(newNode.lon, newNode.latp);
127             var snapped:Point = null;
128             
129             for ( var i:uint; i < length - 1; i++ ) {
130                 var node1:Node = getNode(i);
131                 var node2:Node = getNode(i+1);
132                 var p1:Point = new Point(node1.lon, node1.latp);
133                 var p2:Point = new Point(node2.lon, node2.latp);
134                 
135                 var directDist:Number = Point.distance(p1, p2);
136                 var viaNewDist:Number = Point.distance(p1, nP) + Point.distance(nP, p2);
137                         
138                 var proportion:Number = Math.abs(viaNewDist/directDist - 1);
139                 if ( proportion < closestProportion ) {
140                     newIndex = i+1;
141                     closestProportion = proportion;
142                     snapped = calculateSnappedPoint(p1, p2, nP);
143                 }
144             }
145             
146             // splice in new node
147             if ( isSnap ) {
148                 newNode.latp = snapped.y;
149                 newNode.lon = snapped.x;
150             }
151             insertNode(newIndex, newNode, performAction);
152             return newIndex;
153         }
154         
155         private function calculateSnappedPoint(p1:Point, p2:Point, nP:Point):Point {
156             var w:Number = p2.x - p1.x;
157             var h:Number = p2.y - p1.y;
158             var u:Number = ((nP.x-p1.x) * w + (nP.y-p1.y) * h) / (w*w + h*h);
159             return new Point(p1.x + u*w, p1.y+u*h);
160         }
161         
162         public override function toString():String {
163             return "Way("+id+"@"+version+"): "+getTagList()+
164                      " "+nodes.map(function(item:Node,index:int, arr:Array):String {return item.id.toString();}).join(",");
165         }
166
167                 public function isArea():Boolean {
168                         if (nodes.length==0) { return false; }
169                         return (nodes[0].id==nodes[nodes.length-1].id && nodes.length>2);
170                 }
171                 
172                 public override function remove(performAction:Function):void {
173                         performAction(new DeleteWayAction(this, setDeletedState, nodes));
174                 }
175
176                 public function get clockwise():Boolean {
177                         var lowest:uint=0;
178                         var xmin:Number=-999999; var ymin:Number=-999999;
179                         for (var i:uint=0; i<nodes.length; i++) {
180                                 if      (nodes[i].latp> ymin) { lowest=i; xmin=nodes[i].lon; ymin=nodes[i].latp; }
181                                 else if (nodes[i].latp==ymin
182                                           && nodes[i].lon > xmin) { lowest=i; xmin=nodes[i].lon; ymin=nodes[i].latp; }
183                         }
184                         return (this.onLeft(lowest)>0);
185                 }
186                 
187                 private function onLeft(j:uint):Number {
188                         var left:Number=0;
189                         var i:int, k:int;
190                         if (nodes.length>=3) {
191                                 i=j-1; if (i==-1) { i=nodes.length-2; }
192                                 k=j+1; if (k==nodes.length) { k=1; }
193                                 left=((nodes[j].lon-nodes[i].lon) * (nodes[k].latp-nodes[i].latp) -
194                                           (nodes[k].lon-nodes[i].lon) * (nodes[j].latp-nodes[i].latp));
195                         }
196                         return left;
197                 }
198
199                 internal override function isEmpty():Boolean {
200                         return (deleted || (nodes.length==0));
201                 }
202
203                 public override function getType():String {
204                         return 'way';
205                 }
206     }
207
208 }