File indexing completed on 2024-05-05 03:55:01

0001 # SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
0002 # SPDX-License-Identifier: LGPL-2.0-or-later
0003 
0004 from config import *
0005 import io
0006 import os
0007 from qgis import *
0008 from qgis.core import *
0009 
0010 def tzIdToEnum(tzId):
0011     return tzId.replace('/', '_').replace('-', '_')
0012 
0013 def normalizedTz(tzId):
0014     if tzId in TZID_MAP:
0015         return TZID_MAP[tzId]
0016     return tzId
0017 
0018 def normalizedCountry(code):
0019     if code in ISO3166_1_MAP:
0020         return ISO3166_1_MAP[code]
0021     return code
0022 
0023 # Generate IANA timezone names string table
0024 # This allows us to reference timezones by a uint16_t in other data tables
0025 class TimezoneStringTableTask(QgsTask):
0026     def __init__(self, context):
0027         super().__init__('Generate timezone string table', QgsTask.CanCancel)
0028         self.context = context
0029 
0030     def run(self):
0031         QgsMessageLog.logMessage('Generating timezone string table', LOG_CATEGORY, Qgis.Info)
0032         tzLayer = self.context['tzLayer']
0033         tzIds = set()
0034         for tz in tzLayer.getFeatures():
0035             tzIds.add(tz['tzid'])
0036         tzIds = list(tzIds)
0037         tzIds.sort()
0038         offsets = [0]
0039 
0040         out = open('../../data/timezone_name_table.cpp', 'w')
0041         out.write("""/*
0042  * SPDX-License-Identifier: ODbL-1.0
0043  * SPDX-FileCopyrightText: OpenStreetMap contributors
0044  *
0045  * Autogenerated using QGIS - do not edit!
0046  */
0047 
0048 static constexpr const char timezone_name_table[] =
0049 """)
0050         for tzId in tzIds:
0051             out.write(f"    \"{tzId}\\0\"\n")
0052             offsets.append(offsets[-1] + len(tzId) + 1)
0053         out.seek(out.tell() - 1, io.SEEK_SET) # to make clang-format happy
0054         out.write(";\n")
0055         out.close()
0056 
0057         out = open('../../data/timezone_names_p.h', 'w')
0058         out.write("""/*
0059  * SPDX-License-Identifier: ODbL-1.0
0060  * SPDX-FileCopyrightText: OpenStreetMap contributors
0061  *
0062  * Autogenerated using QGIS - do not edit!
0063  */
0064 
0065 #ifndef TIMEZONE_NAMES_P_H
0066 #define TIMEZONE_NAMES_P_H
0067 
0068 #include <cstdint>
0069 
0070 enum Tz : uint16_t {
0071 """)
0072         for i in range(len(tzIds)):
0073             out.write(f"    {tzIdToEnum(tzIds[i])} = {offsets[i]},\n")
0074         out.write(f"    Undefined = {offsets[-1] - 1},\n") # points to the last null byte
0075         out.write("};\n\n#endif\n")
0076         out.close()
0077         return True
0078 
0079 # Computes country/country subdivision to timezone mapping
0080 class RegionToTimezoneMapTask(QgsTask):
0081     def __init__(self, context):
0082         super().__init__('Computing region to timezone mapping', QgsTask.CanCancel)
0083         self.context = context
0084 
0085     def run(self):
0086         QgsMessageLog.logMessage('Computing region to timezone mapping', LOG_CATEGORY, Qgis.Info)
0087         countryLayer = self.context['countryLayer']
0088         tzLayer = self.context['tzLayer']
0089         countryToTz = {}
0090         for country in countryLayer.getFeatures():
0091             countryCode = country['ISO3166-1']
0092             if not countryCode in countryToTz:
0093                 countryToTz[countryCode] = set()
0094             countryGeom = country.geometry()
0095             countryArea = countryGeom.area()
0096             for tz in tzLayer.getFeatures():
0097                 tzId = normalizedTz(tz['tzId'])
0098                 if tz.geometry().intersects(countryGeom):
0099                     # filter out intersection noise along the boundaries
0100                     area = tz.geometry().intersection(countryGeom).area()
0101                     tzAreaRatio = area / tz.geometry().area()
0102                     countryAreaRatio = area / countryArea
0103                     if tzAreaRatio > 0.01 or countryAreaRatio > 0.1:
0104                         countryToTz[countryCode].add(tzId)
0105 
0106         out = open('../../data/country_timezone_map.cpp', 'w')
0107         out.write("""/*
0108  * SPDX-License-Identifier: ODbL-1.0
0109  * SPDX-FileCopyrightText: OpenStreetMap contributors
0110  *
0111  * Autogenerated using QGIS - do not edit!
0112  */
0113 
0114 #include "isocodes_p.h"
0115 #include "mapentry_p.h"
0116 #include "timezone_names_p.h"
0117 
0118 static constexpr const MapEntry<uint16_t> country_timezone_map[] = {
0119 """)
0120         countries = list(countryToTz)
0121         countries.sort()
0122         for country in countries:
0123             if len(countryToTz[country]) == 1:
0124                 out.write(f"    {{IsoCodes::alpha2CodeToKey(\"{country}\"), Tz::{tzIdToEnum(list(countryToTz[country])[0])}}},\n")
0125 
0126         out.write("};\n")
0127         out.close()
0128 
0129         # for countries with more than one tz, match against subdivisions
0130         subdivToTz = {}
0131         subdivLayer = self.context['subdivLayer']
0132         tzLayer = self.context['tzLayer']
0133         for subdiv in subdivLayer.getFeatures():
0134             code = subdiv['ISO3166-2']
0135             country = code[:2]
0136             if len(countryToTz[country]) <= 1:
0137                 continue
0138             if not code in subdivToTz:
0139                 subdivToTz[code] = {}
0140             subdivGeom = subdiv.geometry()
0141             subdivArea = subdivGeom.area()
0142             for tz in tzLayer.getFeatures():
0143                 tzId = normalizedTz(tz['tzId'])
0144                 if tz.geometry().intersects(subdivGeom):
0145                     # filter out intersection noise along the boundaries
0146                     area = tz.geometry().intersection(subdivGeom).area()
0147                     tzAreaRatio = area / tz.geometry().area()
0148                     subdivAreaRatio = area / subdivArea
0149                     if tzAreaRatio > 0.01 or subdivAreaRatio > 0.1:
0150                         if not tzId in subdivToTz[code]:
0151                             subdivToTz[code][tzId] = area
0152                         else:
0153                             subdivToTz[code][tzId] += area
0154 
0155         out = open('../../data/subdivision_timezone_map.cpp', 'w')
0156         out.write("""/*
0157  * SPDX-License-Identifier: ODbL-1.0
0158  * SPDX-FileCopyrightText: OpenStreetMap contributors
0159  *
0160  * Autogenerated using QGIS - do not edit!
0161  */
0162 
0163 #include "isocodes_p.h"
0164 #include "mapentry_p.h"
0165 #include "timezone_names_p.h"
0166 
0167 static constexpr const MapEntry<uint32_t> subdivision_timezone_map[] = {
0168 """)
0169         subdivs = list(subdivToTz)
0170         subdivs.sort()
0171         for subdiv in subdivs:
0172             # sort by area, biggest one first
0173             tzs = list(subdivToTz[subdiv])
0174             tzs.sort(key = lambda x: subdivToTz[subdiv][x], reverse = True)
0175             for tz in tzs:
0176                 out.write(f"    {{IsoCodes::subdivisionCodeToKey(\"{subdiv}\"), Tz::{tzIdToEnum(tz)}}},\n")
0177             if len(subdivToTz[subdiv]) == 0:
0178                 out.write(f"    // {subdiv}\n")
0179 
0180         out.write("};\n")
0181         out.close()
0182 
0183         self.context['countryToTimezoneMap'] = countryToTz
0184         self.context['subdivisionToTimezoneMap'] = subdivToTz
0185         return True