OpenStreetMap logo OpenStreetMap

(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.");
})();
Email icon Bluesky Icon Facebook Icon LinkedIn Icon Mastodon Icon Telegram Icon X Icon

Discussion

Log in to leave a comment