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.",