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