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
+8
View File
@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>
+23
View File
@@ -0,0 +1,23 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="psycopg2-binary" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="N806" />
<option value="N803" />
<option value="N802" />
</list>
</option>
</inspection_tool>
</profile>
</component>
+6
View File
@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12 (qgis-make)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (qgis-py)" project-jdk-type="Python SDK" />
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/qgis-xian.iml" filepath="$PROJECT_DIR$/.idea/qgis-xian.iml" />
</modules>
</component>
</project>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.12 (qgis-py)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+20
View File
@@ -0,0 +1,20 @@
# 配置端口
web:
host: localhost
port: 18998
allowOrigin: ['*']
# qgis 配置
qgis:
# qgis路径
root: "D:/QGIS"
# 设置dpi
exportDpi: 300
# 连接到自己的数据库
db:
host: 127.0.0.1
port: 5432
database: yjzyk_xian
username: postgres
password: Zzw.0401
+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"]
+169
View File
@@ -0,0 +1,169 @@
import os
import sys
from pathlib import Path
import logging
import json
from pydantic import BaseModel, Field
from qgis._core import QgsApplication, QgsSettings
from core.maps import Maps
import yaml
from fastapi import FastAPI, Request
import uvicorn
import atexit
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
app = FastAPI(
title="QGIS 专题图导出",
description="基于 pyqgis 的专题图制作与导出,用于生成暴雨、地震灾害类分布图",
version="1.0.0"
)
# 全局QGIS应用实例
qgs_app = None
config = None
# 模型参数
class MapModel(BaseModel):
centerX: float = Field(..., description="地图中心点X坐标(经度)")
centerY: float = Field(..., description="地图中心点Y坐标(纬度)")
info: str = Field(..., description="信息文本")
event: str = Field(..., description="事件ID(用于图层过滤)")
mapLayout: str = Field(..., description="QGIS布局名称(如A3")
mapTime: str = Field(..., description="制图时间文本")
mapTitle: str = Field(..., description="地图标题")
mapUint: str = Field(..., description="制图单位文本")
name: str = Field(..., description="地图名称")
outFile: str = Field(..., description="导出文件路径(含文件名)")
path: str = Field(..., description="QGIS模板文件路径(.qgs/.qgz")
queueId: str = Field(..., description="队列ID(用于图层过滤)")
zoomRule: str = Field(default="1", description="缩放规则")
zoomValue: str = Field(default="", description="缩放值")
def load_config(config_path=None):
# 默认配置文件路径
if config_path is None:
# 获取当前脚本所在目录的父目录,拼接配置文件路径
current_dir = Path(__file__).resolve().parent
config_path = current_dir / "config" / "application.yml"
# 转换为字符串路径
config_path_str = str(config_path)
try:
# 检查文件是否存在
if not Path(config_path_str).exists():
raise FileNotFoundError(f"配置文件不存在:{config_path_str}")
# 读取并解析 YAML 文件
with open(config_path_str, 'r', encoding='utf-8') as f:
config_data = yaml.safe_load(f)
return config_data
except Exception as e:
logging.error(f"加载配置文件失败:{str(e)}", exc_info=True)
print(f"加载配置文件失败:{str(e)}", exc_info=True)
raise # 抛出异常,终止程序执行
def init_qgis():
"""
初始化QGIS应用
"""
global qgs_app, config
# 加载配置
if config is None:
config = load_config()
# 初始化QGIS(只初始化一次)
if qgs_app is None:
root = config['qgis']['root']
QgsApplication.setPrefixPath(root, True)
# 设置环境变量
os.environ['QGIS_PREFIX_PATH'] = os.path.join(root, "apps", "qgis")
os.environ['PATH'] = os.path.join(root, "bin") + ";" + os.environ["PATH"]
os.environ['PATH'] = os.path.join(root, "apps", "qgis", "bin") + ";" + os.environ["PATH"]
os.environ['PATH'] = os.path.join(root, "apps", "Python312", "lib") + ";" + os.environ["PATH"]
# 把QGIS的Python路径加入系统
sys.path.insert(0, os.path.join(root, "apps", "qgis", "python"))
sys.path.insert(0, os.path.join(root, "apps", "Python312", "Lib", "site-packages"))
# 创建QgsApplication实例(禁用GUI
qgs_app = QgsApplication([], False)
# 配置QGIS参数
settings = QgsSettings()
settings.setValue("/qgis/render_decorations", False)
settings.setValue("/qgis/parallel_rendering", True)
settings.setValue("/qgis/use_spatial_index", True)
# 加载QGIS提供者
qgs_app.initQgis()
logging.info("QGIS初始化完成")
# 注册程序退出时的清理函数
atexit.register(cleanup_qgis)
def cleanup_qgis():
"""
清理QGIS资源
"""
global qgs_app
if qgs_app is not None:
qgs_app.exitQgis()
qgs_app = None
logging.info("QGIS资源已清理")
def run(model, data):
"""
执行地图生成逻辑
"""
try:
# 核心代码,地图操作
mp = Maps(model, data, config)
mapName = mp.load() # 执行地图加载、处理、导出流程
logging.info(f"地图生成成功:{mapName}")
return mapName
except Exception as e:
logging.error(f"地图生成失败:{str(e)}", exc_info=True)
print(f"地图生成失败:{str(e)}", exc_info=True)
raise # 抛出异常让FastAPI返回错误响应
@app.post("/qgis/make/map", summary="地图导出接口")
async def start(request: Request, model: MapModel):
# 打印原始请求体
raw_body = await request.body()
logging.info(f"原始请求体:{raw_body.decode('utf-8')}")
logging.info("接收到地图导出请求")
# 确保QGIS已初始化
init_qgis()
# 转换请求参数
req = model.dict()
logging.info(f"解析后的参数:{json.dumps(req, ensure_ascii=False, indent=2)}")
# 执行制图逻辑
mapName = run(req, None)
return mapName
if __name__ == "__main__":
# 启动FastAPI服务
uvicorn.run(
app="main:app",
host="0.0.0.0",
port=18998,
reload=False, # 生产环境必须关闭reload!reload会导致重复初始化QGIS
log_level="info"
)
+1
View File
@@ -0,0 +1 @@
# 作为 qgis-api的第三方调用接口
+4
View File
@@ -0,0 +1,4 @@
fastapi>=0.100.0
uvicorn>=0.23.0
pydantic>=2.0.0
pyyaml>=6.0