1 package net.systemeD.halcyon.connection {
2 import flash.geom.Point;
3 import net.systemeD.halcyon.Globals;
5 public class Way extends Entity {
6 private var nodes:Array;
7 public static var entity_type:String = 'way';
9 public function Way(id:Number, version:uint, tags:Object, loaded:Boolean, nodes:Array) {
10 super(id, version, tags, loaded);
12 for each (var node:Node in nodes) { node.addParent(this); }
15 public function update(version:uint, tags:Object, loaded:Boolean, nodes:Array):void {
17 for each (node in this.nodes) { node.removeParent(this); }
18 updateEntityProperties(version,tags,loaded); this.nodes=nodes;
19 for each (node in nodes) { node.addParent(this); }
22 public function get length():uint {
26 public function getNode(index:uint):Node {
30 public function getLastNode():Node {
31 return nodes[nodes.length-1];
34 public function insertNode(index:uint, node:Node):void {
36 nodes.splice(index, 0, node);
38 dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_ADDED, node, this, index));
41 public function appendNode(node:Node):uint {
45 dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_ADDED, node, this, nodes.length - 1));
49 public function prependNode(node:Node):uint {
53 dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_ADDED, node, this, 0));
57 public function indexOfNode(node:Node):uint {
58 return nodes.indexOf(node);
61 public function removeNode(node:Node):void {
63 while ((i=nodes.indexOf(i))>-1) {
65 dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_REMOVED, node, this, i));
67 node.removeParent(this);
71 public function removeNodeByIndex(index:uint):void {
72 var removed:Array=nodes.splice(index, 1);
73 if (nodes.indexOf(removed[0])==-1) { removed[0].removeParent(this); }
75 dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_REMOVED, removed[0], this, index));
78 public function removeAllNodes():void {
80 while (nodes.length) {
82 dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_REMOVED, node, this, 0));
83 // ** the event mechanism calls redraw once per wayNodeRemoved, which isn't too efficient
84 // so we should probably add a 'redraw' flag to WayNodeEvent
85 node.removeParent(this);
87 // ** we should send an event to delete the entire way
90 public function sliceNodes(start:int,end:int):Array {
91 return nodes.slice(start,end);
94 public function deleteNodesFrom(start:int):void {
98 public function mergeWith(way:Way,topos:int,frompos:int):void {
100 Globals.vars.root.addDebug("way "+id+", merging with "+way.id+", adding to "+topos+" , from "+frompos);
103 for each (var r:Relation in way.parentRelations) {
104 // ** needs to copy roles as well
105 if (r.findEntityMemberIndex(this)==-1) {
106 r.appendMember(new RelationMember(this, ''));
111 var t:Object=way.getTagsHash();
112 for (var k:String in t) {
113 if (getTag(k) && getTag(k)!=t[k]) {
114 setTag(k,getTag(k)+"; "+t[k]);
115 // ** send a warning about tags not matching
122 if (frompos==0) { for (i=0; i<way.length; i++) { addToEnd(topos,way.getNode(i)); } }
123 else { for (i=way.length-1; i>=0; i--) { addToEnd(topos,way.getNode(i)); } }
129 private function addToEnd(topos:int,node:Node):void {
130 Globals.vars.root.addDebug("adding "+node.id+" at "+topos);
132 if (nodes[0]==node) { return; }
135 if (nodes[nodes.length-1]==node) { return; }
143 * Finds the 1st way segment which intersects the projected
144 * coordinate and adds the node to that segment. If snap is
145 * specified then the node is moved to exactly bisect the
148 public function insertNodeAtClosestPosition(newNode:Node, isSnap:Boolean):int {
149 var closestProportion:Number = 1;
150 var newIndex:uint = 0;
151 var nP:Point = new Point(newNode.lon, newNode.latp);
152 var snapped:Point = null;
154 for ( var i:uint; i < length - 1; i++ ) {
155 var node1:Node = getNode(i);
156 var node2:Node = getNode(i+1);
157 var p1:Point = new Point(node1.lon, node1.latp);
158 var p2:Point = new Point(node2.lon, node2.latp);
160 var directDist:Number = Point.distance(p1, p2);
161 var viaNewDist:Number = Point.distance(p1, nP) + Point.distance(nP, p2);
163 var proportion:Number = Math.abs(viaNewDist/directDist - 1);
164 if ( proportion < closestProportion ) {
166 closestProportion = proportion;
167 snapped = calculateSnappedPoint(p1, p2, nP);
171 // splice in new node
173 newNode.latp = snapped.y;
174 newNode.lon = snapped.x;
176 insertNode(newIndex, newNode);
180 private function calculateSnappedPoint(p1:Point, p2:Point, nP:Point):Point {
181 var w:Number = p2.x - p1.x;
182 var h:Number = p2.y - p1.y;
183 var u:Number = ((nP.x-p1.x) * w + (nP.y-p1.y) * h) / (w*w + h*h);
184 return new Point(p1.x + u*w, p1.y+u*h);
187 public override function toString():String {
188 return "Way("+id+"@"+version+"): "+getTagList()+
189 " "+nodes.map(function(item:Node,index:int, arr:Array):String {return item.id.toString();}).join(",");
192 public function isArea():Boolean {
193 return (nodes[0].id==nodes[nodes.length-1].id && nodes.length>2);
196 public override function remove():void {
198 for each (var node:Node in nodes) {
199 node.removeParent(this);
200 if (!node.hasParentWays) { node.remove(); }
204 dispatchEvent(new EntityEvent(Connection.WAY_DELETED, this));
207 internal override function isEmpty():Boolean {
208 return (deleted || (nodes.length==0));
211 public override function getType():String {