OpenStreetMap logo OpenStreetMap




// 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');
}



Email icon Bluesky Icon Facebook Icon LinkedIn Icon Mastodon Icon Telegram Icon X Icon

Discussion

Log in to leave a comment