xian_qgis

This commit is contained in:
zzw
2026-06-18 10:12:15 +08:00
commit 9813d6d164
16 changed files with 663 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
import logging # Python内置日志模块
import os # 文件路径处理模块
import datetime # 时间处理模块
def info(str):
"""记录INFO级别的日志到info_日期.log文件"""
script_path = os.path.split(os.path.realpath(__file__))[0] # 获取当前脚本所在目录
nowTime = datetime.datetime.now().strftime('%Y%m%d') # 获取当前日期(年月日)
path = os.path.join(script_path+r'\logs', 'info_' +nowTime+".log") # 日志文件路径(logs子目录下)
isExists = os.path.exists(script_path+r'\logs') # 检查logs目录是否存在
if not isExists:
os.makedirs(script_path+r'\logs') # 不存在则创建logs目录
logger = logging.getLogger("run") # 创建名为"run"的日志器
logger.setLevel(level=logging.INFO) # 设置日志级别为INFO
handler = logging.FileHandler(path) # 创建文件处理器(指定日志文件)
handler.setLevel(logging.INFO) # 处理器日志级别为INFO
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s => %(message)s') # 日志格式
handler.setFormatter(formatter) # 绑定格式到处理器
logger.addHandler(handler) # 日志器添加处理器
logger.info(str) # 写入INFO日志
logger.removeHandler(handler) # 移除处理器(避免重复输出)
def warning(str):
"""记录WARNING级别的日志到info_日期.log文件(与INFO共用文件)"""
# 逻辑与info()基本一致,区别在于日志级别为WARNING
script_path = os.path.split(os.path.realpath(__file__))[0]
nowTime = datetime.datetime.now().strftime('%Y%m%d')
path = os.path.join(script_path+r'\logs', 'info_' +nowTime+".log")
isExists = os.path.exists(script_path+r'\logs')
if not isExists:
os.makedirs(script_path+r'\logs')
logger = logging.getLogger("run")
logger.setLevel(level=logging.WARNING)
handler = logging.FileHandler(path)
handler.setLevel(logging.WARNING)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s => %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.warning(str)
logger.removeHandler(handler)
def error(str):
"""记录ERROR级别的日志到error_日期.log文件"""
# 逻辑与info()基本一致,区别在于日志级别为ERROR,且日志文件前缀为error_
script_path = os.path.split(os.path.realpath(__file__))[0]
nowTime = datetime.datetime.now().strftime('%Y%m%d')
path = os.path.join(script_path+r'\logs', 'error_' +nowTime+".log")
isExists = os.path.exists(script_path+r'\logs')
if not isExists:
os.makedirs(script_path+r'\logs')
logger = logging.getLogger("run")
logger.setLevel(level=logging.ERROR)
handler = logging.FileHandler(path)
handler.setLevel(logging.ERROR)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s => %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.error(str)
logger.removeHandler(handler)
+34
View File
@@ -0,0 +1,34 @@
import logging
def Filter(project, model):
"""
图层过滤,按照不同的规则进行条件过滤(根据event和queueId筛选要素)
"""
if model == None:
logging.warning("过滤参数为空!!!")
return
logging.info("图层过滤")
# 需要按event过滤的图层名称
eqid = ["eqcenter", "震中"]
# 需要按eqqueue_id过滤的图层名称(人员伤亡、经济损失等相关图层)
eqidAndBatch = ["intensity", "intensity_mian", "dz_ryss", "dz_jjss", "dz_rysw", "dz_jzph", "dz_xzjl"]
# 按event字段过滤图层(震中相关图层)
logging.info("图层过滤--" + "event = '" + model["event"] + "'")
for i in eqid:
layers = project.mapLayersByName(i) # 获取图层
if len(layers) > 0:
layer = project.mapLayersByName(i)[0]
# 设置图层子集字符串(过滤条件)
layer.setSubsetString("event = '" + model["event"] + "'")
# 按eqqueue_id字段过滤图层(灾害相关图层)
logging.info("图层过滤--" + "eqqueue_id = '" + model["queueId"] + "'")
for i in eqidAndBatch:
layers = project.mapLayersByName(i)
if len(layers) > 0:
layer = project.mapLayersByName(i)[0]
layer.setSubsetString("eqqueue_id = '" + model["queueId"] + "'")
+197
View File
@@ -0,0 +1,197 @@
import logging # 导入自定义日志工具
from qgis._core import QgsScaleBarSettings, QgsLayoutExporter, QgsCoordinateTransform, QgsCoordinateReferenceSystem, \
QgsPointXY, QgsGeometry, QgsRectangle
class MapUtils:
"""出图工具类(封装地图缩放、文本更新、比例尺调整、导出等功能)"""
def __init__(self, config, project, layout):
self.config = config # 配置参数
self.project = project # QGIS项目对象
self.layout = layout # 布局对象(用于地图排版)
self.imap = layout.itemById("Map") # 获取ID为"Map"的地图项
# 缩放
def Zoom(self, rule, data):
logging.info(data) # 记录缩放参数日志
zoom = MapZoom(self.project, self.layout, self.imap) # 创建缩放操作对象
# 2024 Zoom转换(将数字规则映射为方法名)
# NO("10", "不缩放"),
# PAN("11", "平移"),
# LAYER("12", "单图层"),
# M_LAYER("13", "多图层"),
# DISTANCE("14", "距离"),
# M_LAYER2("15", "按图层合并缩放");
if rule == "11":
rule = "FlatToCenter" # 平移至中心点
elif rule == "12":
rule = "Layer" # 单图层缩放
elif rule == "13":
rule = "LayerIntersect" # 多图层相交缩放
elif rule == "14":
rule = "CenterDistance" # 中心距离缩放
elif rule == "15":
rule = "LayerMerged" # 多图层合并缩放
else:
rule = "FlatToCenter" # 默认平移至中心点
eval("zoom." + rule + "(data)") # 动态调用对应缩放方法
# 文本更新
def Update(self, model, key):
label = self.layout.itemById(key) # 获取布局中ID为key的文本标签
if (label != None):
label.setText(model[key]) # 更新标签文本为model中key对应的值
# 比例尺更新
def UpdateScale(self):
ScaleBar = self.layout.itemById("ScaleBar") # 获取ID为"ScaleBar"的比例尺控件
if ScaleBar == None:
logging.warning("比例尺不存在或控件标识不等于 ScaleBar") # 日志警告
return
# 设置比例尺段大小模式为"适应宽度"
ScaleBar.setSegmentSizeMode(QgsScaleBarSettings.SegmentSizeMode.SegmentSizeFitWidth)
ScaleBar.setMaximumBarWidth(70) # 最大宽度
ScaleBar.setMinimumBarWidth(40) # 最小宽度
# 布局设置器
def QgsLayoutSettings(self):
dpi = self.config["qgis"]["exportDpi"] # 设置dpi
# 设置多个导出格式
settings = {
'PDF': QgsLayoutExporter.PdfExportSettings(),
'PNG': QgsLayoutExporter.ImageExportSettings(),
'JPG': QgsLayoutExporter.ImageExportSettings(),
'SVG': QgsLayoutExporter.SvgExportSettings()
}
for img_format in ['PNG', 'JPG']:
settings[img_format].dpi = dpi
settings['JPG'].jpegQuality = 85
return settings['PNG']
# 导出图片
def Export(self, path):
try:
qle = QgsLayoutExporter(self.layout) # 创建布局导出器
# 布局设置
setting = self.QgsLayoutSettings()
# 导出为图片
res = qle.exportToImage(path, setting)
except Exception as e:
raise Exception(f"导出失败... {str(e)}")
class MapZoom:
"""地图缩放操作类(封装多种缩放逻辑)"""
def __init__(self, project, layout, imap):
self.project = project # QGIS项目对象
self.layout = layout # 布局对象
self.imap = imap # 地图项对象
# 设置坐标系转换
def SetSrc(self, crs):
qct = QgsCoordinateTransform() # 坐标系转换对象
qct.setDestinationCrs(self.imap.crs()) # 目标坐标系为地图当前坐标系
if crs == None:
qct.setSourceCrs(QgsCoordinateReferenceSystem("EPSG:4326")) # 源坐标系默认WGS84(经纬度)
else:
qct.setSourceCrs(crs) # 自定义源坐标系
return qct
# 平移至中心点
def FlatToCenter(self, data):
logging.info("平移至中心点")
qct = self.SetSrc(None) # 源坐标系为WGS84
point = QgsPointXY(float(data["X"]), float(data["Y"])) # 解析中心点坐标(经纬度)
qgm = QgsGeometry.fromWkt(point.asWkt()) # 转换为QGIS几何对象
# 坐标转换(从源坐标系到地图坐标系)
qgm.transform(qct, QgsCoordinateTransform.TransformDirection.ForwardTransform)
cpoint = qgm.asPoint() # 转换后的中心点
# 保持当前地图宽度和高度,以新中心点创建矩形范围
qr = QgsRectangle.fromCenterAndSize(cpoint, self.imap.extent().width(), self.imap.extent().height())
self.imap.zoomToExtent(qr) # 缩放至该范围(实现平移)
# 中心距离缩放(以中心点为圆心,指定距离为半径缩放)
def CenterDistance(self, data):
logging.info("中心距离")
qct = self.SetSrc(None)
point = QgsPointXY(float(data["X"]), float(data["Y"])) # 中心点坐标
qgm = QgsGeometry.fromWkt(point.asWkt())
qgm.transform(qct, QgsCoordinateTransform.TransformDirection.ForwardTransform)
# 缓冲距离(单位:公里,转换为米后缓冲),并扩大10%范围
box = qgm.buffer(float(data["value"]) / 1000, 100).boundingBox() # 平面 4326
# box = qgm.buffer(float(data["value"])/111,100).boundingBox()#投影坐标系下的缓冲计算
self.imap.zoomToExtent(box.buffered(box.width() * 0.1))
# 按单图层缩放(缩放至指定图层范围)
def Layer(self, data):
logging.info("按图层缩放")
layers = self.project.mapLayersByName(data["value"]) # 根据名称获取图层
if len(layers) == 0:
logging.warning("图层不存在:" + data["value"]) # 图层不存在时警告
return
layer = layers[0]
if layer.featureCount() == 0.0: # 图层无要素时,默认平移至中心点
self.FlatToCenter(data)
return
qct = self.SetSrc(layer.crs()) # 源坐标系为图层坐标系
# 将图层范围转换为几何对象
qgm = QgsGeometry.fromWkt(layer.extent().asWktPolygon())
qgm.transform(qct, QgsCoordinateTransform.TransformDirection.ForwardTransform) # 转换到地图坐标系
box = qgm.boundingBox() # 获取范围矩形
self.imap.zoomToExtent(box.buffered(box.width() * 0.1)) # 扩大10%范围后缩放
# 多图层合并缩放(缩放至多个图层的合并范围)
def LayerMerged(self, data):
logging.info("多图层合并")
layers = data["value"].split(",") # 图层名称以逗号分隔
box = None
for lay in layers:
temp = self.project.mapLayersByName(lay)
if len(temp) == 0:
logging.warning("图层不存在:" + lay)
else:
layer = temp[0]
qct = self.SetSrc(layer.crs())
qgm = QgsGeometry.fromWkt(layer.extent().asWktPolygon())
qgm.transform(qct, QgsCoordinateTransform.TransformDirection.ForwardTransform)
if box == None:
box = qgm.boundingBox() # 初始化范围
else:
box.combineExtentWith(qgm.boundingBox()) # 合并范围
if box != None:
self.imap.zoomToExtent(box.buffered(box.width() * 0.1)) # 扩大10%范围后缩放
# 多图层相交缩放(缩放至多个图层的相交范围)
def LayerIntersect(self, data):
logging.info("多图层相交")
layers = data["value"].split(",") # 图层名称以逗号分隔
box = None
for lay in layers:
temp = self.project.mapLayersByName(lay)
if len(temp) == 0:
logging.warning("图层不存在:" + lay)
else:
layer = temp[0]
qct = self.SetSrc(layer.crs())
layer.selectAll() # 选中图层所有要素
geom = None
# 合并选中要素的几何范围
for feature in layer.selectedFeatureIds():
if geom == '':
geom = layer.getGeometry(feature)
else:
geom = geom.combine(layer.getGeometry(feature))
qgm = QgsGeometry.fromWkt(geom.asWkt())
qgm.transform(qct, QgsCoordinateTransform.TransformDirection.ForwardTransform)
if box == None:
box = qgm.boundingBox() # 初始化范围
else:
box = box.intersection(qgm) # 计算相交范围
if box != None:
box = box.boundingBox()
self.imap.zoomToExtent(box.buffered(box.width() * 0.1)) # 扩大10%范围后缩放
+105
View File
@@ -0,0 +1,105 @@
import os
import importlib # 动态导入模块
import logging
import sys
from qgis._core import QgsProject
from .map_utils import MapUtils
class Maps:
def __init__(self, model, data, config):
self.model = model # 震情基本数据
self.data = data # 额外数据
self.config = config # 配置参数
def load(self):
# ---加载地图模板---
project = QgsProject.instance() # 获取QGIS项目实例
project.read(self.model["path"]) # 读取地图模板(.qgs或.qgz文件)
db_config = self.config["db"] # 需在application.yml中添加db配置
# 遍历所有图层,更新PostgreSQL图层的连接
for layer in project.mapLayers().values():
if layer.providerType() == "postgres": # 仅处理 PostgreSQL 图层
try:
# 检查配置项是否完整
required_keys = ["host", "port", "database", "username", "password"]
missing_keys = [k for k in required_keys if k not in db_config]
if missing_keys:
logging.error(f"数据库配置缺失键:{missing_keys},跳过图层 {layer.name()}")
continue
# 获取当前图层的数据源 URI
uri = layer.dataProvider().uri()
# 使用位置参数传递,而非关键字参数
uri.setConnection(
db_config["host"], # 主机
str(db_config["port"]), # 端口转换为字符串
db_config["database"], # 数据库名
db_config["username"], # 用户名
db_config["password"] # 密码
)
# 重新设置数据源,刷新连接
layer.setDataSource(uri.uri(), layer.name(), "postgres")
# 验证图层是否有效
if layer.isValid():
logging.info(f"图层 {layer.name()} 数据库连接更新成功")
else:
logging.error(f"图层 {layer.name()} 更新连接后仍无效,请检查配置")
except Exception as e:
logging.error(f"更新图层 {layer.name()} 连接失败:{str(e)}")
logging.info("读取project完成,模板路径:" + self.model["path"] + " 画幅:" + self.model["mapLayout"])
# logging.info("图层--" + str(len(project.mapLayersByName('eqcenter')))) # 日志记录特定图层数量
qLayout = project.layoutManager().layoutByName(self.model["mapLayout"]) # 获取指定名称的布局
imap = qLayout.itemById("Map") # 获取地图项
mapUtils = MapUtils(self.config, project, qLayout) # 创建地图工具实例
# 设置坐标系(当前注释掉,未启用)
# kid = 32000 + (700 if float(self.model["centerY"]) < 0 else 600) + (int(float(self.model["centerX"])/6)+31)
# logging.info("设置坐标系:" + str(kid))
# imap.setCrs(QgsCoordinateReferenceSystem(kid,QgsCoordinateReferenceSystem.CrsType.EpsgCrsId))
# 修改格网
# logging.info("修改格网")
# grid = imap.grid()
# grid.setLineSymbol(None)
# grid.setAnnotationDisplay(QgsLayoutItemMapGrid.DisplayMode.HideAll,QgsLayoutItemMapGrid.BorderSide.Left)
# grid.setAnnotationDisplay(QgsLayoutItemMapGrid.DisplayMode.HideAll, QgsLayoutItemMapGrid.BorderSide.Right)
# grid.setAnnotationDisplay(QgsLayoutItemMapGrid.DisplayMode.HideAll, QgsLayoutItemMapGrid.BorderSide.Bottom)
# grid.setAnnotationDisplay(QgsLayoutItemMapGrid.DisplayMode.HideAll, QgsLayoutItemMapGrid.BorderSide.Top)
# 按照过滤图层,避免以前的数据干扰(动态加载map_filter.py)
if os.path.exists(os.path.join(os.path.split(os.path.realpath(__file__))[0], 'map_filter.py')):
# 添加模块所在目录到 Python 搜索路径
sys.path.insert(0, os.path.split(os.path.realpath(__file__))[0])
importlib.import_module('map_filter').Filter(project, self.model)
# 缩放地图
logging.info("缩放地图")
# 调用缩放方法(参数:缩放规则、中心点坐标、缩放值)
mapUtils.Zoom(self.model["zoomRule"],
{'X': self.model["centerX"], 'Y': self.model["centerY"], 'value': self.model["zoomValue"]})
# 修改制图时间、单位、地图名称等文本
logging.info("正在修改地震信息...")
mapUtils.Update(self.model, "mapTitle") # 更新地图标题
mapUtils.Update(self.model, "mapTime") # 更新制图时间
mapUtils.Update(self.model, "mapUnit") # 更新单位
mapUtils.Update(self.model, "info") # 更新地震信息
# 修改比例尺
logging.info("修改比例尺")
mapUtils.UpdateScale()
# 导出图片
logging.info("导出图片")
mapUtils.Export(self.model["outFile"]) # 导出至指定路径
logging.info(self.model["event"] + "导出完成") # 记录导出完成日志
return self.model["name"]