Optimise db query. Add more error detail.
This commit is contained in:
+28
-22
@@ -66,18 +66,27 @@ app.get('/api/line-of-sight', async (req, res) => {
|
||||
pathPoints.push(calculateDestination(startLat, startLon, bearing, dist));
|
||||
}
|
||||
|
||||
// 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
|
||||
WHERE ST_DWithin(geom, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography, 500000)
|
||||
LIMIT 1
|
||||
) as has_land;
|
||||
`;
|
||||
const r = await pool.query(checkQuery, [p.lon, p.lat]);
|
||||
return !r.rows[0].has_land;
|
||||
}));
|
||||
// Bulk land check: use a single query for all points to avoid connection pool exhaustion
|
||||
const waterStart = Date.now();
|
||||
const waterCheckQuery = `
|
||||
WITH points AS (
|
||||
SELECT i, ST_SetSRID(ST_MakePoint(val[1], val[2]), 4326)::geography as p_geom
|
||||
FROM unnest($1::float8[][]) WITH ORDINALITY AS t(val, i)
|
||||
)
|
||||
SELECT i, EXISTS (
|
||||
SELECT 1 FROM cities
|
||||
WHERE ST_DWithin(geom, p_geom, 500000)
|
||||
LIMIT 1
|
||||
) as has_land
|
||||
FROM points
|
||||
ORDER BY i;
|
||||
`;
|
||||
|
||||
const pointsArray = pathPoints.map(p => [p.lon, p.lat]);
|
||||
const waterResults = await pool.query(waterCheckQuery, [pointsArray]);
|
||||
const waterChecks = waterResults.rows.map(r => !r.has_land);
|
||||
|
||||
console.log(`Bulk water check completed in ${Date.now() - waterStart}ms for ${pathPoints.length} points.`);
|
||||
|
||||
const pathPointsWithWater = pathPoints.map((p, i) => ({
|
||||
...p,
|
||||
@@ -116,10 +125,13 @@ app.get('/api/line-of-sight', async (req, res) => {
|
||||
ORDER BY pos_on_line ASC;
|
||||
`;
|
||||
|
||||
const startTime = Date.now();
|
||||
const result = await pool.query(query, [lineWKT, toleranceKm, startLon, startLat]);
|
||||
const queryTime = Date.now() - startTime;
|
||||
console.log(`Query completed in ${queryTime}ms. Found ${result.rows.length} candidates.`);
|
||||
|
||||
// Greedy dynamic deduplication: sort by population desc, accept a city only if
|
||||
// it satisfies a distance threshold that depends on its importance (population).
|
||||
const startDedupe = Date.now();
|
||||
// Greedy dynamic deduplication...
|
||||
const byPopulation = [...result.rows].sort((a, b) => (b.population || 0) - (a.population || 0));
|
||||
const accepted = [];
|
||||
for (const city of byPopulation) {
|
||||
@@ -127,21 +139,14 @@ app.get('/api/line-of-sight', async (req, res) => {
|
||||
const tooClose = accepted.some(s => {
|
||||
const dist = haversineKm(s.lat, s.lon, city.lat, city.lon);
|
||||
const sPop = s.population || 0;
|
||||
|
||||
// Major hubs (>1M) can be closer (30km) to each other (e.g., Tokyo/Yokohama)
|
||||
if (cityPop > 1000000 && sPop > 1000000) return dist < 30;
|
||||
|
||||
// Large cities (>100k) need at least 50km space
|
||||
if (cityPop > 100000 || sPop > 100000) return dist < 50;
|
||||
|
||||
// Smaller towns and villages need 80km space from any other accepted place
|
||||
// to prevent clutter in high-density regions (like Europe/East Coast).
|
||||
// Isolated towns (Alaska/Outback) will still show up as there's nothing else near them.
|
||||
return dist < 80;
|
||||
});
|
||||
if (!tooClose) accepted.push(city);
|
||||
}
|
||||
accepted.sort((a, b) => a.pos_on_line - b.pos_on_line);
|
||||
console.log(`Deduplication completed in ${Date.now() - startDedupe}ms. Final count: ${accepted.length}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -159,6 +164,7 @@ app.get('/api/line-of-sight', async (req, res) => {
|
||||
line_coordinates: pathPointsWithWater
|
||||
}
|
||||
});
|
||||
console.log(`Request fully processed in ${Date.now() - startTime}ms`);
|
||||
} catch (err) {
|
||||
console.error('Database query error:', err);
|
||||
res.status(500).json({ success: false, error: 'Database query failed' });
|
||||
|
||||
@@ -751,7 +751,8 @@ const APP = () => {
|
||||
renderLineOnMap(response.data.data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching line of sight:', error);
|
||||
setApiError('Failed to fetch route. Please try again.');
|
||||
const msg = error.response?.data?.error || error.message || 'Failed to fetch route.';
|
||||
setApiError(`Error: ${msg}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// Reset flight progress for a new line calculation
|
||||
|
||||
@@ -4,7 +4,7 @@ const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 10000,
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user