提升效率并删除不必要的文件

This commit is contained in:
wzy-warehouse
2026-06-21 15:24:09 +08:00
parent e84aae3ea6
commit 95a74d00f3
24 changed files with 31 additions and 575 deletions
-17
View File
@@ -218,23 +218,6 @@ def _build_qgis_config(batch_folder: str) -> dict:
"qgis": {
"exportDpi": getattr(settings, "QGIS_EXPORT_DPI", 300),
},
"template_override": {
"enabled": getattr(settings, "QGIS_TEMPLATE_OVERRIDE_ENABLED", False),
"original": {
"host": getattr(settings, "QGIS_TEMPLATE_OVERRIDE_ORIGINAL_HOST", "localhost"),
"port": getattr(settings, "QGIS_TEMPLATE_OVERRIDE_ORIGINAL_PORT", 5432),
"dbname": getattr(settings, "QGIS_TEMPLATE_OVERRIDE_ORIGINAL_DB_NAME", "yjzyk_xian"),
"schema": getattr(settings, "QGIS_TEMPLATE_OVERRIDE_ORIGINAL_SCHEMA", "base"),
},
"actual": {
"host": getattr(settings, "QGIS_TEMPLATE_OVERRIDE_ACTUAL_HOST", ""),
"port": getattr(settings, "QGIS_TEMPLATE_OVERRIDE_ACTUAL_PORT", ""),
"dbname": getattr(settings, "QGIS_TEMPLATE_OVERRIDE_ACTUAL_DB_NAME", "xian_new"),
"schema": getattr(settings, "QGIS_TEMPLATE_OVERRIDE_ACTUAL_SCHEMA", "qgis"),
"username": getattr(settings, "DB_USER", "postgres"),
"password": getattr(settings, "DB_PASSWORD", ""),
},
},
"static_layers": build_static_layers_config(gpkg_dir),
"batch_folder": batch_folder,
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-244
View File
@@ -1,244 +0,0 @@
{
"templates": {
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨内涝潜在隐患点及人口分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\4c40d3feeddf4567.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.95707951802657,
"ymin": 32.9127480757085,
"xmax": 109.03945634967837,
"ymax": 33.622777448228966
}
},
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨内涝潜在隐患点及农作物分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\19470628504c856d.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.95707951802657,
"ymin": 32.9127480757085,
"xmax": 109.03945634967837,
"ymax": 33.622777448228966
}
},
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨泥石流潜在隐患点及农作物分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\32dc5df99342a7da.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.95707951802657,
"ymin": 32.9127480757085,
"xmax": 109.03945634967837,
"ymax": 33.622777448228966
}
},
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨防汛物资分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\f0cf04a82b3bfb58.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.95707951802657,
"ymin": 32.9127480757085,
"xmax": 109.03945634967837,
"ymax": 33.622777448228966
}
},
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨山洪潜在隐患点及人口分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\66a88abaa976d72a.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.95707951802657,
"ymin": 32.9127480757085,
"xmax": 109.03945634967837,
"ymax": 33.622777448228966
}
},
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨山洪潜在隐患点及农作物分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\3bac2f2636b197e9.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.95707951802657,
"ymin": 32.9127480757085,
"xmax": 109.03945634967837,
"ymax": 33.622777448228966
}
},
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨附近医院分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\ef423854adc709b4.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.95707951802657,
"ymin": 32.9127480757085,
"xmax": 109.03945634967837,
"ymax": 33.622777448228966
}
},
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨滑坡潜在隐患点及人口分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\98c5467c5904eee5.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.95707951802657,
"ymin": 32.9127480757085,
"xmax": 109.03945634967837,
"ymax": 33.622777448228966
}
},
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨地灾风险区分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\41f47cbdd9c9c8d0.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.87768948431949,
"ymin": 32.972841920667406,
"xmax": 108.96006631597123,
"ymax": 33.68287129318787
}
},
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨附近水库分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\dc2534d5788a805f.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.95707951802657,
"ymin": 32.9127480757085,
"xmax": 109.03945634967837,
"ymax": 33.622777448228966
}
},
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨滑坡潜在隐患点及农作物分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\bc46a5234f68e54e.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.95707951802657,
"ymin": 32.9127480757085,
"xmax": 109.03945634967837,
"ymax": 33.622777448228966
}
},
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨救援队伍分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\d8c1e99a6640a0fa.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.95707951802657,
"ymin": 32.9127480757085,
"xmax": 109.03945634967837,
"ymax": 33.622777448228966
}
},
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨城市生命线工程分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\82ba30aacf8a53dd.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.46472706539424,
"ymin": 32.921385746843555,
"xmax": 108.51635924537791,
"ymax": 33.61124690706137
}
},
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨泥石流潜在隐患点及人口分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\5a480fbeb8b2c66c.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.95707951802657,
"ymin": 32.9127480757085,
"xmax": 109.03945634967837,
"ymax": 33.622777448228966
}
},
"F:/project/xian/xian_algorithm_new/app/data/template/rainfall/暴雨避难场所分布图.qgz": {
"file": "F:\\project\\xian\\xian_algorithm_new\\app\\data\\cache\\qgis_templates\\d94f0d54dd607cbf.qgz",
"layout": "A3",
"texts": {
"mapTitle": "四川长宁6.0级地震震中附近乡镇距离分布图",
"mapTime": "制图时间:2020年11月",
"mapUint": "制图单位:四川省地震应急分服务中心",
"info": "时间:2019年06月17日22点55分\n震级:6.0级\n位置:四川省宜宾市长宁县双河镇"
},
"extent": {
"xmin": 107.46472706539424,
"ymin": 32.921385746843555,
"xmax": 108.51635924537791,
"ymax": 33.61124690706137
}
}
}
}
+3 -6
View File
@@ -124,12 +124,9 @@ def main():
# 从 config.settings 读取数据库配置
try:
from config import settings
host = getattr(settings, "QGIS_TEMPLATE_OVERRIDE_ACTUAL_HOST",
getattr(settings, "DB_HOST", "47.92.216.173"))
port = str(getattr(settings, "QGIS_TEMPLATE_OVERRIDE_ACTUAL_PORT",
getattr(settings, "DB_PORT", 7654)))
dbname = getattr(settings, "QGIS_TEMPLATE_OVERRIDE_ACTUAL_DB_NAME",
getattr(settings, "DB_NAME", "xian_new"))
host = getattr(settings, "DB_HOST", "47.92.216.173")
port = str(getattr(settings, "DB_PORT", 7654))
dbname = getattr(settings, "DB_NAME", "xian_new")
user = getattr(settings, "DB_USER", "postgres")
password = getattr(settings, "DB_PASSWORD", "zhangsan")
except Exception:
+23 -75
View File
@@ -1,6 +1,6 @@
"""
地图生成主流程控制器。
协调模板加载图层过滤、缩放、文本更新导出。
模板加载图层过滤 → 缩放 → 文本更新导出。
"""
import os
import time
@@ -9,7 +9,6 @@ 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_cache import TemplateCache
from .template_modifier import TemplateModifier
from .layer_filter import LayerFilter
from .map_exporter import MapExporter
@@ -17,56 +16,37 @@ from app.utils.map_zoom import MapZoom
logger = get_logger("qgis.service")
# 全局模板缓存(跨请求复用)
template_cache = TemplateCache()
class MapService:
def __init__(self, config: dict):
self.config = config
def generate(self, model: dict) -> str:
"""
执行完整的地图生成流程。
"""
_timing = {} # {step: seconds}
_timing = {}
t_total = time.time()
template_path = model["path"]
template_name = os.path.basename(template_path)
project = QgsProject.instance()
is_cache_hit = template_cache.is_loaded(template_path)
# ── 步骤 1:加载/恢复模板 ──
# ── 步骤 1:加载模板 ──
t0 = time.time()
if is_cache_hit:
logger.info(f"[缓存命中] {template_name}")
project.clear()
project, texts, extent = template_cache.restore_template(template_path)
template_cache.reset_project_state(project, texts, extent)
else:
logger.info(f"[首次加载] {template_name}")
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
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()
if not is_cache_hit:
self._fix_invalid_layers(project)
self._fix_invalid_layers(project)
_timing["2.connections"] = time.time() - t0
# ★ 首次加载完成后再保存缓存(确保图层已修正)
if not is_cache_hit:
template_cache.save_current(template_path, model.get("mapLayout", "A3"))
# ── 步骤 3:图层过滤 ──
t0 = time.time()
LayerFilter().apply(project, model)
@@ -77,15 +57,11 @@ class MapService:
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}"
)
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"],
"X": model["centerX"], "Y": model["centerY"], "value": model["zoomValue"],
})
_timing["4.zoom"] = time.time() - t0
@@ -99,18 +75,14 @@ class MapService:
total = time.time() - t_total
steps = ", ".join(f"{k}={v:.1f}s" for k, v in _timing.items())
logger.info(
f"{'[缓存命中]' if is_cache_hit else '[首次加载]'} "
f"{template_name}{total:.1f}s ({steps})"
)
logger.info(f"{template_name}{total:.1f}s ({steps})")
return model["name"]
def _fix_invalid_layers(self, project: QgsProject) -> None:
"""只修复 TemplateModifier 处理后仍无效的图层TemplateModifier 已修正大部分连接)"""
"""只修复 TemplateModifier 处理后仍无效的图层"""
t0 = time.time()
db_config = self.config["db"]
override = self.config.get("template_override", {})
actual_schema = override.get("actual", {}).get("schema", "qgis")
actual_schema = "qgis"
fixed = 0
failed = 0
@@ -120,7 +92,7 @@ class MapService:
continue
total += 1
if layer.isValid():
continue # TemplateModifier 已修正,跳过
continue
try:
self._fix_postgres_layer(layer, db_config, actual_schema)
if layer.isValid():
@@ -138,42 +110,21 @@ class MapService:
f"修复{fixed}, 仍失败{failed}, 耗时{elapsed:.1f}s"
)
@staticmethod
def _resolve_postgres_layers(project: QgsProject) -> None:
"""FlagDontResolveLayers 跳过了数据库连接,重新 setDataSource 触发"""
for layer in project.mapLayers().values():
if layer.providerType() != "postgres":
continue
try:
source = layer.source()
if source:
layer.setDataSource(source, layer.name(), "postgres")
except Exception:
pass
@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"],
db_config["host"], str(db_config["port"]),
db_config["database"], db_config["username"], db_config["password"],
)
# Schema 替换
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_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}"'
@@ -181,17 +132,14 @@ class MapService:
if full_old in uri_str:
uri_str = uri_str.replace(full_old, full_new)
uri = QgsDataSourceUri(uri_str)
# SRID 修正
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}")
logger.error(f" 更新图层 {layer.name()} 连接失败: {e}")
+2 -11
View File
@@ -115,18 +115,10 @@ def main():
qgs_app = QgsApplication([], False)
qgs_app.initQgis()
from app.services.qgis.map_service import MapService, template_cache
from app.services.qgis.template_modifier import TemplateModifier
from app.services.qgis.map_service import MapService
from app.config.qgis_mappings import build_static_layers_config, get_gpkg_dir
# 从磁盘恢复已缓存的模板
cached_count = template_cache.load_persistent_cache()
print(f"[qgis_daemon] QGIS 已启动, 磁盘缓存: {cached_count} 个模板",
file=sys.stderr, flush=True)
total_cached = len(template_cache._cache)
print(f"[qgis_daemon] 就绪: 缓存共{total_cached}个模板",
file=sys.stderr, flush=True)
print("[qgis_daemon] QGIS 已启动", file=sys.stderr, flush=True)
# ── 请求处理循环 ──
print("[qgis_daemon] 等待请求...", file=sys.stderr, flush=True)
@@ -197,7 +189,6 @@ def main():
# 清理
project = QgsProject.instance()
project.clear()
template_cache.cleanup()
print("[qgis_daemon] 已退出", file=sys.stderr, flush=True)
-6
View File
@@ -138,12 +138,6 @@ def main():
# 初始化 QgsApplication(只做一次)
qgs_app = _init_qgis()
# 从磁盘恢复已缓存的模板(跨进程加速)
from app.services.qgis.map_service import template_cache
cached_count = template_cache.load_persistent_cache()
if cached_count > 0:
print(f"[qgis_runner] 磁盘缓存命中: {cached_count} 个模板", file=sys.stderr)
try:
from app.services.qgis.map_service import MapService
-192
View File
@@ -1,192 +0,0 @@
"""
模板缓存引擎(支持持久化)。
流程:
1. 首次加载:TemplateModifier 修改模板 → project.read() → 保存到磁盘缓存
2. 同进程内缓存命中:从内存恢复(~1s)
3. 跨进程/重启:从磁盘缓存恢复(首次 ~5s,后续 ~1s)
"""
import hashlib
import json
import os
import shutil
import time
import tempfile
from qgis.core import QgsProject, QgsRectangle
from app.config.paths import get_logger
logger = get_logger("qgis.cache")
# 持久化缓存目录(项目根目录下)
_CACHE_DIR = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))),
"app", "data", "cache", "qgis_templates",
)
_INDEX_FILE = os.path.join(_CACHE_DIR, "index.json")
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 save_current(self, template_path: str, layout_name: str = "A4") -> None:
"""将当前已加载的项目保存到内存缓存 + 磁盘缓存"""
project = QgsProject.instance()
# ── 保存项目到持久化缓存目录 ──
os.makedirs(_CACHE_DIR, exist_ok=True)
key = self._path_key(template_path)
cache_file = os.path.join(_CACHE_DIR, f"{key}.qgz")
t_save = time.time()
project.write(cache_file)
logger.info(f" 缓存写入磁盘: {os.path.basename(cache_file)} ({time.time() - t_save:.1f}s)")
# ── 记录状态 ──
texts = {}
extent = None
layout = project.layoutManager().layoutByName(layout_name)
if layout:
for item_id in ["mapTitle", "mapTime", "mapUint", "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())
entry = {
"file": cache_file,
"texts": texts,
"extent": extent,
"layout": layout_name,
}
self._cache[template_path] = entry
# ── 更新索引文件 ──
self._update_index(template_path, entry)
logger.info(f" 模板已缓存: {os.path.basename(template_path)} ({len(self._cache)} 个)")
def restore_template(self, template_path: str) -> tuple:
"""从缓存恢复模板"""
cached = self._cache.get(template_path)
if not cached:
raise RuntimeError(f"模板未缓存: {template_path}")
project = QgsProject.instance()
t0 = time.time()
# 大部分图层已 GPKG 本地化,不需要 FlagDontResolveLayers
project.read(cached["file"])
logger.debug(f" 恢复耗时: {time.time() - t0:.1f}s")
return project, cached["texts"], cached["extent"]
def reset_project_state(self, project: QgsProject, texts: dict, extent) -> None:
"""重置项目到干净状态"""
for layer in project.mapLayers().values():
if layer.subsetString():
layer.setSubsetString("")
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)
def load_persistent_cache(self) -> int:
"""从磁盘恢复所有缓存到内存(qgis_runner 启动时调用)"""
if not os.path.isfile(_INDEX_FILE):
logger.info(" 磁盘缓存为空,首次启动需要加载模板")
return 0
try:
with open(_INDEX_FILE, "r", encoding="utf-8") as f:
index = json.load(f)
except (json.JSONDecodeError, OSError) as e:
logger.warning(f" 缓存索引损坏: {e}")
return 0
count = 0
for template_path, entry in index.get("templates", {}).items():
cache_file = entry.get("file", "")
if not cache_file or not os.path.isfile(cache_file):
logger.debug(f" 跳过无效缓存: {os.path.basename(template_path)}")
continue
texts = entry.get("texts", {})
extent = entry.get("extent")
# 反序列化 extent
if extent and isinstance(extent, dict):
extent = QgsRectangle(
extent.get("xmin", 0), extent.get("ymin", 0),
extent.get("xmax", 0), extent.get("ymax", 0),
)
else:
extent = None
self._cache[template_path] = {
"file": cache_file,
"texts": texts,
"extent": extent,
"layout": entry.get("layout", "A4"),
}
count += 1
logger.info(f" 磁盘缓存加载: {count} 个模板")
return count
def cleanup(self) -> None:
"""清理内存缓存(磁盘缓存保留)"""
self._cache.clear()
logger.info("已清理内存缓存")
# ── 内部方法 ──
@staticmethod
def _path_key(template_path: str) -> str:
"""模板路径 → 缓存文件名 key"""
return hashlib.md5(template_path.encode()).hexdigest()[:16]
def _update_index(self, template_path: str, entry: dict) -> None:
"""更新磁盘索引文件"""
index = {}
if os.path.isfile(_INDEX_FILE):
try:
with open(_INDEX_FILE, "r", encoding="utf-8") as f:
index = json.load(f)
except (json.JSONDecodeError, OSError):
pass
index.setdefault("templates", {})[template_path] = {
"file": entry["file"],
"layout": entry.get("layout", "A4"),
"texts": entry.get("texts", {}),
"extent": (
{
"xmin": entry["extent"].xMinimum(),
"ymin": entry["extent"].yMinimum(),
"xmax": entry["extent"].xMaximum(),
"ymax": entry["extent"].yMaximum(),
}
if entry.get("extent") else None
),
}
with open(_INDEX_FILE, "w", encoding="utf-8") as f:
json.dump(index, f, ensure_ascii=False, indent=2)
+3 -4
View File
@@ -57,14 +57,13 @@ class TemplateModifier:
def modify(self, template_path: str) -> str:
"""修改模板文件,返回修改后的临时 .qgz 路径"""
override = self.config.get("template_override")
has_override = override and override.get("enabled", False)
has_static = bool(self._static_map)
if not has_override and not has_static:
if not override and not has_static:
return template_path
orig = override["original"] if has_override else None
actual = override["actual"] if has_override else None
orig = override.get("original") if override else None
actual = override.get("actual") if override else None
try:
tmp = tempfile.NamedTemporaryFile(
-20
View File
@@ -85,16 +85,6 @@ QGIS_ROOT = "D:/QGIS"
# 专题图输出子目录
QGIS_OUTPUT_DIR = "xian/qgis/map/:eventType/:disasterTime"
QGIS_DEFAULTS_MAP_UNIT = "制图单位:西安市应急管理局"
# 模板数据库覆盖:将模板中硬编码的连接替换为实际环境连接
QGIS_TEMPLATE_OVERRIDE_ENABLED = true
QGIS_TEMPLATE_OVERRIDE_ORIGINAL_HOST = "localhost"
QGIS_TEMPLATE_OVERRIDE_ORIGINAL_PORT = 5432
QGIS_TEMPLATE_OVERRIDE_ORIGINAL_DB_NAME = "yjzyk_xian"
QGIS_TEMPLATE_OVERRIDE_ORIGINAL_SCHEMA = "base"
QGIS_TEMPLATE_OVERRIDE_ACTUAL_HOST = "47.92.216.173"
QGIS_TEMPLATE_OVERRIDE_ACTUAL_PORT = 7654
QGIS_TEMPLATE_OVERRIDE_ACTUAL_DB_NAME = "xian_new"
QGIS_TEMPLATE_OVERRIDE_ACTUAL_SCHEMA = "qgis"
# ============================================================
# 生产环境
@@ -133,13 +123,3 @@ FILE_STORE_DIR = "/data"
# QGIS 配置
# ============================================================
QGIS_ROOT = "/home/QGIS"
# 模板数据库覆盖:将模板中硬编码的连接替换为实际环境连接
QGIS_TEMPLATE_OVERRIDE_ENABLED = true
QGIS_TEMPLATE_OVERRIDE_ORIGINAL_HOST = "localhost"
QGIS_TEMPLATE_OVERRIDE_ORIGINAL_PORT = 5432
QGIS_TEMPLATE_OVERRIDE_ORIGINAL_DB_NAME = "yjzyk_xian"
QGIS_TEMPLATE_OVERRIDE_ORIGINAL_SCHEMA = "base"
QGIS_TEMPLATE_OVERRIDE_ACTUAL_HOST = "10.22.245.138"
QGIS_TEMPLATE_OVERRIDE_ACTUAL_PORT = 54321
QGIS_TEMPLATE_OVERRIDE_ACTUAL_DB_NAME = "xian_new"
QGIS_TEMPLATE_OVERRIDE_ACTUAL_SCHEMA = "qgis"