(function() {
const Node = Java.type("org.openstreetmap.josm.data.osm.Node");
const Way = Java.type("org.openstreetmap.josm.data.osm.Way");
const MainApplication = Java.type("org.openstreetmap.josm.gui.MainApplication");
const ProjectionRegistry = Java.type("org.openstreetmap.josm.data.projection.ProjectionRegistry");
const EastNorth = Java.type("org.openstreetmap.josm.data.coor.EastNorth");
const Logging = Java.type("org.openstreetmap.josm.tools.Logging");
// Parameters
const SNAP_DISTANCE_M = 1.5; // snapping tolerance
const MAX_SEARCH_DISTANCE = 200; // maximum distance to consider road segments
const ALLOWED_HIGHWAYS = ["residential", "unclassified", "tertiary"];
// Helpers for coordinate conversion
function enOf(node) { return ProjectionRegistry.getProjection().latlon2eastNorth(node.getCoor()); }
function llOf(en) { return ProjectionRegistry.getProjection().eastNorth2latlon(en); }
// Project a point onto a segment (returns a Node at projection)
function projectPointOnSegment(p, a, b) {
const ep = enOf(p), ea = enOf(a), eb = enOf(b);
const ax = ea.east(), ay = ea.north();
const bx = eb.east(), by = eb.north();
const px = ep.east(), py = ep.north();
const dx = bx - ax, dy = by - ay;
const t = Math.max(0, Math.min(1, ((px-ax)*dx + (py-ay)*dy) / (dx*dx + dy*dy)));
return new Node(llOf(new EastNorth(ax + t*dx, ay + t*dy)));
}
// Simple Euclidean distance in EastNorth space
function distanceEN(n1, n2) {
const e1 = enOf(n1), e2 = enOf(n2);
return Math.hypot(e1.east() - e2.east(), e1.north() - e2.north());
}
// Find nearest allowed road segment
function findNearestSegment(node, ds) {
let best = null, bestDist = Infinity, bestProj = null, bestRoad = null, bestIndex = -1;
ds.getWays().forEach(function(road) {
const hw = road.get("highway");
if (!ALLOWED_HIGHWAYS.includes(hw)) return;
const nodes = road.getNodes().toArray();
for (let https://wiki.openstreetmap.org/wiki/Tag:i=0; i<nodes.length-1; i++) {
const a = nodes[i], b = nodes[i+1];
const proj = projectPointOnSegment(node, a, b);
const d = distanceEN(node, proj);
if (d > MAX_SEARCH_DISTANCE) continue;
if (d < bestDist) {
bestDist = d; bestProj = proj; bestRoad = road; bestIndex = i+1;
}
}
});
return bestRoad ? {road: bestRoad, projected: bestProj, dist: bestDist, insertIndex: bestIndex} : null;
}
// Snap or insert node
function snapOrInsert(road, projected, insertIndex, ds) {
const snapNode = road.getNodes().toArray().find(rn => distanceEN(rn, projected) <= SNAP_DISTANCE_M);
if (snapNode) return snapNode;
ds.addPrimitive(projected);
road.addNode(insertIndex, projected);
return projected;
}
// Create driveway way (reversed: start at road, end at endpoint)
function createDrivewayReversed(startNode, roadNode, ds) {
const w = new Way();
w.addNode(roadNode); // start at main road
w.addNode(startNode); // end at driveway endpoint
w.put("highway","service");
w.put("service","driveway");
w.put("access","private");
ds.addPrimitive(w);
}
// Main
const ds = MainApplication.getLayerManager().getEditDataSet();
if (ds == null) { Logging.info("No active dataset."); return; }
const selectedNodes = ds.getSelectedNodes();
if (selectedNodes.isEmpty()) { Logging.info("Select one or more driveway endpoints first."); return; }
selectedNodes.forEach(function(start) {
const nearest = findNearestSegment(start, ds);
if (!nearest) {
Logging.info("No nearby allowed road found for node " + start.getId());
return;
}
const junction = snapOrInsert(nearest.road, nearest.projected, nearest.insertIndex, ds);
createDrivewayReversed(start, junction, ds);
Logging.info("Driveway created (road�𨬓ndpoint) for node " + start.getId());
});
Logging.info("Driveway creation finished for all selected nodes.");
})();
Discussion