135 lines
4.7 KiB
Python
135 lines
4.7 KiB
Python
"""
|
||
地图生成主流程控制器。
|
||
协调模板加载、图层过滤、缩放、文本更新、导出。
|
||
"""
|
||
import os
|
||
import time
|
||
|
||
from qgis.core import QgsProject, QgsDataSourceUri
|
||
|
||
from .template_cache import TemplateCache
|
||
from .template_modifier import TemplateModifier
|
||
from .layer_filter import LayerFilter
|
||
from .map_exporter import MapExporter
|
||
from app.utils.map_zoom import MapZoom
|
||
from app.config.paths import get_logger
|
||
|
||
logger = get_logger("qgis.service")
|
||
|
||
# 全局模板缓存(跨请求复用)
|
||
template_cache = TemplateCache()
|
||
|
||
|
||
class MapService:
|
||
def __init__(self, config: dict):
|
||
self.config = config
|
||
|
||
def generate(self, model: dict) -> str:
|
||
"""
|
||
执行完整的地图生成流程。
|
||
|
||
Args:
|
||
model: 包含地图参数的字典
|
||
|
||
Returns:
|
||
地图名称
|
||
"""
|
||
t_start = time.time()
|
||
template_path = model["path"]
|
||
project = QgsProject.instance()
|
||
is_cache_hit = template_cache.is_loaded(template_path)
|
||
|
||
# 加载/恢复模板
|
||
if is_cache_hit:
|
||
logger.info(f"[缓存命中] {os.path.basename(template_path)}")
|
||
project, texts, extent = template_cache.restore_template(template_path)
|
||
template_cache.reset_project_state(project, texts, extent)
|
||
else:
|
||
logger.info(f"[首次加载] {os.path.basename(template_path)}")
|
||
modifier = TemplateModifier(self.config)
|
||
actual_path = modifier.modify(template_path)
|
||
template_cache.load_template(actual_path, layout_name=model.get("mapLayout", "A4"))
|
||
if actual_path != template_path:
|
||
try:
|
||
os.remove(actual_path)
|
||
except OSError:
|
||
pass
|
||
|
||
# 更新 PostgreSQL 图层连接(仅首次加载)
|
||
if not is_cache_hit:
|
||
self._update_db_connections(project)
|
||
|
||
# 图层过滤
|
||
LayerFilter().apply(project, model)
|
||
|
||
# 地图缩放
|
||
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"],
|
||
})
|
||
|
||
# 文本更新 + 比例尺 + 导出
|
||
exporter = MapExporter(self.config, layout)
|
||
exporter.update_texts(model)
|
||
exporter.update_scale_bar()
|
||
exporter.export(model["outFile"])
|
||
|
||
elapsed = time.time() - t_start
|
||
logger.info(
|
||
f"{'[缓存命中]' if is_cache_hit else '[首次加载]'} "
|
||
f"导出完成: {model['name']},耗时 {elapsed:.1f}s"
|
||
)
|
||
return model["name"]
|
||
|
||
def _update_db_connections(self, project: QgsProject) -> None:
|
||
"""更新所有 PostgreSQL 图层的数据库连接参数"""
|
||
db_config = self.config["db"]
|
||
override = self.config.get("template_override", {})
|
||
actual_schema = override.get("actual", {}).get("schema", "qgis")
|
||
static_count = 0
|
||
|
||
for layer in project.mapLayers().values():
|
||
if layer.providerType() == "ogr":
|
||
static_count += 1
|
||
continue
|
||
|
||
if layer.providerType() != "postgres":
|
||
continue
|
||
|
||
try:
|
||
uri = layer.dataProvider().uri()
|
||
uri.setConnection(
|
||
db_config["host"],
|
||
str(db_config["port"]),
|
||
db_config["database"],
|
||
db_config["username"],
|
||
db_config["password"],
|
||
)
|
||
# 更新 schema(table="base"."xxx" → table="qgis"."xxx")
|
||
old_uri = uri.uri()
|
||
if f'table="{actual_schema}".' not in old_uri:
|
||
new_uri = old_uri.replace('table="base".', f'table="{actual_schema}".')
|
||
if new_uri != old_uri:
|
||
uri = QgsDataSourceUri(new_uri)
|
||
|
||
layer.setDataSource(uri.uri(), layer.name(), "postgres")
|
||
|
||
if layer.isValid():
|
||
logger.info(f"图层 {layer.name()} 连接更新成功")
|
||
else:
|
||
logger.error(f"图层 {layer.name()} 更新后仍无效")
|
||
except Exception as e:
|
||
logger.error(f"更新图层 {layer.name()} 连接失败: {e}")
|
||
|
||
if static_count:
|
||
logger.info(f"静态底图已本地化: {static_count} 个 GPKG 图层跳过远程连接") |