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 import os 0005 import requests 0006 from qgis import * 0007 from qgis.core import * 0008 import time 0009 import zipfile 0010 from config import * 0011 0012 # Download and unpack Shapefiles 0013 class LayerDownloadTask(QgsTask): 0014 def __init__(self, url, dest): 0015 super().__init__('Download Shapefile', QgsTask.CanCancel) 0016 self.url = url 0017 self.dest = dest 0018 0019 def run(self): 0020 try: 0021 QgsMessageLog.logMessage(f"Downloading and unpacking {self.dest}...", LOG_CATEGORY, Qgis.Info) 0022 if not os.path.exists(self.dest): 0023 r = requests.get(self.url) 0024 if r.status_code < 400: 0025 with open(self.dest, 'wb') as f: 0026 f.write(r.content) 0027 with zipfile.ZipFile(self.dest, 'r') as z: 0028 z.extractall('.') 0029 QgsMessageLog.logMessage(f"Downloaded and unpacked {self.dest}.", LOG_CATEGORY, Qgis.Info) 0030 except Exception as e: 0031 QgsMessageLog.logMessage(f"Exception in task: {e}", LOG_CATEGORY, Qgis.Critical) 0032 return True 0033 0034 0035 # Load and simplify Shapefile layers 0036 # Simplification is done to massively speed up geometry intersection computation 0037 # (for reference: the original KItinerary tz spatial index took 8h to compute without simplification, 0038 # and about 15 minutes with a Douglas Peucker simplification with a 0.001 threshold, with no practical 0039 # loss of precision 0040 class LoadLayerTask(QgsTask): 0041 def __init__(self, url, fileName, context, layerName): 0042 super().__init__(f"Loading layer {fileName}", QgsTask.CanCancel) 0043 self.layer = None 0044 self.url = url 0045 self.fileName = fileName 0046 self.context = context 0047 self.layerName = layerName 0048 self.downloadTask = LayerDownloadTask(url, fileName) 0049 self.addSubTask(self.downloadTask, [], QgsTask.ParentDependsOnSubTask) 0050 0051 def run(self): 0052 QgsMessageLog.logMessage(f"Simplifying layer {self.fileName}", LOG_CATEGORY, Qgis.Info) 0053 fullLayer = QgsVectorLayer(self.fileName, f"{self.fileName}-full-resolution", 'ogr') 0054 if not fullLayer.isValid(): 0055 QgsMessageLog.logMessage(f"Failed to load layer {self.fileName}!", LOG_CATEGORY, Qgis.Critical) 0056 result = processing.run('qgis:simplifygeometries', {'INPUT': fullLayer, 'METHOD': 0, 'TOLERANCE': 0.001, 'OUTPUT': 'TEMPORARY_OUTPUT' }) 0057 self.layer = result['OUTPUT'] 0058 self.layer.setName(f"{self.fileName}-simplified") 0059 self.context[self.layerName] = self.layer 0060 QgsMessageLog.logMessage(f"Simplified layer {self.fileName}", LOG_CATEGORY, Qgis.Info) 0061 return True 0062 0063 def finished(self, result): 0064 QgsProject.instance().addMapLayer(self.layer) 0065 0066 0067 # Filter out too small elements in the ISO 3166-2 layer 0068 class Iso3166_2FilterTask(QgsTask): 0069 def __init__(self, context): 0070 super().__init__('Filtering ISO 3166-2 layer', QgsTask.CanCancel) 0071 self.context = context 0072 0073 def run(self): 0074 QgsMessageLog.logMessage('Filtering ISO 3166-2 layer', LOG_CATEGORY, Qgis.Info) 0075 subdivLayer = self.context['subdivLayer'] 0076 toBeRemoved = [] 0077 for feature in subdivLayer.getFeatures(): 0078 # sic: the key is really "admin_leve" in the input file, due to length restrictions in the Shapefile... 0079 level = feature['admin_leve'] 0080 country = feature['ISO3166-2'][:2] 0081 if not isinstance(level, str) or not isinstance(country, str): 0082 continue 0083 for filter in ISO3166_2_FILTER: 0084 if int(level) == filter['admin_level'] and country == filter['country']: 0085 toBeRemoved.append(feature.id()) 0086 break 0087 subdivLayer.dataProvider().deleteFeatures(toBeRemoved) 0088 return True 0089 0090 0091 # Setup all data layers we need 0092 class LoadLayersTask(QgsTask): 0093 def __init__(self, context): 0094 super().__init__('Loading layers...', QgsTask.CanCancel) 0095 self.context = context 0096 self.tasks = [ 0097 LoadLayerTask(TZDATA_URL, f"timezones.shapefile-{TZDATA_VERSION}.zip", context, 'tzLayer'), 0098 LoadLayerTask(ISO3166_1_URL, f"iso3166-1-boundaries.shp-{ISO3166_1_VERSION}.zip", context, 'countryLayer'), 0099 LoadLayerTask(ISO3166_2_URL, f"iso3166-2-boundaries.shp-{ISO3166_2_VERSION}.zip", context, 'subdivLayer') 0100 ] 0101 for task in self.tasks: 0102 self.addSubTask(task, [], QgsTask.ParentDependsOnSubTask) 0103 0104 self.filterTask = Iso3166_2FilterTask(context) 0105 self.addSubTask(self.filterTask, [self.tasks[2]], QgsTask.ParentDependsOnSubTask) 0106 0107 def run(self): 0108 return True