Commit 495c32cd authored by Volker Krause's avatar Volker Krause
Browse files

Properly compute terminal <-> station distances

This now uses the full outer polygon of the terminal for the computation,
which gives us the necessary precision that the previous approximations
didn't deliver. Fix the FRA, GDN and LIS tests.
parent 2c7468e4
......@@ -226,7 +226,7 @@ private Q_SLOTS:
QTest::newRow("BUD") << s("BUD") << 47.43279f << 19.26115f << 100;
QTest::newRow("CGN") << s("CGN") << 50.87856f << 7.12107f << 150;
QTest::newRow("CPH") << s("CPH") << 55.6295693f << 12.6492994f << 50;
QTest::newRow("DEL") << s("DEL") << 28.55681f << 77.08718f << 100;
QTest::newRow("DEL") << s("DEL") << 28.55681f << 77.08718f << 50;
QTest::newRow("DEN") << s("DEN") << 39.84790f << -104.67340f << 150;
QTest::newRow("DUB") << s("DUB") << 53.4273328f << -6.2437352f << 150;
QTest::newRow("DUS") << s("DUS") << 51.27889f << 6.76566f << 150;
......@@ -235,11 +235,11 @@ private Q_SLOTS:
QTest::newRow("EWR") << s("EWR") << 40.69049f << -74.17765f << 250;
QTest::newRow("FCO") << s("FCO") << 41.79348f << 12.25208f << 50;
QTest::newRow("FRA") << s("FRA") << 50.05100f << 8.571590f << 50;
QTest::newRow("GDN") << s("GDN") << 54.38234f << 18.46640f << 50;
QTest::newRow("GDN") << s("GDN") << 54.38234f << 18.46640f << 150;
QTest::newRow("GLA") << s("GLA") << 55.86405f << -4.43181f << 50;
QTest::newRow("GOT") << s("GOT") << 57.66771f << 12.29549f << 150;
QTest::newRow("GRU") << s("GRU") << -23.42560f << -46.48165f << 100;
QTest::newRow("GVA") << s("GVA") << 46.23020f << 6.10828f << 250;
QTest::newRow("GVA") << s("GVA") << 46.23020f << 6.10828f << 200;
QTest::newRow("HAJ") << s("HAJ") << 52.45849f << 9.69898f << 50;
QTest::newRow("HAM") << s("HAM") << 53.63214f << 10.00648f << 100;
QTest::newRow("HEL") << s("HEL") << 60.31619f << 24.96914f << 50;
......@@ -248,15 +248,15 @@ private Q_SLOTS:
QTest::newRow("LAX") << s("LAX") << 33.94356f << -118.40786f << 150;
QTest::newRow("LEI") << s("LEI") << 36.84775f << -2.37242f << 50;
QTest::newRow("LEJ") << s("LEJ") << 51.42020f << 12.22122f << 400; // we get the station here, which is fine
QTest::newRow("LIS") << s("LIS") << 38.76876f << -9.12844f << 100;
QTest::newRow("LIS") << s("LIS") << 38.76876f << -9.12844f << 50;
QTest::newRow("LUX") << s("LUX") << 49.63506f << 6.21650f << 200;
QTest::newRow("LYS") << s("LYS") << 45.72065f << 5.07807f << 150;
QTest::newRow("MUC") << s("MUC") << 48.35378f << 11.78633f << 50;
QTest::newRow("NRT") << s("NRT") << 35.77059f << 140.38679f << 300;
QTest::newRow("NUE") << s("NUE") << 49.49411f << 11.07867f << 100;
QTest::newRow("NUE") << s("NUE") << 49.49411f << 11.07867f << 50;
QTest::newRow("ORD") << s("ORD") << 41.97779f << -87.90269f << 50;
QTest::newRow("OSL") << s("OSL") << 60.19361f << 11.09758f << 100;
QTest::newRow("OTP") << s("OTP") << 44.57040f << 26.07763f << 150;
QTest::newRow("OTP") << s("OTP") << 44.57040f << 26.07763f << 200;
QTest::newRow("PDX") << s("PDX") << 45.58833f << -122.59240f << 150;
QTest::newRow("PRG") << s("PRG") << 50.10640f << 14.26784f << 100;
QTest::newRow("PVG") << s("PVG") << 31.15240f << 121.80214f << 100;
......@@ -293,12 +293,9 @@ private Q_SLOTS:
#if 0
QEXPECT_FAIL("BUD", "closed terminal 1 (w8557242) interfering", Continue);
QEXPECT_FAIL("FRA", "spurious entrance node in OSM data", Continue);
QEXPECT_FAIL("GDN", "station/terminal proximity metric is too simple", Continue);
QEXPECT_FAIL("GLA", "airport is not a polygon in OSM", Continue);
QEXPECT_FAIL("GRU", "w777206182 interfering", Continue);
QEXPECT_FAIL("HKG", "better station selection", Continue);
QEXPECT_FAIL("LIS", "station/terminal proximity metric is too simple", Continue);
QEXPECT_FAIL("PRG", "private/military terminals 3 and 4 interfering", Continue);
QEXPECT_FAIL("PVG", "complicated", Continue);
QEXPECT_FAIL("RIG", "open polygon in OSM", Continue);
......
......@@ -17,6 +17,7 @@
#include "osmairportdb.h"
#include <osm/math.h>
#include <osm/xmlparser.h>
#include <QDebug>
......@@ -24,6 +25,7 @@
enum {
StationClusterDistance = 100, // in meter
StationToTerminalDistance = 75, // in meter
};
template <typename T>
......@@ -41,25 +43,6 @@ static bool isActiveAirport(const T &elem)
return aeroway == QLatin1String("aerodrome");
}
template <typename T>
static bool isTerminal(const T &elem)
{
const auto aeroway = OSM::tagValue(elem, QLatin1String("aeroway"));
if (aeroway != QLatin1String("terminal")) {
return false;
}
// filter out freight terminals
const auto usage = OSM::tagValue(elem, QLatin1String("usage"));
const auto traffic_mode = OSM::tagValue(elem, QLatin1String("traffic_mode"));
const auto building = OSM::tagValue(elem, QLatin1String("building"));
const auto industrial = OSM::tagValue(elem, QLatin1String("industrial"));
return usage != QLatin1String("freight")
&& traffic_mode != QLatin1String("freigt")
&& building != QLatin1String("industrial")
&& industrial.isEmpty();
}
void OSMAirportDb::load(const QString &path)
{
QFile f(path);
......@@ -93,14 +76,7 @@ void OSMAirportDb::load(const QString &path)
}
// find all terminal buildings, and add them to their airports
for (const auto &rel : m_dataset.relations) {
loadTerminal(rel);
}
for (const auto &way : m_dataset.ways) {
if (isTerminal(way)) {
loadTerminal(way);
}
}
OSM::for_each(m_dataset, [this](const auto &elem) { loadTerminal(elem); });
// load railway stations
OSM::for_each(m_dataset, [this](const auto &elem) { loadStation(elem); });
......@@ -109,18 +85,18 @@ void OSMAirportDb::load(const QString &path)
}
qDebug() << "airports:" << m_iataMap.size();
qDebug() << " with a single terminal:" << std::count_if(m_iataMap.begin(), m_iataMap.end(), [](const auto &a) { return a.second.terminalBboxes.size() == 1; } );
qDebug() << " with multiple terminals:" << std::count_if(m_iataMap.begin(), m_iataMap.end(), [](const auto &a) { return a.second.terminalBboxes.size() > 1; } );
qDebug() << " with a single terminal:" << std::count_if(m_iataMap.begin(), m_iataMap.end(), [](const auto &a) { return a.second.terminals.size() == 1; } );
qDebug() << " with multiple terminals:" << std::count_if(m_iataMap.begin(), m_iataMap.end(), [](const auto &a) { return a.second.terminals.size() > 1; } );
qDebug() << " with a single entrance:" << std::count_if(m_iataMap.begin(), m_iataMap.end(), [](const auto &a) { return a.second.terminalEntrances.size() == 1; } );
qDebug() << " with multiple entrances:" << std::count_if(m_iataMap.begin(), m_iataMap.end(), [](const auto &a) { return a.second.terminalEntrances.size() > 1; } );
qDebug() << " with a single station:" << std::count_if(m_iataMap.begin(), m_iataMap.end(), [](const auto &a) { return a.second.stations.size() == 1; } );
qDebug() << " with multiple stations:" << std::count_if(m_iataMap.begin(), m_iataMap.end(), [](const auto &a) { return a.second.stations.size() > 1; } );
qDebug() << " with at least one singular feature:" << std::count_if(m_iataMap.begin(), m_iataMap.end(), [](const auto &a) {
return a.second.stations.size() == 1 || a.second.terminalBboxes.size() == 1 || a.second.terminalEntrances.size() == 1;
return a.second.stations.size() == 1 || a.second.terminals.size() == 1 || a.second.terminalEntrances.size() == 1;
});
qDebug() << " with conflicting features:" << std::count_if(m_iataMap.begin(), m_iataMap.end(), [](const auto &a) {
return a.second.stations.size() != 1 && a.second.terminalBboxes.size() != 1 && a.second.terminalEntrances.size() != 1 &&
!(a.second.stations.empty() && a.second.terminalBboxes.empty() && a.second.terminalEntrances.empty());
return a.second.stations.size() != 1 && a.second.terminals.size() != 1 && a.second.terminalEntrances.size() != 1 &&
!(a.second.stations.empty() && a.second.terminals.empty() && a.second.terminalEntrances.empty());
});
}
......@@ -224,59 +200,53 @@ void OSMAirportDb::loadAirport(const OSM::Way &elem, const QString &iataCode)
m_iataMap[iataCode] = std::move(airport);
}
void OSMAirportDb::loadTerminal(const OSM::Relation& elem)
void OSMAirportDb::loadTerminal(OSM::Element elem)
{
if (!isTerminal(elem)) {
const auto aeroway = elem.tagValue(QLatin1String("aeroway"));
if (aeroway != QLatin1String("terminal")) {
return;
}
// we assume type == multipolygon here
for (const auto &member : elem.members) {
if (member.role != QLatin1String("outer")) {
continue;
}
const auto it = std::lower_bound(m_dataset.ways.begin(), m_dataset.ways.end(), member.id);
if (it != m_dataset.ways.end() && (*it).id == member.id) {
loadTerminal(*it);
}
// filter out freight terminals
const auto usage = elem.tagValue(QLatin1String("usage"));
const auto traffic_mode = elem.tagValue(QLatin1String("traffic_mode"));
const auto building = elem.tagValue(QLatin1String("building"));
const auto industrial = elem.tagValue(QLatin1String("industrial"));
if (usage == QLatin1String("freight")
|| traffic_mode == QLatin1String("freigt")
|| building == QLatin1String("industrial")
|| !industrial.isEmpty()) {
return;
}
}
void OSMAirportDb::loadTerminal(const OSM::Way &elem)
{
// find matching airport
for (auto it = m_iataMap.begin(); it != m_iataMap.end(); ++it) {
if (!OSM::intersects((*it).second.bbox, elem.bbox)) {
if (!OSM::intersects((*it).second.bbox, elem.boundingBox())) {
continue;
}
// check against the exact airport boundary, not just the bounding box,
// this excludes terminal buildings from adjacent sites we don't care about
// example: the Airbus delivery buildings next to TLS
if (!(*it).second.airportPolygon.intersects(QRectF(QPointF(elem.bbox.min.latF(), elem.bbox.min.lonF()), QPointF(elem.bbox.max.latF(), elem.bbox.max.lonF())))) {
if (!(*it).second.airportPolygon.intersects(QRectF(QPointF(elem.boundingBox().min.latF(), elem.boundingBox().min.lonF()), QPointF(elem.boundingBox().max.latF(), elem.boundingBox().max.lonF())))) {
continue;
}
//qDebug() << "found terminal for airport:" << elem.url() << (*it).first << (*it).second.source;
(*it).second.terminalBboxes.push_back(elem.bbox);
(*it).second.terminals.push_back(elem);
// look for entrances to terminals
for (const auto &nodeId : elem.nodes) {
const auto nodeIt = std::lower_bound(m_dataset.nodes.begin(), m_dataset.nodes.end(), nodeId);
if (nodeIt == m_dataset.nodes.end() || (*nodeIt).id != nodeId) {
continue;
}
for (auto node : elem.outerPath(m_dataset)) {
// filter out inaccessible entrances, or gates
const auto access = OSM::tagValue(*nodeIt, QLatin1String("access"));
const auto aeroway = OSM::tagValue(*nodeIt, QLatin1String("gate"));
const auto access = OSM::tagValue(*node, QLatin1String("access"));
const auto aeroway = OSM::tagValue(*node, QLatin1String("gate"));
if (access == QLatin1String("private") || access == QLatin1String("no") || aeroway == QLatin1String("gate")) {
continue;
}
const auto entrance = OSM::tagValue(*nodeIt, QLatin1String("entrance"));
const auto entrance = OSM::tagValue(*node, QLatin1String("entrance"));
if (entrance == QLatin1String("yes") || entrance == QLatin1String("main")) {
//qDebug() << " found entrance for terminal:" << (*nodeIt).url() << entrance << access;
(*it).second.terminalEntrances.push_back((*nodeIt).coordinate);
(*it).second.terminalEntrances.push_back(node->coordinate);
}
}
}
......@@ -307,16 +277,19 @@ void OSMAirportDb::loadStation(OSM::Element elem)
const auto onPremises = airport.airportPolygon.containsPoint(QPointF(elem.center().latF(), elem.center().lonF()), Qt::WindingFill);
// one would assume that terminals are always within the airport bounds, but that's not the case
// they sometimes expand beyond them. A station inside a terminal is however most likely something relevant for us
const auto inTerminal = std::any_of(airport.terminalBboxes.begin(), airport.terminalBboxes.end(), [&elem](const auto &terminal) {
return OSM::contains(terminal, elem.center());
const auto inTerminal = std::any_of(airport.terminals.begin(), airport.terminals.end(), [&elem](const auto &terminal) {
return OSM::contains(terminal.boundingBox(), elem.center());
});
const auto isCloseToTerminal = std::any_of(airport.terminalBboxes.begin(), airport.terminalBboxes.end(), [&elem](const auto &terminal) {
return OSM::distance(terminal.center(), elem.center()) < 100;
});
// distance of the station to the terminal outer polygon
uint32_t distanceToTerminal = std::numeric_limits<uint32_t>::max();
for (auto terminal : airport.terminals) {
const auto outerPath = terminal.outerPath(m_dataset);
distanceToTerminal = std::min(distanceToTerminal, OSM::distance(outerPath, elem.center()));
}
if (onPremises || inTerminal || isCloseToTerminal) {
//qDebug() << "found station for airport:" << elem.url() << (*it).first << (*it).second.source << onPremises << inTerminal << isCloseToTerminal;
if (onPremises || inTerminal || distanceToTerminal < StationToTerminalDistance) {
qDebug() << "found station for airport:" << elem.url() << (*it).first << (*it).second.source << onPremises << inTerminal << distanceToTerminal;
(*it).second.stations.push_back(elem);
}
}
......@@ -356,9 +329,13 @@ OSM::Coordinate OSMAirportDb::lookup(const QString &iata, float lat, float lon)
qDebug() << "Airport" << iata << "is not where we expect it to be!?" << airport.source << airport.bbox << lat << lon;
return {};
}
if (airport.terminals.empty() && airport.terminalEntrances.empty() && airport.stations.empty()) {
// no details available for this airport
return {};
}
qDebug() << "Optimizing" << iata << airport.source << lat << lon << airport.bbox;
qDebug() << " entrances:" << airport.terminalEntrances.size() << "terminals:" << airport.terminalBboxes.size() << "stations:" << airport.stations.size();
qDebug() << " entrances:" << airport.terminalEntrances.size() << "terminals:" << airport.terminals.size() << "stations:" << airport.stations.size();
// single station
if (airport.stations.size() == 1) {
......@@ -384,14 +361,16 @@ OSM::Coordinate OSMAirportDb::lookup(const QString &iata, float lat, float lon)
}
// single terminal
if (airport.terminalBboxes.size() == 1) {
qDebug() << " by terminal:" << airport.terminalBboxes[0].center();
return airport.terminalBboxes[0].center();
if (airport.terminals.size() == 1) {
qDebug() << " by terminal:" << airport.terminals[0].url() << airport.terminals[0].center();
return airport.terminals[0].center();
}
// multiple terminals: take the center of the sum of all bounding boxes, and TODO check the result isn't ridiculously large
if (airport.terminalBboxes.size() > 1) {
const auto terminalBbox = std::accumulate(airport.terminalBboxes.begin(), airport.terminalBboxes.end(), OSM::BoundingBox(), OSM::unite);
if (airport.terminals.size() > 1) {
const auto terminalBbox = std::accumulate(airport.terminals.begin(), airport.terminals.end(), OSM::BoundingBox(), [](const auto &bbox, auto terminal) {
return OSM::unite(bbox, terminal.boundingBox());
});
// if the original coordinate is outside the terminal bounding box, this is highly likely an improvement,
// otherwise we cannot be sure (see MUC, where the Wikidata coordinate is ideal).
//qDebug() << " considering terminal bbox:" << terminalBbox;
......
......@@ -34,7 +34,7 @@ struct OSMAirportData
std::vector<const OSM::Way*> airportPaths;
QPolygonF airportPolygon;
std::vector<OSM::BoundingBox> terminalBboxes;
std::vector<OSM::Element> terminals;
std::vector<OSM::Coordinate> terminalEntrances;
std::vector<OSM::Element> stations;
};
......@@ -50,8 +50,7 @@ private:
template <typename T> void loadAirport(const T &elem);
void loadAirport(const OSM::Relation &elem, const QString &iataCode);
void loadAirport(const OSM::Way &elem, const QString &iataCode);
void loadTerminal(const OSM::Relation &elem);
void loadTerminal(const OSM::Way &elem);
void loadTerminal(OSM::Element elem);
void loadStation(OSM::Element elem);
void filterStations(OSMAirportData &airport);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment