// JOSM Scripting plugin (GraalJS, JavaScript API v3)
// Split a closed polygon (even number of unique nodes) into adjoining units,
// using two adjacent https://wiki.openstreetmap.org/wiki/Tag:CORN=Determined nodes as one corner pair and the
// two nodes exactly opposite them (N/2 apart) as the other corner pair.
// Adjacent units share nodes (no new nodes created).
const Way = Java.type('org.openstreetmap.josm.data.osm.Way');
const MainApplication = Java.type('org.openstreetmap.josm.gui.MainApplication');
const ds = MainApplication.getLayerManager().getEditDataSet();
if (!ds) {
throw new Error('No editable data set found. Open an editable layer first.');
}
// Helpers
function getNodes(way) {
return way.getNodes(); // Java List<Node>
}
function sameNode(a, b) {
return a https://wiki.openstreetmap.org/wiki/Tag:=== b || (a.getUniqueId && b.getUniqueId && a.getUniqueId() https://wiki.openstreetmap.org/wiki/Tag:=== b.getUniqueId());
}
function mod(i, n) {
let r = i % n;
return r < 0 ? r + n : r;
}
// Inclusive forward range along ring indices
function rangeForward(start, endInclusive, n) {
const out = [];
let i = start;
while (true) {
out.push(mod(i, n));
if (mod(i, n) https://wiki.openstreetmap.org/wiki/Tag:=== mod(endInclusive, n)) break;
i++;
}
return out;
}
// Iterate selected ways
const selectedWays = ds.getSelectedWays();
const it = selectedWays.iterator();
while (it.hasNext()) {
const way = it.next();
// Ensure way is closed (duplicate the first node if necessary)
let nodesFull = getNodes(way);
let totalNodesFull = nodesFull.size();
if (totalNodesFull < 2) {
way.put('gen', 'fail'); continue;
}
const first = nodesFull.get(0);
const last = nodesFull.get(totalNodesFull - 1);
if (!sameNode(first, last)) {
way.addNode(first);
nodesFull = getNodes(way);
totalNodesFull = nodesFull.size();
}
// Work on unique ring nodes (exclude closing duplicate)
const N = totalNodesFull - 1;
if (N < 4 || N % 2 https://wiki.openstreetmap.org/wiki/Tag:!== 0) { // must be even and at least 4
way.put('gen', 'fail'); continue;
}
const ringNodes = [];
for (let i = 0; i < N; i++) ringNodes.push(nodesFull.get(i));
// Find exactly two https://wiki.openstreetmap.org/wiki/Tag:CORN=Determined nodes
const cornerIdxs = [];
for (let i = 0; i < N; i++) {
if (ringNodes[i].get('CORN') https://wiki.openstreetmap.org/wiki/Tag:=== 'Determined') cornerIdxs.push(i);
}
if (cornerIdxs.length https://wiki.openstreetmap.org/wiki/Tag:!== 2) {
way.put('gen', 'fail'); continue;
}
// Normalize to adjacent order along ring (allow wrap-around)
let c0 = cornerIdxs[0], c1 = cornerIdxs[1];
const diffForward = mod(c1 - c0, N);
const diffBackward = mod(c0 - c1, N);
if (diffForward https://wiki.openstreetmap.org/wiki/Tag:=== 1) {
// already c1 is next after c0
} else if (diffBackward https://wiki.openstreetmap.org/wiki/Tag:=== 1) {
// swap so c1 is next after c0
const tmp = c0; c0 = c1; c1 = tmp;
} else {
// Not adjacent along ring �� required by your rule
way.put('gen', 'fail'); continue;
}
// Opposite corner pair: N/2 steps ahead
const o0 = mod(c0 + N / 2, N); // opposite of c0
const o1 = mod(c1 + N / 2, N); // opposite of c1
// Build sides:
// side1 goes from c0 -> o0 (inclusive)
// side2 goes from o1 -> c1 (inclusive), then reversed to mirror side1
const side1Idxs = rangeForward(c0, o0, N);
const side2IdxsFwd = rangeForward(o1, c1, N);
const side2Idxs = side2IdxsFwd.slice().reverse();
const side1 = side1Idxs.map(i => ringNodes[i]);
const side2 = side2Idxs.map(i => ringNodes[i]);
// Each side should have N/2 + 1 nodes; blocks = N/2
if (side1.length https://wiki.openstreetmap.org/wiki/Tag:!== side2.length || side1.length https://wiki.openstreetmap.org/wiki/Tag:!== (N / 2 + 1)) {
way.put('gen', 'fail'); continue;
}
const blocks = side1.length - 1;
// Create adjoining units with shared nodes
for (let i = 0; i < blocks; i++) {
const n1 = side1[i];
const n2 = side1[i + 1];
const n3 = side2[i + 1];
const n4 = side2[i];
const newWay = new Way();
newWay.addNode(n1);
newWay.addNode(n2);
newWay.addNode(n3);
newWay.addNode(n4);
newWay.addNode(n1); // close
newWay.put('amenity', 'parking_space'); // or newWay.put('building', 'terrace');
ds.addPrimitive(newWay);
}
way.put('gen', 'ok');
}
Discussion