""" 模板缓存引擎。解决 QgsProject 单例问题。 流程: 1. 首次请求:project.read() 加载模板(慢,仅一次) 2. 加载后 project.write() 保存到临时文件 3. 后续同模板请求:从临时文件恢复(快,连接复用) 4. 手动恢复文本/过滤/缩放(毫秒级) """ import os import time import tempfile from qgis.core import QgsProject, QgsRectangle from app.config.paths import get_logger logger = get_logger("qgis.cache") class TemplateCache: def __init__(self): self._cache: dict[str, dict] = {} def is_loaded(self, template_path: str) -> bool: return template_path in self._cache def load_template(self, template_path: str, layout_name: str = "A4") -> None: """首次加载模板""" start = time.time() project = QgsProject.instance() logger.info(f"首次加载: {os.path.basename(template_path)}") project.read(template_path) logger.info(f"project.read() 耗时: {time.time() - start:.1f}s") # 保存到临时文件 tmp_file = tempfile.NamedTemporaryFile( suffix=".qgz", delete=False, dir=os.path.dirname(template_path), ) tmp_path = tmp_file.name tmp_file.close() t_save = time.time() project.write(tmp_path) logger.info(f"项目保存耗时: {time.time() - t_save:.1f}s") # 记录初始状态 texts = {} extent = None layout = project.layoutManager().layoutByName(layout_name) if layout: for item_id in ["mapTitle", "mapTime", "mapUnit", "info"]: item = layout.itemById(item_id) if item: texts[item_id] = item.text() map_item = layout.itemById("Map") if map_item: extent = QgsRectangle(map_item.extent()) self._cache[template_path] = { "file": tmp_path, "texts": texts, "extent": extent, "layout": layout_name, } logger.info(f"模板加载完成,总耗时: {time.time() - start:.1f}s") def restore_template(self, template_path: str) -> tuple: """从缓存恢复模板""" cached = self._cache.get(template_path) if not cached: raise RuntimeError(f"模板未缓存: {template_path}") start = time.time() project = QgsProject.instance() logger.info(f"恢复模板: {os.path.basename(template_path)}") project.read(cached["file"]) logger.info(f"project.read() 耗时: {time.time() - start:.1f}s") return project, cached["texts"], cached["extent"] def reset_project_state(self, project: QgsProject, texts: dict, extent) -> None: """重置项目到干净状态""" start = time.time() for layer in project.mapLayers().values(): if layer.subsetString(): layer.setSubsetString("") # 获取 layout 名称(从缓存中) layout_name = "A4" for cached in self._cache.values(): layout_name = cached.get("layout", "A4") break layout = project.layoutManager().layoutByName(layout_name) if layout: for item_id, text in texts.items(): item = layout.itemById(item_id) if item: item.setText(text) if extent: map_item = layout.itemById("Map") if map_item: map_item.zoomToExtent(extent) logger.info(f"状态重置耗时: {time.time() - start:.3f}s") def cleanup(self) -> None: """清理所有临时文件""" for cached in self._cache.values(): try: os.remove(cached["file"]) except OSError: pass self._cache.clear() logger.info("已清理所有缓存")