From 8b6bb964ad85dc710dd3fb6c725afba89f4add59 Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Sat, 2 Feb 2013 23:52:16 +0100 Subject: [PATCH 1/3] Added orthogonalize function (square corners) --- index.html | 2 + js/id/actions/orthogonalize.js | 132 ++++++++++++++++++++++++++++++ js/id/operations/orthogonalize.js | 25 ++++++ locale/en.js | 9 ++ 4 files changed, 168 insertions(+) create mode 100644 js/id/actions/orthogonalize.js create mode 100644 js/id/operations/orthogonalize.js diff --git a/index.html b/index.html index d8be52dcd..adfe92752 100644 --- a/index.html +++ b/index.html @@ -85,6 +85,7 @@ + @@ -112,6 +113,7 @@ + diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js new file mode 100644 index 000000000..c0a9d64ed --- /dev/null +++ b/js/id/actions/orthogonalize.js @@ -0,0 +1,132 @@ +iD.actions.Orthogonalize = function(wayId, map) { + + var action = function(graph) { + var way = graph.entity(wayId), + nodes = graph.childNodes(way), + tags = {},key,role; + + var points = nodes.map(function(n) { + return map.projection(n.loc); + }), + quad_nodes = []; + + var score = squareness(); + for (var i = 0; i < 1000; ++i) { + var motions = points.map(stepMap); + //return false; + for (var j = 0; j < motions.length; ++j) { + points[j] = addPoints(points[j],motions[j]); + } + var newScore = squareness(); + if (newScore > score) { + return false; + } + score = newScore; + if (score < 1.0e-8) { + break; + } + } + for (var i = 0; i < points.length; i++) { + quad_nodes.push(iD.Node({ loc: map.projection.invert(points[i]) })); + } + quad_nodes.push(quad_nodes[0]); + + for (var i = 0; i < nodes.length; i++) { + graph = graph.remove(nodes[i]); + } + + for (var i = 0; i < quad_nodes.length; i++) { + graph = graph.replace(quad_nodes[i]); + } + + return graph.replace(way.update({ + nodes: _.pluck(quad_nodes, 'id') + })); + + + function stepMap(b,i,array){ + var a,c,p,q = []; + a = array[(i-1+array.length) % array.length]; + c = array[(i+1) % array.length]; + p = subtractPoints(a,b); + q = subtractPoints(c,b); + + + var scale = p.length + q.length; + p = normalizePoint(p,1.0); + q = normalizePoint(q,1.0); + var dotp = p[0]*q[0] + p[1]*q[1]; + // nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270). + if (dotp < -0.707106781186547) { + dotp += 1.0; + } + var v = []; + v = addPoints(p,q); + v = normalizePoint(v,0.1 * dotp * scale); + return v; + } + + function squareness(){ + + var g = 0.0; + for (var i = 1; i < points.length - 1; i++) { + var score = scoreOfPoints(points[i-1], points[i], points[i+1]); + g += score; + } + var startScore = scoreOfPoints(points[points.length-1], points[0], points[1]); + var endScore = scoreOfPoints(points[points.length-2], points[points.length-1], points[0]); + g += startScore; + g += endScore; + return g; + } + + function scoreOfPoints(a, b, c) { + var p,q = []; + p = subtractPoints(a,b); + q = subtractPoints(c,b); + + p = normalizePoint(p,1.0); + q = normalizePoint(q,1.0); + + var dotp = p[0]*q[0] + p[1]*q[1]; + // score is constructed so that +1, -1 and 0 are all scored 0, any other angle + // is scored higher. + var score = 2.0 * Math.min(Math.abs(dotp-1.0), Math.min(Math.abs(dotp), Math.abs(dotp+1))); + return score; + } + + function subtractPoints(a,b){ + var vector = [0,0]; + vector[0] = a[0]-b[0]; + vector[1] = a[1]-b[1]; + return vector; + } + + function addPoints(a,b){ + var vector = [0,0]; + vector[0] = a[0]+b[0]; + vector[1] = a[1]+b[1]; + return vector; + } + + function normalizePoint(point,thickness){ + var vector = [0,0]; + var length = Math.sqrt( point[0] * point[0] + point[1] * point[1]); + if(length != 0){ + vector[0] = point[0]/length; + vector[1] = point[1]/length; + } + + vector[0] *= thickness; + vector[1] *= thickness; + + return vector; + } + }; + + action.enabled = function(graph) { + return graph.entity(wayId).isClosed(); + }; + + return action; +}; diff --git a/js/id/operations/orthogonalize.js b/js/id/operations/orthogonalize.js new file mode 100644 index 000000000..888d0015e --- /dev/null +++ b/js/id/operations/orthogonalize.js @@ -0,0 +1,25 @@ +iD.operations.Orthogonalize = function(selection, context) { + var entityId = selection[0], + action = iD.actions.Orthogonalize(entityId, context.map()); + + var operation = function() { + var annotation = t('operations.orthogonalize.annotation.' + context.geometry(entityId)); + context.perform(action, annotation); + }; + + operation.available = function() { + return selection.length === 1 && + context.entity(entityId).type === 'way'; + }; + + operation.enabled = function() { + return action.enabled(context.graph()); + }; + + operation.id = "orthogonalize"; + operation.key = t('operations.orthogonalize.key'); + operation.title = t('operations.orthogonalize.title'); + operation.description = t('operations.orthogonalize.description'); + + return operation; +}; diff --git a/locale/en.js b/locale/en.js index 866c7eca3..31d6a13de 100644 --- a/locale/en.js +++ b/locale/en.js @@ -65,6 +65,15 @@ locale.en = { area: "Made an area circular." } }, + orthogonalize: { + title: "Orthogonalize", + description: "Square these corners.", + key: "Q", + annotation: { + line: "Squared the corners of a line.", + area: "Squared the corners of an area." + } + }, delete: { title: "Delete", description: "Remove this from the map.", From 26b8b8789e1415c0e3adca277deb2131c929ca28 Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Sun, 3 Feb 2013 00:05:11 +0100 Subject: [PATCH 2/3] Fix case where parentnodes present --- js/id/actions/orthogonalize.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index c0a9d64ed..80c6a12c8 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -19,7 +19,7 @@ iD.actions.Orthogonalize = function(wayId, map) { } var newScore = squareness(); if (newScore > score) { - return false; + return graph; } score = newScore; if (score < 1.0e-8) { @@ -31,8 +31,22 @@ iD.actions.Orthogonalize = function(wayId, map) { } quad_nodes.push(quad_nodes[0]); - for (var i = 0; i < nodes.length; i++) { + for (i = 0; i < nodes.length; i++) { + if (graph.parentWays(nodes[i]).length > 1) { + var closest, closest_dist = Infinity, dist; + for (var j = 0; j < quad_nodes.length; j++) { + dist = iD.geo.dist(quad_nodes[j].loc, nodes[i].loc); + if (dist < closest_dist) { + closest_dist = dist; + closest = j; + } + } + quad_nodes.splice(closest, 1, nodes[i]); + if (closest === 0) quad_nodes.splice(quad_nodes.length - 1, 1, nodes[i]); + else if (closest === quad_nodes.length - 1) quad_nodes.splice(0, 1, nodes[i]); + } else { graph = graph.remove(nodes[i]); + } } for (var i = 0; i < quad_nodes.length; i++) { From b912097ee42fc540be636542c03be88b235a8f7d Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Sun, 3 Feb 2013 08:17:03 +0100 Subject: [PATCH 3/3] Tidy up: fix indents, spaces etc. - Update orthogonalize to match new circularize. - Add orthoganalize to test index.html - Revert whitespace on index.html --- js/id/actions/orthogonalize.js | 109 ++++++++++++++---------------- js/id/operations/orthogonalize.js | 2 +- test/index.html | 2 + 3 files changed, 54 insertions(+), 59 deletions(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index 80c6a12c8..830d1a1eb 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -1,20 +1,18 @@ -iD.actions.Orthogonalize = function(wayId, map) { +iD.actions.Orthogonalize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), - nodes = graph.childNodes(way), - tags = {},key,role; + nodes = graph.childNodes(way); var points = nodes.map(function(n) { - return map.projection(n.loc); - }), - quad_nodes = []; + return projection(n.loc); + }), + quad_nodes = []; var score = squareness(); - for (var i = 0; i < 1000; ++i) { + for (var i = 0; i < 1000; i++) { var motions = points.map(stepMap); - //return false; - for (var j = 0; j < motions.length; ++j) { + for (var j = 0; j < motions.length; j++) { points[j] = addPoints(points[j],motions[j]); } var newScore = squareness(); @@ -26,10 +24,9 @@ iD.actions.Orthogonalize = function(wayId, map) { break; } } - for (var i = 0; i < points.length; i++) { - quad_nodes.push(iD.Node({ loc: map.projection.invert(points[i]) })); + for (i = 0; i < points.length - 1; i++) { + quad_nodes.push(iD.Node({ loc: projection.invert(points[i]) })); } - quad_nodes.push(quad_nodes[0]); for (i = 0; i < nodes.length; i++) { if (graph.parentWays(nodes[i]).length > 1) { @@ -42,93 +39,89 @@ iD.actions.Orthogonalize = function(wayId, map) { } } quad_nodes.splice(closest, 1, nodes[i]); - if (closest === 0) quad_nodes.splice(quad_nodes.length - 1, 1, nodes[i]); - else if (closest === quad_nodes.length - 1) quad_nodes.splice(0, 1, nodes[i]); - } else { - graph = graph.remove(nodes[i]); } } - for (var i = 0; i < quad_nodes.length; i++) { - graph = graph.replace(quad_nodes[i]); + for (i = 0; i < quad_nodes.length; i++) { + graph = graph.replace(quad_nodes[i]); } - return graph.replace(way.update({ - nodes: _.pluck(quad_nodes, 'id') - })); + var ids = _.pluck(quad_nodes, 'id'), + difference = _.difference(_.uniq(way.nodes), ids); + ids.push(ids[0]); - function stepMap(b,i,array){ - var a,c,p,q = []; - a = array[(i-1+array.length) % array.length]; - c = array[(i+1) % array.length]; - p = subtractPoints(a,b); - q = subtractPoints(c,b); + graph = graph.replace(way.update({nodes: ids})); + for (i = 0; i < difference.length; i++) { + graph = iD.actions.DeleteNode(difference[i])(graph); + } + + return graph; + + function stepMap(b, i, array) { + var a, c, p, q = []; + a = array[(i - 1 + array.length) % array.length]; + c = array[(i + 1) % array.length]; + p = subtractPoints(a, b); + q = subtractPoints(c, b); var scale = p.length + q.length; - p = normalizePoint(p,1.0); - q = normalizePoint(q,1.0); - var dotp = p[0]*q[0] + p[1]*q[1]; + p = normalizePoint(p, 1.0); + q = normalizePoint(q, 1.0); + var dotp = p[0] *q[0] + p[1] *q[1]; // nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270). if (dotp < -0.707106781186547) { dotp += 1.0; } var v = []; - v = addPoints(p,q); - v = normalizePoint(v,0.1 * dotp * scale); + v = addPoints(p, q); + v = normalizePoint(v, 0.1 * dotp * scale); return v; } - function squareness(){ + function squareness() { var g = 0.0; for (var i = 1; i < points.length - 1; i++) { - var score = scoreOfPoints(points[i-1], points[i], points[i+1]); + var score = scoreOfPoints(points[i - 1], points[i], points[i + 1]); g += score; } - var startScore = scoreOfPoints(points[points.length-1], points[0], points[1]); - var endScore = scoreOfPoints(points[points.length-2], points[points.length-1], points[0]); + var startScore = scoreOfPoints(points[points.length - 1], points[0], points[1]); + var endScore = scoreOfPoints(points[points.length - 2], points[points.length - 1], points[0]); g += startScore; g += endScore; return g; } function scoreOfPoints(a, b, c) { - var p,q = []; - p = subtractPoints(a,b); - q = subtractPoints(c,b); + var p, q = []; + p = subtractPoints(a, b); + q = subtractPoints(c, b); + p = normalizePoint(p, 1.0); + q = normalizePoint(q, 1.0); - p = normalizePoint(p,1.0); - q = normalizePoint(q,1.0); - - var dotp = p[0]*q[0] + p[1]*q[1]; + var dotp = p[0] * q[0] + p[1] * q[1]; // score is constructed so that +1, -1 and 0 are all scored 0, any other angle // is scored higher. - var score = 2.0 * Math.min(Math.abs(dotp-1.0), Math.min(Math.abs(dotp), Math.abs(dotp+1))); + var score = 2.0 * Math.min(Math.abs(dotp - 1.0), Math.min(Math.abs(dotp), Math.abs(dotp + 1))); return score; } - function subtractPoints(a,b){ - var vector = [0,0]; - vector[0] = a[0]-b[0]; - vector[1] = a[1]-b[1]; - return vector; + function subtractPoints(a, b) { + return [a[0] - b[0], a[1] - b[1]]; } - function addPoints(a,b){ - var vector = [0,0]; - vector[0] = a[0]+b[0]; - vector[1] = a[1]+b[1]; - return vector; + function addPoints(a, b) { + return [a[0]+b[0],a[1]+b[1]]; } - function normalizePoint(point,thickness){ + function normalizePoint(point, thickness) { var vector = [0,0]; - var length = Math.sqrt( point[0] * point[0] + point[1] * point[1]); + var length = Math.sqrt(point[0] * point[0] + point[1] * point[1]); if(length != 0){ - vector[0] = point[0]/length; - vector[1] = point[1]/length; + vector[0] = point[0] / length; + vector[1] = point[1] / length; } vector[0] *= thickness; diff --git a/js/id/operations/orthogonalize.js b/js/id/operations/orthogonalize.js index 888d0015e..c59ed13e4 100644 --- a/js/id/operations/orthogonalize.js +++ b/js/id/operations/orthogonalize.js @@ -1,6 +1,6 @@ iD.operations.Orthogonalize = function(selection, context) { var entityId = selection[0], - action = iD.actions.Orthogonalize(entityId, context.map()); + action = iD.actions.Orthogonalize(entityId, context.projection); var operation = function() { var annotation = t('operations.orthogonalize.annotation.' + context.geometry(entityId)); diff --git a/test/index.html b/test/index.html index b6d925923..551a87621 100644 --- a/test/index.html +++ b/test/index.html @@ -73,6 +73,7 @@ + @@ -108,6 +109,7 @@ +