""" 地图生成主流程控制器。 模板加载 → 图层过滤 → 缩放 → 文本更新 → 导出。 """ import os import time from qgis.core import QgsProject, QgsDataSourceUri from app.config.paths import get_logger from app.config.qgis_mappings import TABLE_RENAMES, SCHEMA_REPLACEMENTS from .template_modifier import TemplateModifier from .layer_filter import LayerFilter from .map_exporter import MapExporter from app.utils.map_zoom import MapZoom logger = get_logger("qgis.service") class MapService: def __init__(self, config: dict): self.config = config def generate(self, model: dict) -> str: _timing = {} t_total = time.time() template_path = model["path"] template_name = os.path.basename(template_path) project = QgsProject.instance() # ── 步骤 1:加载模板 ── t0 = time.time() project.clear() modifier = TemplateModifier(self.config) actual_path = modifier.modify(template_path) project.read(actual_path) if actual_path != template_path: try: os.remove(actual_path) except OSError: pass _timing["1.load"] = time.time() - t0 # ── 步骤 2:图层连接修正 ── t0 = time.time() self._fix_invalid_layers(project) _timing["2.connections"] = time.time() - t0 # ── 步骤 3:图层过滤 ── t0 = time.time() LayerFilter().apply(project, model) _timing["3.filter"] = time.time() - t0 # ── 步骤 4:地图缩放 ── t0 = time.time() layout = project.layoutManager().layoutByName(model["mapLayout"]) if layout is None: available = [l.name() for l in project.layoutManager().layouts()] raise RuntimeError(f"模板中未找到布局 '{model['mapLayout']}',可用布局:{available}") map_item = layout.itemById("Map") zoom = MapZoom(project, layout, map_item) zoom.execute(model["zoomRule"], { "X": model["centerX"], "Y": model["centerY"], "value": model["zoomValue"], }) _timing["4.zoom"] = time.time() - t0 # ── 步骤 5:文本 + 比例尺 + 导出 ── t0 = time.time() exporter = MapExporter(self.config, layout) exporter.update_texts(model) exporter.update_scale_bar() exporter.export(model["outFile"]) _timing["5.export"] = time.time() - t0 total = time.time() - t_total steps = ", ".join(f"{k}={v:.1f}s" for k, v in _timing.items()) logger.info(f"{template_name} → {total:.1f}s ({steps})") return model["name"] def _fix_invalid_layers(self, project: QgsProject) -> None: """只修复 TemplateModifier 处理后仍无效的图层""" t0 = time.time() db_config = self.config["db"] actual_schema = "qgis" fixed = 0 failed = 0 total = 0 for layer in project.mapLayers().values(): if layer.providerType() != "postgres": continue total += 1 if layer.isValid(): continue try: self._fix_postgres_layer(layer, db_config, actual_schema) if layer.isValid(): fixed += 1 else: failed += 1 except Exception as e: logger.error(f" 修复图层 {layer.name()} 失败: {e}") failed += 1 elapsed = time.time() - t0 if total: logger.info( f" 图层修正: 总计{total}个PG层, 跳过{total - fixed - failed}(已有效), " f"修复{fixed}, 仍失败{failed}, 耗时{elapsed:.1f}s" ) @staticmethod def _fix_postgres_layer(layer, db_config, actual_schema): """修正单个 PostgreSQL 图层的连接参数""" try: uri = layer.dataProvider().uri() uri.setConnection( db_config["host"], str(db_config["port"]), db_config["database"], db_config["username"], db_config["password"], ) uri_str = uri.uri() for old_schema in SCHEMA_REPLACEMENTS: if f'table="{old_schema}".' in uri_str: uri_str = uri_str.replace(f'table="{old_schema}".', f'table="{actual_schema}".') uri = QgsDataSourceUri(uri_str) break uri_str = uri.uri() for old_name, new_name in TABLE_RENAMES.items(): full_old = f'table="{actual_schema}"."{old_name}"' full_new = f'table="{actual_schema}"."{new_name}"' if full_old in uri_str: uri_str = uri_str.replace(full_old, full_new) uri = QgsDataSourceUri(uri_str) uri_str = uri.uri() if " srid=0 " in uri_str: uri_str = uri_str.replace(" srid=0 ", " srid=4326 ") uri = QgsDataSourceUri(uri_str) layer.setDataSource(uri.uri(), layer.name(), "postgres") if layer.isValid(): logger.debug(f" 图层 {layer.name()} 连接更新成功") else: logger.error(f" 图层 {layer.name()} 更新后仍无效") except Exception as e: logger.error(f" 更新图层 {layer.name()} 连接失败: {e}")