feat: implement geometry snapping logic for manual road insertion and approved roads topology
This commit is contained in:
@@ -467,6 +467,17 @@ export class GeocodingService {
|
||||
ORDER BY location <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 5
|
||||
`, [lng, lat]));
|
||||
|
||||
queryPromises.push(repo.query(`
|
||||
SELECT
|
||||
id::text, COALESCE(name, 'Street') as name, COALESCE(name, 'طريق معتمد') as name_ar, 'street' as category,
|
||||
'' as neighbourhood, '' as district, '' as governorate,
|
||||
ST_Y(ST_Centroid(geometry::geometry))::text as latitude, ST_X(ST_Centroid(geometry::geometry))::text as longitude, '' as address, 'approved_road' as source,
|
||||
ST_DistanceSphere(geometry::geometry, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) as distance
|
||||
FROM approved_roads
|
||||
WHERE geometry IS NOT NULL AND name IS NOT NULL AND trim(name) != ''
|
||||
ORDER BY geometry::geometry <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 5
|
||||
`, [lng, lat]).catch(() => []));
|
||||
|
||||
const [results, iraqiAddress] = await Promise.all([
|
||||
Promise.allSettled(queryPromises),
|
||||
iraqiAddressPromise,
|
||||
|
||||
@@ -91,20 +91,100 @@ export class RoadRefinementController {
|
||||
const name = body.name || 'Unnamed Road';
|
||||
const highway = body.highway || 'residential';
|
||||
|
||||
const result = await this.dataSource.query(
|
||||
`INSERT INTO candidate_roads (
|
||||
geometry, "uniqueDriverCount", "totalPoints", "averageSpeed", "lengthMeters",
|
||||
confidence, status, source, name, highway, oneway, "discoveredAt"
|
||||
)
|
||||
VALUES (
|
||||
ST_SetSRID(ST_GeomFromGeoJSON($1), 4326),
|
||||
1, 10, 30.0,
|
||||
ST_Length(ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON($1), 4326), 3857)),
|
||||
0.90, 'pending', 'manual', $2, $3, 0, NOW()
|
||||
)
|
||||
RETURNING id, name, highway, confidence, status`,
|
||||
[geojsonStr, name, highway]
|
||||
);
|
||||
let result;
|
||||
try {
|
||||
result = await this.dataSource.query(
|
||||
`WITH raw_input AS (
|
||||
SELECT ST_SetSRID(ST_GeomFromGeoJSON($1), 4326) AS geom
|
||||
),
|
||||
start_snap AS (
|
||||
SELECT COALESCE(
|
||||
(
|
||||
SELECT ST_SetSRID(ST_MakePoint(n.lon/1e7, n.lat/1e7), 4326)
|
||||
FROM raw_input r
|
||||
CROSS JOIN LATERAL (SELECT ST_Transform(ST_StartPoint(r.geom), 3857) AS pt) p
|
||||
JOIN planet_osm_line l ON l.highway IS NOT NULL AND l.way && ST_Expand(p.pt, 60)
|
||||
JOIN planet_osm_ways w ON w.id = l.osm_id
|
||||
CROSS JOIN LATERAL unnest(w.nodes) AS wn(node_id)
|
||||
JOIN planet_osm_nodes n ON n.id = wn.node_id
|
||||
WHERE ST_Distance(ST_SetSRID(ST_MakePoint(n.lon/1e7, n.lat/1e7), 4326)::geography, ST_StartPoint(r.geom)::geography) < 30
|
||||
ORDER BY ST_Distance(ST_SetSRID(ST_MakePoint(n.lon/1e7, n.lat/1e7), 4326)::geography, ST_StartPoint(r.geom)::geography) ASC
|
||||
LIMIT 1
|
||||
),
|
||||
(
|
||||
SELECT ST_Transform(ST_ClosestPoint(l.way, ST_Transform(ST_StartPoint(r.geom), 3857)), 4326)
|
||||
FROM raw_input r
|
||||
JOIN planet_osm_line l ON l.highway IS NOT NULL AND l.way && ST_Expand(ST_Transform(ST_StartPoint(r.geom), 3857), 60)
|
||||
WHERE ST_Distance(ST_Transform(l.way, 4326)::geography, ST_StartPoint(r.geom)::geography) < 30
|
||||
ORDER BY ST_Distance(l.way, ST_Transform(ST_StartPoint(r.geom), 3857)) ASC
|
||||
LIMIT 1
|
||||
),
|
||||
(SELECT ST_StartPoint(geom) FROM raw_input)
|
||||
) AS pt
|
||||
),
|
||||
end_snap AS (
|
||||
SELECT COALESCE(
|
||||
(
|
||||
SELECT ST_SetSRID(ST_MakePoint(n.lon/1e7, n.lat/1e7), 4326)
|
||||
FROM raw_input r
|
||||
CROSS JOIN LATERAL (SELECT ST_Transform(ST_EndPoint(r.geom), 3857) AS pt) p
|
||||
JOIN planet_osm_line l ON l.highway IS NOT NULL AND l.way && ST_Expand(p.pt, 60)
|
||||
JOIN planet_osm_ways w ON w.id = l.osm_id
|
||||
CROSS JOIN LATERAL unnest(w.nodes) AS wn(node_id)
|
||||
JOIN planet_osm_nodes n ON n.id = wn.node_id
|
||||
WHERE ST_Distance(ST_SetSRID(ST_MakePoint(n.lon/1e7, n.lat/1e7), 4326)::geography, ST_EndPoint(r.geom)::geography) < 30
|
||||
ORDER BY ST_Distance(ST_SetSRID(ST_MakePoint(n.lon/1e7, n.lat/1e7), 4326)::geography, ST_EndPoint(r.geom)::geography) ASC
|
||||
LIMIT 1
|
||||
),
|
||||
(
|
||||
SELECT ST_Transform(ST_ClosestPoint(l.way, ST_Transform(ST_EndPoint(r.geom), 3857)), 4326)
|
||||
FROM raw_input r
|
||||
JOIN planet_osm_line l ON l.highway IS NOT NULL AND l.way && ST_Expand(ST_Transform(ST_EndPoint(r.geom), 3857), 60)
|
||||
WHERE ST_Distance(ST_Transform(l.way, 4326)::geography, ST_EndPoint(r.geom)::geography) < 30
|
||||
ORDER BY ST_Distance(l.way, ST_Transform(ST_EndPoint(r.geom), 3857)) ASC
|
||||
LIMIT 1
|
||||
),
|
||||
(SELECT ST_EndPoint(geom) FROM raw_input)
|
||||
) AS pt
|
||||
),
|
||||
snapped_input AS (
|
||||
SELECT ST_SetPoint(
|
||||
ST_SetPoint(r.geom, 0, s.pt),
|
||||
ST_NPoints(r.geom) - 1,
|
||||
e.pt
|
||||
) AS geom
|
||||
FROM raw_input r, start_snap s, end_snap e
|
||||
)
|
||||
INSERT INTO candidate_roads (
|
||||
geometry, "uniqueDriverCount", "totalPoints", "averageSpeed", "lengthMeters",
|
||||
confidence, status, source, name, highway, oneway, "discoveredAt"
|
||||
)
|
||||
SELECT
|
||||
geom,
|
||||
1, 10, 30.0,
|
||||
ST_Length(ST_Transform(geom, 3857)),
|
||||
0.90, 'pending', 'manual', $2, $3, 0, NOW()
|
||||
FROM snapped_input
|
||||
RETURNING id, name, highway, confidence, status`,
|
||||
[geojsonStr, name, highway]
|
||||
);
|
||||
} catch (err: any) {
|
||||
this.logger.warn(`Complex snapping query failed, falling back to direct insertion: ${err.message}`);
|
||||
result = await this.dataSource.query(
|
||||
`INSERT INTO candidate_roads (
|
||||
geometry, "uniqueDriverCount", "totalPoints", "averageSpeed", "lengthMeters",
|
||||
confidence, status, source, name, highway, oneway, "discoveredAt"
|
||||
)
|
||||
VALUES (
|
||||
ST_SetSRID(ST_GeomFromGeoJSON($1), 4326),
|
||||
1, 10, 30.0,
|
||||
ST_Length(ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON($1), 4326), 3857)),
|
||||
0.90, 'pending', 'manual', $2, $3, 0, NOW()
|
||||
)
|
||||
RETURNING id, name, highway, confidence, status`,
|
||||
[geojsonStr, name, highway]
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(`✍️ Manual candidate road registered: ${result[0]?.id} (${name})`);
|
||||
return { success: true, candidate: result[0] };
|
||||
@@ -115,7 +195,7 @@ export class RoadRefinementController {
|
||||
}
|
||||
|
||||
@Patch('candidates/:id/approve')
|
||||
@ApiOperation({ summary: 'Approve candidate road and move to approved_roads ✅' })
|
||||
@ApiOperation({ summary: 'Approve candidate road and move to approved_roads with auto-snapped topology ✅' })
|
||||
@ApiParam({ name: 'id', description: 'Candidate UUID' })
|
||||
async approveCandidate(@Param('id') id: string) {
|
||||
try {
|
||||
@@ -123,7 +203,7 @@ export class RoadRefinementController {
|
||||
await this.dataSource.query(`
|
||||
CREATE TABLE IF NOT EXISTS approved_roads (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
candidate_id UUID,
|
||||
candidate_id UUID UNIQUE,
|
||||
geometry GEOMETRY(LineString, 4326),
|
||||
name VARCHAR(255),
|
||||
highway VARCHAR(32),
|
||||
@@ -135,6 +215,7 @@ export class RoadRefinementController {
|
||||
approved_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS approved_roads_geom_idx ON approved_roads USING GIST(geometry);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS approved_roads_cand_uidx ON approved_roads(candidate_id) WHERE candidate_id IS NOT NULL;
|
||||
`);
|
||||
|
||||
// Update candidate status
|
||||
@@ -143,15 +224,160 @@ export class RoadRefinementController {
|
||||
reviewedAt: new Date(),
|
||||
});
|
||||
|
||||
// Insert into approved_roads
|
||||
// Snap geometry & resolve OSM start_node / end_node for routing
|
||||
await this.dataSource.query(`
|
||||
INSERT INTO approved_roads (candidate_id, geometry, name, highway, confidence, "uniqueDriverCount", oneway)
|
||||
SELECT id, geometry::geometry, name, COALESCE(highway, 'residential'), confidence, "uniqueDriverCount", oneway
|
||||
FROM candidate_roads
|
||||
WHERE id = $1
|
||||
ON CONFLICT DO NOTHING
|
||||
DO $$
|
||||
DECLARE
|
||||
cand RECORD;
|
||||
start_pt GEOMETRY;
|
||||
end_pt GEOMETRY;
|
||||
snapped_geom GEOMETRY;
|
||||
s_node BIGINT := NULL;
|
||||
e_node BIGINT := NULL;
|
||||
node_rec RECORD;
|
||||
line_rec RECORD;
|
||||
BEGIN
|
||||
SELECT id, geometry::geometry as geom, name, COALESCE(highway, 'residential') as highway, confidence, "uniqueDriverCount", oneway
|
||||
INTO cand
|
||||
FROM candidate_roads WHERE id = $1;
|
||||
|
||||
IF FOUND THEN
|
||||
snapped_geom := cand.geom;
|
||||
start_pt := ST_StartPoint(snapped_geom);
|
||||
end_pt := ST_EndPoint(snapped_geom);
|
||||
|
||||
-- 1. Start node & geometry snapping
|
||||
BEGIN
|
||||
WITH p AS (SELECT ST_Transform(start_pt, 3857) AS pt)
|
||||
SELECT n.id, n.lon/1e7 as lon, n.lat/1e7 as lat
|
||||
INTO node_rec
|
||||
FROM p
|
||||
JOIN planet_osm_line l ON l.highway IS NOT NULL AND l.way && ST_Expand(p.pt, 60)
|
||||
JOIN planet_osm_ways w ON w.id = l.osm_id
|
||||
CROSS JOIN LATERAL unnest(w.nodes) AS wn(node_id)
|
||||
JOIN planet_osm_nodes n ON n.id = wn.node_id
|
||||
WHERE ST_Distance(ST_SetSRID(ST_MakePoint(n.lon/1e7, n.lat/1e7), 4326)::geography, start_pt::geography) < 30
|
||||
ORDER BY ST_Distance(ST_SetSRID(ST_MakePoint(n.lon/1e7, n.lat/1e7), 4326)::geography, start_pt::geography) ASC
|
||||
LIMIT 1;
|
||||
|
||||
IF FOUND THEN
|
||||
s_node := node_rec.id;
|
||||
snapped_geom := ST_SetPoint(snapped_geom, 0, ST_SetSRID(ST_MakePoint(node_rec.lon, node_rec.lat), 4326));
|
||||
ELSE
|
||||
WITH p AS (SELECT ST_Transform(start_pt, 3857) AS pt)
|
||||
SELECT ST_X(ST_Transform(ST_ClosestPoint(l.way, p.pt), 4326)) as lon,
|
||||
ST_Y(ST_Transform(ST_ClosestPoint(l.way, p.pt), 4326)) as lat
|
||||
INTO line_rec
|
||||
FROM p
|
||||
JOIN planet_osm_line l ON l.highway IS NOT NULL AND l.way && ST_Expand(p.pt, 60)
|
||||
WHERE ST_Distance(ST_Transform(l.way, 4326)::geography, start_pt::geography) < 30
|
||||
ORDER BY ST_Distance(l.way, p.pt) ASC
|
||||
LIMIT 1;
|
||||
|
||||
IF FOUND THEN
|
||||
snapped_geom := ST_SetPoint(snapped_geom, 0, ST_SetSRID(ST_MakePoint(line_rec.lon, line_rec.lat), 4326));
|
||||
END IF;
|
||||
END IF;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
NULL;
|
||||
END;
|
||||
|
||||
-- 2. End node & geometry snapping
|
||||
BEGIN
|
||||
WITH p AS (SELECT ST_Transform(end_pt, 3857) AS pt)
|
||||
SELECT n.id, n.lon/1e7 as lon, n.lat/1e7 as lat
|
||||
INTO node_rec
|
||||
FROM p
|
||||
JOIN planet_osm_line l ON l.highway IS NOT NULL AND l.way && ST_Expand(p.pt, 60)
|
||||
JOIN planet_osm_ways w ON w.id = l.osm_id
|
||||
CROSS JOIN LATERAL unnest(w.nodes) AS wn(node_id)
|
||||
JOIN planet_osm_nodes n ON n.id = wn.node_id
|
||||
WHERE ST_Distance(ST_SetSRID(ST_MakePoint(n.lon/1e7, n.lat/1e7), 4326)::geography, end_pt::geography) < 30
|
||||
ORDER BY ST_Distance(ST_SetSRID(ST_MakePoint(n.lon/1e7, n.lat/1e7), 4326)::geography, end_pt::geography) ASC
|
||||
LIMIT 1;
|
||||
|
||||
IF FOUND THEN
|
||||
e_node := node_rec.id;
|
||||
snapped_geom := ST_SetPoint(snapped_geom, ST_NPoints(snapped_geom) - 1, ST_SetSRID(ST_MakePoint(node_rec.lon, node_rec.lat), 4326));
|
||||
ELSE
|
||||
WITH p AS (SELECT ST_Transform(end_pt, 3857) AS pt)
|
||||
SELECT ST_X(ST_Transform(ST_ClosestPoint(l.way, p.pt), 4326)) as lon,
|
||||
ST_Y(ST_Transform(ST_ClosestPoint(l.way, p.pt), 4326)) as lat
|
||||
INTO line_rec
|
||||
FROM p
|
||||
JOIN planet_osm_line l ON l.highway IS NOT NULL AND l.way && ST_Expand(p.pt, 60)
|
||||
WHERE ST_Distance(ST_Transform(l.way, 4326)::geography, end_pt::geography) < 30
|
||||
ORDER BY ST_Distance(l.way, p.pt) ASC
|
||||
LIMIT 1;
|
||||
|
||||
IF FOUND THEN
|
||||
snapped_geom := ST_SetPoint(snapped_geom, ST_NPoints(snapped_geom) - 1, ST_SetSRID(ST_MakePoint(line_rec.lon, line_rec.lat), 4326));
|
||||
END IF;
|
||||
END IF;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
NULL;
|
||||
END;
|
||||
|
||||
-- Update candidate_roads
|
||||
UPDATE candidate_roads SET geometry = snapped_geom WHERE id = cand.id;
|
||||
|
||||
-- Insert or update approved_roads
|
||||
INSERT INTO approved_roads (
|
||||
candidate_id, geometry, name, highway, confidence, "uniqueDriverCount", oneway, start_node, end_node
|
||||
)
|
||||
VALUES (
|
||||
cand.id, snapped_geom, cand.name, cand.highway, cand.confidence, cand."uniqueDriverCount", cand.oneway, s_node, e_node
|
||||
)
|
||||
ON CONFLICT (candidate_id) DO UPDATE SET
|
||||
geometry = EXCLUDED.geometry,
|
||||
start_node = EXCLUDED.start_node,
|
||||
end_node = EXCLUDED.end_node;
|
||||
|
||||
END IF;
|
||||
END $$;
|
||||
`, [id]);
|
||||
|
||||
// If road has a name, index it into places geocoding table
|
||||
try {
|
||||
await this.dataSource.query(`
|
||||
DO $$
|
||||
DECLARE
|
||||
r RECORD;
|
||||
target_table TEXT := 'places_jordan';
|
||||
center_lat FLOAT;
|
||||
center_lng FLOAT;
|
||||
BEGIN
|
||||
SELECT name, ST_Y(ST_Centroid(geometry::geometry)) as lat, ST_X(ST_Centroid(geometry::geometry)) as lng
|
||||
INTO r
|
||||
FROM candidate_roads WHERE id = $1 AND name IS NOT NULL AND trim(name) != '';
|
||||
|
||||
IF FOUND THEN
|
||||
center_lat := r.lat;
|
||||
center_lng := r.lng;
|
||||
|
||||
IF center_lat >= 29 AND center_lat <= 37.5 AND center_lng >= 38.7 AND center_lng <= 48.8 THEN
|
||||
target_table := 'places_iraq';
|
||||
ELSIF center_lat >= 29 AND center_lat <= 37.5 AND center_lng >= 34.5 AND center_lng <= 42.5 THEN
|
||||
IF center_lat > 32.5 AND center_lng > 35.8 THEN
|
||||
target_table := 'places_syria';
|
||||
ELSE
|
||||
target_table := 'places_jordan';
|
||||
END IF;
|
||||
ELSIF center_lat >= 22 AND center_lat <= 32 AND center_lng >= 24.5 AND center_lng <= 37 THEN
|
||||
target_table := 'places_egypt';
|
||||
END IF;
|
||||
|
||||
EXECUTE format('
|
||||
INSERT INTO %I (name, name_ar, category, latitude, longitude, location, source)
|
||||
VALUES ($1, $1, $2, $3, $4, ST_SetSRID(ST_MakePoint($4, $3), 4326), $5)
|
||||
', target_table) USING r.name, 'street', center_lat, center_lng, 'approved_road';
|
||||
END IF;
|
||||
END $$;
|
||||
`, [id]);
|
||||
} catch (err: any) {
|
||||
this.logger.warn(`Could not index approved road to places: ${err.message}`);
|
||||
}
|
||||
|
||||
this.logger.log(`✅ Road candidate ${id} approved & published.`);
|
||||
return { success: true, id, status: 'approved' };
|
||||
} catch (e: any) {
|
||||
@@ -231,4 +457,114 @@ export class RoadRefinementController {
|
||||
async discoverClosures() {
|
||||
return { success: true, roadsClosed: 0 };
|
||||
}
|
||||
|
||||
@Post('snap-existing-roads')
|
||||
@ApiOperation({ summary: 'Auto-snap and resolve topology nodes for all existing approved roads' })
|
||||
async snapExistingApprovedRoads() {
|
||||
try {
|
||||
await this.dataSource.query(`
|
||||
DO $$
|
||||
DECLARE
|
||||
r RECORD;
|
||||
start_pt GEOMETRY;
|
||||
end_pt GEOMETRY;
|
||||
snapped_geom GEOMETRY;
|
||||
s_node BIGINT;
|
||||
e_node BIGINT;
|
||||
node_rec RECORD;
|
||||
line_rec RECORD;
|
||||
BEGIN
|
||||
FOR r IN SELECT id, geometry FROM approved_roads LOOP
|
||||
snapped_geom := r.geometry;
|
||||
s_node := NULL;
|
||||
e_node := NULL;
|
||||
start_pt := ST_StartPoint(snapped_geom);
|
||||
end_pt := ST_EndPoint(snapped_geom);
|
||||
|
||||
-- Snap start
|
||||
BEGIN
|
||||
WITH p AS (SELECT ST_Transform(start_pt, 3857) AS pt)
|
||||
SELECT n.id, n.lon/1e7 as lon, n.lat/1e7 as lat
|
||||
INTO node_rec
|
||||
FROM p
|
||||
JOIN planet_osm_line l ON l.highway IS NOT NULL AND l.way && ST_Expand(p.pt, 60)
|
||||
JOIN planet_osm_ways w ON w.id = l.osm_id
|
||||
CROSS JOIN LATERAL unnest(w.nodes) AS wn(node_id)
|
||||
JOIN planet_osm_nodes n ON n.id = wn.node_id
|
||||
WHERE ST_Distance(ST_SetSRID(ST_MakePoint(n.lon/1e7, n.lat/1e7), 4326)::geography, start_pt::geography) < 30
|
||||
ORDER BY ST_Distance(ST_SetSRID(ST_MakePoint(n.lon/1e7, n.lat/1e7), 4326)::geography, start_pt::geography) ASC
|
||||
LIMIT 1;
|
||||
|
||||
IF FOUND THEN
|
||||
s_node := node_rec.id;
|
||||
snapped_geom := ST_SetPoint(snapped_geom, 0, ST_SetSRID(ST_MakePoint(node_rec.lon, node_rec.lat), 4326));
|
||||
ELSE
|
||||
WITH p AS (SELECT ST_Transform(start_pt, 3857) AS pt)
|
||||
SELECT ST_X(ST_Transform(ST_ClosestPoint(l.way, p.pt), 4326)) as lon,
|
||||
ST_Y(ST_Transform(ST_ClosestPoint(l.way, p.pt), 4326)) as lat
|
||||
INTO line_rec
|
||||
FROM p
|
||||
JOIN planet_osm_line l ON l.highway IS NOT NULL AND l.way && ST_Expand(p.pt, 60)
|
||||
WHERE ST_Distance(ST_Transform(l.way, 4326)::geography, start_pt::geography) < 30
|
||||
ORDER BY ST_Distance(l.way, p.pt) ASC
|
||||
LIMIT 1;
|
||||
|
||||
IF FOUND THEN
|
||||
snapped_geom := ST_SetPoint(snapped_geom, 0, ST_SetSRID(ST_MakePoint(line_rec.lon, line_rec.lat), 4326));
|
||||
END IF;
|
||||
END IF;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
NULL;
|
||||
END;
|
||||
|
||||
-- Snap end
|
||||
BEGIN
|
||||
WITH p AS (SELECT ST_Transform(end_pt, 3857) AS pt)
|
||||
SELECT n.id, n.lon/1e7 as lon, n.lat/1e7 as lat
|
||||
INTO node_rec
|
||||
FROM p
|
||||
JOIN planet_osm_line l ON l.highway IS NOT NULL AND l.way && ST_Expand(p.pt, 60)
|
||||
JOIN planet_osm_ways w ON w.id = l.osm_id
|
||||
CROSS JOIN LATERAL unnest(w.nodes) AS wn(node_id)
|
||||
JOIN planet_osm_nodes n ON n.id = wn.node_id
|
||||
WHERE ST_Distance(ST_SetSRID(ST_MakePoint(n.lon/1e7, n.lat/1e7), 4326)::geography, end_pt::geography) < 30
|
||||
ORDER BY ST_Distance(ST_SetSRID(ST_MakePoint(n.lon/1e7, n.lat/1e7), 4326)::geography, end_pt::geography) ASC
|
||||
LIMIT 1;
|
||||
|
||||
IF FOUND THEN
|
||||
e_node := node_rec.id;
|
||||
snapped_geom := ST_SetPoint(snapped_geom, ST_NPoints(snapped_geom) - 1, ST_SetSRID(ST_MakePoint(node_rec.lon, node_rec.lat), 4326));
|
||||
ELSE
|
||||
WITH p AS (SELECT ST_Transform(end_pt, 3857) AS pt)
|
||||
SELECT ST_X(ST_Transform(ST_ClosestPoint(l.way, p.pt), 4326)) as lon,
|
||||
ST_Y(ST_Transform(ST_ClosestPoint(l.way, p.pt), 4326)) as lat
|
||||
INTO line_rec
|
||||
FROM p
|
||||
JOIN planet_osm_line l ON l.highway IS NOT NULL AND l.way && ST_Expand(p.pt, 60)
|
||||
WHERE ST_Distance(ST_Transform(l.way, 4326)::geography, end_pt::geography) < 30
|
||||
ORDER BY ST_Distance(l.way, p.pt) ASC
|
||||
LIMIT 1;
|
||||
|
||||
IF FOUND THEN
|
||||
snapped_geom := ST_SetPoint(snapped_geom, ST_NPoints(snapped_geom) - 1, ST_SetSRID(ST_MakePoint(line_rec.lon, line_rec.lat), 4326));
|
||||
END IF;
|
||||
END IF;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
NULL;
|
||||
END;
|
||||
|
||||
UPDATE approved_roads
|
||||
SET geometry = snapped_geom,
|
||||
start_node = COALESCE(s_node, start_node),
|
||||
end_node = COALESCE(e_node, end_node)
|
||||
WHERE id = r.id;
|
||||
END LOOP;
|
||||
END $$;
|
||||
`);
|
||||
return { success: true, message: 'All approved roads snapped & nodes resolved' };
|
||||
} catch (e: any) {
|
||||
this.logger.error(`Failed to snap existing roads: ${e.message}`);
|
||||
throw new HttpException(e.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user