CRS not update the config file

本文记录了一次启动Oracle数据库实例过程中遇到的SPFILE加载失败问题。尝试使用特定命令创建并指定SPFILE,但启动时出现错误提示,无法正确识别指定的SPFILE文件路径。错误涉及文件路径配置、文件权限或文件存在性等问题。
create spfile='+DATA0411/spfile' from pfile='/u01/app/base/admin/racstr/scripts/init.ora';
srvctl modify database -d racstr -p +DATA0411/spfile

[cdccrs@node01 trace]$ sqlplus / as sysdba
SQL*Plus: Release 12.1.0.0.2 Beta on Sun Apr 15 19:15:33 2012
Copyright (c) 1982, 2012, Oracle.  All rights reserved.
Connected to an idle instance.
SQL>
SQL> startup mount;
ORA-01078: failure in processing system parameters
ORA-01565: error in identifying file '+DATA0411/racstr/spfileracstr.ora'
ORA-17503: ksfdopn:2 Failed to open file +DATA0411/racstr/spfileracstr.ora
ORA-15056: additional error message
ORA-17503: ksfdopn:2 Failed to open file +DATA0411/racstr/spfileracstr.ora
ORA-15173: entry 'spfileracstr.ora' does not exist in directory 'racstr'
ORA-06512: at line 4


