R语言ggplot2可视化:使用ggpubr包的geom_exec函数执行geom_*函数

90 篇文章 ¥59.90 ¥99.00
本文介绍了R语言ggplot2扩展包ggpubr中的geom_exec函数,该函数可以根据字符串参数执行geom_*函数,实现动态数据可视化。通过示例展示了如何使用geom_exec绘制散点图和线图,强调了其灵活性和便捷性。

R语言ggplot2可视化:使用ggpubr包的geom_exec函数执行geom_*函数

在R语言中,ggplot2是一个流行的数据可视化包,它提供了强大而灵活的绘图功能。而ggpubr包是一个扩展包,它在ggplot2的基础上提供了一些额外的功能和自定义选项。其中,geom_exec函数是ggpubr包中的一个特殊函数,它可以根据字符串参数执行对应的geom_*函数,从而实现灵活的绘图。

使用geom_exec函数可以帮助我们动态地生成图形,而不需要提前指定具体的geom_*函数。下面我们将详细介绍如何使用ggpubr包的geom_exec函数来执行geom_*函数。

首先,我们需要安装并加载ggplot2和ggpubr包。可以使用以下代码在R中安装这两个包:

install.packages("ggplot2")
install.packages("ggpubr")

library(ggplot2)
library(ggpubr)

安装完毕并加载包后,我们可以开始使用geom_exec函数。

假设我们有一个数据集data,包含两列数据:x和y。我们想要根据不同的几何对象函数(如geom_point、geom_line等)绘制不同类型的图形。以绘制散点图为例,我们可以通过以下代码使用geom_exec函数执行geom_point函数:

ggplot(data, aes(x = x, y = y)) +
  geom_exec("point")

在上述代码中,我们首先使

