Optimise db query. Add more error detail.
Tests / backend-test (pull_request) Successful in 6s
Tests / frontend-test (pull_request) Failing after 7s
Tests / e2e-test (pull_request) Failing after 1m31s

This commit is contained in:
(jenkins)
2026-04-17 00:34:55 +01:00
parent 411d10bbc6
commit 8890e64d0e
3 changed files with 31 additions and 24 deletions
+28 -22
View File
@@ -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' });
+2 -1
View File
@@ -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
+1 -1
View File
@@ -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'
}