diff --git a/backend/app/server.js b/backend/app/server.js index 507ce0c..3cd62b7 100644 --- a/backend/app/server.js +++ b/backend/app/server.js @@ -53,8 +53,8 @@ app.get('/api/line-of-sight', async (req, res) => { try { // Generate path points for visualization and spatial query const pathPoints = []; - const totalDistance = 10000; - const steps = 20; + const totalDistance = 20000; + const steps = 40; for (let i = 0; i <= steps; i++) { const dist = (totalDistance * i) / steps; @@ -65,7 +65,8 @@ app.get('/api/line-of-sight', async (req, res) => { const query = ` WITH path AS ( - SELECT ST_GeogFromText($1) as route + SELECT ST_GeogFromText($1) as route, + ST_MakePoint($3, $4)::geography as start_node ) SELECT id, @@ -74,15 +75,21 @@ app.get('/api/line-of-sight', async (req, res) => { 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_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 20; + LIMIT 100; `; - const result = await pool.query(query, [lineWKT, toleranceKm]); + const result = await pool.query(query, [lineWKT, toleranceKm, startLon, startLat]); + + console.log(`Found ${result.rows.length} conurbations near the line.`); + if (result.rows.length > 0) { + console.log('Sample result row:', result.rows[0]); + } res.json({ success: true, @@ -92,7 +99,10 @@ app.get('/api/line-of-sight', async (req, res) => { tolerance_km: toleranceKm, conurbations: result.rows.map(row => ({ ...row, - distance_km: Math.round(row.distance_to_line_km) + name: row.name || 'Unknown', + country: row.country || 'Unknown', + distance_km: Math.round(row.distance_from_start_km), + off_line_km: Math.round(row.distance_off_line_km) })), line_coordinates: pathPoints } diff --git a/backend/scripts/import_cities.js b/backend/scripts/import_cities.js index 5aa2e4b..c4279df 100644 --- a/backend/scripts/import_cities.js +++ b/backend/scripts/import_cities.js @@ -6,7 +6,7 @@ const DATA_URL = 'https://raw.githubusercontent.com/nvkelso/natural-earth-vector async function importCities() { const client = new Client({ - connectionString: process.env.DATABASE_URL || 'postgresql://line_of_sight:line_of_sight_pass@localhost:5432/line_of_sight' + connectionString: process.env.DATABASE_URL || 'postgresql://line_of_sight:line_of_sight_pass@postgres:5432/line_of_sight' }); try { @@ -19,6 +19,11 @@ async function importCities() { console.log(`Downloaded ${data.features.length} features. Preparing database...`); + // Sample properties to verify structure + if (data.features.length > 0) { + console.log('Sample properties:', data.features[0].properties); + } + // Ensure table exists and is clean await client.query('TRUNCATE TABLE cities'); @@ -28,40 +33,28 @@ async function importCities() { for (let i = 0; i < data.features.length; i += batchSize) { const batch = data.features.slice(i, i + batchSize); - const values = []; const queryParts = []; - - batch.forEach((feature, index) => { - const props = feature.properties; - const coords = feature.geometry.coordinates; // [lon, lat] - - const name = props.NAME || 'Unknown'; - const population = props.POP_MAX || 0; - const country = props.ADM0NAME || 'Unknown'; - const lon = coords[0]; - const lat = coords[1]; - - const baseIndex = index * 4; - queryParts.push(`($${baseIndex + 1}, $${baseIndex + 2}, $${baseIndex + 3}, ST_SetSRID(ST_MakePoint($${baseIndex + 4}, $${baseIndex + 1}), 4326)::geography)`); - values.push(lat, name, population, country, lon); // Note: ST_MakePoint takes lon, lat - }); - - // Simple positional mapping for query (lat, name, pop, country, lon) - // Actually let's refine the query to be clearer - const refinedQueryParts = []; - const refinedValues = []; + const values = []; batch.forEach((feature, index) => { const p = feature.properties; const c = feature.geometry.coordinates; + + // Map properties - Natural Earth simple GeoJSON usually has name, pop_max, adm0name + const name = p.name || p.NAME || 'Unknown'; + const pop = p.pop_max || p.POP_MAX || 0; + const country = p.adm0name || p.ADM0NAME || 'Unknown'; + const lon = c[0]; + const lat = c[1]; + const base = index * 5; - refinedQueryParts.push(`($${base + 1}, $${base + 2}, $${base + 3}, ST_SetSRID(ST_MakePoint($${base + 4}, $${base + 5}), 4326)::geography)`); - refinedValues.push(p.NAME || 'Unknown', p.POP_MAX || 0, p.ADM0NAME || 'Unknown', c[0], c[1]); + queryParts.push(`($${base + 1}, $${base + 2}, $${base + 3}, ST_SetSRID(ST_MakePoint($${base + 4}, $${base + 5}), 4326)::geography)`); + values.push(name, pop, country, lon, lat); }); await client.query( - `INSERT INTO cities (name, population, country, geom) VALUES ${refinedQueryParts.join(',')}`, - refinedValues + `INSERT INTO cities (name, population, country, geom) VALUES ${queryParts.join(',')}`, + values ); count += batch.length; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index cbef092..d0acf89 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -15,6 +15,7 @@ const APP = () => { const [loading, setLoading] = useState(false); const [mapStyle, setMapStyle] = useState('light'); // 'light' or 'dark' const [tolerance, setTolerance] = useState(50); + const [selectedCity, setSelectedCity] = useState(null); useEffect(() => { // Initialize MapLibre map @@ -158,6 +159,7 @@ const APP = () => { const handleShowLineOfSight = async () => { setLoading(true); + setSelectedCity(null); try { const response = await apiService.getLineOfSight( selectedPoint.lat, @@ -217,15 +219,16 @@ const APP = () => { // Add city markers data.conurbations.forEach((city, index) => { - const markerId = `city-${index}`; + const displayIndex = index + 1; // Create marker element const el = document.createElement('div'); el.className = 'city-marker'; el.innerHTML = ` +
Click a city for details
| # | City | Population | -Distance | +Dist. | |
|---|---|---|---|---|---|
| {index + 1} | {city.name} | -{(city.population / 1000000).toFixed(1)}M | -{city.distance_km} km | +{(city.population / 1000).toFixed(0)}k | +{city.distance_km}km |
... and {lineOfSightData.conurbations.length - 10} more cities
+ {lineOfSightData.conurbations.length > 50 && ( +... and {lineOfSightData.conurbations.length - 50} more
)}