请你按照这个改import os import sys import numpy as np import matplotlib import matplotlib.pyplot as plt from osgeo import gdal, ogr, osr, gdal_array from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure from matplotlib.patches import Rectangle, Polygon from matplotlib.lines import Line2D from matplotlib.colors import ListedColormap from matplotlib.widgets import RectangleSelector from PySide6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel, QPushButton, QFileDialog, QTextEdit, QComboBox, QLineEdit, QMessageBox, QSplitter, QScrollArea, QListView, QTreeView, QMenu, QMenuBar, QStatusBar, QDockWidget, QSizePolicy, QDialog, QColorDialog, QSlider, QGridLayout, QListWidget, QListWidgetItem, QAbstractItemView, QFrame, QInputDialog, QToolBar, QToolButton ) from PySide6.QtGui import QAction, QActionGroup, QIcon, QColor from PySide6.QtCore import Qt, QSize, Signal # 启用GDAL异常处理 gdal.UseExceptions() # 自定义图层类 class LayerItem: def __init__(self, name, layer_type, data_source, path=None): self.name = name self.layer_type = layer_type # 'raster' or 'vector' self.data_source = data_source self.path = path self.visible = True self.opacity = 1.0 self.color = None # 用于矢量图层的颜色 self.band_index = 1 # 用于栅格图层的波段索引 self.list_item = None # 存储关联的列表项 def get_display_name(self): return f"{self.name} ({'栅格' if self.layer_type == 'raster' else '矢量'})" # 改进的测量工具类 class EnhancedMeasureTool: def __init__(self, canvas, ax, transform, measure_type): self.canvas = canvas self.ax = ax self.transform = transform # 像素坐标到地理坐标的转换函数 self.measure_type = measure_type self.start_point = None self.current_point = None self.measure_result = None self.line = None self.rect = None self.temp_line = None self.temp_rect = None self.text_annotation = None self.cid_press = None self.cid_move = None self.cid_release = None def enable(self): # 连接事件 self.cid_press = self.canvas.mpl_connect('button_press_event', self.on_press) self.cid_move = self.canvas.mpl_connect('motion_notify_event', self.on_move) self.cid_release = self.canvas.mpl_connect('button_release_event', self.on_release) def disable(self): # 断开事件 if self.cid_press: self.canvas.mpl_disconnect(self.cid_press) if self.cid_move: self.canvas.mpl_disconnect(self.cid_move) if self.cid_release: self.canvas.mpl_disconnect(self.cid_release) self.reset() def reset(self): # 清除所有图形 self.clear_temp_artists() self.clear_final_artists() self.start_point = None self.current_point = None self.measure_result = None def clear_temp_artists(self): if self.temp_line: try: self.temp_line.remove() except Exception: pass self.temp_line = None if self.temp_rect: try: self.temp_rect.remove() except Exception: pass self.temp_rect = None def clear_final_artists(self): if self.line: try: self.line.remove() except Exception: pass self.line = None if self.rect: try: self.rect.remove() except Exception: pass self.rect = None if self.text_annotation: try: self.text_annotation.remove() except Exception: pass self.text_annotation = None def on_press(self, event): if event.inaxes != self.ax: return # 记录起点 self.start_point = (event.xdata, event.ydata) self.current_point = self.start_point # 清除之前的临时图形 self.clear_temp_artists() def on_move(self, event): if event.inaxes != self.ax or not self.start_point: return # 更新当前点 self.current_point = (event.xdata, event.ydata) # 更新临时图形 self.update_temp_artists() def on_release(self, event): if event.inaxes != self.ax or not self.start_point: return # 记录终点 self.current_point = (event.xdata, event.ydata) # 清除临时图形 self.clear_temp_artists() # 计算测量结果 self.calculate_measure() # 显示结果 self.display_result() self.canvas.draw_idle() def update_temp_artists(self): # 清除之前的临时图形 self.clear_temp_artists() # 绘制临时图形 if self.measure_type == 'length': # 绘制临时线段 x = [self.start_point[0], self.current_point[0]] y = [self.start_point[1], self.current_point[1]] self.temp_line = Line2D(x, y, color='red', linewidth=2, alpha=0.7, linestyle='--') self.ax.add_line(self.temp_line) else: # area # 绘制临时矩形 x = min(self.start_point[0], self.current_point[0]) y = min(self.start_point[1], self.current_point[1]) width = abs(self.current_point[0] - self.start_point[0]) height = abs(self.current_point[1] - self.start_point[1]) self.temp_rect = Rectangle((x, y), width, height, fill=False, edgecolor='red', linewidth=2, alpha=0.7, linestyle='--') self.ax.add_patch(self.temp_rect) self.canvas.draw_idle() def calculate_measure(self): if not self.start_point or not self.current_point: return try: # 转换为地理坐标 x1, y1 = self.transform(self.start_point[0], self.start_point[1]) x2, y2 = self.transform(self.current_point[0], self.current_point[1]) if self.measure_type == 'length': # 计算欧氏距离 distance = np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) self.measure_result = f"距离: {distance:.2f} 米" elif self.measure_type == 'area': # 计算矩形面积 width = abs(x2 - x1) height = abs(y2 - y1) area = width * height self.measure_result = f"面积: {area:.2f} 平方米" except Exception as e: print(f"测量计算错误: {str(e)}") self.measure_result = "测量失败" def display_result(self): if not self.measure_result: return # 清除之前的图形 self.clear_final_artists() # 根据测量类型绘制图形 if self.measure_type == 'length': # 绘制线段 x = [self.start_point[0], self.current_point[0]] y = [self.start_point[1], self.current_point[1]] self.line = Line2D(x, y, color='red', linewidth=2) self.ax.add_line(self.line) else: # area # 绘制矩形 x = min(self.start_point[0], self.current_point[0]) y = min(self.start_point[1], self.current_point[1]) width = abs(self.current_point[0] - self.start_point[0]) height = abs(self.current_point[1] - self.start_point[1]) self.rect = Rectangle((x, y), width, height, fill=False, edgecolor='red', linewidth=2) self.ax.add_patch(self.rect) # 添加文本标注 mid_x = (self.start_point[0] + self.current_point[0]) / 2 mid_y = (self.start_point[1] + self.current_point[1]) / 2 self.text_annotation = self.ax.text( mid_x, mid_y, self.measure_result, fontsize=10, color='red', bbox=dict(facecolor='white', alpha=0.7) ) # 属性表对话框 class AttributeTableDialog(QDialog): def __init__(self, layer, parent=None): super().__init__(parent) self.setWindowTitle(f"属性表 - {layer.name}") self.setMinimumSize(600, 400) self.layout = QVBoxLayout(self) # 创建表格 self.table = QTreeView() self.layout.addWidget(self.table) # 加载属性数据 self.load_attribute_data(layer) def load_attribute_data(self, layer): if layer.layer_type != 'vector': QMessageBox.warning(self, "错误", "只有矢量图层有属性表") return try: # 获取图层 layer_obj = layer.data_source.GetLayer() if not layer_obj: raise Exception("无法获取图层") layer_defn = layer_obj.GetLayerDefn() # 创建模型 from PySide6.QtGui import QStandardItemModel, QStandardItem model = QStandardItemModel() model.setHorizontalHeaderLabels( ["FID"] + [layer_defn.GetFieldDefn(i).GetName() for i in range(layer_defn.GetFieldCount())]) # 添加数据 layer_obj.ResetReading() for feature in layer_obj: fid = feature.GetFID() row = [QStandardItem(str(fid))] for i in range(layer_defn.GetFieldCount()): value = feature.GetFieldAsString(i) row.append(QStandardItem(value)) model.appendRow(row) self.table.setModel(model) self.table.expandAll() except Exception as e: QMessageBox.critical(self, "错误", f"加载属性表失败: {str(e)}") # 颜色和透明度设置对话框 class LayerStyleDialog(QDialog): def __init__(self, layer, parent=None): super().__init__(parent) self.setWindowTitle(f"图层样式 - {layer.name}") self.setMinimumSize(400, 300) self.layout = QVBoxLayout(self) # 透明度设置 self.layout.addWidget(QLabel("透明度:")) self.opacity_slider = QSlider(Qt.Horizontal) self.opacity_slider.setRange(0, 100) self.opacity_slider.setValue(int(layer.opacity * 100)) self.opacity_slider.valueChanged.connect(self.update_opacity) self.layout.addWidget(self.opacity_slider) # 颜色设置(仅矢量) if layer.layer_type == 'vector': self.layout.addWidget(QLabel("颜色:")) self.color_button = QPushButton() self.color_button.setFixedSize(80, 30) self.update_color_button(layer) self.color_button.clicked.connect(lambda: self.choose_color(layer)) self.layout.addWidget(self.color_button) # 波段选择(仅栅格) if layer.layer_type == 'raster': self.layout.addWidget(QLabel("显示波段:")) self.band_combo = QComboBox() try: if layer.data_source: for i in range(1, layer.data_source.RasterCount + 1): self.band_combo.addItem(f"波段 {i}") self.band_combo.setCurrentIndex(layer.band_index - 1) self.band_combo.currentIndexChanged.connect(self.update_band) else: self.layout.addWidget(QLabel("无效的栅格数据")) except Exception: self.layout.addWidget(QLabel("无法获取波段信息")) self.layout.addWidget(self.band_combo) # 应用按钮 self.apply_button = QPushButton("应用") self.apply_button.clicked.connect(self.accept) self.layout.addWidget(self.apply_button) def update_opacity(self, value): try: self.parent().layer.opacity = value / 100.0 except Exception: pass def update_band(self, index): try: self.parent().layer.band_index = index + 1 except Exception: pass def choose_color(self, layer): try: color = QColorDialog.getColor() if color.isValid(): layer.color = (color.red() / 255, color.green() / 255, color.blue() / 255) self.update_color_button(layer) except Exception: pass def update_color_button(self, layer): try: if layer.color: color = QColor( int(layer.color[0] * 255), int(layer.color[1] * 255), int(layer.color[2] * 255) ) self.color_button.setStyleSheet(f"background-color: {color.name()};") else: self.color_button.setStyleSheet("") except Exception: pass # 主应用窗口 class GDALAnalyzer(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("GDAL地图文档分析与处理工具") self.setGeometry(100, 100, 1200, 800) # 初始化变量 self.layers = [] # 存储所有图层 self.current_tool = None self.measure_tool = None self.zoom_mode = None self.last_event_cid = None # 记录上次事件的连接ID self.pan_mode = False # 平移模式状态 # 创建主控件 self.main_widget = QWidget() self.setCentralWidget(self.main_widget) # 主布局 self.main_layout = QVBoxLayout(self.main_widget) # 创建顶部工具栏 - ArcGIS风格 self.create_arcgis_toolbar() # 创建主内容区域 self.create_main_content() # 创建状态栏 self.statusBar().showMessage("就绪") def create_arcgis_toolbar(self): main_toolbar = QToolBar("主工具栏") main_toolbar.setIconSize(QSize(32, 32)) self.addToolBar(Qt.TopToolBarArea, main_toolbar) # 文件操作组 file_button = QToolButton() file_button.setText("文件") file_button.setPopupMode(QToolButton.MenuButtonPopup) file_menu = QMenu(self) file_menu.addAction(QIcon.fromTheme("document-open"), "加载栅格数据", self.load_raster_data) file_menu.addAction(QIcon.fromTheme("document-open"), "加载矢量数据", self.load_vector_data) file_menu.addAction(QIcon.fromTheme("document-save"), "保存") file_button.setMenu(file_menu) main_toolbar.addWidget(file_button) # 视图操作组 view_button = QToolButton() view_button.setText("视图") view_button.setPopupMode(QToolButton.MenuButtonPopup) view_menu = QMenu(self) view_menu.addAction(QIcon.fromTheme("zoom-in"), "放大", self.activate_enlarge_tool) view_menu.addAction(QIcon.fromTheme("zoom-out"), "缩小", self.activate_reduce_tool) view_menu.addAction(QIcon.fromTheme("zoom-fit-best"), "全图", self.activate_zoom_to_select_tool) pan_action = QAction(QIcon.fromTheme("transform-move"), "平移", self) pan_action.setCheckable(True) pan_action.toggled.connect(self.toggle_pan_mode) view_menu.addAction(pan_action) view_button.setMenu(view_menu) main_toolbar.addWidget(view_button) # 测量工具组 measure_button = QToolButton() measure_button.setText("测量") measure_button.setPopupMode(QToolButton.MenuButtonPopup) measure_menu = QMenu(self) measure_menu.addAction(QIcon.fromTheme("measure"), "长度测量", self.activate_length_measure_tool) measure_menu.addAction(QIcon.fromTheme("measure-area"), "面积测量", self.activate_area_measure_tool) measure_button.setMenu(measure_menu) main_toolbar.addWidget(measure_button) # 图层操作组 layer_button = QToolButton() layer_button.setText("图层") layer_button.setPopupMode(QToolButton.MenuButtonPopup) layer_menu = QMenu(self) layer_menu.addAction(QIcon.fromTheme("draw-point"), "新建点图层", lambda: self.create_vector_layer(ogr.wkbPoint)) layer_menu.addAction(QIcon.fromTheme("draw-line"), "新建线图层", lambda: self.create_vector_layer(ogr.wkbLineString)) layer_menu.addAction(QIcon.fromTheme("draw-polygon"), "新建面图层", lambda: self.create_vector_layer(ogr.wkbPolygon)) layer_button.setMenu(layer_menu) main_toolbar.addWidget(layer_button) # 处理工具组 processing_button = QToolButton() processing_button.setText("处理") processing_button.setPopupMode(QToolButton.MenuButtonPopup) processing_menu = QMenu(self) processing_menu.addAction(QIcon.fromTheme("color-gradient"), "波段融合", self.perform_band_fusion) processing_menu.addAction(QIcon.fromTheme("transform-rotate"), "波段交换", self.perform_band_swap) processing_button.setMenu(processing_menu) main_toolbar.addWidget(processing_button) # 查询工具组 query_button = QToolButton() query_button.setText("查询") query_button.setPopupMode(QToolButton.MenuButtonPopup) query_menu = QMenu(self) query_menu.addAction(QIcon.fromTheme("find"), "属性查询", self.select_by_attributes) query_menu.addAction(QIcon.fromTheme("find-location"), "空间查询", self.select_by_location) query_button.setMenu(query_menu) main_toolbar.addWidget(query_button) # 其他功能 main_toolbar.addAction(QIcon.fromTheme("image-x-generic"), "保存地图", self.save_map_image) def toggle_pan_mode(self, checked): """切换平移模式""" self.pan_mode = checked if checked: self.statusBar().showMessage("平移模式已激活 - 按住鼠标左键拖动地图") # 禁用其他工具 if self.measure_tool: self.measure_tool.disable() self.measure_tool = None if self.last_event_cid: self.canvas.mpl_disconnect(self.last_event_cid) else: self.statusBar().showMessage("平移模式已禁用") # 连接/断开平移事件 if self.figure.axes: ax = self.figure.axes[0] if self.pan_mode: self.last_event_cid = self.canvas.mpl_connect('button_press_event', self.on_pan_press) else: if self.last_event_cid: self.canvas.mpl_disconnect(self.last_event_cid) self.last_event_cid = None def on_pan_press(self, event): """处理平移操作""" if event.inaxes != self.figure.axes[0]: return if event.button == 1: # 左键 self.pan_start = (event.xdata, event.ydata) self.cid_pan_move = self.canvas.mpl_connect('motion_notify_event', self.on_pan_move) self.cid_pan_release = self.canvas.mpl_connect('button_release_event', self.on_pan_release) def on_pan_move(self, event): """处理平移移动""" if event.inaxes != self.figure.axes[0]: return if hasattr(self, 'pan_start'): ax = self.figure.axes[0] x_start, y_start = self.pan_start x_current, y_current = event.xdata, event.ydata dx = x_current - x_start dy = y_current - y_start # 更新坐标范围 try: ax.set_xlim(ax.get_xlim() - dx) ax.set_ylim(ax.get_ylim() - dy) except Exception as e: print(f"平移错误: {str(e)}") self.canvas.draw_idle() def on_pan_release(self, event): """处理平移结束""" if hasattr(self, 'pan_start'): self.canvas.mpl_disconnect(self.cid_pan_move) self.canvas.mpl_disconnect(self.cid_pan_release) del self.pan_start def create_main_content(self): # 主内容区域 - 水平布局 content_layout = QHBoxLayout() # 左侧面板 - 图层列表 left_dock = QDockWidget("图层列表", self) left_dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) left_dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) left_dock.setMinimumWidth(250) self.layer_list = QListWidget() # 启用拖拽排序 self.layer_list.setDragDropMode(QAbstractItemView.InternalMove) self.layer_list.setDropIndicatorShown(True) self.layer_list.model().rowsMoved.connect(self.on_layers_reordered) self.layer_list.setContextMenuPolicy(Qt.CustomContextMenu) self.layer_list.customContextMenuRequested.connect(self.show_layer_context_menu) self.layer_list.itemSelectionChanged.connect(self.layer_selection_changed) self.layer_list.itemChanged.connect(self.on_layer_item_changed) left_dock.setWidget(self.layer_list) self.addDockWidget(Qt.LeftDockWidgetArea, left_dock) # 中央区域 - 可视化画布 self.figure = Figure(figsize=(10, 8)) self.canvas = FigureCanvas(self.figure) self.canvas.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # 中央区域布局 center_widget = QWidget() center_layout = QVBoxLayout(center_widget) center_layout.addWidget(self.canvas) content_layout.addWidget(center_widget) self.main_layout.addLayout(content_layout) def on_layers_reordered(self, parent, start, end, destination, row): """当图层顺序改变时更新图层列表""" try: # 更新图层顺序 moved_layer = self.layers.pop(start) self.layers.insert(row, moved_layer) # 更新图层列表显示 self.update_layer_list() # 重绘地图 self.redraw_map() except Exception as e: print(f"图层顺序更新错误: {str(e)}") def on_layer_item_changed(self, item): """处理图层可见性变更""" try: row = self.layer_list.row(item) if row < len(self.layers): self.layers[row].visible = (item.checkState() == Qt.Checked) self.redraw_map() except Exception as e: print(f"图层可见性变更错误: {str(e)}") def show_layer_context_menu(self, pos): # 获取选中的图层项 item = self.layer_list.itemAt(pos) if not item: return try: # 获取对应的图层对象 row = self.layer_list.row(item) layer = self.layers[row] # 创建上下文菜单 menu = QMenu(self) # 属性表 attr_action = QAction("属性表", self) attr_action.triggered.connect(lambda: self.show_attribute_table(layer)) menu.addAction(attr_action) # 改变颜色和透明度 style_action = QAction("改变颜色和透明度", self) style_action.triggered.connect(lambda: self.change_layer_style(layer)) menu.addAction(style_action) # 删除图层 delete_action = QAction("删除图层", self) delete_action.triggered.connect(lambda: self.remove_layer(layer)) menu.addAction(delete_action) # 显示菜单 menu.exec_(self.layer_list.mapToGlobal(pos)) except Exception as e: print(f"图层上下文菜单错误: {str(e)}") def layer_selection_changed(self): # 高亮选中的图层 try: self.redraw_map() except Exception as e: print(f"图层选择变更错误: {str(e)}") def show_attribute_table(self, layer): try: dialog = AttributeTableDialog(layer, self) dialog.exec_() except Exception as e: QMessageBox.critical(self, "错误", f"打开属性表失败: {str(e)}") def change_layer_style(self, layer): try: dialog = LayerStyleDialog(layer, self) if dialog.exec_() == QDialog.Accepted: self.redraw_map() except Exception as e: QMessageBox.critical(self, "错误", f"更改图层样式失败: {str(e)}") def remove_layer(self, layer): try: # 从列表中移除图层 self.layers.remove(layer) # 更新图层列表显示 self.update_layer_list() # 重绘地图 self.redraw_map() except Exception as e: print(f"删除图层错误: {str(e)}") def update_layer_list(self): try: self.layer_list.clear() for layer in self.layers: item = QListWidgetItem(layer.get_display_name()) item.setFlags( item.flags() | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled) item.setCheckState(Qt.Checked if layer.visible else Qt.Unchecked) self.layer_list.addItem(item) layer.list_item = item # 存储关联的列表项 except Exception as e: print(f"更新图层列表错误: {str(e)}") def activate_enlarge_tool(self): try: # 断开之前的事件 if self.last_event_cid: self.canvas.mpl_disconnect(self.last_event_cid) # 设置当前工具 self.current_tool = 'enlarge' self.zoom_mode = 'enlarge' # 禁用其他工具 self.pan_mode = False if self.measure_tool: self.measure_tool.disable() self.measure_tool = None # 连接事件 self.last_event_cid = self.canvas.mpl_connect('button_press_event', self.zoom_click) self.statusBar().showMessage("放大工具已激活 - 在图像上单击进行放大") except Exception as e: print(f"激活放大工具错误: {str(e)}") def activate_reduce_tool(self): try: if self.last_event_cid: self.canvas.mpl_disconnect(self.last_event_cid) self.current_tool = 'reduce' self.zoom_mode = 'reduce' self.pan_mode = False if self.measure_tool: self.measure_tool.disable() self.measure_tool = None self.last_event_cid = self.canvas.mpl_connect('button_press_event', self.zoom_click) self.statusBar().showMessage("缩小工具已激活 - 在图像上单击进行缩小") except Exception as e: print(f"激活缩小工具错误: {str(e)}") def activate_zoom_to_select_tool(self): try: if self.last_event_cid: self.canvas.mpl_disconnect(self.last_event_cid) self.current_tool = 'zoom_to_select' self.pan_mode = False if self.measure_tool: self.measure_tool.disable() self.measure_tool = None # 这里简化为缩放到整个视图 if self.figure.axes: ax = self.figure.axes[0] ax.autoscale() self.canvas.draw_idle() self.statusBar().showMessage("缩放到选择工具已激活") except Exception as e: print(f"激活全图工具错误: {str(e)}") def activate_length_measure_tool(self): try: if self.last_event_cid: self.canvas.mpl_disconnect(self.last_event_cid) self.current_tool = 'length_measure' self.zoom_mode = None self.pan_mode = False if self.figure.axes: ax = self.figure.axes[0] # 获取坐标转换函数 transform = self.get_pixel_to_geo_transform() if transform: self.measure_tool = EnhancedMeasureTool(self.canvas, ax, transform, 'length') self.measure_tool.enable() self.statusBar().showMessage("长度测量工具已激活 - 在图像上拖动绘制测量线") else: self.statusBar().showMessage("无法获取坐标转换信息") else: self.statusBar().showMessage("请先加载数据") except Exception as e: print(f"激活长度测量工具错误: {str(e)}") def activate_area_measure_tool(self): try: if self.last_event_cid: self.canvas.mpl_disconnect(self.last_event_cid) self.current_tool = 'area_measure' self.zoom_mode = None self.pan_mode = False if self.figure.axes: ax = self.figure.axes[0] transform = self.get_pixel_to_geo_transform() if transform: self.measure_tool = EnhancedMeasureTool(self.canvas, ax, transform, 'area') self.measure_tool.enable() self.statusBar().showMessage("面积测量工具已激活 - 在图像上拖动绘制测量区域") else: self.statusBar().showMessage("无法获取坐标转换信息") else: self.statusBar().showMessage("请先加载数据") except Exception as e: print(f"激活面积测量工具错误: {str(e)}") def get_pixel_to_geo_transform(self): """获取从像素坐标到地理坐标的转换函数""" try: if self.layers and self.layers[0].layer_type == 'raster': gt = self.layers[0].data_source.GetGeoTransform() # 转换函数: (col, row) -> (x, y) return lambda col, row: ( gt[0] + col * gt[1] + row * gt[2], gt[3] + col * gt[4] + row * gt[5] ) return None except Exception: return None def zoom_click(self, event): try: if event.inaxes and self.zoom_mode: ax = event.inaxes x, y = event.xdata, event.ydata if self.zoom_mode == 'enlarge': zoom_factor = 1.2 ax.set_xlim(ax.get_xlim()[0] + (x - ax.get_xlim()[0]) * (1 - 1 / zoom_factor), ax.get_xlim()[1] - (ax.get_xlim()[1] - x) * (1 - 1 / zoom_factor)) ax.set_ylim(ax.get_ylim()[0] + (y - ax.get_ylim()[0]) * (1 - 1 / zoom_factor), ax.get_ylim()[1] - (ax.get_ylim()[1] - y) * (1 - 1 / zoom_factor)) else: # reduce zoom_factor = 1.2 new_xlim_left = ax.get_xlim()[0] - (x - ax.get_xlim()[0]) * (zoom_factor - 1) new_xlim_right = ax.get_xlim()[1] + (ax.get_xlim()[1] - x) * (zoom_factor - 1) new_ylim_bottom = ax.get_ylim()[0] - (y - ax.get_ylim()[0]) * (zoom_factor - 1) new_ylim_top = ax.get_ylim()[1] + (ax.get_ylim()[1] - y) * (zoom_factor - 1) ax.set_xlim(new_xlim_left, new_xlim_right) ax.set_ylim(new_ylim_bottom, new_ylim_top) ax.figure.canvas.draw_idle() except Exception as e: print(f"缩放操作错误: {str(e)}") def load_raster_data(self): file_path, _ = QFileDialog.getOpenFileName( self, "打开栅格文件", "", "栅格文件 (*.tif *.tiff *.img *.jp2 *.png *.jpg);;所有文件 (*.*)" ) if file_path: try: dataset = gdal.Open(file_path, gdal.GA_ReadOnly) if not dataset: raise Exception("无法打开栅格文件") # 检查波段数量 if dataset.RasterCount < 1: raise Exception("栅格文件没有有效波段") # 创建图层对象 layer_name = os.path.basename(file_path) layer = LayerItem(layer_name, 'raster', dataset, file_path) # 添加到图层列表 self.layers.append(layer) self.update_layer_list() self.statusBar().showMessage(f"已加载栅格文件: {layer_name}") self.redraw_map() except Exception as e: QMessageBox.critical(self, "错误", f"加载栅格文件失败: {str(e)}") def load_vector_data(self): file_path, _ = QFileDialog.getOpenFileName( self, "打开矢量文件", "", "矢量文件 (*.shp *.geojson *.gpkg);;所有文件 (*.*)" ) if file_path: try: dataset = ogr.Open(file_path) if not dataset: raise Exception("无法打开矢量文件") # 检查图层 layer_count = dataset.GetLayerCount() if layer_count < 1: raise Exception("矢量文件没有有效图层") # 获取第一个图层 layer = dataset.GetLayer(0) if not layer: raise Exception("无法获取矢量图层") # 检查要素数量 feature_count = layer.GetFeatureCount() if feature_count < 0: # 可能返回-1表示未知 layer.ResetReading() feature_count = sum(1 for _ in layer) # 创建图层对象 layer_name = os.path.basename(file_path) layer_item = LayerItem(layer_name, 'vector', dataset, file_path) # 设置默认颜色 geom_type = layer.GetGeomType() if geom_type == ogr.wkbPoint: layer_item.color = (1, 0, 0) # 红色 elif geom_type == ogr.wkbLineString: layer_item.color = (0, 0, 1) # 蓝色 elif geom_type == ogr.wkbPolygon: layer_item.color = (0, 1, 0) # 绿色 # 添加到图层列表 self.layers.append(layer_item) self.update_layer_list() self.statusBar().showMessage(f"已加载矢量文件: {layer_name}") self.redraw_map() except Exception as e: QMessageBox.critical(self, "错误", f"加载矢量文件失败: {str(e)}") def redraw_map(self): """重绘所有可见图层""" try: self.figure.clear() if not self.layers: self.canvas.draw() return ax = self.figure.add_subplot(111) # 先绘制栅格图层(作为底图) for layer in reversed(self.layers): # 从底层到顶层 if not layer.visible: continue if layer.layer_type == 'raster': self.plot_raster_layer(ax, layer) # 再绘制矢量图层(在上层) for layer in reversed(self.layers): # 从底层到顶层 if not layer.visible: continue if layer.layer_type == 'vector': self.plot_vector_layer(ax, layer) self.canvas.draw() except Exception as e: print(f"重绘地图错误: {str(e)}") def plot_raster_layer(self, ax, layer): """绘制栅格图层""" try: # 检查数据源是否有效 if not layer.data_source: return # 检查波段索引 if layer.band_index < 1 or layer.band_index > layer.data_source.RasterCount: layer.band_index = 1 # 重置为默认波段 # 读取指定波段 band = layer.data_source.GetRasterBand(layer.band_index) data = band.ReadAsArray() # 获取地理范围 gt = layer.data_source.GetGeoTransform() extent = ( gt[0], gt[0] + layer.data_source.RasterXSize * gt[1], gt[3] + layer.data_source.RasterYSize * gt[5], gt[3] ) # 显示图像 ax.imshow(data, extent=extent, cmap='gray', alpha=layer.opacity) # 设置标题 ax.set_title(layer.name) ax.axis('auto') # 自动调整坐标轴 except Exception as e: print(f"绘制栅格数据失败: {str(e)}") def plot_vector_layer(self, ax, layer): """绘制矢量图层""" try: # 获取图层 ds_layer = layer.data_source.GetLayer() if not ds_layer: return # 重置读取位置 ds_layer.ResetReading() # 设置默认颜色 if not layer.color: geom_type = ds_layer.GetGeomType() if geom_type == ogr.wkbPoint: layer.color = (1, 0, 0) # 红色 elif geom_type == ogr.wkbLineString: layer.color = (0, 0, 1) # 蓝色 elif geom_type == ogr.wkbPolygon: layer.color = (0, 1, 0) # 绿色 # 遍历要素 for feature in ds_layer: geom = feature.GetGeometryRef() if not geom: continue geom_type = geom.GetGeometryType() if geom_type == ogr.wkbPoint: x, y, _ = geom.GetPoint() ax.plot(x, y, 'o', color=layer.color, markersize=5, alpha=layer.opacity) elif geom_type == ogr.wkbLineString: points = geom.GetPoints() if len(points) > 1: xs, ys = zip(*points) ax.plot(xs, ys, '-', color=layer.color, linewidth=2, alpha=layer.opacity) elif geom_type == ogr.wkbPolygon: # 只绘制外环 ring = geom.GetGeometryRef(0) if ring: points = ring.GetPoints() if len(points) > 2: xs, ys = zip(*points) ax.fill(xs, ys, color=layer.color, alpha=layer.opacity * 0.5) except Exception as e: print(f"绘制矢量数据失败: {str(e)}") def perform_band_fusion(self): try: # 弹出对话框选择波段 if not self.layers: QMessageBox.warning(self, "警告", "请先加载栅格数据") return raster_layers = [layer for layer in self.layers if layer.layer_type == 'raster'] if not raster_layers: QMessageBox.warning(self, "警告", "没有可用的栅格图层") return # 创建波段选择对话框 dialog = QDialog(self) dialog.setWindowTitle("波段融合") layout = QVBoxLayout(dialog) layout.addWidget(QLabel("选择图层:")) layer_combo = QComboBox() for layer in raster_layers: layer_combo.addItem(layer.name) layout.addWidget(layer_combo) layout.addWidget(QLabel("选择波段组合:")) band_combo = QComboBox() band_combo.addItem("RGB (1,2,3)") band_combo.addItem("假彩色 (4,3,2)") band_combo.addItem("自定义...") layout.addWidget(band_combo) # 自定义波段输入 custom_layout = QHBoxLayout() custom_layout.addWidget(QLabel("红:")) red_band = QLineEdit("1") custom_layout.addWidget(red_band) custom_layout.addWidget(QLabel("绿:")) green_band = QLineEdit("2") custom_layout.addWidget(green_band) custom_layout.addWidget(QLabel("蓝:")) blue_band = QLineEdit("3") custom_layout.addWidget(blue_band) layout.addLayout(custom_layout) # 确定按钮 btn_ok = QPushButton("确定") btn_ok.clicked.connect(dialog.accept) layout.addWidget(btn_ok) if dialog.exec_() == QDialog.Accepted: # 获取选择的图层 selected_layer = raster_layers[layer_combo.currentIndex()] # 获取波段组合 if band_combo.currentIndex() == 0: # RGB bands = [1, 2, 3] elif band_combo.currentIndex() == 1: # 假彩色 bands = [4, 3, 2] else: # 自定义 try: bands = [ int(red_band.text()), int(green_band.text()), int(blue_band.text()) ] except ValueError: QMessageBox.warning(self, "警告", "请输入有效的波段数字") return # 执行波段融合 self.execute_band_fusion(selected_layer, bands) except Exception as e: QMessageBox.critical(self, "错误", f"波段融合失败: {str(e)}") def execute_band_fusion(self, layer, bands): try: # 检查数据源是否有效 if not layer.data_source: raise Exception("无效的栅格数据源") # 检查波段数量 if max(bands) > layer.data_source.RasterCount: raise Exception(f"选择的波段超出图层波段范围(最大波段数: {layer.data_source.RasterCount})") # 检查波段是否在有效范围内 for band_idx in bands: if band_idx < 1 or band_idx > layer.data_source.RasterCount: raise Exception(f"波段 {band_idx} 超出有效范围") # 读取波段 band_data = [] for band_idx in bands: band = layer.data_source.GetRasterBand(band_idx) arr = band.ReadAsArray() # 拉伸到0-1 min_val, max_val = band.ComputeRasterMinMax() if max_val > min_val: arr = (arr - min_val) / (max_val - min_val) else: arr = arr.astype(np.float32) / np.max(arr) band_data.append(arr) # 创建RGB图像 rgb = np.dstack(band_data) # 创建新图层 new_layer = LayerItem(f"{layer.name}_融合", 'raster', None) new_layer.rgb_data = rgb new_layer.extent = ( layer.data_source.GetGeoTransform()[0], layer.data_source.GetGeoTransform()[0] + layer.data_source.RasterXSize * layer.data_source.GetGeoTransform()[1], layer.data_source.GetGeoTransform()[3] + layer.data_source.RasterYSize * layer.data_source.GetGeoTransform()[5], layer.data_source.GetGeoTransform()[3] ) # 添加到图层列表 self.layers.append(new_layer) self.update_layer_list() self.redraw_map() self.statusBar().showMessage("波段融合完成") except Exception as e: QMessageBox.critical(self, "错误", f"波段融合失败: {str(e)}") def perform_band_swap(self): try: # 弹出对话框选择波段 if not self.layers: QMessageBox.warning(self, "警告", "请先加载栅格数据") return raster_layers = [layer for layer in self.layers if layer.layer_type == 'raster'] if not raster_layers: QMessageBox.warning(self, "警告", "没有可用的栅格图层") return # 创建波段选择对话框 dialog = QDialog(self) dialog.setWindowTitle("波段交换") layout = QVBoxLayout(dialog) layout.addWidget(QLabel("选择图层:")) layer_combo = QComboBox() for layer in raster_layers: layer_combo.addItem(layer.name) layout.addWidget(layer_combo) layout.addWidget(QLabel("交换波段:")) swap_layout = QHBoxLayout() swap_layout.addWidget(QLabel("波段1:")) band1_combo = QComboBox() # 获取当前图层的波段数 current_layer = raster_layers[0] band_count = current_layer.data_source.RasterCount if current_layer.data_source else 1 for i in range(1, band_count + 1): band1_combo.addItem(f"波段 {i}") swap_layout.addWidget(band1_combo) swap_layout.addWidget(QLabel("波段2:")) band2_combo = QComboBox() for i in range(1, band_count + 1): band2_combo.addItem(f"波段 {i}") if band_count > 2: band2_combo.setCurrentIndex(2) # 默认第三个波段 swap_layout.addWidget(band2_combo) layout.addLayout(swap_layout) # 确定按钮 btn_ok = QPushButton("确定") btn_ok.clicked.connect(dialog.accept) layout.addWidget(btn_ok) if dialog.exec_() == QDialog.Accepted: # 获取选择的图层 selected_layer = raster_layers[layer_combo.currentIndex()] # 获取波段索引 band1_idx = band1_combo.currentIndex() + 1 band2_idx = band2_combo.currentIndex() + 1 # 执行波段交换 self.execute_band_swap(selected_layer, band1_idx, band2_idx) except Exception as e: QMessageBox.critical(self, "错误", f"波段交换失败: {str(e)}") def execute_band_swap(self, layer, band1_idx, band2_idx): try: # 检查数据源 if not layer.data_source: raise Exception("无效的栅格数据源") # 检查波段索引是否有效 if band1_idx < 1 or band1_idx > layer.data_source.RasterCount: raise Exception(f"无效的波段1索引: {band1_idx}") if band2_idx < 1 or band2_idx > layer.data_source.RasterCount: raise Exception(f"无效的波段2索引: {band2_idx}") # 读取两个波段 band1 = layer.data_source.GetRasterBand(band1_idx).ReadAsArray() band2 = layer.data_source.GetRasterBand(band2_idx).ReadAsArray() # 创建新图层 new_layer = LayerItem(f"{layer.name}_交换波段{band1_idx}&{band2_idx}", 'raster', layer.data_source) new_layer.band_index = band1_idx # 临时使用 # 保存交换后的数据 new_layer.swapped_band1 = band2 new_layer.swapped_band2 = band1 # 添加到图层列表 self.layers.append(new_layer) self.update_layer_list() self.redraw_map() self.statusBar().showMessage(f"波段 {band1_idx} 和 {band2_idx} 交换完成") except Exception as e: QMessageBox.critical(self, "错误", f"波段交换失败: {str(e)}") def select_by_attributes(self): try: if not self.layers: QMessageBox.warning(self, "警告", "请先加载矢量数据") return vector_layers = [layer for layer in self.layers if layer.layer_type == 'vector'] if not vector_layers: QMessageBox.warning(self, "警告", "没有可用的矢量图层") return dialog = QDialog(self) dialog.setWindowTitle("按属性选择") layout = QVBoxLayout(dialog) layout.addWidget(QLabel("选择图层:")) layer_combo = QComboBox() for layer in vector_layers: layer_combo.addItem(layer.name) layout.addWidget(layer_combo) field_label = QLabel("字段:") field_combo = QComboBox() value_edit = QLineEdit() def update_fields(index): layer = vector_layers[index] ds_layer = layer.data_source.GetLayer() if ds_layer: layer_defn = ds_layer.GetLayerDefn() field_combo.clear() for i in range(layer_defn.GetFieldCount()): field_combo.addItem(layer_defn.GetFieldDefn(i).GetName()) layer_combo.currentIndexChanged.connect(update_fields) update_fields(0) layout.addWidget(field_label) layout.addWidget(field_combo) layout.addWidget(QLabel("值:")) layout.addWidget(value_edit) btn_ok = QPushButton("确定") btn_ok.clicked.connect(dialog.accept) layout.addWidget(btn_ok) if dialog.exec_() == QDialog.Accepted: selected_layer = vector_layers[layer_combo.currentIndex()] field_name = field_combo.currentText() value = value_edit.text() self.execute_attribute_query(selected_layer, field_name, value) except Exception as e: QMessageBox.critical(self, "错误", f"属性选择失败: {str(e)}") def select_by_location(self): try: if not self.layers: QMessageBox.warning(self, "警告", "请先加载矢量数据") return vector_layers = [layer for layer in self.layers if layer.layer_type == 'vector'] if not vector_layers: QMessageBox.warning(self, "警告", "没有可用的矢量图层") return dialog = QDialog(self) dialog.setWindowTitle("按位置选择") layout = QVBoxLayout(dialog) layout.addWidget(QLabel("选择图层:")) layer_combo = QComboBox() for layer in vector_layers: layer_combo.addItem(layer.name) layout.addWidget(layer_combo) relation_combo = QComboBox() relation_combo.addItems(["含", "相交", "接触", "在...之内"]) layout.addWidget(relation_combo) btn_ok = QPushButton("确定") btn_ok.clicked.connect(dialog.accept) layout.addWidget(btn_ok) if dialog.exec_() == QDialog.Accepted: selected_layer = vector_layers[layer_combo.currentIndex()] relation = relation_combo.currentText() self.execute_spatial_query(selected_layer, relation) except Exception as e: QMessageBox.critical(self, "错误", f"空间选择失败: {str(e)}") def execute_attribute_query(self, layer, field_name, value): try: # 获取图层 ds_layer = layer.data_source.GetLayer() if not ds_layer: raise Exception("无法获取矢量图层") # 设置属性过滤器 ds_layer.SetAttributeFilter(f"{field_name} = '{value}'") count = ds_layer.GetFeatureCount() QMessageBox.information(self, "查询结果", f"找到 {count} 个满足条件的要素") except Exception as e: QMessageBox.critical(self, "错误", f"属性查询失败: {str(e)}") def execute_spatial_query(self, layer, relation): try: # 获取图层 ds_layer = layer.data_source.GetLayer() if not ds_layer: raise Exception("无法获取矢量图层") # 这里简化处理,实际应使用空间过滤 count = ds_layer.GetFeatureCount() QMessageBox.information(self, "查询结果", f"找到 {count} 个要素") except Exception as e: QMessageBox.critical(self, "错误", f"空间查询失败: {str(e)}") def create_vector_layer(self, geom_type): try: # 获取图层名称 layer_name, ok = QInputDialog.getText(self, "新建图层", "输入图层名称:") if not ok or not layer_name: return # 创建内存数据源 driver = ogr.GetDriverByName('Memory') ds = driver.CreateDataSource('temp') # 根据几何类型创建图层 srs = osr.SpatialReference() srs.ImportFromEPSG(4326) # WGS84 if geom_type == ogr.wkbPoint: layer = ds.CreateLayer(layer_name, srs, geom_type) layer_type = '点' color = (1, 0, 0) # 红色 elif geom_type == ogr.wkbLineString: layer = ds.CreateLayer(layer_name, srs, geom_type) layer_type = '线' color = (0, 0, 1) # 蓝色 elif geom_type == ogr.wkbPolygon: layer = ds.CreateLayer(layer_name, srs, geom_type) layer_type = '面' color = (0, 1, 0) # 绿色 else: raise Exception("不支持的几何类型") # 添加ID字段 field_defn = ogr.FieldDefn('id', ogr.OFTInteger) layer.CreateField(field_defn) # 创建图层对象 new_layer = LayerItem(layer_name, 'vector', ds) new_layer.color = color # 添加到图层列表 self.layers.append(new_layer) self.update_layer_list() self.statusBar().showMessage(f"已创建新的{layer_type}图层: {layer_name}") except Exception as e: QMessageBox.critical(self, "错误", f"创建图层失败: {str(e)}") def save_map_image(self): """保存当前地图图像""" try: if not self.layers: QMessageBox.warning(self, "警告", "没有可保存的地图内容") return file_path, _ = QFileDialog.getSaveFileName( self, "保存地图图像", "", "PNG图像 (*.png);;JPEG图像 (*.jpg);;所有文件 (*.*)" ) if file_path: # 保存当前视图 self.figure.savefig(file_path, dpi=300, bbox_inches='tight') self.statusBar().showMessage(f"地图已保存到: {file_path}") except Exception as e: QMessageBox.critical(self, "错误", f"保存地图失败: {str(e)}") if __name__ == "__main__": app = QApplication(sys.argv) window = GDALAnalyzer() window.show() sys.exit(app.exec())
07-16
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值