Add better handling of places along line.
This commit is contained in:
+22
-7
@@ -99,31 +99,46 @@ app.get('/api/line-of-sight', async (req, res) => {
|
||||
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
|
||||
ST_LineLocatePoint((SELECT route FROM path)::geography::geometry, geom::geometry) as pos_on_line,
|
||||
FLOOR(ST_LineLocatePoint((SELECT route FROM path)::geography::geometry, geom::geometry) * 200)::int as bin_200km
|
||||
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
|
||||
ROW_NUMBER() OVER (PARTITION BY bin_200km ORDER BY population DESC NULLS LAST) as rank_in_bin
|
||||
FROM candidates
|
||||
)
|
||||
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
|
||||
WHERE rank_in_bin <= 10
|
||||
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.
|
||||
// Greedy dynamic deduplication: sort by population desc, accept a city only if
|
||||
// it satisfies a distance threshold that depends on its importance (population).
|
||||
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);
|
||||
const cityPop = city.population || 0;
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user