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;
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 {
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.
15 public static function circularise(way:Way,map:Map,performAction:Function):void {
16 if (way.length<4) { return; }
18 var a:Node=way.getNode(0);
19 var b:Node=way.getNode(way.length-1);
22 new Circularise(way, map, performAction).run();
27 private var performAction:Function;
30 private var cx:Number=0;
31 private var cy:Number=0;
34 private var d:Number=0;
37 private var action:CompositeUndoableAction = new CompositeUndoableAction("Circularise");
39 // recording the node lats lons so we're not relying on instant update
40 private var lats:Array = [];
41 private var lons:Array = [];
43 function Circularise(way:Way, map:Map, performAction:Function) {
46 this.performAction = performAction;
49 private function run():void {
51 calculateCentreDistance();
58 for (i=0; i<way.length-1; 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);
65 // record the lat lons we're using as the node won't update
71 // Insert extra nodes to make circle
72 // clockwise: angles decrease, wrapping round from -170 to 170
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);
82 if (a2>a1) { a2=a2-360; }
85 for (ang=a1-20; ang>a2+10; ang-=20) {
91 if (a1>a2) { a1=a1-360; }
94 for (ang=a1+20; ang<a2-10; ang+=20) {
103 performAction(action);
106 private function calculateCentre():void {
108 // ** should be refactored to be within Way.as, so we can share with WayUI.as
111 /* This code is buggy - in some instances (particularly rectangles?) the centre is displaced, meaning an excessively large circle
112 in the wrong place. */
114 var b:Node=way.getNode(way.length-1);
115 var patharea:Number=0;
117 var ly:Number=b.latp;
118 var totx:Number=0, toty:Number=0;
119 for (i=0; i<way.length; 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;
131 trace('DEBUG cx, cy: ' + cx + ',' + cy);
132 trace('DEBUG totx, toty: ' + totx + ',' + toty);
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++) {
141 cx = totx / way.length;
142 cy = toty / way.length;
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);
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));
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 = way.connection.createNode({}, map.latp2lat(lat), lon, action.push);
167 way.insertNode(index, newNode, action.push);