JOSM script to select redundant nodes in buildings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Given a selection of buildings, select redundant (i.e. collinear, non-tagged, | |
// non-glued) nodes. | |
// | |
// Dependencies: | |
// | |
// - [JOSM Scripting Plugin](https://gubaer.github.io/josm-scripting-plugin/) | |
// | |
// Usage: | |
// | |
// 1. Select some buildings for analysis, e.g. via Search → `new building`. | |
// 2. Run the script. | |
// 3. Review the selected nodes, deselecting false positives. | |
// 4. Delete the selected nodes. | |
(function(options) { | |
var util = require("josm/util"); | |
var RelationMember = org.openstreetmap.josm.data.osm.RelationMember; | |
function angle(a, b, c) { | |
var [ab, bc, ca] = [distance(a, b), distance(b, c), distance(c, a)]; | |
var x = clamp(-1, 1, (ab * ab + bc * bc - ca * ca) / (2 * ab * bc)); | |
return Math.acos(x) * (180 / Math.PI); | |
} | |
function clamp(min, max, n) { | |
return Math.min(Math.max(n, min), max); | |
} | |
function distance(a, b) { | |
return Math.sqrt(Math.pow(b.lon - a.lon, 2) + Math.pow(b.lat - a.lat, 2)); | |
} | |
function eachVertex(way, f) { | |
util.assert(way.first === way.last, "expected closed way"); | |
var nodes = way.nodes.slice(0, -1); | |
var length = nodes.length; | |
nodes.forEach(function(node, i) { | |
f(node, nodes[(i + 1) % length], nodes[(i + 2) % length]); | |
}); | |
} | |
function flatMap(xs, f) { | |
return xs.reduce(function(acc, x) { | |
return acc.concat(f(x)); | |
}, []); | |
} | |
function getMembers(relation) { | |
return relation.members.map(function(member) { | |
return member instanceof RelationMember ? member.member : member; | |
}); | |
} | |
function getMultipolygon(way) { | |
for (var iter = way.getReferrers().iterator(); iter.hasNext(); ) { | |
var referrer = iter.next(); | |
if (referrer.has("type", "multipolygon")) { | |
return referrer; | |
} | |
} | |
} | |
function ifLet(x, f) { | |
if (x) { | |
return f(x); | |
} | |
} | |
function isGlued(node) { | |
return node.getReferrers().size() > 1; | |
} | |
function isTagged(node) { | |
return node.hasKeys(); | |
} | |
var selection = josm.layers.activeLayer.data.selection; | |
var ways = selection.ways | |
.filter(function(way) { | |
return ( | |
way.has("building") || | |
!!ifLet(getMultipolygon(way), function(multipolygon) { | |
return multipolygon.has("building"); | |
}) | |
); | |
}) | |
.concat( | |
flatMap(selection.relations, function(relation) { | |
return relation.has("building") | |
? getMembers(relation).filter(function(member) { | |
return member.isWay; | |
}) | |
: []; | |
}) | |
); | |
util.assert(ways.length > 0, "expected buildings to be selected"); | |
var redundantNodes = []; | |
ways.forEach(function(building) { | |
eachVertex(building, function(a, b, c) { | |
if ( | |
180 - angle(a, b, c) < options.threshold && | |
!isTagged(b) && | |
!isGlued(b) | |
) { | |
redundantNodes.push(b); | |
} | |
}); | |
}); | |
selection.set(redundantNodes); | |
})({ threshold: 5.0 /* deg */ }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment