Commit bbe3b4a4 authored by Volker Krause's avatar Volker Krause
Browse files

Read the QGIS world file to compute the map projection correctly

Our previous naive approach introduced an error of about 10km, which is
significantly larger than the resolution of the timezone image (1.5km),
and is actually relevant for densely populated areas close to timezone
borders, as well as small islands (as our search radius for those is
just 5km).
parent df720198
......@@ -51,6 +51,12 @@ private Q_SLOTS:
QVERIFY(station.coordinate.isValid());
QCOMPARE(station.timezone.toQTimeZone(), QTimeZone("Europe/Zurich"));
QCOMPARE(station.country, CountryId{"CH"});
// Aachen West, very close to the NL border, should be in DE timezone
station = KnowledgeDb::stationForIbnr(IBNR{8000404});
QVERIFY(station.coordinate.isValid());
QCOMPARE(station.timezone.toQTimeZone(), QTimeZone("Europe/Berlin"));
QCOMPARE(station.country, CountryId{"DE"});
}
void testGaresConnexionsIdLookup()
......
data/*
timezones.png
timezones.pgw
timezones.colormap
timezones.qgs~
timezones.shapefile*
......
......@@ -11,8 +11,8 @@ Extract the zip file into this folder.
(2) Generate timezone lookup map
Open timezones.qgs in QGIS. Select "Project" > "Layouts" > "timezone_lookup_map" and then
"Layout" > "Export as Image". Choose "timezones.png" in this folder, 2400dpi and *no* anti-
aliasing.
"Layout" > "Export as Image". Choose "timezones.png" in this folder, 2400dpi, enable
"Generate world file" and *disable* anti-aliasing.
(3) Run the code generator
......
......@@ -29,6 +29,7 @@ using namespace KItinerary::Generator;
Timezones::Timezones()
{
// load the color to timezone mapping file
QFile colorMap(QStringLiteral("timezones.colormap"));
if (!colorMap.open(QFile::ReadOnly)) {
qCritical() << "Unable to open timezone colormap file: " << colorMap.errorString();
......@@ -66,6 +67,27 @@ Timezones::Timezones()
offset += tz.size() + 1; // +1 of the trailing null byte
}
// load the wold file for correcting the pixel to coordinate mapping in the timezone image
// see https://en.wikipedia.org/wiki/World_file for format and math behind this
QFile worldFile(QStringLiteral("timezones.pgw"));
if (!worldFile.open(QFile::ReadOnly|QFile::Text)) {
qCritical() << "Unable to open world file: " << worldFile.errorString();
exit(1);
}
const auto worldFileContent = worldFile.readAll().split('\n');
if (worldFileContent.size() < 6) {
qCritical() << "Invalid world map file format.";
exit(1);
}
if (worldFileContent[1].toDouble() != 0.0 || worldFileContent[2].toDouble() != 0.0) {
qCritical() << "Timezone map is rotated, that is not supported!";
exit(1);
}
m_xMapUnitsPerPixel = worldFileContent[0].toDouble();
m_yMapUnitsPerPixel = worldFileContent[3].toDouble();
m_topLeftMapUnitX = worldFileContent[4].toDouble();
m_topLeftMapUnitY = worldFileContent[5].toDouble();
// load zone.tab for country mapping
QFile zoneTab(QStringLiteral("/usr/share/zoneinfo/zone1970.tab"));
if (!zoneTab.open(QFile::ReadOnly)) {
......@@ -119,23 +141,18 @@ QByteArray Timezones::timezoneForLocation(const QString &isoCode, const Knowledg
exit(1);
}
const int x = qRound(m_map.width() * ((coord.longitude + 180.0f)/ 360.0f));
const int y = qRound(-m_map.height() * ((coord.latitude - 90.0f) / 180.0f));
//qDebug() << x << y << m_map.width() << m_map.height() << longitude << latitude << QColor(m_map.pixel(x, y)) << m_zones.value(m_map.pixel(x, y));
const auto tz = timezoneForPixel(x, y);
const auto p = coordinateToPixel(coord);
//qDebug() << p.x() << p.y() << m_map.width() << m_map.height() << coord.longitude << coord.latitude << QColor(m_map.pixel(p)) << m_colorMap.value(m_map.pixel(p));
const auto tz = timezoneForPixel(p.x(), p.y());
if (!tz.isEmpty()) {
coordTzs.insert(tz);
}
// search the vicinity, helps with costal/island airports
if (coordTzs.isEmpty()) {
const struct {
int x;
int y;
} offsets[] = { {-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1} };
for (int i = 0; i < 8; ++i) {
const auto tz = timezoneForPixel(x + offsets[i].x, y + offsets[i].y);
const QPoint offsets[] = { {-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1} };
for (auto offset : offsets) {
const auto tz = timezoneForPixel(p.x() + offset.x(), p.y() + offset.y());
if (!tz.isEmpty()) {
coordTzs.insert(tz);
}
......@@ -173,3 +190,12 @@ uint16_t Timezones::offset(const QByteArray& tz) const
}
return m_zoneOffsets[std::distance(m_zones.begin(), it)];
}
QPoint Timezones::coordinateToPixel(const KnowledgeDb::Coordinate &coord) const
{
QPoint p;
p.setX(qRound((coord.longitude - m_topLeftMapUnitX) / m_xMapUnitsPerPixel));
p.setY(qRound((coord.latitude - m_topLeftMapUnitY) / m_yMapUnitsPerPixel));
qDebug() << coord.longitude << coord.latitude << p;
return p;
}
......@@ -44,10 +44,16 @@ public:
private:
friend class TimezoneDbGenerator;
QPoint coordinateToPixel(const KnowledgeDb::Coordinate &coord) const;
QByteArray timezoneForPixel(int x, int y) const;
mutable QImage m_map;
QHash<QRgb, QByteArray> m_colorMap;
double m_xMapUnitsPerPixel;
double m_yMapUnitsPerPixel;
double m_topLeftMapUnitX;
double m_topLeftMapUnitY;
std::vector<QByteArray> m_zones;
std::vector<uint16_t> m_zoneOffsets;
......
......@@ -9883,7 +9883,7 @@ def my_form_open(dialog, layer, feature):
</projectMetadata>
<Annotations/>
<Layouts>
<Layout worldFileMap="" name="timezone_lookup_map" printResolution="300" units="mm">
<Layout worldFileMap="" name="timezone_lookup_map" printResolution="2400" units="mm">
<Snapper snapToGuides="1" snapToItems="1" snapToGrid="0" tolerance="5"/>
<Grid offsetY="0" resolution="10" offsetUnits="mm" offsetX="0" resUnits="mm"/>
<PageCollection>
......
This diff is collapsed.
......@@ -909,7 +909,7 @@ static const TrainStation trainstation_table[] = {
{Coordinate{11.9361, 44.5404}, Tz::Europe_Rome, CountryId{"IT"}}, // Q16610093
{Coordinate{20.8686, 52.1911}, Tz::Europe_Warsaw, CountryId{"PL"}}, // Warszawa Ursus Niedźwiadek railway station
{Coordinate{11.5933, 50.8838}, Tz::Europe_Berlin, CountryId{"DE"}}, // Jena-Göschwitz station
{Coordinate{22.6306, 48.2019}, Tz::Europe_Budapest, CountryId{"UA"}}, // Q16692574
{Coordinate{22.6306, 48.2019}, Tz::Europe_Uzhgorod, CountryId{"UA"}}, // Q16692574
{Coordinate{}, Timezone{}, CountryId{"UA"}}, // Q16702723
{Coordinate{5.65167, 53.0322}, Tz::Europe_Amsterdam, CountryId{"NL"}}, // Sneek railway station
{Coordinate{8.26995, 47.3484}, Tz::Europe_Zurich, CountryId{"CH"}}, // Wohlen railway station
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