# SPDX-FileCopyrightText: 2021 Volker Krause # SPDX-License-Identifier: LGPL-2.0-or-later from config import * import io import os from qgis import * from qgis.core import * def tzIdToEnum(tzId): return tzId.replace('/', '_').replace('-', '_') def normalizedTz(tzId): if tzId in TZID_MAP: return TZID_MAP[tzId] return tzId def normalizedCountry(code): if code in ISO3166_1_MAP: return ISO3166_1_MAP[code] return code # Generate IANA timezone names string table # This allows us to reference timezones by a uint16_t in other data tables class TimezoneStringTableTask(QgsTask): def __init__(self, context): super().__init__('Generate timezone string table', QgsTask.CanCancel) self.context = context def run(self): QgsMessageLog.logMessage('Generating timezone string table', LOG_CATEGORY, Qgis.Info) tzLayer = self.context['tzLayer'] tzIds = set() for tz in tzLayer.getFeatures(): tzIds.add(tz['tzid']) tzIds = list(tzIds) tzIds.sort() offsets = [0] out = open('../../data/timezone_name_table.cpp', 'w') out.write("""/* * SPDX-License-Identifier: ODbL-1.0 * SPDX-FileCopyrightText: OpenStreetMap contributors * * Autogenerated using QGIS - do not edit! */ static constexpr const char timezone_name_table[] = """) for tzId in tzIds: out.write(f" \"{tzId}\\0\"\n") offsets.append(offsets[-1] + len(tzId) + 1) out.seek(out.tell() - 1, io.SEEK_SET) # to make clang-format happy out.write(";\n") out.close() out = open('../../data/timezone_names_p.h', 'w') out.write("""/* * SPDX-License-Identifier: ODbL-1.0 * SPDX-FileCopyrightText: OpenStreetMap contributors * * Autogenerated using QGIS - do not edit! */ #ifndef TIMEZONE_NAMES_P_H #define TIMEZONE_NAMES_P_H #include enum Tz : uint16_t { """) for i in range(len(tzIds)): out.write(f" {tzIdToEnum(tzIds[i])} = {offsets[i]},\n") out.write(f" Undefined = {offsets[-1] - 1},\n") # points to the last null byte out.write("};\n\n#endif\n") out.close() return True # Computes country/country subdivision to timezone mapping class RegionToTimezoneMapTask(QgsTask): def __init__(self, context): super().__init__('Computing region to timezone mapping', QgsTask.CanCancel) self.context = context def run(self): QgsMessageLog.logMessage('Computing region to timezone mapping', LOG_CATEGORY, Qgis.Info) countryLayer = self.context['countryLayer'] tzLayer = self.context['tzLayer'] countryToTz = {} for country in countryLayer.getFeatures(): countryCode = country['ISO3166-1'] if not countryCode in countryToTz: countryToTz[countryCode] = set() countryGeom = country.geometry() countryArea = countryGeom.area() for tz in tzLayer.getFeatures(): tzId = normalizedTz(tz['tzId']) if tz.geometry().intersects(countryGeom): # filter out intersection noise along the boundaries area = tz.geometry().intersection(countryGeom).area() tzAreaRatio = area / tz.geometry().area() countryAreaRatio = area / countryArea if tzAreaRatio > 0.01 or countryAreaRatio > 0.1: countryToTz[countryCode].add(tzId) out = open('../../data/country_timezone_map.cpp', 'w') out.write("""/* * SPDX-License-Identifier: ODbL-1.0 * SPDX-FileCopyrightText: OpenStreetMap contributors * * Autogenerated using QGIS - do not edit! */ #include "isocodes_p.h" #include "mapentry_p.h" #include "timezone_names_p.h" static constexpr const MapEntry country_timezone_map[] = { """) countries = list(countryToTz) countries.sort() for country in countries: if len(countryToTz[country]) == 1: out.write(f" {{IsoCodes::alpha2CodeToKey(\"{country}\"), Tz::{tzIdToEnum(list(countryToTz[country])[0])}}},\n") out.write("};\n") out.close() # for countries with more than one tz, match against subdivisions subdivToTz = {} subdivLayer = self.context['subdivLayer'] tzLayer = self.context['tzLayer'] for subdiv in subdivLayer.getFeatures(): code = subdiv['ISO3166-2'] country = code[:2] if len(countryToTz[country]) <= 1: continue if not code in subdivToTz: subdivToTz[code] = {} subdivGeom = subdiv.geometry() subdivArea = subdivGeom.area() for tz in tzLayer.getFeatures(): tzId = normalizedTz(tz['tzId']) if tz.geometry().intersects(subdivGeom): # filter out intersection noise along the boundaries area = tz.geometry().intersection(subdivGeom).area() tzAreaRatio = area / tz.geometry().area() subdivAreaRatio = area / subdivArea if tzAreaRatio > 0.01 or subdivAreaRatio > 0.1: if not tzId in subdivToTz[code]: subdivToTz[code][tzId] = area else: subdivToTz[code][tzId] += area out = open('../../data/subdivision_timezone_map.cpp', 'w') out.write("""/* * SPDX-License-Identifier: ODbL-1.0 * SPDX-FileCopyrightText: OpenStreetMap contributors * * Autogenerated using QGIS - do not edit! */ #include "isocodes_p.h" #include "mapentry_p.h" #include "timezone_names_p.h" static constexpr const MapEntry subdivision_timezone_map[] = { """) subdivs = list(subdivToTz) subdivs.sort() for subdiv in subdivs: # sort by area, biggest one first tzs = list(subdivToTz[subdiv]) tzs.sort(key = lambda x: subdivToTz[subdiv][x], reverse = True) for tz in tzs: out.write(f" {{IsoCodes::subdivisionCodeToKey(\"{subdiv}\"), Tz::{tzIdToEnum(tz)}}},\n") if len(subdivToTz[subdiv]) == 0: out.write(f" // {subdiv}\n") out.write("};\n") out.close() self.context['countryToTimezoneMap'] = countryToTz self.context['subdivisionToTimezoneMap'] = subdivToTz return True