Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
JOSM script to select redundant nodes in buildings
// 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