6a0cf9f479e72f2dcbb000f14b7f036267dccab5
[potlatch2.git] / net / systemeD / potlatch2 / tools / Circularise.as
1 package net.systemeD.potlatch2.tools {
2         import net.systemeD.halcyon.Map;
3         import net.systemeD.halcyon.connection.CompositeUndoableAction;
4         import net.systemeD.halcyon.connection.Node;
5         import net.systemeD.halcyon.connection.Way;
6
7         /** Tool to transform a closed way of at least 3 distinct points into a circular shape, inserting more nodes as necessary. Call only the static function circularise(). */
8         public class Circularise {
9
10                 /** Carries out the circularisation of a way.
11                  * @param way The way to be made round: must be closed, must have at least 3 distinct points.
12                  * @param map The map that the way belongs to.
13                  * @param performAction A function that will be passed a CompositeUndoableAction representing the transformation. In other words, a function that will push the result onto an undo stack.
14                  * */ 
15                 public static function circularise(way:Way,map:Map,performAction:Function):void {
16                         if (way.length<4) { return; }
17
18                         var a:Node=way.getNode(0);
19                         var b:Node=way.getNode(way.length-1);
20                         if (a!=b) { return; }
21
22             new Circularise(way, map, performAction).run();
23         }
24         
25         private var way:Way;
26         private var map:Map;
27                 private var performAction:Function;
28         
29         // centre
30         private var cx:Number=0;
31         private var cy:Number=0;
32         
33         // distance to centre
34                 private var d:Number=0;
35         
36         // our undoable
37         private var action:CompositeUndoableAction = new CompositeUndoableAction("Circularise");
38
39         // recording the node lats lons so we're not relying on instant update
40         private var lats:Array = [];
41                 private var lons:Array = [];
42
43         function Circularise(way:Way, map:Map, performAction:Function) {
44             this.way = way;
45             this.map = map;
46                         this.performAction = performAction;
47         }
48         
49         private function run():void {
50             calculateCentre();
51             calculateCentreDistance();
52
53             var i:uint;
54             var j:uint;
55             var n:Node;
56             
57                         // Move each node
58                         for (i=0; i<way.length-1; i++) {
59                                 n=way.getNode(i);
60                                 var c:Number=Math.sqrt(Math.pow(n.lon-cx,2)+Math.pow(n.latp-cy,2));
61                                 var lat:Number = cy+(n.latp-cy)/c*d;
62                                 var lon:Number = cx+(n.lon -cx)/c*d;
63                                 n.setLonLatp(lon, lat, action.push);
64                                 
65                                 // record the lat lons we're using as the node won't update
66                                 // till later
67                                 lats.push(lat);
68                                 lons.push(lon);
69                         }
70
71                         // Insert extra nodes to make circle
72                         // clockwise: angles decrease, wrapping round from -170 to 170
73                         i=0;
74                         var clockwise:Boolean=way.clockwise;
75                         var diff:Number, ang:Number;
76                         while (i<lons.length) {
77                                 j=(i+1) % lons.length;
78                                 var a1:Number=Math.atan2(lons[i]-cx, lats[i]-cy)*(180/Math.PI);
79                                 var a2:Number=Math.atan2(lons[j]-cx, lats[j]-cy)*(180/Math.PI);
80
81                                 if (clockwise) {
82                                         if (a2>a1) { a2=a2-360; }
83                                         diff=a1-a2;
84                                         if (diff>20) {
85                                                 for (ang=a1-20; ang>a2+10; ang-=20) {
86                                                     insertNode(ang, i+1);
87                                                         j++; i++;
88                                                 }
89                                         }
90                                 } else {
91                                         if (a1>a2) { a1=a1-360; }
92                                         diff=a2-a1;
93                                         if (diff>20) {
94                                                 for (ang=a1+20; ang<a2-10; ang+=20) {
95                                                     insertNode(ang, i+1);
96                                                         j++; i++;
97                                                 }
98                                         }
99                                 }
100                                 i++;
101                         }
102
103                         performAction(action);
104                 }
105
106         private function calculateCentre():void {
107                         // Find centre-point
108                         // ** should be refactored to be within Way.as, so we can share with WayUI.as
109             var i:uint, n:Node;
110                         
111                         /* This code is buggy - in some instances (particularly rectangles?) the centre is displaced, meaning an excessively large circle
112                            in the wrong place. */
113                         /*  
114                         var b:Node=way.getNode(way.length-1);
115                         var patharea:Number=0;
116                         var lx:Number=b.lon;
117                         var ly:Number=b.latp;
118                         var totx:Number=0, toty:Number=0;
119                         for (i=0; i<way.length; i++) {
120                                 n=way.getNode(i);
121                 trace('DEBUG lon, latp: ' + n.lon + ',' + n.latp);
122                                 var sc:Number = (lx*n.latp-n.lon*ly);
123                                 cx += (lx+n.lon )*sc;
124                                 cy += (ly+n.latp)*sc;
125                                 patharea += sc;
126                                 lx=n.lon; ly=n.latp;
127                         }
128                         patharea/=2;
129                         cx/=patharea*6;
130                         cy/=patharea*6;
131             trace('DEBUG cx, cy: ' + cx + ',' + cy);
132                         trace('DEBUG totx, toty: ' + totx + ',' + toty);
133                         */
134             /* Naive algorithm seems to produce a good result. */
135             var totx:Number=0, toty:Number=0;
136             for (i=0; i<way.length; i++) {
137                 n=way.getNode(i);
138                 totx+=n.lon;
139                 toty+=n.latp;
140             }
141             cx = totx / way.length;
142             cy = toty / way.length;
143                         /*
144                         Alternative: use WayUI centroid algorithm, although that has the same bug?
145                         cx = map.coord2lon(WayUI(map.paint.wayuis[way.id]).centroid_x);
146                         cy = map.coord2latp(WayUI(map.paint.wayuis[way.id]).centroid_y);
147                         */
148                         
149         }
150
151         private function calculateCentreDistance():void {
152                         // Average distance to centre
153                         // (first + last are the same node, don't use twice)
154                         for (var i:uint = 0; i < way.length - 1; i++) {
155                                 d+=Math.sqrt(Math.pow(way.getNode(i).lon -cx,2)+
156                                                          Math.pow(way.getNode(i).latp-cy,2));
157                         }
158                         d /= way.length - 1;
159             }
160
161                 private function insertNode(ang:Number, index:int):void {
162                         var lat:Number = cy+Math.cos(ang*Math.PI/180)*d;
163                         var lon:Number = cx+Math.sin(ang*Math.PI/180)*d;
164                         lats.splice(index, 0, lat);
165                         lons.splice(index, 0, lon);
166                         var newNode:Node = map.connection.createNode({}, map.latp2lat(lat), lon, action.push);
167                         way.insertNode(index, newNode, action.push);
168                 }
169         }
170 }