Last active
January 29, 2020 19:18
-
-
Save AndrewKvalheim/e8d8b0ace9b49544042829fcc525b26f to your computer and use it in GitHub Desktop.
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