gah
[potlatch2.git] / net / systemeD / halcyon / connection / Way.as
1 package net.systemeD.halcyon.connection {
2     import flash.geom.Point;
3     
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, uid:Number = NaN, timestamp:String = null) {
15             super(id, version, tags, loaded, uid, timestamp);
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, parentsLoaded:Boolean, nodes:Array, uid:Number = NaN, timestamp:String = null):void {
22                         var node:Node;
23                         for each (node in this.nodes) { node.removeParent(this); }
24                         updateEntityProperties(version,tags,loaded,parentsLoaded,uid,timestamp); 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 override function within(left:Number,right:Number,top:Number,bottom:Number):Boolean {
47                         if (!edge_l ||
48                                 (edge_l<left   && edge_r<left  ) ||
49                             (edge_l>right  && edge_r>right ) ||
50                             (edge_b<bottom && edge_t<bottom) ||
51                             (edge_b>top    && edge_b>top   ) || deleted) { return false; }
52                         return true;
53                 }
54         
55         public function getNode(index:uint):Node {
56             return nodes[index];
57         }
58
59                 public function getLastNode():Node {
60                         return nodes[nodes.length-1];
61                 }
62                 
63                 /** Given one node, return the next in sequence, cycling around a loop if necessary. */
64                 // TODO make behave correctly for P-shaped topologies?
65                 public function getNextNode(node:Node):Node {
66                         // If the last node in a loop is selected, this behaves correctly.
67                     var i:uint = indexOfNode(node);
68                     if(i < length-1)
69                     return nodes[i+1];
70                 return null;
71                 // What should happen for very short lengths?      
72                 }
73         
74         // TODO make behave correctly for P-shaped topologies?
75         /** Given one node, return the previous, cycling around a loop if necessary. */
76         public function getPrevNode(node:Node):Node {
77             var i:uint = indexOfNode(node);
78             if(i > 0)
79                 return nodes[i-1];
80             if(i == 0 && isArea() )
81                 return nodes[nodes.length - 2]
82             return null;
83             // What should happen for very short lengths?      
84         }
85
86         public function insertNode(index:uint, node:Node, performAction:Function):void {
87                         performAction(new AddNodeToWayAction(this, node, nodes, index));
88         }
89
90         public function appendNode(node:Node, performAction:Function):uint {
91                         performAction(new AddNodeToWayAction(this, node, nodes, -1));
92             return nodes.length + 1;
93         }
94         
95         public function prependNode(node:Node, performAction:Function):uint {
96                         performAction(new AddNodeToWayAction(this, node, nodes, 0));
97             return nodes.length + 1;
98         }
99         
100         // return the index of the Node, or -1 if not found
101         public function indexOfNode(node:Node):int {
102             return nodes.indexOf(node);
103         }
104
105                 public function hasOnceOnly(node:Node):Boolean {
106                         return nodes.indexOf(node)==nodes.lastIndexOf(node);
107                 }
108                 
109                 public function hasLockedNodes():Boolean {
110                         for each (var node:Node in nodes) {
111                                 if (node.locked) { return true; }
112                         }
113                         return false;
114                 }
115
116                 public function removeNode(node:Node, performAction:Function):void {
117                         performAction(new RemoveNodeFromWayAction(this, node, nodes));
118                 }
119
120         public function removeNodeByIndex(index:uint, performAction:Function, fireEvent:Boolean=true):void {
121             performAction(new RemoveNodeByIndexAction(this, nodes, index, fireEvent));
122         }
123
124                 public function sliceNodes(start:int,end:int):Array {
125                         return nodes.slice(start,end);
126                 }
127
128         public function deleteNodesFrom(start:int, performAction:Function):void {
129             for (var i:int=nodes.length-1; i>=start; i--) {
130               performAction(new RemoveNodeByIndexAction(this, nodes, i));
131             }
132             markDirty();
133         }
134
135                 public function mergeWith(way:Way,topos:int,frompos:int, performAction:Function):void {
136                         performAction(new MergeWaysAction(this, way, topos, frompos));
137                 }
138                 
139                 public function addToEnd(topos:int,node:Node, performAction:Function):void {
140                         if (topos==0) {
141                                 if (nodes[0]==node) { return; }
142                                 prependNode(node, performAction);
143                         } else {
144                                 if (nodes[nodes.length-1]==node) { return; }
145                                 appendNode(node, performAction);
146                         }
147                 }
148
149         public function reverseNodes(performAction:Function):void {
150             performAction(new ReverseNodesAction(this, nodes));
151         }
152
153         /**
154          * Finds the 1st way segment which intersects the projected
155          * coordinate and adds the node to that segment. If snap is
156          * specified then the node is moved to exactly bisect the
157          * segment.
158          */
159         public function insertNodeAtClosestPosition(newNode:Node, isSnap:Boolean, performAction:Function):int {
160             var closestProportion:Number = 1;
161             var newIndex:uint = 0;
162             var nP:Point = new Point(newNode.lon, newNode.latp);
163             var snapped:Point = null;
164             
165             for ( var i:uint; i < length - 1; i++ ) {
166                 var node1:Node = getNode(i);
167                 var node2:Node = getNode(i+1);
168                 var p1:Point = new Point(node1.lon, node1.latp);
169                 var p2:Point = new Point(node2.lon, node2.latp);
170                 
171                 var directDist:Number = Point.distance(p1, p2);
172                 var viaNewDist:Number = Point.distance(p1, nP) + Point.distance(nP, p2);
173                         
174                 var proportion:Number = Math.abs(viaNewDist/directDist - 1);
175                 if ( proportion < closestProportion ) {
176                     newIndex = i+1;
177                     closestProportion = proportion;
178                     snapped = calculateSnappedPoint(p1, p2, nP);
179                 }
180             }
181             
182             // splice in new node
183             if ( isSnap ) {
184                 newNode.setLonLatp(snapped.x, snapped.y, performAction);
185             }
186             insertNode(newIndex, newNode, performAction);
187             return newIndex;
188         }
189         
190         private function calculateSnappedPoint(p1:Point, p2:Point, nP:Point):Point {
191             var w:Number = p2.x - p1.x;
192             var h:Number = p2.y - p1.y;
193             var u:Number = ((nP.x-p1.x) * w + (nP.y-p1.y) * h) / (w*w + h*h);
194             return new Point(p1.x + u*w, p1.y+u*h);
195         }
196         
197         public override function toString():String {
198             return "Way("+id+"@"+version+"): "+getTagList()+
199                      " "+nodes.map(function(item:Node,index:int, arr:Array):String {return item.id.toString();}).join(",");
200         }
201
202                 public function isArea():Boolean {
203                         if (nodes.length==0) { return false; }
204                         return (nodes[0].id==nodes[nodes.length-1].id && nodes.length>2);
205                 }
206                 
207                 public function endsWith(node:Node):Boolean {
208                         return (nodes[0]==node || nodes[nodes.length-1]==node);
209                 }
210                 
211                 public override function remove(performAction:Function):void {
212                         performAction(new DeleteWayAction(this, setDeletedState, nodes));
213                 }
214
215                 public override function nullify():void {
216                         nullifyEntity();
217                         nodes=[];
218                         edge_l=edge_r=edge_t=edge_b=NaN;
219                 }
220                 
221                 public function get clockwise():Boolean {
222                         var lowest:uint=0;
223                         var xmin:Number=-999999; var ymin:Number=-999999;
224                         for (var i:uint=0; i<nodes.length; i++) {
225                                 if      (nodes[i].latp> ymin) { lowest=i; xmin=nodes[i].lon; ymin=nodes[i].latp; }
226                                 else if (nodes[i].latp==ymin
227                                           && nodes[i].lon > xmin) { lowest=i; xmin=nodes[i].lon; ymin=nodes[i].latp; }
228                         }
229                         return (this.onLeft(lowest)>0);
230                 }
231                 
232                 private function onLeft(j:uint):Number {
233                         var left:Number=0;
234                         var i:int, k:int;
235                         if (nodes.length>=3) {
236                                 i=j-1; if (i==-1) { i=nodes.length-2; }
237                                 k=j+1; if (k==nodes.length) { k=1; }
238                                 left=((nodes[j].lon-nodes[i].lon) * (nodes[k].latp-nodes[i].latp) -
239                                           (nodes[k].lon-nodes[i].lon) * (nodes[j].latp-nodes[i].latp));
240                         }
241                         return left;
242                 }
243
244         public function get angle():Number {
245             var dx:Number = nodes[nodes.length-1].lon - nodes[0].lon;
246             var dy:Number = nodes[nodes.length-1].latp - nodes[0].latp;
247             if (dx != 0 || dy != 0) {
248                 return Math.atan2(dx,dy)*(180/Math.PI);
249             } else {
250                 return 0;
251             }
252         }
253
254                 internal override function isEmpty():Boolean {
255                         return (deleted || (nodes.length==0));
256                 }
257
258                 public override function getType():String {
259                         return 'way';
260                 }
261                 
262                 public override function isType(str:String):Boolean {
263                         if (str=='way') return true;
264                         if (str=='line' && !isArea()) return true;
265                         if (str=='area' &&  isArea()) return true;
266                         return false;
267                 }
268     }
269
270 }