UNPKG

carthorse

Version:

A geospatial trail data processing pipeline for building 3D trail databases with elevation data

253 lines 9.06 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExportSqlHelpers = void 0; class ExportSqlHelpers { constructor(pgClient, stagingSchema) { this.pgClient = pgClient; this.stagingSchema = stagingSchema; } /** * Create optimized indices for export queries */ async createExportIndices() { console.log(`🔧 Creating optimized indices for export queries in ${this.stagingSchema}...`); try { // Indices for trails table await this.pgClient.query(` CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_${this.stagingSchema}_trails_geometry_export ON ${this.stagingSchema}.trails USING GIST(geometry) WHERE geometry IS NOT NULL `); await this.pgClient.query(` CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_${this.stagingSchema}_trails_name_export ON ${this.stagingSchema}.trails(name) WHERE geometry IS NOT NULL `); // Indices for routing nodes table await this.pgClient.query(` CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_${this.stagingSchema}_routing_nodes_geometry_export ON ${this.stagingSchema}.ways_noded_vertices_pgr USING GIST(the_geom) WHERE the_geom IS NOT NULL `); await this.pgClient.query(` CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_${this.stagingSchema}_routing_nodes_id_export ON ${this.stagingSchema}.ways_noded_vertices_pgr(id) `); // Indices for routing edges table await this.pgClient.query(` CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_${this.stagingSchema}_routing_edges_geometry_export ON ${this.stagingSchema}.routing_edges USING GIST(geometry) WHERE geometry IS NOT NULL `); await this.pgClient.query(` CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_${this.stagingSchema}_routing_edges_id_export ON ${this.stagingSchema}.routing_edges(id) `); // Indices for route recommendations table await this.pgClient.query(` CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_${this.stagingSchema}_route_recommendations_edges_export ON ${this.stagingSchema}.route_recommendations(route_edges) WHERE route_edges IS NOT NULL AND route_edges != 'null' `); await this.pgClient.query(` CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_${this.stagingSchema}_route_recommendations_score_export ON ${this.stagingSchema}.route_recommendations(route_score DESC) WHERE route_edges IS NOT NULL AND route_edges != 'null' `); console.log('✅ Created optimized indices for export queries'); } catch (error) { console.error('❌ Failed to create export indices:', error); throw error; } } /** * Export trail data for GeoJSON */ async exportTrailsForGeoJSON() { // Ensure indices are created for optimal performance await this.createExportIndices(); const trailsResult = await this.pgClient.query(` SELECT app_uuid, name, region, trail_type, surface, difficulty, length_km, elevation_gain, elevation_loss, max_elevation, min_elevation, avg_elevation, bbox_min_lng, bbox_max_lng, bbox_min_lat, bbox_max_lat, ST_AsGeoJSON(geometry, 6, 0) as geojson FROM ${this.stagingSchema}.trails WHERE geometry IS NOT NULL ORDER BY name `); return trailsResult.rows; } /** * Export routing nodes for GeoJSON */ async exportRoutingNodesForGeoJSON() { // Ensure indices are created for optimal performance await this.createExportIndices(); const nodesResult = await this.pgClient.query(` SELECT id, id as node_uuid, ST_Y(the_geom) as lat, ST_X(the_geom) as lng, 0 as elevation, node_type, '' as connected_trails, ARRAY[]::text[] as trail_ids, ST_AsGeoJSON(the_geom, 6, 0) as geojson FROM ${this.stagingSchema}.ways_noded_vertices_pgr ORDER BY id `); return nodesResult.rows; } /** * Export routing edges for GeoJSON */ async exportRoutingEdgesForGeoJSON() { // Ensure indices are created for optimal performance await this.createExportIndices(); const edgesResult = await this.pgClient.query(` SELECT id, source, target, trail_id, trail_name, length_km, elevation_gain, elevation_loss, is_bidirectional, ST_AsGeoJSON(geometry, 6, 0) as geojson FROM ${this.stagingSchema}.routing_edges ORDER BY id `); return edgesResult.rows; } /** * Export all data for GeoJSON */ async exportAllDataForGeoJSON() { // Ensure indices are created for optimal performance await this.createExportIndices(); const [trails, nodes, edges] = await Promise.all([ this.exportTrailsForGeoJSON(), this.exportRoutingNodesForGeoJSON(), this.exportRoutingEdgesForGeoJSON() ]); return { trails, nodes, edges }; } /** * Export trail segments only (for trails-only export) */ async exportTrailSegmentsOnly() { // Ensure indices are created for optimal performance await this.createExportIndices(); const trailsResult = await this.pgClient.query(` SELECT app_uuid, name, trail_type, surface, difficulty, length_km, elevation_gain, elevation_loss, max_elevation, min_elevation, avg_elevation, ST_AsGeoJSON(geometry, 6, 0) as geojson FROM ${this.stagingSchema}.trails WHERE geometry IS NOT NULL ORDER BY name `); return trailsResult.rows; } /** * Export route recommendations for both GeoJSON and SQLite strategies */ async exportRouteRecommendations() { // Ensure indices are created for optimal performance await this.createExportIndices(); // First check if there are any routes to export const countResult = await this.pgClient.query(` SELECT COUNT(*) as route_count FROM ${this.stagingSchema}.route_recommendations WHERE route_edges IS NOT NULL AND route_edges != 'null' `); const routeCount = parseInt(countResult.rows[0].route_count); if (routeCount === 0) { console.log('📊 No routes to export - returning empty array'); return []; } const recommendationsResult = await this.pgClient.query(` SELECT r.route_uuid, r.route_name, r.route_type, r.route_shape, r.input_length_km, r.input_elevation_gain, r.recommended_length_km, r.recommended_elevation_gain, r.route_path, r.route_edges, r.trail_count, r.route_score, r.similarity_score, r.region, r.created_at, -- Generate GeoJSON geometry from route_path CASE WHEN r.route_path IS NOT NULL AND r.route_path != 'null' AND r.route_path != '[]' THEN ST_AsGeoJSON(ST_GeomFromGeoJSON(r.route_path), 6, 0) ELSE NULL END as geojson, -- Generate complete_route_data in the expected format json_build_object( 'routeId', r.route_uuid, 'routeName', COALESCE(r.route_name, 'Unnamed Route'), 'routeType', CASE WHEN r.trail_count > 1 THEN 'multi' ELSE 'single' END, 'totalDistance', r.recommended_length_km, 'totalElevationGain', r.recommended_elevation_gain, 'routeShape', COALESCE(r.route_shape, 'out-and-back'), 'similarityScore', COALESCE(r.similarity_score, 0.0), 'trailSegments', COALESCE(r.route_edges, '[]'::jsonb), 'connectivity', json_build_object( 'segmentConnections', '[]'::jsonb, 'routeContinuity', true, 'gaps', '[]'::jsonb ), 'combinedPath', COALESCE(r.route_path, '[]'::jsonb), 'combinedBbox', '[]'::jsonb, 'createdAt', r.created_at, 'region', r.region, 'inputParameters', json_build_object( 'targetDistance', r.input_length_km, 'targetElevationGain', r.input_elevation_gain, 'distanceTolerance', 0.5, 'elevationTolerance', 100.0 ) ) as complete_route_data FROM ${this.stagingSchema}.route_recommendations r WHERE r.route_edges IS NOT NULL AND r.route_edges != 'null' ORDER BY r.route_score DESC `); return recommendationsResult.rows; } } exports.ExportSqlHelpers = ExportSqlHelpers; //# sourceMappingURL=export-sql-helpers.js.map