检查以下代码bug,是否需要优化,怎么优化。 ```python import os import subprocess import time import logging from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler import configparser import concurrent.futures import sys import json import subprocess from osgeo import gdal, osr import rasterio # 创建 logger logger = logging.getLogger() logger.setLevel(logging.INFO) # 创建文件处理器并设置编码 file_handler = logging.FileHandler('application.log', encoding='utf-8') formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') file_handler.setFormatter(formatter) # 将文件处理器添加至 logger logger.addHandler(file_handler) # 裁剪tif文件 cutline_path = "result\squares.geojson" # with open(cutline_path, "w") as f: # json.dump(geojson, f) # 获取图像信息(分辨率、坐标系) def get_info(path): with rasterio.open(path) as src: return { 'crs': src.crs.to_string(), 'res': (abs(src.transform.a), abs(src.transform.e)), 'bounds': src.bounds } # 裁剪函数 def crop_with_cutline(input_path, output_path, cutline, target_crs, target_res): warp_options = gdal.WarpOptions( format='GTiff', cutlineDSName=cutline, cropToCutline=True, dstSRS=target_crs, xRes=target_res[0], yRes=target_res[1], resampleAlg='bilinear', # dstAlpha=True, # 关键修改:添加alpha通道 # dstNodata=None # 可选:显式设置为None,让GDAL自动处理 ) ds = gdal.Warp(output_path, input_path, options=warp_options) ds = None def caijian_main(file1, file2, out1, out2): # 获取统一的 CRS 和分辨率 info1 = get_info(file1) info2 = get_info(file2) target_crs = info1['crs'] target_res = (min(info1['res'][0], info2['res'][0]), min(info1['res'][1], info2['res'][1])) # 使用自定义多边形裁剪两个图像 crop_with_cutline(file1, out1, cutline_path, target_crs, target_res) crop_with_cutline(file2, out2, cutline_path, target_crs, target_res) print("✅ 裁剪完成,输出图像已对齐!") def run_command(command): try: print(f"运行命令: {command}") result = subprocess.run(command, shell=True, check=True) return result.returncode except subprocess.CalledProcessError as e: logger.error(f"命令 {command} 执行失败: {e}") return e.returncode def get_folder_size(folder_path): try: total_size = 0 for dirpath, _, filenames in os.walk(folder_path): for f in filenames: fp = os.path.join(dirpath, f) total_size += os.path.getsize(fp) return total_size except Exception as e: logger.error(f"获取文件夹大小时出错: {e}") return None def process_folder(folder_name, folder_full_path, config,config_build): logger.info(f"开始处理文件夹: {folder_name}") # # 获取Python的安装路径 # python_install_path = sys.executable # # 获取Python安装路径的上级目录,即包含Scripts文件夹的目录 # python_base_path = os.path.dirname(python_install_path) # # 拼接Scripts/gdal2tiles.py路径 # qietu_path = os.path.join(python_base_path, 'Scripts', 'gdal2tiles.py') gdal_translate_path = config_build['settings']['gdal_translate_path'] qietu_path = config_build['settings']['gdal2tiles_path'] logger.info(f"切图工具路径: {qietu_path}") logger.info(f"转化工具路径: {gdal_translate_path}") # 强制转换字符串为布尔值 istype_zhengshe = config['settings']['zhengshe'] == 'True' istype_qingxie = config['settings']['qingxie'] == 'True' istype_quanjian = config['settings']['quanjian'] == 'True' istype_ndvi = config['settings']['ndvi'] == 'True' jingdu = config['settings']['precision'] print(f"建模类型: {istype_zhengshe}, {istype_qingxie}, {istype_quanjian}, {istype_ndvi}, 精度: {jingdu}") isNIR=False isRED=False precision_mapping = { "快速": 'lowest', "普通": 'lowest', "精细": 'high' } jingdu_value = precision_mapping.get(jingdu, 'lowest') # 只建正射 if istype_zhengshe: # 建模型 # 建模型 if folder_name == "allimages": # 如果新文件夹名是 allimages,允许运行 docker 命令并生成 orthophoto command_docker = ( f"docker run -ti --rm -v {config['settings']['projectpath']}:/datasets --gpus all opendronemap/odm:gpu " f"--project-path /datasets \"/datasets/{config['settings']['taskqueuepath']}/allimages\" --skip-report --skip-3dmodel " f"--feature-quality {jingdu_value} --copy-to \"/datasets/{config['settings']['resultNameDir']}\"" ) logger.info(f"只建正射 运行 docker 命令command_docker: {command_docker}") return_code = run_command(command_docker) if return_code == 0: # 如果命令成功,再运行 node 789.js command33 = ( rf"python {qietu_path} -z 1-21 " rf"{config['settings']['imagesfolderpath']}/{folder_name}/odm_orthophoto/odm_orthophoto.original.tif " rf"{config['settings']['projectpath']}/{config['settings']['resultNameDir']}/tiles " rf"--xyz --processes 4" ) logger.info(f"运行 gdal2tiles.py 命令command33: {command33}") run_command(command33) update_progress_ini("allimages", 1) # 更新 Progress.ini 文件 else: command3 = ( f"docker run -ti --rm -v {config['settings']['projectpath']}:/datasets --gpus all opendronemap/odm:gpu " f"--project-path /datasets \"/datasets/{config['settings']['taskqueuepath']}/{folder_name}\" --skip-report --skip-3dmodel --pc-rectify --pc-ept --pc-quality lowest " f"--feature-quality lowest" ) logger.info(f"只建正射 运行 docker 命令command3: {command3}") return_code2 = run_command(command3) if return_code2 == 0: # 如果命令成功,再运行 gdal2tiles.py command2 = ( rf"python {qietu_path} -z 10-18 " rf"{config['settings']['imagesfolderpath']}/{folder_name}/odm_orthophoto/odm_orthophoto.original.tif " rf"{config['settings']['projectpath']}/{config['settings']['resultNameDir']}/tiles " rf"--xyz --processes 4" ) logger.info(f"运行 gdal2tiles.py 命令command2: {command2}") run_command(command2) update_progress_ini(None, 1) # 更新 Progress.ini 文件 # 只建倾斜 elif istype_qingxie: # 建模型 # 建模型 if folder_name == "allimages": # 如果新文件夹名是 allimages,允许运行 docker 命令并生成 orthophoto command_docker = ( f"docker run -ti --rm -v {config['settings']['projectpath']}:/datasets --gpus all opendronemap/odm:gpu " f"--project-path /datasets \"/datasets/{config['settings']['taskqueuepath']}/allimages\" --skip-report --skip-orthophoto --pc-rectify --pc-ept --pc-quality {jingdu_value} " f"--feature-quality {jingdu_value} --3d-tiles --copy-to \"/datasets/{config['settings']['resultNameDir']}\"" ) logger.info(f"只建倾斜001 运行 docker 命令command_docker: {command_docker}") return_code = run_command(command_docker) logger.info(f"allimages第一次失败的时间时分秒:,{time.time()}") if return_code == 0: update_progress_ini("allimages", 1) # 更新 Progress.ini 文件 else: # 如果失败等两秒在运行一次命令 time.sleep(20) logger.info(f"allimages第二次重建的时间时分秒:,{time.time()}") command_docker = ( f"docker run -ti --rm -v {config['settings']['projectpath']}:/datasets --gpus all opendronemap/odm:gpu " f"--project-path /datasets \"/datasets/{config['settings']['taskqueuepath']}/allimages\" --skip-report --skip-orthophoto --pc-rectify --pc-ept --pc-quality {jingdu_value} " f"--feature-quality {jingdu_value} --rerun-all --3d-tiles --copy-to \"/datasets/{config['settings']['resultNameDir']}\"" ) logger.info(f"只建倾斜失败运行第二次002 运行 docker 命令command_docker: {command_docker}") return_code = run_command(command_docker) if return_code == 0: update_progress_ini("allimages", 1) # 更新 Progress.ini 文件 else: command3 = ( f"docker run -ti --rm -v {config['settings']['projectpath']}:/datasets --gpus all opendronemap/odm:gpu " f"--project-path /datasets \"/datasets/{config['settings']['taskqueuepath']}/{folder_name}\" --skip-report --skip-3dmodel --pc-rectify --pc-ept --pc-quality lowest " f"--feature-quality lowest" ) logger.info(f"只建倾斜003 运行 docker 命令command3: {command3}") return_code2 = run_command(command3) logger.info(f"imgs1第一次失败的时间时分秒:,{time.time()}") if return_code2 == 0: # 如果命令成功,再运行 gdal2tiles.py command2 = ( rf"python {qietu_path} -z 10-18 " rf"{config['settings']['imagesfolderpath']}/{folder_name}/odm_orthophoto/odm_orthophoto.original.tif " rf"{config['settings']['projectpath']}/{config['settings']['resultNameDir']}/tiles " rf"--xyz --processes 4" ) logger.info(f"运行 gdal2tiles.py004 命令command2: {command2}") run_command(command2) update_progress_ini(None, 1) # 更新 Progress.ini 文件 elif return_code2 != 0 and folder_name == "imgs1": logger.info("imgs1 切图失败,尝试重新运行") hhimg="imgs1" time.sleep(20) logger.info(f"imgs1第二次重建的时间时分秒:,{time.time()}") # 如果失败等两秒在运行一次命令 command3 = ( f"docker run -ti --rm -v {config['settings']['projectpath']}:/datasets --gpus all opendronemap/odm:gpu " f"--project-path /datasets \"/datasets/{config['settings']['taskqueuepath']}/{hhimg}\" --rerun-all --skip-report --skip-3dmodel --pc-rectify --pc-ept --pc-quality lowest " f"--feature-quality lowest" ) logger.info(f"只建倾斜 运行005 docker 命令command3: {command3}") return_code2 = run_command(command3) if return_code2 == 0: # 如果命令成功,再运行 gdal2tiles.py command2 = ( rf"python {qietu_path} -z 10-18 " rf"{config['settings']['imagesfolderpath']}/{hhimg}/odm_orthophoto/odm_orthophoto.original.tif " rf"{config['settings']['projectpath']}/{config['settings']['resultNameDir']}/tiles " rf"--xyz --processes 4" ) logger.info(f"运行 gdal2tiles.py006 命令command2: {command2}") run_command(command2) update_progress_ini(None, 1) # 更新 Progress.ini 文件 # 全建 elif istype_quanjian: # 建模型 # 建模型 if folder_name == "allimages": # 如果新文件夹名是 allimages,允许运行 docker 命令并生成 orthophoto command_docker = ( f"docker run -ti --rm -v {config['settings']['projectpath']}:/datasets --gpus all opendronemap/odm:gpu " f"--project-path /datasets \"/datasets/{config['settings']['taskqueuepath']}/allimages\" --skip-report --pc-rectify --pc-ept --pc-quality {jingdu_value} " f"--feature-quality {jingdu_value} --3d-tiles --copy-to \"/datasets/{config['settings']['resultNameDir']}\"" ) logger.info(f"全建 运行 docker 命令command_docker: {command_docker}") return_code = run_command(command_docker) if return_code == 0: # 如果命令成功,再运行 node 789.js command33 = ( rf"python {qietu_path} -z 1-21 " rf"{config['settings']['imagesfolderpath']}/{folder_name}/odm_orthophoto/odm_orthophoto.original.tif " rf"{config['settings']['projectpath']}/{config['settings']['resultNameDir']}/tiles " rf"--xyz --processes 4" ) logger.info(f"运行 gdal2tiles.py 命令command33: {command33}") run_command(command33) update_progress_ini("allimages", 1) # 更新 Progress.ini 文件 else: command3 = ( f"docker run -ti --rm -v {config['settings']['projectpath']}:/datasets --gpus all opendronemap/odm:gpu " f"--project-path /datasets \"/datasets/{config['settings']['taskqueuepath']}/{folder_name}\" --skip-report --skip-3dmodel --pc-rectify --pc-ept --pc-quality lowest " f"--feature-quality lowest" ) logger.info(f"全建 运行 docker 命令command3: {command3}") return_code2 = run_command(command3) if return_code2 == 0: # 如果命令成功,再运行 gdal2tiles.py command2 = ( rf"python {qietu_path} -z 10-18 " rf"{config['settings']['imagesfolderpath']}/{folder_name}/odm_orthophoto/odm_orthophoto.original.tif " rf"{config['settings']['projectpath']}/{config['settings']['resultNameDir']}/tiles " rf"--xyz --processes 4" ) logger.info(f"运行 gdal2tiles.py 命令command2: {command2}") run_command(command2) update_progress_ini(None, 1) # 更新 Progress.ini 文件 # 只建NDVI elif istype_ndvi: if folder_name == "NIR_allimages": # 如果新文件夹名是 allimages,允许运行 docker 命令并生成 orthophoto NIRcommand_docker = ( f"docker run -ti --rm -v {config['settings']['projectpath']}:/datasets --gpus all opendronemap/odm:gpu " f"--project-path /datasets \"/datasets/{config['settings']['taskqueuepath']}/NIR_allimages\" --skip-report --skip-3dmodel " # f"--copy-to \"/datasets/{config['settings']['resultNameDir']}/NIR\"" # f"--feature-quality {jingdu_value}" ) logger.info(f"只建正射 运行 docker 命令command_docker: {NIRcommand_docker}") return_code = run_command(NIRcommand_docker) if return_code == 0: isNIR=True if isNIR and isRED: file1 = f"{config['settings']['imagesfolderpath']}/NIR_allimages/odm_orthophoto/odm_orthophoto.original.tif" file2 = f"{config['settings']['imagesfolderpath']}/RED_allimages/odm_orthophoto/odm_orthophoto.original.tif" out1 =f"{config['settings']['imagesfolderpath']}/RED_allimages/odm_orthophoto/odm_orthophoto.original_NIR.tif" out2 =f"{config['settings']['imagesfolderpath']}/RED_allimages/odm_orthophoto/odm_orthophoto.original_red.tif" caijian_main(file1, file2, out1, out2) # 计算NDVI_command NDVI_command = f"python D:/Python39/Scripts/gdal_calc.py -A {out1} -B {out2} --outfile={config['settings']['resultnamedir']}/NDVI.tif --calc=\"(A-B)/(A+B)\"" logger.info(f"运行 计算NDVI_command 命令: {NDVI_command}") NDVI_code= run_command(NDVI_command) if NDVI_code == 0: # 如果命令成功,再运行 node 789.js command33 = ( rf"python {qietu_path} -z 1-21 " rf"{config['settings']['resultnamedir']}/NDVI.tif " rf"{config['settings']['projectpath']}/{config['settings']['resultNameDir']}/tiles " rf"--xyz --processes 10" ) logger.info(f"运行 gdal2tiles.py 命令command33: {command33}") run_command(command33) update_progress_ini("allimages", 1) # 更新 Progress.ini 文件 elif folder_name== "NIR_allimages": # 如果新文件夹名是 allimages,允许运行 docker 命令并生成 orthophoto NIRcommand_docker = ( f"docker run -ti --rm -v {config['settings']['projectpath']}:/datasets --gpus all opendronemap/odm:gpu " f"--project-path /datasets \"/datasets/{config['settings']['taskqueuepath']}/NIR_allimages\" --skip-report --skip-3dmodel " # f"--copy-to \"/datasets/{config['settings']['resultNameDir']}/NIR\"" # f"--feature-quality {jingdu_value}" ) logger.info(f"只建正射 运行 docker 命令command_docker: {NIRcommand_docker}") return_code = run_command(NIRcommand_docker) if return_code == 0: isRED=True if isNIR and isRED: file1 = f"{config['settings']['imagesfolderpath']}/NIR_allimages/odm_orthophoto/odm_orthophoto.original.tif" file2 = f"{config['settings']['imagesfolderpath']}/RED_allimages/odm_orthophoto/odm_orthophoto.original.tif" out1 =f"{config['settings']['imagesfolderpath']}/RED_allimages/odm_orthophoto/odm_orthophoto.original_NIR.tif" out2 =f"{config['settings']['imagesfolderpath']}/RED_allimages/odm_orthophoto/odm_orthophoto.original_red.tif" caijian_main(file1, file2, out1, out2) # 计算NDVI_command NDVI_command = f"python {config['settings']['gdal_calc_path']} -A {out1} -B {out2} --outfile={config['settings']['resultnamedir']}/NDVI.tif --calc=\"(A-B)/(A+B)\"" logger.info(f"运行 计算NDVI_command 命令: {NDVI_command}") NDVI_code= run_command(NDVI_command) if NDVI_code == 0: # 如果命令成功,再运行 node 789.js command33 = ( rf"python {qietu_path} -z 1-21 " rf"{config['settings']['resultnamedir']}/NDVI.tif " rf"{config['settings']['projectpath']}/{config['settings']['resultNameDir']}/tiles " rf"--xyz --processes 10" ) logger.info(f"运行 gdal2tiles.py 命令command33: {command33}") run_command(command33) update_progress_ini("allimages", 1) # 更新 Progress.ini 文件 # else是imgs1,imgs2、、、、、、等前缀是imgs,避免出现其他文件夹 elif folder_name.startswith('NIRimgs'): command3 = ( f"docker run -ti --rm -v {config['settings']['projectpath']}:/datasets --gpus all opendronemap/odm:gpu " f"--project-path /datasets \"/datasets/{config['settings']['taskqueuepath']}/{folder_name}\" --skip-report --skip-3dmodel --pc-rectify --pc-ept" # f" --pc-quality lowest --feature-quality lowest" ) logger.info(f"只建NIR 运行 docker 命令command3: {command3}") return_code2 = run_command(command3) if return_code2 == 0: # 转化为RGBA带透明通道tif tif_command = ( rf"{gdal_translate_path} -b 1 -b 1 -b 1 -b 2 -colorinterp red,green,blue,alpha -a_nodata 0 {config['settings']['imagesfolderpath']}/{folder_name}/odm_orthophoto/odm_orthophoto.original.tif {config['settings']['imagesfolderpath']}/{folder_name}/odm_orthophoto/odm_orthophoto.original_NIR.tif" ) logger.info(f"运行 gdal_translate 命令tif_command: {tif_command}") Translator_command= run_command(tif_command) if Translator_command == 0: # 如果命令成功,再运行 gdal2tiles.py command2 = ( rf"python {qietu_path} -z 10-18 " rf"{config['settings']['imagesfolderpath']}/{folder_name}/odm_orthophoto/odm_orthophoto.original_NIR.tif " rf"{config['settings']['projectpath']}/{config['settings']['resultNameDir']}/tiles " rf"--xyz --processes 10" ) logger.info(f"运行 gdal2tiles.py 命令command2: {command2}") run_command(command2) update_progress_ini(None, 1) # 更新 Progress.ini 文件 def update_progress_ini(folder_name, value): config_progress = configparser.ConfigParser() progress_file = 'Progress.ini' if os.path.exists(progress_file): with open(progress_file, 'r', encoding='utf-8') as progressfile: config_progress.read_file(progressfile) else: config_progress.add_section('Progress') if folder_name == "allimages": config_progress.set('Progress', 'allimages', str(value)) else: # 查找下一个 imgs 键,例如 imgs1, imgs2, ... last_imgs_key = None for key in config_progress['Progress']: if key.startswith('imgs'): last_imgs_key = int(key[4:]) if last_imgs_key is not None: new_imgs_key = f"imgs{last_imgs_key + 1}" else: new_imgs_key = "imgs1" config_progress.set('Progress', new_imgs_key, str(value)) with open(progress_file, 'w', encoding='utf-8') as progressfile: config_progress.write(progressfile) logger.info(f"更新 Progress.ini 文件: {new_imgs_key if folder_name != 'allimages' else 'allimages'} = {value}") class NewFolderHandler(FileSystemEventHandler): def __init__(self, folder_path): super().__init__() self.folder_path = folder_path self.folder_sizes = {} # 用于存储文件夹大小 self.size_check_interval = 2 self.size_check_threshold = 5 self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=10) # 设置最大线程数 def on_created(self, event): if event.is_directory and os.path.basename(event.src_path) != 'config.ini': folder_name = os.path.basename(event.src_path) folder_full_path = os.path.join(self.folder_path, folder_name) logger.info(f"新文件夹创建: {folder_name}") # 初始化文件夹大小 current_size = get_folder_size(folder_full_path) if current_size is not None: self.folder_sizes[folder_name] = { 'size': current_size, 'last_checked': time.time() } logger.info(f"初始文件夹大小: {folder_name}, 大小: {self.folder_sizes[folder_name]['size']} bytes") def check_folder_size(self): current_time = time.time() for folder_name, size_info in list(self.folder_sizes.items()): folder_full_path = os.path.join(self.folder_path, folder_name) current_size = get_folder_size(folder_full_path) if current_size is None: continue last_checked = size_info['last_checked'] if current_size != size_info['size']: logger.info(f"文件夹大小发生变化: {folder_name}, 大小: {current_size} bytes") self.folder_sizes[folder_name] = { 'size': current_size, 'last_checked': current_time } elif current_time - last_checked > self.size_check_threshold: logger.info(f"文件夹大小在 {self.size_check_threshold} 秒内没有变化: {folder_name}, 大小: {current_size} bytes") config = configparser.ConfigParser() # 读入配置文件时,使用 open() 函数指定编码 with open('config.ini', 'r', encoding='utf-8') as configfile: config.read_file(configfile) # 加载配置文件读取切图工具路径 config_build = configparser.ConfigParser() # 读入配置文件时,使用 open() 函数指定编码 with open('build.ini', 'r', encoding='utf-8') as configfile: config_build.read_file(configfile) # 提交任务到线程池 self.executor.submit(process_folder, folder_name, folder_full_path, config, config_build) # 从字典中移除已经处理的文件夹 del self.folder_sizes[folder_name] def main(): config = configparser.ConfigParser() try: with open('config.ini', 'r', encoding='utf-8') as configfile: config.read_file(configfile) except Exception as e: print(f"读取配置文件时出错: {e}") return folder_path = config.get('settings', 'imagesFolderPath', fallback=None) if not folder_path: print("配置文件中未找到 imagesFolderPath,请检查配置文件。") return if not os.path.exists(folder_path): print(f"文件夹路径 {folder_path} 不存在,请检查配置文件。") return print(f"监控文件夹: {folder_path}") # 生成geojson文件===================================start import xml.etree.ElementTree as ET import json def kml_to_geojson(kml_file): tree = ET.parse(kml_file) root = tree.getroot() namespace = {'kml': 'http://www.opengis.net/kml/2.2'} geojson = {"type": "FeatureCollection", "features": []} for placemark in root.findall('.//kml:Placemark', namespace): feature = {"type": "Feature", "properties": {}, "geometry": {}} name = placemark.find('kml:name', namespace) if name is not None: feature['properties']['name'] = name.text point = placemark.find('.//kml:Point/kml:coordinates', namespace) if point is not None: coordinates = point.text.strip().split(',') feature['geometry']['type'] = 'Point' feature['geometry']['coordinates'] = [float(coordinates[0]), float(coordinates[1])] else: polygon = placemark.find('.//kml:Polygon/kml:outerBoundaryIs/kml:LinearRing/kml:coordinates', namespace) if polygon is not None: coordinates = polygon.text.strip().split() feature['geometry']['type'] = 'Polygon' feature['geometry']['coordinates'] = [[list(map(float, coord.split(','))) for coord in coordinates]] if feature['geometry']: geojson['features'].append(feature) return geojson def save_geojson(geojson, filename): print('Saving GeoJSON to file:', geojson ) with open(filename, 'w', encoding='utf-8') as f: json.dump(geojson, f, ensure_ascii=False, indent=4) def extract_first_point(kml_file): tree = ET.parse(kml_file) root = tree.getroot() namespace = {'kml': 'http://www.opengis.net/kml/2.2'} for placemark in root.findall('.//kml:Placemark', namespace): point = placemark.find('.//kml:Point/kml:coordinates', namespace) if point is not None: coordinates = point.text.strip().split(',') return float(coordinates[0]), float(coordinates[1]) else: polygon = placemark.find('.//kml:Polygon/kml:outerBoundaryIs/kml:LinearRing/kml:coordinates', namespace) if polygon is not None: coordinates = polygon.text.strip().split() first_point_coords = coordinates[0].split(',') return float(first_point_coords[0]), float(first_point_coords[1]) return None, None def write_to_config(centerpointlongitude, centerpointlatitude, config_file): config = configparser.ConfigParser() # 读入配置文件时,使用 open() 函数指定编码 if os.path.exists(config_file): with open(config_file, 'r', encoding='utf-8') as configfile: config.read_file(configfile) else: config.add_section('DEFAULT') # 设置 centerpointlongitude 和 centerpointlatitude 的值 config.set('settings', 'centerpointlongitude', str(centerpointlongitude)) config.set('settings', 'centerpointlatitude', str(centerpointlatitude)) # 将配置写入 config.ini 文件 with open(config_file, 'w', encoding='utf-8') as configfile: config.write(configfile) # 示例使用 kml_file_path = config.get('settings', 'kmlpath', fallback=None) # 替换为你的 KML 文件路径 geojson_result = kml_to_geojson(kml_file_path) save_geojson(geojson_result, './result/squares.geojson') # 保存为 GeoJSON 文件 if kml_file_path: centerpointlongitude, centerpointlatitude = extract_first_point(kml_file_path) if centerpointlongitude is not None and centerpointlatitude is not None: write_to_config(centerpointlongitude, centerpointlatitude, 'config.ini') else: print("未找到点的坐标") else: print("配置文件中未找到 kmlpath,请检查配置文件。") # 生成geojson文件===================================end handler = NewFolderHandler(folder_path) observer = Observer() observer.schedule(handler, path=folder_path, recursive=False) observer.start() try: while True: time.sleep(handler.size_check_interval) # 根据配置文件中的间隔时间检查一次 handler.check_folder_size() # 检查文件夹大小是否有变化 except KeyboardInterrupt: observer.stop() observer.join() # 关闭线程池 handler.executor.shutdown(wait=True) logger.info("监听器已停止") if __name__ == "__main__": main() ```
09-20
# stable_elevation_visualizer.py import os import sys import logging import traceback import numpy as np import matplotlib matplotlib.use('TkAgg') # 确保使用Tkinter兼容的后端 import matplotlib.pyplot as plt from datetime import datetime from matplotlib.colors import BoundaryNorm import cartopy.crs as ccrs import rioxarray as rxr import geopandas as gpd import tkinter as tk from tkinter import ttk, filedialog, messagebox, StringVar from PIL import Image, ImageTk import threading import requests import json import platform import psutil # 用于内存监控 import gc # 垃圾回收控制 class GeoElevationVisualizerStable: def __init__(self, root): self.root = root self.root.title("高程可视化工具 (稳定版)") self.root.geometry("1100x750") self.root.resizable(True, True) # 绑定关闭事件 self.root.protocol("WM_DELETE_WINDOW", self.on_closing) # 配置日志 self.setup_logging() # 初始化变量 self.region_gdf = None self.running_thread = None self.stop_event = threading.Event() # 创建GUI框架 self.create_widgets() # 设置默认值 self.default_values() # 加载初始数据(如果存在) self.load_last_settings() # 启动内存监控 self.start_memory_monitor() self.logger.info("应用程序初始化完成") def on_closing(self): """窗口关闭时的处理""" self.logger.info("应用程序关闭") # 停止任何正在运行的线程 self.stop_event.set() if self.running_thread and self.running_thread.is_alive(): self.running_thread.join(timeout=2.0) self.root.destroy() def setup_logging(self): """配置日志记录""" log_dir = os.path.join(os.path.expanduser("~"), "pyMet", "logs") os.makedirs(log_dir, exist_ok=True) log_file = os.path.join(log_dir, f"elevation_visualizer_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log") self.logger = logging.getLogger('ElevationVisualizer') self.logger.setLevel(logging.DEBUG) # 文件处理器 fh = logging.FileHandler(log_file) fh.setLevel(logging.DEBUG) # 控制台处理器 ch = logging.StreamHandler() ch.setLevel(logging.INFO) # 格式器 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(formatter) ch.setFormatter(formatter) # 添加处理器 self.logger.addHandler(fh) self.logger.addHandler(ch) # 设置全局异常处理器 sys.excepthook = self.handle_uncaught_exception self.logger.info("应用程序启动") self.logger.info(f"Python版本: {sys.version}") self.logger.info(f"操作系统: {platform.system()} {platform.release()}") def handle_uncaught_exception(self, exc_type, exc_value, exc_traceback): """处理未捕获的异常""" error_msg = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback)) self.logger.critical(f"未捕获的异常:\n{error_msg}") # 在主线程中显示错误对话框 self.root.after(0, lambda: self.show_error_dialog( f"程序遇到严重错误:\n\n{exc_type.__name__}: {exc_value}\n\n详细信息已记录到日志。" )) def start_memory_monitor(self): """启动内存监控线程""" def monitor(): while not self.stop_event.is_set(): try: mem = psutil.virtual_memory() if mem.percent > 85: self.logger.warning(f"内存使用率过高: {mem.percent}%") self.root.after(0, lambda: self.status_var.set( f"警告: 内存使用率过高 ({mem.percent}%)" )) # 每10秒检查一次 self.stop_event.wait(10) except Exception as e: self.logger.error(f"内存监控错误: {str(e)}") monitor_thread = threading.Thread(target=monitor, daemon=True) monitor_thread.start() def show_error_dialog(self, error_msg): """显示错误对话框""" error_dialog = tk.Toplevel(self.root) error_dialog.title("程序错误") error_dialog.geometry("600x400") error_dialog.resizable(True, True) # 错误信息标签 label = ttk.Label(error_dialog, text="程序遇到错误:", font=("Arial", 12, "bold")) label.pack(pady=(10, 5)) # 错误详情文本框 text_frame = ttk.Frame(error_dialog) text_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) scrollbar = ttk.Scrollbar(text_frame) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) error_text = tk.Text(text_frame, wrap=tk.WORD, yscrollcommand=scrollbar.set) error_text.pack(fill=tk.BOTH, expand=True) error_text.insert(tk.END, error_msg) error_text.config(state=tk.DISABLED) scrollbar.config(command=error_text.yview) # 按钮区域 btn_frame = ttk.Frame(error_dialog) btn_frame.pack(pady=10) # 继续按钮 ttk.Button(btn_frame, text="关闭", command=error_dialog.destroy).pack(padx=5) def create_widgets(self): """创建GUI组件""" # 创建主框架 main_frame = ttk.Frame(self.root, padding="10") main_frame.pack(fill=tk.BOTH, expand=True) # 左侧控制面板 control_frame = ttk.LabelFrame(main_frame, text="控制面板", padding="10") control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10)) # 右侧预览面板 preview_frame = ttk.LabelFrame(main_frame, text="预览", padding="10") preview_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True) # 行政区划选择部分 region_frame = ttk.LabelFrame(control_frame, text="行政区划选择", padding="5") region_frame.pack(fill=tk.X, pady=5) # 行政区划级别选择 level_frame = ttk.Frame(region_frame) level_frame.pack(fill=tk.X, pady=5) ttk.Label(level_frame, text="行政区划级别:").pack(side=tk.LEFT, padx=5) self.region_level = StringVar(value="省") ttk.Combobox(level_frame, textvariable=self.region_level, values=["省", "市", "县"], state="readonly", width=8).pack(side=tk.LEFT, padx=5) # 行政区划名称 name_frame = ttk.Frame(region_frame) name_frame.pack(fill=tk.X, pady=5) ttk.Label(name_frame, text="行政区划名称:").pack(side=tk.LEFT, padx=5) self.region_name = StringVar(value="山西省") self.region_entry = ttk.Entry(name_frame, textvariable=self.region_name, width=25) self.region_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) # 行政区划边界获取按钮 ttk.Button(region_frame, text="获取行政区划边界", command=self.safe_fetch_region_boundary).pack(pady=5) # 行政区划信息显示 self.region_info = tk.Text(region_frame, height=4, width=40) self.region_info.pack(fill=tk.X, pady=5) self.region_info.insert(tk.END, "行政区划信息将在此显示") self.region_info.config(state=tk.DISABLED) # 输入文件部分 file_frame = ttk.LabelFrame(control_frame, text="高程数据", padding="5") file_frame.pack(fill=tk.X, pady=5) ttk.Label(file_frame, text="高程数据文件:").pack(anchor=tk.W) self.file_entry = ttk.Entry(file_frame) self.file_entry.pack(fill=tk.X, padx=5, pady=2) ttk.Button(file_frame, text="浏览...", command=self.safe_browse_file).pack(anchor=tk.E, pady=2) # 输出设置 output_frame = ttk.LabelFrame(control_frame, text="输出设置", padding="5") output_frame.pack(fill=tk.X, pady=5) ttk.Label(output_frame, text="输出目录:").pack(anchor=tk.W) self.output_dir_entry = ttk.Entry(output_frame) self.output_dir_entry.pack(fill=tk.X, padx=5, pady=2) ttk.Button(output_frame, text="浏览...", command=self.safe_browse_output_dir).pack(anchor=tk.E, pady=2) # 选项设置 options_frame = ttk.LabelFrame(control_frame, text="选项", padding="5") options_frame.pack(fill=tk.X, pady=5) self.grid_var = tk.BooleanVar(value=True) ttk.Checkbutton(options_frame, text="显示网格", variable=self.grid_var).pack(anchor=tk.W) self.dpi_var = tk.IntVar(value=300) # 降低默认DPI以减少内存使用 ttk.Label(options_frame, text="图像DPI:").pack(anchor=tk.W) ttk.Scale(options_frame, from_=100, to=1200, variable=self.dpi_var, orient=tk.HORIZONTAL, length=180).pack(fill=tk.X) self.dpi_label = ttk.Label(options_frame, text=f"当前DPI: {self.dpi_var.get()}") self.dpi_label.pack(anchor=tk.E) self.dpi_var.trace_add("write", self.update_dpi_label) # 按钮区域 button_frame = ttk.Frame(control_frame) button_frame.pack(fill=tk.X, pady=10) ttk.Button(button_frame, text="预览", command=self.safe_preview).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text="生成高程图", command=self.safe_generate_images).pack(side=tk.LEFT, padx=5) self.cancel_btn = ttk.Button(button_frame, text="取消操作", command=self.cancel_operation, state=tk.DISABLED) self.cancel_btn.pack(side=tk.RIGHT, padx=5) # 预览区域 self.preview_frame = ttk.Frame(preview_frame) self.preview_frame.pack(fill=tk.BOTH, expand=True) # 创建两个预览标签 self.preview_label1 = ttk.Label(self.preview_frame, text="基础高程图预览区域") self.preview_label1.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.preview_label2 = ttk.Label(self.preview_frame, text="详细高程图预览区域") self.preview_label2.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True) # 状态栏 self.status_var = tk.StringVar() self.status_var.set("就绪") status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(side=tk.BOTTOM, fill=tk.X) # 内存状态 self.mem_var = tk.StringVar() self.mem_var.set("内存: --") mem_bar = ttk.Label(self.root, textvariable=self.mem_var, relief=tk.SUNKEN, anchor=tk.W) mem_bar.pack(side=tk.BOTTOM, fill=tk.X) def update_dpi_label(self, *args): """更新DPI标签""" self.dpi_label.config(text=f"当前DPI: {self.dpi_var.get()}") def safe_browse_file(self): """安全浏览高程数据文件""" try: file_path = filedialog.askopenfilename( title="选择高程数据文件", filetypes=[("TIFF文件", "*.tif *.tiff"), ("所有文件", "*.*")] ) if file_path: self.file_entry.delete(0, tk.END) self.file_entry.insert(0, file_path) except Exception as e: self.logger.error(f"浏览文件时出错: {str(e)}") self.status_var.set(f"错误: {str(e)}") def safe_browse_output_dir(self): """安全浏览输出目录""" try: dir_path = filedialog.askdirectory(title="选择输出目录") if dir_path: self.output_dir_entry.delete(0, tk.END) self.output_dir_entry.insert(0, dir_path) except Exception as e: self.logger.error(f"浏览目录时出错: {str(e)}") self.status_var.set(f"错误: {str(e)}") def safe_fetch_region_boundary(self): """安全获取行政区划边界""" if not self.validate_region_input(): return # 检查是否已有线程在运行 if self.running_thread and self.running_thread.is_alive(): messagebox.showinfo("操作进行中", "请等待当前操作完成") return self.status_var.set(f"正在获取{self.region_name.get()}边界数据...") self.cancel_btn.config(state=tk.NORMAL) self.stop_event.clear() # 在新线程中获取边界数据 self.running_thread = threading.Thread( target=self._safe_fetch_region_boundary, daemon=True ) self.running_thread.start() def _safe_fetch_region_boundary(self): """线程安全的边界获取""" region_level = self.region_level.get() region_name = self.region_name.get().strip() try: # 使用阿里云行政区划API if region_level == "省": url = f"https://geo.datav.aliyun.com/areas_v3/bound/geojson?code=100000_full" elif region_level == "市": # 尝试获取省级编码 province_url = f"https://geo.datav.aliyun.com/areas_v3/bound/geojson?code=100000_full" province_resp = requests.get(province_url, timeout=15) province_resp.raise_for_status() province_data = province_resp.json() # 查找匹配的省份 for feature in province_data['features']: if feature['properties']['name'] == region_name: adcode = feature['properties']['adcode'] url = f"https://geo.datav.aliyun.com/areas_v3/bound/geojson?code={adcode}_full" break else: raise ValueError(f"未找到匹配的省份: {region_name}") else: # 县 url = f"https://geo.datav.aliyun.com/areas_v3/bound/geojson?code={region_name}" self.logger.info(f"请求行政区划API: {url}") response = requests.get(url, timeout=15) response.raise_for_status() # 解析GeoJSON数据 geojson = response.json() self.region_gdf = gpd.GeoDataFrame.from_features(geojson['features']) # 计算边界范围 bounds = self.region_gdf.total_bounds xmin, ymin, xmax, ymax = bounds # 更新显示信息 self.root.after(0, lambda: self.update_region_info( region_name, region_level, xmin, xmax, ymin, ymax )) self.root.after(0, lambda: self.status_var.set(f"成功获取{region_name}边界数据")) self.logger.info(f"成功获取{region_name}边界数据") except requests.exceptions.Timeout: error_msg = f"获取{region_name}边界数据超时" self.root.after(0, lambda: self.status_var.set(error_msg)) self.root.after(0, lambda: messagebox.showerror("超时错误", error_msg)) self.logger.error(error_msg) except Exception as e: self.root.after(0, lambda: self.show_error_dialog(f"获取边界失败: {str(e)}")) self.logger.error(f"获取边界失败: {str(e)}") finally: self.root.after(0, lambda: self.cancel_btn.config(state=tk.DISABLED)) def update_region_info(self, region_name, region_level, xmin, xmax, ymin, ymax): """更新行政区划信息显示""" self.region_info.config(state=tk.NORMAL) self.region_info.delete(1.0, tk.END) self.region_info.insert(tk.END, f"行政区划: {region_name} ({region_level})\n" f"边界范围: \n" f"经度: {xmin:.4f} - {xmax:.4f}\n" f"纬度: {ymin:.4f} - {ymax:.4f}\n" f"包含{len(self.region_gdf)}个多边形" ) self.region_info.config(state=tk.DISABLED) def validate_region_input(self): """验证行政区划输入""" region_name = self.region_name.get().strip() if not region_name: messagebox.showerror("输入错误", "请输入行政区划名称!") return False return True def validate_inputs(self): """验证所有输入参数""" # 验证行政区划输入 if not self.validate_region_input(): return False # 验证文件路径 file_path = self.file_entry.get().strip() if not file_path: messagebox.showerror("输入错误", "请选择高程数据文件!") return False if not os.path.isfile(file_path): messagebox.showerror("文件错误", f"高程数据文件不存在: {file_path}") return False # 检查是否已获取边界数据 if self.region_gdf is None: messagebox.showerror("数据错误", "请先获取行政区划边界数据!") return False return True def safe_preview(self): """安全预览地图""" if not self.validate_inputs(): return # 检查是否已有线程在运行 if self.running_thread and self.running_thread.is_alive(): messagebox.showinfo("操作进行中", "请等待当前操作完成") return self.status_var.set("正在生成预览...") self.cancel_btn.config(state=tk.NORMAL) self.stop_event.clear() # 在新线程中运行预览 self.running_thread = threading.Thread( target=self._safe_generate_preview, daemon=True ) self.running_thread.start() def _safe_generate_preview(self): """线程安全的预览生成""" try: region_name = self.region_name.get() region_level = self.region_level.get() elevation_file = self.file_entry.get() # 获取边界范围 bounds = self.region_gdf.total_bounds xmin, ymin, xmax, ymax = bounds # 扩展边界以确保完整覆盖 padding = 0.2 xmin -= padding xmax += padding ymin -= padding ymax += padding # 读取高程数据 self.logger.info("读取高程数据...") self.root.after(0, lambda: self.status_var.set("正在读取高程数据...")) # 使用分块读取避免大文件内存溢出 with rxr.open_rasterio(elevation_file, chunks=True) as src: src = src.isel(band=0) # 裁剪数据 self.logger.info("裁剪数据...") self.root.after(0, lambda: self.status_var.set("正在裁剪数据...")) lon = src.coords["x"] lat = src.coords["y"] da = src.loc[dict( x=lon[(lon >= xmin) & (lon <= xmax)], y=lat[(lat >= ymin) & (lat <= ymax)] )] # 重新提取经纬度 lon = da.coords["x"] lat = da.coords["y"] Lon, Lat = np.meshgrid(lon, lat) # 创建基础高程图 self.logger.info("创建基础高程图预览...") self.root.after(0, lambda: self.status_var.set("正在生成基础高程图...")) plt.rcParams['font.family'] = 'SimHei' plt.rcParams['axes.unicode_minus'] = False mapcrs = ccrs.PlateCarree() # 基础高程图 fig1 = plt.figure(figsize=(6, 6)) ax1 = plt.axes(projection=mapcrs) # 添加行政区划边界 ax1.add_geometries( self.region_gdf['geometry'], crs=mapcrs, facecolor='none', edgecolor='red', linewidth=1.5 ) # 绘制高程图 pm1 = ax1.pcolormesh( Lon, Lat, da, cmap='terrain', transform=mapcrs ) # 添加标题 ax1.set_title(f"{region_name}基础高程图", fontsize=12) # 保存预览图像 preview_path1 = os.path.join(os.path.expanduser("~"), "pyMet", "preview1.png") plt.savefig(preview_path1, dpi=150, bbox_inches='tight') plt.close(fig1) # 释放内存 del fig1, ax1, pm1 gc.collect() # 创建详细高程图 self.logger.info("创建详细高程图预览...") self.root.after(0, lambda: self.status_var.set("正在生成详细高程图...")) fig2 = plt.figure(figsize=(6, 6)) ax2 = plt.axes(projection=mapcrs) # 添加行政区划边界 ax2.add_geometries( self.region_gdf['geometry'], crs=mapcrs, facecolor='none', edgecolor='black', linewidth=1.2 ) # 设置高程色阶 levels = np.arange(50, 2501, 100) colorMap = plt.colormaps['terrain'] colorNorm = BoundaryNorm(levels, ncolors=colorMap.N, extend='both') # 绘制高程图 pm2 = ax2.pcolormesh( Lon, Lat, da, cmap=colorMap, norm=colorNorm, transform=mapcrs ) # 添加颜色条 cb2 = fig2.colorbar( pm2, ax=ax2, shrink=0.7, location="right", pad=0.05 ) cb2.set_label('高程 (米)', fontsize=9) # 添加标题 title = f"{region_name}{region_level}高程地形图" ax2.set_title(title, fontsize=12, pad=10) # 在图上标注行政区划名称 centroid = self.region_gdf.geometry.centroid.iloc[0] ax2.text( centroid.x, centroid.y, region_name, fontsize=14, fontweight='bold', color='darkred', ha='center', va='center', bbox=dict(facecolor='white', alpha=0.7, boxstyle='round,pad=0.3'), transform=mapcrs ) # 保存预览图像 preview_path2 = os.path.join(os.path.expanduser("~"), "pyMet", "preview2.png") plt.savefig(preview_path2, dpi=150, bbox_inches='tight') plt.close(fig2) # 释放内存 del fig2, ax2, pm2, cb2 gc.collect() # 在GUI中显示预览 self.root.after(0, lambda: self.update_preview( preview_path1, preview_path2, "预览生成完成" )) except Exception as e: self.root.after(0, lambda: self.show_error_dialog(f"生成预览失败: {str(e)}")) self.logger.error(f"生成预览失败: {str(e)}") finally: self.root.after(0, lambda: self.cancel_btn.config(state=tk.DISABLED)) def update_preview(self, path1, path2, status): """更新预览图像""" try: img1 = Image.open(path1) img1.thumbnail((450, 450), Image.LANCZOS) photo1 = ImageTk.PhotoImage(img1) img2 = Image.open(path2) img2.thumbnail((450, 450), Image.LANCZOS) photo2 = ImageTk.PhotoImage(img2) self.preview_label1.config(image=photo1) self.preview_label1.image = photo1 self.preview_label2.config(image=photo2) self.preview_label2.image = photo2 self.status_var.set(status) self.logger.info(status) except Exception as e: self.logger.error(f"更新预览时出错: {str(e)}") self.status_var.set(f"更新预览失败: {str(e)}") def safe_generate_images(self): """安全生成两张高质量高程图""" if not self.validate_inputs(): return # 检查是否已有线程在运行 if self.running_thread and self.running_thread.is_alive(): messagebox.showinfo("操作进行中", "请等待当前操作完成") return self.status_var.set("正在生成高质量高程图...") self.cancel_btn.config(state=tk.NORMAL) self.stop_event.clear() # 在新线程中运行图像生成 self.running_thread = threading.Thread( target=self._safe_generate_images, daemon=True ) self.running_thread.start() def _safe_generate_images(self): """线程安全的图像生成""" try: region_name = self.region_name.get() region_level = self.region_level.get() elevation_file = self.file_entry.get() output_dir = self.output_dir_entry.get() show_grid = self.grid_var.get() dpi = self.dpi_var.get() # 确保输出目录存在 os.makedirs(output_dir, exist_ok=True) # 生成文件名 base_output_path = os.path.join(output_dir, f"{region_name}_{region_level}_基础高程图.png") detail_output_path = os.path.join(output_dir, f"{region_name}_{region_level}_详细高程图.png") # 获取边界范围 bounds = self.region_gdf.total_bounds xmin, ymin, xmax, ymax = bounds # 扩展边界以确保完整覆盖 padding = 0.2 xmin -= padding xmax += padding ymin -= padding ymax += padding # 读取高程数据 self.logger.info("读取高程数据...") self.root.after(0, lambda: self.status_var.set("正在读取高程数据...")) # 使用分块读取避免大文件内存溢出 with rxr.open_rasterio(elevation_file, chunks=True) as src: src = src.isel(band=0) # 裁剪数据 self.logger.info("裁剪数据...") self.root.after(0, lambda: self.status_var.set("正在裁剪数据...")) lon = src.coords["x"] lat = src.coords["y"] da = src.loc[dict( x=lon[(lon >= xmin) & (lon <= xmax)], y=lat[(lat >= ymin) & (lat <= ymax)] )] # 重新提取经纬度 lon = da.coords["x"] lat = da.coords["y"] Lon, Lat = np.meshgrid(lon, lat) # 创建基础高程图 self.logger.info("创建基础高程图...") self.root.after(0, lambda: self.status_var.set("正在生成基础高程图...")) plt.rcParams['font.family'] = 'SimHei' plt.rcParams['axes.unicode_minus'] = False mapcrs = ccrs.PlateCarree() fig1 = plt.figure(figsize=(10, 10)) ax1 = plt.axes(projection=mapcrs) # 添加行政区划边界 ax1.add_geometries( self.region_gdf['geometry'], crs=mapcrs, facecolor='none', edgecolor='red', linewidth=2.0 ) # 绘制高程图 pm1 = ax1.pcolormesh( Lon, Lat, da, cmap='terrain', transform=mapcrs ) # 添加标题 ax1.set_title(f"{region_name}{region_level}基础高程图", fontsize=16, pad=15) # 在图上标注行政区划名称 centroid = self.region_gdf.geometry.centroid.iloc[0] ax1.text( centroid.x, centroid.y, region_name, fontsize=18, fontweight='bold', color='darkred', ha='center', va='center', bbox=dict(facecolor='white', alpha=0.8, boxstyle='round,pad=0.5'), transform=mapcrs ) # 保存图像 plt.savefig(base_output_path, dpi=dpi, bbox_inches='tight') plt.close(fig1) # 释放内存 del fig1, ax1, pm1 gc.collect() # 创建详细高程图 self.logger.info("创建详细高程图...") self.root.after(0, lambda: self.status_var.set("正在生成详细高程图...")) fig2 = plt.figure(figsize=(10, 10)) ax2 = plt.axes(projection=mapcrs) # 添加行政区划边界 ax2.add_geometries( self.region_gdf['geometry'], crs=mapcrs, facecolor='none', edgecolor='black', linewidth=1.5 ) # 设置高程色阶 levels = np.arange(50, 2501, 100) colorMap = plt.colormaps['terrain'] colorNorm = BoundaryNorm(levels, ncolors=colorMap.N, extend='both') # 绘制高程图 pm2 = ax2.pcolormesh( Lon, Lat, da, cmap=colorMap, norm=colorNorm, transform=mapcrs ) # 添加颜色条 cb2 = fig2.colorbar( pm2, ax=ax2, shrink=0.8, location="right", pad=0.05 ) cb2.set_label('高程 (米)', fontsize=12) # 添加标题 title = f"{region_name}{region_level}高程地形图" ax2.set_title(title, fontsize=16, pad=15) # 在图上标注行政区划名称 ax2.text( centroid.x, centroid.y, region_name, fontsize=22, fontweight='bold', color='darkred', ha='center', va='center', bbox=dict(facecolor='white', alpha=0.8, boxstyle='round,pad=0.8'), transform=mapcrs ) # 添加网格线 if show_grid: gl = ax2.gridlines( crs=mapcrs, draw_labels=True, linewidth=0.5, linestyle='--', color='black', alpha=0.7 ) gl.top_labels = False gl.right_labels = False # 保存图像 plt.savefig(detail_output_path, dpi=dpi, bbox_inches='tight') plt.close(fig2) # 释放内存 del fig2, ax2, pm2, cb2 gc.collect() # 更新状态和预览 status = f"图像已保存到: {output_dir}" self.root.after(0, lambda: self.update_preview( base_output_path, detail_output_path, status )) self.root.after(0, lambda: messagebox.showinfo("生成成功", f"两张高程图已生成:\n" f"1. {base_output_path}\n" f"2. {detail_output_path}")) except Exception as e: self.root.after(0, lambda: self.show_error_dialog(f"生成图像失败: {str(e)}")) self.logger.error(f"生成图像失败: {str(e)}") finally: self.root.after(0, lambda: self.cancel_btn.config(state=tk.DISABLED)) def cancel_operation(self): """取消当前操作""" self.stop_event.set() self.status_var.set("操作已取消") self.cancel_btn.config(state=tk.DISABLED) if self.running_thread and self.running_thread.is_alive(): # 无法直接停止线程,但可以设置标志让线程自己退出 self.logger.warning("用户取消操作") def default_values(self): """设置默认值""" # 设置合理的默认值避免大文件处理 self.file_entry.insert(0, r"C:/path/to/sample_dem.tif") self.output_dir_entry.insert(0, r"C:/pyMet/figures") def save_settings(self): """保存当前设置到文件""" try: settings_dir = os.path.join(os.path.expanduser("~"), "pyMet", "settings") os.makedirs(settings_dir, exist_ok=True) settings_file = os.path.join(settings_dir, "last_settings.txt") with open(settings_file, 'w') as f: f.write(f"elevation_file={self.file_entry.get()}\n") f.write(f"region_level={self.region_level.get()}\n") f.write(f"region_name={self.region_name.get()}\n") f.write(f"output_dir={self.output_dir_entry.get()}\n") f.write(f"show_grid={self.grid_var.get()}\n") f.write(f"dpi={self.dpi_var.get()}\n") self.logger.info("设置已保存") messagebox.showinfo("保存成功", "设置已成功保存") except Exception as e: self.logger.error(f"保存设置失败: {str(e)}") messagebox.showerror("保存错误", f"保存设置时出错: {str(e)}") def load_last_settings(self): """加载上次保存的设置""" try: settings_file = os.path.join(os.path.expanduser("~"), "pyMet", "settings", "last_settings.txt") if not os.path.exists(settings_file): return settings = {} with open(settings_file, 'r') as f: for line in f: key, value = line.strip().split('=', 1) settings[key] = value # 应用设置 if "elevation_file" in settings: self.file_entry.delete(0, tk.END) self.file_entry.insert(0, settings["elevation_file"]) if "region_level" in settings: self.region_level.set(settings["region_level"]) if "region_name" in settings: self.region_name.set(settings["region_name"]) if "output_dir" in settings: self.output_dir_entry.delete(0, tk.END) self.output_dir_entry.insert(0, settings["output_dir"]) if "show_grid" in settings: self.grid_var.set(settings["show_grid"].lower() == "true") if "dpi" in settings: self.dpi_var.set(int(settings["dpi"])) self.logger.info("已加载上次的设置") except Exception as e: self.logger.error(f"加载设置失败: {str(e)}") # ... 后面的代码保持不变 ... # 在按钮区域添加保存设置按钮 # 在create_widgets方法中找到button_frame部分,添加保存按钮 # 修改后的button_frame部分: button_frame = ttk.Frame(control_frame) button_frame.pack(fill=tk.X, pady=10) ttk.Button(button_frame, text="预览", command=self.safe_preview).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text="生成高程图", command=self.safe_generate_images).pack(side=tk.LEFT, padx=5) self.cancel_btn = ttk.Button(button_frame, text="取消操作", command=self.cancel_operation, state=tk.DISABLED) self.cancel_btn.pack(side=tk.RIGHT, padx=5) # 添加保存设置按钮 ttk.Button(button_frame, text="保存设置", command=self.save_settings).pack(side=tk.RIGHT, padx=5) 看看有什么问题,闪退不正常运行程序
06-20
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值