carthorse
Version:
A geospatial trail data processing pipeline for building 3D trail databases with elevation data
239 lines โข 11.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PgNodeNetworkStrategy = void 0;
class PgNodeNetworkStrategy {
async createNetwork(pgClient, config) {
console.log('๐ Using pgr_nodeNetwork strategy (enhanced approach)...');
try {
const { stagingSchema, tolerances } = config;
// Step 1: Use pgr_nodeNetwork() to create nodes from ALL intersection points
console.log('๐ Using pgr_nodeNetwork() to create vertices from all intersection points...');
// First, create a ways table in the staging schema for pgr_nodeNetwork input
await pgClient.query(`
CREATE TABLE ${stagingSchema}.temp_ways AS
SELECT id, the_geom FROM ${stagingSchema}.ways
`);
// Since we're using both-split-algos, the segments should already be clean LineStrings
// Just do a light validation to ensure compatibility
console.log('๐ Validating already-split segments for pgr_nodeNetwork...');
// Remove any remaining problematic geometries (should be minimal after our splitting)
await pgClient.query(`
DELETE FROM ${stagingSchema}.temp_ways
WHERE ST_GeometryType(the_geom) != 'ST_LineString'
OR ST_IsEmpty(the_geom)
OR NOT ST_IsValid(the_geom)
OR NOT ST_IsSimple(the_geom)
OR ST_NumPoints(the_geom) < 2
OR ST_Length(the_geom) < 0.0001
`);
// OPTIMIZATION: Add spatial index for faster pgr_nodeNetwork processing
console.log('๐ Adding spatial index for optimized processing...');
await pgClient.query(`
CREATE INDEX IF NOT EXISTS idx_temp_ways_geom ON ${stagingSchema}.temp_ways USING GIST(the_geom)
`);
// Verify the temp table was created
const tempTableCheck = await pgClient.query(`
SELECT COUNT(*) as count FROM ${stagingSchema}.temp_ways
`);
console.log(`โ
Created ${stagingSchema}.temp_ways table with ${tempTableCheck.rows[0].count} already-split segments`);
// Final validation: ensure all geometries are simple LineStrings
const finalValidation = await pgClient.query(`
SELECT COUNT(*) as count
FROM ${stagingSchema}.temp_ways
WHERE ST_GeometryType(the_geom) != 'ST_LineString'
`);
if (finalValidation.rows[0].count > 0) {
throw new Error(`Found ${finalValidation.rows[0].count} non-LineString geometries after cleanup. pgr_nodeNetwork requires simple LineStrings.`);
}
console.log('โ
All already-split segments validated as simple LineStrings for pgr_nodeNetwork');
// OPTIMIZATION: Call pgr_nodeNetwork() ONCE and store results in a regular table
console.log('๐ฏ Running pgr_nodeNetwork() on already-split segments...');
await pgClient.query(`
CREATE TABLE ${stagingSchema}.node_network_results AS
SELECT * FROM pgr_nodeNetwork('${stagingSchema}.temp_ways', ${tolerances.intersectionDetectionTolerance}, 'id', 'the_geom')
`);
console.log('โ
pgr_nodeNetwork() completed successfully');
// OPTIMIZATION: Add spatial index to node_network_results for faster joins
await pgClient.query(`
CREATE INDEX IF NOT EXISTS idx_node_network_geom ON ${stagingSchema}.node_network_results USING GIST(the_geom)
`);
// Step 2: Create ways_noded table from stored pgr_nodeNetwork results
console.log(`๐ Creating ways_noded table in ${stagingSchema}.ways_noded from pgr_nodeNetwork results...`);
await pgClient.query(`
CREATE TABLE ${stagingSchema}.ways_noded AS
SELECT
ROW_NUMBER() OVER (ORDER BY id) as id,
old_id,
sub_id,
the_geom,
app_uuid,
name,
length_km,
elevation_gain,
elevation_loss
FROM (
SELECT
wn.id,
w.app_uuid,
w.name,
w.length_km,
w.elevation_gain,
w.elevation_loss,
wn.old_id,
wn.sub_id,
wn.the_geom
FROM ${stagingSchema}.node_network_results wn
JOIN ${stagingSchema}.ways w ON wn.old_id = w.id
) subquery
`);
console.log('โ
Created ways_noded table from pgr_nodeNetwork results');
// Populate trail_id_mapping table for UUID โ Integer ID conversion
console.log('๐ Populating trail_id_mapping table...');
await pgClient.query(`
INSERT INTO ${stagingSchema}.trail_id_mapping (app_uuid, trail_id)
SELECT DISTINCT app_uuid, id as trail_id
FROM ${stagingSchema}.ways_noded
ORDER BY id
`);
console.log('โ
Populated trail_id_mapping table');
// OPTIMIZATION: Add spatial index to ways_noded for faster spatial operations
await pgClient.query(`
CREATE INDEX IF NOT EXISTS idx_ways_noded_geom ON ${stagingSchema}.ways_noded USING GIST(the_geom)
`);
// Step 3: Create ways_noded_vertices_pgr from stored pgr_nodeNetwork results
console.log(`๐ Creating vertices table in ${stagingSchema}.ways_noded_vertices_pgr from pgr_nodeNetwork results...`);
await pgClient.query(`
CREATE TABLE ${stagingSchema}.ways_noded_vertices_pgr AS
SELECT
id,
the_geom,
cnt,
chk,
ein,
eout,
CASE
WHEN cnt >= 2 THEN 'intersection'
WHEN cnt = 1 THEN 'endpoint'
ELSE 'endpoint'
END as node_type
FROM ${stagingSchema}.node_network_results
`);
console.log('โ
Created vertices table from pgr_nodeNetwork results');
// OPTIMIZATION: Add spatial index to vertices table for faster spatial joins
await pgClient.query(`
CREATE INDEX IF NOT EXISTS idx_vertices_geom ON ${stagingSchema}.ways_noded_vertices_pgr USING GIST(the_geom)
`);
// Step 4: Add source and target columns to ways_noded
await pgClient.query(`
ALTER TABLE ${stagingSchema}.ways_noded
ADD COLUMN source INTEGER,
ADD COLUMN target INTEGER
`);
// Step 5: Update source and target based on vertex proximity
await pgClient.query(`
UPDATE ${stagingSchema}.ways_noded wn
SET
source = (
SELECT v.id
FROM ${stagingSchema}.ways_noded_vertices_pgr v
WHERE ST_DWithin(ST_StartPoint(wn.the_geom), v.the_geom, ${tolerances.edgeToVertexTolerance})
LIMIT 1
),
target = (
SELECT v.id
FROM ${stagingSchema}.ways_noded_vertices_pgr v
WHERE ST_DWithin(ST_EndPoint(wn.the_geom), v.the_geom, ${tolerances.edgeToVertexTolerance})
LIMIT 1
)
`);
// Step 6: Remove edges that couldn't be connected to vertices
await pgClient.query(`
DELETE FROM ${stagingSchema}.ways_noded
WHERE source IS NULL OR target IS NULL
`);
console.log('โ
Connected edges to vertices');
// Step 7: Preserve true loop trails but remove problematic self-loops
console.log('๐ Preserving true loop trails...');
await pgClient.query(`
ALTER TABLE ${stagingSchema}.ways_noded
ADD COLUMN is_true_loop BOOLEAN DEFAULT FALSE
`);
await pgClient.query(`
UPDATE ${stagingSchema}.ways_noded
SET is_true_loop = TRUE
WHERE ST_Distance(ST_StartPoint(the_geom)::geography, ST_EndPoint(the_geom)::geography) < ${tolerances.trueLoopTolerance}
`);
const selfLoopResult = await pgClient.query(`
DELETE FROM ${stagingSchema}.ways_noded
WHERE source = target AND NOT is_true_loop
`);
console.log(`โ
Removed ${selfLoopResult.rowCount} problematic self-loop edges, preserved true loops`);
await pgClient.query(`
ALTER TABLE ${stagingSchema}.ways_noded
DROP COLUMN is_true_loop
`);
// Step 8: Add missing metadata columns to ways_noded for export compatibility
console.log('๐ค๏ธ Adding metadata columns to ways_noded...');
await pgClient.query(`
ALTER TABLE ${stagingSchema}.ways_noded
ADD COLUMN IF NOT EXISTS is_bidirectional BOOLEAN DEFAULT TRUE,
ADD COLUMN IF NOT EXISTS created_at TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS geojson TEXT
`);
// Update geojson column with computed GeoJSON
await pgClient.query(`
UPDATE ${stagingSchema}.ways_noded
SET geojson = ST_AsGeoJSON(the_geom, 6, 0)
WHERE geojson IS NULL
`);
console.log('โ
Added metadata columns to ways_noded');
// Step 9: Clean up temporary tables
console.log('๐งน Cleaning up temporary tables...');
await pgClient.query(`DROP TABLE IF EXISTS ${stagingSchema}.temp_ways`);
await pgClient.query(`DROP TABLE IF EXISTS ${stagingSchema}.node_network_results`);
console.log('โ
Cleaned up temporary tables');
// Step 9: Get statistics
const nodeCountResult = await pgClient.query(`SELECT COUNT(*) FROM ${stagingSchema}.ways_noded_vertices_pgr`);
const edgeCountResult = await pgClient.query(`SELECT COUNT(*) FROM ${stagingSchema}.ways_noded`);
const isolatedNodesResult = await pgClient.query(`
SELECT COUNT(*) as isolated_count
FROM ${stagingSchema}.ways_noded_vertices_pgr n
WHERE NOT EXISTS (
SELECT 1 FROM ${stagingSchema}.ways_noded e
WHERE e.source = n.id OR e.target = n.id
)
`);
const orphanedEdgesResult = await pgClient.query(`
SELECT COUNT(*) as orphaned_count
FROM ${stagingSchema}.ways_noded e
WHERE e.source NOT IN (SELECT id FROM ${stagingSchema}.ways_noded_vertices_pgr)
OR e.target NOT IN (SELECT id FROM ${stagingSchema}.ways_noded_vertices_pgr)
`);
console.log('โ
pgr_nodeNetwork strategy completed successfully');
return {
success: true,
stats: {
nodesCreated: parseInt(nodeCountResult.rows[0].count),
edgesCreated: parseInt(edgeCountResult.rows[0].count),
isolatedNodes: parseInt(isolatedNodesResult.rows[0].isolated_count),
orphanedEdges: parseInt(orphanedEdgesResult.rows[0].orphaned_count)
}
};
}
catch (error) {
console.error('โ pgr_nodeNetwork strategy failed:', error);
return {
success: false,
error: error instanceof Error ? error.message : String(error),
stats: {
nodesCreated: 0,
edgesCreated: 0,
isolatedNodes: 0,
orphanedEdges: 0
}
};
}
}
}
exports.PgNodeNetworkStrategy = PgNodeNetworkStrategy;
//# sourceMappingURL=pg-node-network-strategy.js.map