Update application ports to 3051 and enhance frontend/backend configurations
This commit is contained in:
+54
-32
@@ -4,9 +4,8 @@ const { Pool } = require('pg');
|
||||
require('dotenv').config();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
const PORT = process.env.PORT || 3051;
|
||||
|
||||
// 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'
|
||||
});
|
||||
@@ -14,7 +13,6 @@ const pool = new Pool({
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// 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;
|
||||
@@ -39,40 +37,46 @@ const calculateDestination = (lat, lon, bearing, distance) => {
|
||||
};
|
||||
};
|
||||
|
||||
// Real API endpoint - uses PostGIS for spatial queries
|
||||
const haversineKm = (lat1, lon1, lat2, lon2) => {
|
||||
const R = 6371;
|
||||
const toRad = (d) => d * Math.PI / 180;
|
||||
const dLat = toRad(lat2 - lat1);
|
||||
const dLon = toRad(lon2 - lon1);
|
||||
const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
|
||||
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
};
|
||||
|
||||
app.get('/api/line-of-sight', async (req, res) => {
|
||||
const { lat, lon, direction, tolerance } = req.query;
|
||||
|
||||
|
||||
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}`);
|
||||
console.log(`Processing request: lat=${startLat}, lon=${startLon}, bearing=${bearing}, tolerance=${toleranceKm}`);
|
||||
|
||||
try {
|
||||
// Generate path points for visualization and spatial query
|
||||
const pathPoints = [];
|
||||
const totalDistance = 40074; // Full Earth circumference (km)
|
||||
const totalDistance = 40074;
|
||||
const steps = 160;
|
||||
|
||||
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
const dist = (totalDistance * i) / steps;
|
||||
pathPoints.push(calculateDestination(startLat, startLon, bearing, dist));
|
||||
}
|
||||
|
||||
// Batch check for 'over water' status for all path points
|
||||
// We'll consider a point 'over water' if no city is within 500km
|
||||
// Batch water check: a point is "over water" if no city is within 500 km
|
||||
const waterChecks = await Promise.all(pathPoints.map(async (p) => {
|
||||
const checkQuery = `
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM cities
|
||||
SELECT 1 FROM cities
|
||||
WHERE ST_DWithin(geom, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography, 500000)
|
||||
LIMIT 1
|
||||
) as has_land;
|
||||
`;
|
||||
const res = await pool.query(checkQuery, [p.lon, p.lat]);
|
||||
return !res.rows[0].has_land;
|
||||
const r = await pool.query(checkQuery, [p.lon, p.lat]);
|
||||
return !r.rows[0].has_land;
|
||||
}));
|
||||
|
||||
const pathPointsWithWater = pathPoints.map((p, i) => ({
|
||||
@@ -81,37 +85,56 @@ app.get('/api/line-of-sight', async (req, res) => {
|
||||
}));
|
||||
|
||||
const lineWKT = `LINESTRING(${pathPoints.map(p => `${p.lon} ${p.lat}`).join(',')})`;
|
||||
|
||||
|
||||
// Top 5 cities per 100 km bin, ranked by population descending
|
||||
const query = `
|
||||
WITH path AS (
|
||||
SELECT ST_GeogFromText($1) as route,
|
||||
ST_MakePoint($3, $4)::geography as start_node
|
||||
),
|
||||
candidates AS (
|
||||
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_off_line_km,
|
||||
ST_Distance(geom, (SELECT start_node FROM path)) / 1000 as distance_from_start_km,
|
||||
ST_LineLocatePoint((SELECT route FROM path)::geometry, geom::geometry) as pos_on_line,
|
||||
FLOOR(ST_Distance(geom, (SELECT start_node FROM path)) / 1000 / 100)::int as bin_100km
|
||||
FROM cities
|
||||
WHERE ST_DWithin(geom, (SELECT route FROM path), $2 * 1000)
|
||||
),
|
||||
ranked AS (
|
||||
SELECT *,
|
||||
ROW_NUMBER() OVER (PARTITION BY bin_100km ORDER BY population DESC NULLS LAST) as rank_in_bin
|
||||
FROM candidates
|
||||
)
|
||||
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_off_line_km,
|
||||
ST_Distance(geom, (SELECT start_node FROM path)) / 1000 as distance_from_start_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 200;
|
||||
SELECT id, name, population, country, lat, lon,
|
||||
distance_off_line_km, distance_from_start_km, pos_on_line
|
||||
FROM ranked
|
||||
WHERE rank_in_bin <= 5
|
||||
ORDER BY pos_on_line ASC;
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [lineWKT, toleranceKm, startLon, startLat]);
|
||||
|
||||
|
||||
// Greedy 30 km deduplication: sort by population desc, accept a city only if
|
||||
// it's at least 30 km from every already-accepted city.
|
||||
const byPopulation = [...result.rows].sort((a, b) => (b.population || 0) - (a.population || 0));
|
||||
const accepted = [];
|
||||
for (const city of byPopulation) {
|
||||
const tooClose = accepted.some(s => haversineKm(s.lat, s.lon, city.lat, city.lon) < 30);
|
||||
if (!tooClose) accepted.push(city);
|
||||
}
|
||||
accepted.sort((a, b) => a.pos_on_line - b.pos_on_line);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
start_point: { lat: startLat, lon: startLon },
|
||||
direction: bearing,
|
||||
tolerance_km: toleranceKm,
|
||||
conurbations: result.rows.map(row => ({
|
||||
conurbations: accepted.map(row => ({
|
||||
...row,
|
||||
name: row.name || 'Unknown',
|
||||
country: row.country || 'Unknown',
|
||||
@@ -127,7 +150,6 @@ app.get('/api/line-of-sight', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user