1 package net.systemeD.potlatch2.tools {
2 import net.systemeD.halcyon.connection.Way;
3 import net.systemeD.halcyon.connection.Node;
4 import flash.geom.Point;
5 import net.systemeD.halcyon.connection.*;
7 /** Tool to transform a closed way by making corners right-angled, if possible. Call the static function quadrilateralise(). */
8 public class Quadrilateralise {
9 private static const NUM_STEPS:uint = 1000;
10 private static const TOLERANCE:Number = 1.0e-8;
13 * Attempts to make all corners of a way right angles. Returns true if it
14 * thought it was successful and false if it failed. If it fails it does not
16 * @param way Way to be transformed.
17 * @performAction Function that will be passed a CompositeUndoableAction parameter representing the transformation.
19 public static function quadrilateralise(way:Way,performAction:Function):Boolean {
20 // needs a closed way to work properly.
25 var functor:Quadrilateralise = new Quadrilateralise(way,performAction);
26 var score:Number = functor.goodness;
27 for (var i:uint = 0; i < NUM_STEPS; ++i) {
29 var newScore:Number = functor.goodness;
30 if (newScore > score) {
31 trace("Quadrilateralise blew up! " + newScore + " > " + score);
35 if (score < TOLERANCE) {
45 private var points:Array;
46 private var performAction:Function;
48 /** Private function that had to be declared public - do not call from outside this package. */
49 public function Quadrilateralise(way_:Way, performAction_:Function) {
51 performAction = performAction_;
52 points = way.sliceNodes(0, way.length - 1).map(function (n:Node, i:int, a:Array) : Point {
53 return new Point(n.lon, n.latp);
58 * returns the score of a particular corner, which is constructed so that all corners
59 * which are straight lines or 90-degree corners score close to zero and other angles
60 * score higher. The goal of this action is to minimise the sum of all scores.
62 private function scoreOf(a:Point, b:Point, c:Point):Number {
63 var p:Point = a.subtract(b);
64 var q:Point = c.subtract(b);
67 var dotp:Number = p.x*q.x + p.y*q.y;
68 // score is constructed so that +1, -1 and 0 are all scored 0, any other angle
70 var score:Number = 2.0 * Math.min(Math.abs(dotp-1.0), Math.min(Math.abs(dotp), Math.abs(dotp+1)));
74 // get the goodness (sum of scores) of the whole way.
75 private function get goodness():Number {
77 for (var i:uint = 1; i < points.length - 1; ++i) {
78 var score:Number = scoreOf(points[i-1], points[i], points[i+1]);
81 var startScore:Number = scoreOf(points[points.length-1], points[0], points[1]);
82 var endScore:Number = scoreOf(points[points.length-2], points[points.length-1], points[0]);
89 * One step of the solver. Moves points towards their neighbours, or away from them, depending on
90 * the angle of that corner.
92 private function step():void {
93 var motions:Array = points.map(function (b:Point, i:int, array:Array) : Point {
94 var a:Point = array[(i-1+array.length) % array.length];
95 var c:Point = array[(i+1) % array.length];
96 var p:Point = a.subtract(b);
97 var q:Point = c.subtract(b);
98 var scale:Number = p.length + q.length;
101 var dotp:Number = p.x*q.x + p.y*q.y;
102 // nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270).
103 if (dotp < -0.707106781186547) {
106 var v:Point = p.add(q);
107 v.normalize(0.1 * dotp * scale);
110 for (var i:uint = 0; i < motions.length; ++i) {
111 points[i] = points[i].add(motions[i]);
116 * call this only when happy with the result - it writes the positions back to the
117 * way, and hence to the DB or whatever.
119 private function updateWay():void {
120 var moveAction:CompositeUndoableAction = new CompositeUndoableAction("Move Way");
121 for (var i:uint = 0; i < points.length; ++i) {
122 way.getNode(i).setLonLatp( points[i].x, points[i].y, moveAction.push);
124 performAction(moveAction);