Files
xian_qgis/apps/core/map_utils.py
T
2026-06-18 10:12:15 +08:00

198 lines
8.8 KiB
Python

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%范围后缩放