Implement real GIS data import and PostGIS spatial queries
This commit is contained in:
+90
-50
@@ -1,66 +1,106 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const { Pool } = require('pg');
|
||||
require('dotenv').config();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// Database connection pool
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL || 'postgresql://line_of_sight:line_of_sight_pass@postgres:5432/line_of_sight'
|
||||
});
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Mock conurbation data for MVP
|
||||
const MOCK_CONURBATIONS = [
|
||||
{ id: 1, name: "London", population: 9000000, distance_km: 0, lat: 51.5074, lon: -0.1278 },
|
||||
{ id: 2, name: "Paris", population: 2161000, distance_km: 344, lat: 48.8566, lon: 2.3522 },
|
||||
{ id: 3, name: "Berlin", population: 3644000, distance_km: 878, lat: 52.5200, lon: 13.4050 },
|
||||
{ id: 4, name: "Warsaw", population: 1793000, distance_km: 1200, lat: 52.2297, lon: 21.0122 },
|
||||
{ id: 5, name: "Moscow", population: 12506000, distance_km: 2063, lat: 55.7558, lon: 37.6173 },
|
||||
{ id: 6, name: "Kazan", population: 1257000, distance_km: 2850, lat: 55.7897, lon: 49.1219 },
|
||||
{ id: 7, name: "Almaty", population: 2000000, distance_km: 3900, lat: 43.2220, lon: 76.8512 },
|
||||
{ id: 8, name: "Urumqi", population: 3500000, distance_km: 4500, lat: 43.8256, lon: 87.6168 },
|
||||
{ id: 9, name: "Lahore", population: 11126000, distance_km: 5400, lat: 31.5204, lon: 74.3587 },
|
||||
{ id: 10, name: "New Delhi", population: 29399000, distance_km: 5800, lat: 28.6139, lon: 77.2090 },
|
||||
{ id: 11, name: "Dhaka", population: 21006000, distance_km: 6200, lat: 23.8103, lon: 90.4125 },
|
||||
{ id: 12, name: "Chennai", population: 10971000, distance_km: 6500, lat: 13.0827, lon: 80.2707 },
|
||||
{ id: 13, name: "Bangkok", population: 10539000, distance_km: 7200, lat: 13.7563, lon: 100.5018 },
|
||||
{ id: 14, name: "Jakarta", population: 10562000, distance_km: 8100, lat: -6.2088, lon: 106.8456 },
|
||||
{ id: 15, name: "Singapore", population: 5686000, distance_km: 8300, lat: 1.3521, lon: 103.8198 },
|
||||
{ id: 16, name: "Manila", population: 17801000, distance_km: 8700, lat: 14.5995, lon: 120.9842 },
|
||||
{ id: 17, name: "Tokyo", population: 37400000, distance_km: 9500, lat: 35.6762, lon: 139.6503 },
|
||||
{ id: 18, name: "Seoul", population: 9720000, distance_km: 9200, lat: 37.5665, lon: 126.9780 },
|
||||
{ id: 19, name: "Beijing", population: 21540000, distance_km: 8900, lat: 39.9042, lon: 116.4074 },
|
||||
{ id: 20, name: "Shanghai", population: 27058000, distance_km: 9000, lat: 31.2304, lon: 121.4737 }
|
||||
];
|
||||
// Helper to calculate destination point given start, bearing, and distance (km)
|
||||
const calculateDestination = (lat, lon, bearing, distance) => {
|
||||
const R = 6371;
|
||||
const brng = (bearing * Math.PI) / 180;
|
||||
const φ1 = (lat * Math.PI) / 180;
|
||||
const λ1 = (lon * Math.PI) / 180;
|
||||
const δ = distance / R;
|
||||
|
||||
// Mock API endpoint - returns dummy conurbations based on input coordinates
|
||||
app.get('/api/line-of-sight', (req, res) => {
|
||||
const φ2 = Math.asin(
|
||||
Math.sin(φ1) * Math.cos(δ) +
|
||||
Math.cos(φ1) * Math.sin(δ) * Math.cos(brng)
|
||||
);
|
||||
const λ2 =
|
||||
λ1 +
|
||||
Math.atan2(
|
||||
Math.sin(brng) * Math.sin(δ) * Math.cos(φ1),
|
||||
Math.cos(δ) - Math.sin(φ1) * Math.sin(φ2)
|
||||
);
|
||||
|
||||
return {
|
||||
lat: (φ2 * 180) / Math.PI,
|
||||
lon: (((λ2 * 180) / Math.PI + 540) % 360) - 180
|
||||
};
|
||||
};
|
||||
|
||||
// Real API endpoint - uses PostGIS for spatial queries
|
||||
app.get('/api/line-of-sight', async (req, res) => {
|
||||
const { lat, lon, direction, tolerance } = req.query;
|
||||
|
||||
console.log(`Received request: lat=${lat}, lon=${lon}, direction=${direction}, tolerance=${tolerance}`);
|
||||
|
||||
// Return mock data for MVP
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
start_point: { lat: parseFloat(lat) || 51.5074, lon: parseFloat(lon) || -0.1278 },
|
||||
direction: parseInt(direction) || 45,
|
||||
tolerance_km: parseInt(tolerance) || 50,
|
||||
conurbations: MOCK_CONURBATIONS.slice(0, 20),
|
||||
line_coordinates: [
|
||||
{ lat: 51.5074, lon: -0.1278 },
|
||||
{ lat: 48.8566, lon: 2.3522 },
|
||||
{ lat: 52.5200, lon: 13.4050 },
|
||||
{ lat: 55.7558, lon: 37.6173 },
|
||||
{ lat: 43.2220, lon: 76.8512 },
|
||||
{ lat: 28.6139, lon: 77.2090 },
|
||||
{ lat: 13.7563, lon: 100.5018 },
|
||||
{ lat: -6.2088, lon: 106.8456 },
|
||||
{ lat: 35.6762, lon: 139.6503 },
|
||||
{ lat: 51.5074, lon: -0.1278 } // Complete the circle
|
||||
]
|
||||
},
|
||||
message: "Mock data returned for MVP - Real geospatial calculations coming soon"
|
||||
});
|
||||
const startLat = parseFloat(lat) || 51.5074;
|
||||
const startLon = parseFloat(lon) || -0.1278;
|
||||
const bearing = parseInt(direction) || 0;
|
||||
const toleranceKm = parseInt(tolerance) || 50;
|
||||
|
||||
console.log(`Processing real request: lat=${startLat}, lon=${startLon}, bearing=${bearing}, tolerance=${toleranceKm}`);
|
||||
|
||||
try {
|
||||
// Generate path points for visualization and spatial query
|
||||
const pathPoints = [];
|
||||
const totalDistance = 10000;
|
||||
const steps = 20;
|
||||
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
const dist = (totalDistance * i) / steps;
|
||||
pathPoints.push(calculateDestination(startLat, startLon, bearing, dist));
|
||||
}
|
||||
|
||||
const lineWKT = `LINESTRING(${pathPoints.map(p => `${p.lon} ${p.lat}`).join(',')})`;
|
||||
|
||||
const query = `
|
||||
WITH path AS (
|
||||
SELECT ST_GeogFromText($1) as route
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
population,
|
||||
country,
|
||||
ST_Y(geom::geometry) as lat,
|
||||
ST_X(geom::geometry) as lon,
|
||||
ST_Distance(geom, (SELECT route FROM path)) / 1000 as distance_to_line_km,
|
||||
ST_LineLocatePoint((SELECT route FROM path)::geometry, geom::geometry) as pos_on_line
|
||||
FROM cities
|
||||
WHERE ST_DWithin(geom, (SELECT route FROM path), $2 * 1000)
|
||||
ORDER BY pos_on_line ASC
|
||||
LIMIT 20;
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [lineWKT, toleranceKm]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
start_point: { lat: startLat, lon: startLon },
|
||||
direction: bearing,
|
||||
tolerance_km: toleranceKm,
|
||||
conurbations: result.rows.map(row => ({
|
||||
...row,
|
||||
distance_km: Math.round(row.distance_to_line_km)
|
||||
})),
|
||||
line_coordinates: pathPoints
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Database query error:', err);
|
||||
res.status(500).json({ success: false, error: 'Database query failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// Health check endpoint
|
||||
|
||||
Reference in New Issue
Block a user