data/GeoData.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QByteArray> | ||
| 4 | #include <QHash> | ||
| 5 | #include <QList> | ||
| 6 | #include <QString> | ||
| 7 | #include <QStringList> | ||
| 8 | #include <memory> | ||
| 9 | |||
| 10 | // Bundled offline geodata (Sprint 103 S103.0.5/S103.4.2, D-10/D-11): | ||
| 11 | // countries with outlines (Natural Earth 1:50m), cities ≥ 1000 inhabitants | ||
| 12 | // and their names (GeoNames), admin-1 regions, time zones and the postal | ||
| 13 | // codes of DE/AT/CH. Built by scripts/geodata/build_geodata.py into | ||
| 14 | // resources/geo/world.mjgeo (format documented there); checksum-verified on | ||
| 15 | // load (S103.1.12). Read-only after load and safe to share between threads. | ||
| 16 | // | ||
| 17 | // Coordinates are kept as integers (degrees × 100000) to keep 170k cities | ||
| 18 | // in a few megabytes; names are read from the string table on demand. | ||
| 19 | class GeoData { | ||
| 20 | public: | ||
| 21 | static constexpr int Scale = 100000; | ||
| 22 | |||
| 23 | struct Country { | ||
| 24 | QByteArray iso2; | ||
| 25 | QByteArray iso3; | ||
| 26 | int labelRank = 9; | ||
| 27 | quint32 nameEn = 0, nameDe = 0, names = 0, timeZone = 0; // string offsets | ||
| 28 | qint32 labelLat = 0, labelLon = 0; | ||
| 29 | qint32 minLat = 0, minLon = 0, maxLat = 0, maxLon = 0; | ||
| 30 | quint32 firstRing = 0, ringCount = 0; | ||
| 31 | }; | ||
| 32 | struct Ring { | ||
| 33 | quint32 firstPoint = 0; | ||
| 34 | quint32 pointCount = 0; | ||
| 35 | }; | ||
| 36 | struct Point { | ||
| 37 | qint32 lat = 0; | ||
| 38 | qint32 lon = 0; | ||
| 39 | }; | ||
| 40 | struct Admin1 { | ||
| 41 | quint16 country = 0; | ||
| 42 | quint32 names = 0, code = 0; | ||
| 43 | qint32 lat = 0, lon = 0; | ||
| 44 | }; | ||
| 45 | struct City { | ||
| 46 | qint32 lat = 0, lon = 0; | ||
| 47 | quint16 country = 0; | ||
| 48 | quint16 admin1 = 0xFFFF; | ||
| 49 | quint32 population = 0; | ||
| 50 | quint32 name = 0; | ||
| 51 | quint16 timeZone = 0xFFFF; | ||
| 52 | }; | ||
| 53 | struct Postal { | ||
| 54 | quint16 country = 0; | ||
| 55 | quint32 code = 0, place = 0; | ||
| 56 | qint32 lat = 0, lon = 0; | ||
| 57 | }; | ||
| 58 | struct NameEntry { | ||
| 59 | quint32 key = 0; | ||
| 60 | quint32 city = 0; | ||
| 61 | }; | ||
| 62 | |||
| 63 | // Parses a world.mjgeo file; nullptr and *error on a bad magic, version, | ||
| 64 | // checksum or section table. | ||
| 65 | // `verify` checks the payload's SHA-256 (S103.1.12); the bundled | ||
| 66 | // resource skips it (verified by its test, 79 ms per start otherwise). | ||
| 67 | static std::shared_ptr<const GeoData> fromBytes(const QByteArray &file, QString *error = nullptr, bool verify = true); | ||
| 68 | static std::shared_ptr<const GeoData> load(const QString &path, QString *error = nullptr); | ||
| 69 | // The bundled data (resource ":/geo/world.mjgeo"), loaded once on first | ||
| 70 | // use from any thread; nullptr when the resource is missing. | ||
| 71 | static std::shared_ptr<const GeoData> builtIn(); | ||
| 72 | |||
| 73 | // Lookup key of a place name (identical to geo_key() of the builder): | ||
| 74 | // NFKD, marks removed, lower case, ß/ø/æ/œ/ł/đ/ð/þ/ı spelled out, | ||
| 75 | // punctuation as spaces, whitespace collapsed. | ||
| 76 | static QString key(const QString &text); | ||
| 77 | |||
| 78 | 3095 | const QList<Country> &countries() const { return m_countries; } | |
| 79 | 2 | const QList<Ring> &rings() const { return m_rings; } | |
| 80 | 3 | const QList<Admin1> &admin1() const { return m_admin1; } | |
| 81 | // The large tables are read in place from the payload (no copy at load). | ||
| 82 | 1 | int pointCount() const { return m_pointCount; } | |
| 83 | Point point(int index) const; | ||
| 84 | 1 | int cityCount() const { return m_cityCount; } | |
| 85 | City city(int index) const; | ||
| 86 | 1 | int postalCount() const { return m_postalCount; } | |
| 87 | Postal postal(int index) const; | ||
| 88 | QString string(quint32 offset) const; | ||
| 89 | QString timeZoneName(quint16 index) const; | ||
| 90 | |||
| 91 | // Country index for a name, ISO code or native name in any bundled | ||
| 92 | // language (case and accents ignored); -1 when unknown. | ||
| 93 | int countryIndex(const QString &name) const; | ||
| 94 | // Cities whose name or alternate folds to `key`, most populous first. | ||
| 95 | QList<int> citiesNamed(const QString &key, int limit = 32) const; | ||
| 96 | // Admin-1 regions of a country whose name folds to `key`. | ||
| 97 | int admin1Index(int country, const QString &key) const; | ||
| 98 | // The postal code entry (country + normalised code), -1 when none. | ||
| 99 | int postalIndex(int country, const QString &code) const; | ||
| 100 | // Cities whose key starts with `prefix` (completion), most populous first. | ||
| 101 | QList<int> citiesWithPrefix(const QString &prefix, int limit = 8) const; | ||
| 102 | |||
| 103 | 2506 | static double degrees(qint32 value) { return double(value) / Scale; } | |
| 104 | |||
| 105 | private: | ||
| 106 | QByteArray m_payload; | ||
| 107 | QByteArray m_owner; // owns the bytes of a v2 file read from disk | ||
| 108 | QByteArray m_strings; | ||
| 109 | QList<Country> m_countries; | ||
| 110 | QList<Ring> m_rings; | ||
| 111 | QList<Admin1> m_admin1; | ||
| 112 | QList<quint32> m_timeZones; | ||
| 113 | const char *m_pointData = nullptr; | ||
| 114 | int m_pointCount = 0; | ||
| 115 | const char *m_cityData = nullptr; | ||
| 116 | int m_cityCount = 0; | ||
| 117 | const char *m_postalData = nullptr; | ||
| 118 | int m_postalCount = 0; | ||
| 119 | const char *m_nameData = nullptr; | ||
| 120 | int m_nameCount = 0; | ||
| 121 | QHash<int, QPair<int, int>> m_postalRanges; // country → [first, last) in POST | ||
| 122 | QHash<QString, int> m_countryKeys; // built at load (≈ 3,000 keys) | ||
| 123 | QHash<QString, QList<int>> m_admin1Keys; // "<country>|<key>" → admin1 indices | ||
| 124 | const char *keyBytes(quint32 offset) const; | ||
| 125 | NameEntry nameEntry(int index) const; | ||
| 126 | int firstName(const QByteArray &key) const; | ||
| 127 | }; | ||
| 128 |