1 package net.systemeD.halcyon.connection {
2 import flash.geom.Point;
4 import net.systemeD.halcyon.connection.actions.*;
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';
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);
17 for each (var node:Node in nodes) { node.addParent(this); }
21 public function update(version:uint, tags:Object, loaded:Boolean, parentsLoaded:Boolean, nodes:Array, uid:Number = NaN, timestamp:String = null):void {
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); }
29 public function get length():uint {
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); }
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);
46 public override function within(left:Number,right:Number,top:Number,bottom:Number):Boolean {
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; }
55 public function getNode(index:uint):Node {
59 public function getFirstNode():Node {
63 public function getLastNode():Node {
64 return nodes[nodes.length-1];
67 /** Given one node, return the next in sequence, cycling around a loop if necessary. */
68 // TODO make behave correctly for P-shaped topologies?
69 public function getNextNode(node:Node):Node {
70 // If the last node in a loop is selected, this behaves correctly.
71 var i:uint = indexOfNode(node);
75 // What should happen for very short lengths?
78 // TODO make behave correctly for P-shaped topologies?
79 /** Given one node, return the previous, cycling around a loop if necessary. */
80 public function getPrevNode(node:Node):Node {
81 var i:uint = indexOfNode(node);
84 if(i == 0 && isArea() )
85 return nodes[nodes.length - 2]
87 // What should happen for very short lengths?
90 public function insertNode(index:uint, node:Node, performAction:Function):void {
91 performAction(new AddNodeToWayAction(this, node, nodes, index));
94 public function appendNode(node:Node, performAction:Function):uint {
95 performAction(new AddNodeToWayAction(this, node, nodes, -1));
96 return nodes.length + 1;
99 public function prependNode(node:Node, performAction:Function):uint {
100 performAction(new AddNodeToWayAction(this, node, nodes, 0));
101 return nodes.length + 1;
104 // return the index of the Node, or -1 if not found
105 public function indexOfNode(node:Node):int {
106 return nodes.indexOf(node);
109 public function hasOnceOnly(node:Node):Boolean {
110 return nodes.indexOf(node)==nodes.lastIndexOf(node);
113 public function hasLockedNodes():Boolean {
114 for each (var node:Node in nodes) {
115 if (node.locked) { return true; }
120 public function removeNode(node:Node, performAction:Function):void {
121 performAction(new RemoveNodeFromWayAction(this, node, nodes));
124 public function removeNodeByIndex(index:uint, performAction:Function, fireEvent:Boolean=true):void {
125 performAction(new RemoveNodeByIndexAction(this, nodes, index, fireEvent));
128 public function sliceNodes(start:int,end:int):Array {
129 return nodes.slice(start,end);
132 public function deleteNodesFrom(start:int, performAction:Function):void {
133 for (var i:int=nodes.length-1; i>=start; i--) {
134 performAction(new RemoveNodeByIndexAction(this, nodes, i));
139 /** Merges another way into this one, removing the other one. */
140 public function mergeWith(way:Way,topos:int,frompos:int, performAction:Function):void {
141 performAction(new MergeWaysAction(this, way, topos, frompos));
144 public function addToEnd(topos:int,node:Node, performAction:Function):void {
146 if (nodes[0]==node) { return; }
147 prependNode(node, performAction);
149 if (nodes[nodes.length-1]==node) { return; }
150 appendNode(node, performAction);
154 public function reverseNodes(performAction:Function):void {
155 performAction(new ReverseNodesAction(this, nodes));
159 * Finds the 1st way segment which intersects the projected
160 * coordinate and adds the node to that segment. If snap is
161 * specified then the node is moved to exactly bisect the
164 public function insertNodeAtClosestPosition(newNode:Node, isSnap:Boolean, performAction:Function):int {
165 var closestProportion:Number = 1;
166 var newIndex:uint = 0;
167 var nP:Point = new Point(newNode.lon, newNode.latp);
168 var snapped:Point = null;
170 for ( var i:uint; i < length - 1; i++ ) {
171 var node1:Node = getNode(i);
172 var node2:Node = getNode(i+1);
173 var p1:Point = new Point(node1.lon, node1.latp);
174 var p2:Point = new Point(node2.lon, node2.latp);
176 var directDist:Number = Point.distance(p1, p2);
177 var viaNewDist:Number = Point.distance(p1, nP) + Point.distance(nP, p2);
179 var proportion:Number = Math.abs(viaNewDist/directDist - 1);
180 if ( proportion < closestProportion ) {
182 closestProportion = proportion;
183 snapped = calculateSnappedPoint(p1, p2, nP);
187 // splice in new node
189 newNode.setLonLatp(snapped.x, snapped.y, performAction);
191 insertNode(newIndex, newNode, performAction);
195 private function calculateSnappedPoint(p1:Point, p2:Point, nP:Point):Point {
196 var w:Number = p2.x - p1.x;
197 var h:Number = p2.y - p1.y;
198 var u:Number = ((nP.x-p1.x) * w + (nP.y-p1.y) * h) / (w*w + h*h);
199 return new Point(p1.x + u*w, p1.y+u*h);
202 public override function toString():String {
203 return "Way("+id+"@"+version+"): "+getTagList()+
204 " "+nodes.map(function(item:Node,index:int, arr:Array):String {return item.id.toString();}).join(",");
207 public function isArea():Boolean {
208 if (nodes.length==0) { return false; }
209 return (nodes[0].id==nodes[nodes.length-1].id && nodes.length>2);
212 public function endsWith(node:Node):Boolean {
213 return (nodes[0]==node || nodes[nodes.length-1]==node);
216 public override function remove(performAction:Function):void {
217 performAction(new DeleteWayAction(this, setDeletedState, nodes));
220 public override function nullify():void {
223 edge_l=edge_r=edge_t=edge_b=NaN;
226 public function get clockwise():Boolean {
228 var xmin:Number=-999999; var ymin:Number=-999999;
229 for (var i:uint=0; i<nodes.length; i++) {
230 if (nodes[i].latp> ymin) { lowest=i; xmin=nodes[i].lon; ymin=nodes[i].latp; }
231 else if (nodes[i].latp==ymin
232 && nodes[i].lon > xmin) { lowest=i; xmin=nodes[i].lon; ymin=nodes[i].latp; }
234 return (this.onLeft(lowest)>0);
237 private function onLeft(j:uint):Number {
240 if (nodes.length>=3) {
241 i=j-1; if (i==-1) { i=nodes.length-2; }
242 k=j+1; if (k==nodes.length) { k=1; }
243 left=((nodes[j].lon-nodes[i].lon) * (nodes[k].latp-nodes[i].latp) -
244 (nodes[k].lon-nodes[i].lon) * (nodes[j].latp-nodes[i].latp));
249 public function get angle():Number {
250 var dx:Number = nodes[nodes.length-1].lon - nodes[0].lon;
251 var dy:Number = nodes[nodes.length-1].latp - nodes[0].latp;
252 if (dx != 0 || dy != 0) {
253 return Math.atan2(dx,dy)*(180/Math.PI);
259 internal override function isEmpty():Boolean {
260 return (deleted || (nodes.length==0));
263 public override function getType():String {
267 public override function isType(str:String):Boolean {
268 if (str=='way') return true;
269 if (str=='line' && !isArea()) return true;
270 if (str=='area' && isArea()) return true;
274 /** Whether the way has a loop that joins back midway along its length */
275 public function isPShape():Boolean {
276 return getFirstNode() != getLastNode() && (!hasOnceOnly(getFirstNode()) || !hasOnceOnly(getLastNode()) );
279 /** Given a P-shaped way, return the index of midway node that one end connects back to. */
280 public function getPJunctionNodeIndex():uint {
282 if (hasOnceOnly(getFirstNode())) {
283 // nodes[0] is the free end
284 return nodes.indexOf(getLastNode());
286 // nodes[0] is in the loop
287 return nodes.lastIndexOf(getFirstNode());