diff --git a/ReadMe.md b/ReadMe.md index 0d5dbc1..82f1e0c 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -9,4 +9,17 @@ Python3 + PyQt + QGIS? pyqt qgis gdal numpy # 打包方式 -comming soon \ No newline at end of file +comming soon + +# 功能 + +1. 证书检查与生成 + 1. 基于MAC地址与过期时间进行证书生成,启动时检查证书,过期则退出 +2. 项目管理 + 1. 以项目为单位进行数据管理与生产 + 2. 提供项目保存与导入功能 + 3. 提供多种格式的栅格数据导入(TIF、PNG、BMP、JPG)等 + 4. 提供矢量数据导入 +3. 基本工具 + 1. 双视图同步浏览 + 2. 格网展示 diff --git a/actions/actions.py b/actions/actions.py deleted file mode 100644 index 4b2d4d4..0000000 --- a/actions/actions.py +++ /dev/null @@ -1,341 +0,0 @@ -import logging -import os -from PyQt5 import QtCore, QtGui, QtWidgets -from PyQt5.QtWidgets import QAction, QActionGroup, QLabel, QFileDialog -from gui.about import AboutDialog -from gui.project import Create -from utils.project import Project - -class ActionManager(QtCore.QObject): - - def __init__(self, - double_map, - layer_tree, - follow_box, - result_box, - message_box, - parent=None): - super().__init__(parent) - self.w_parent = parent - self.actions = {} - self.action_groups = {} - self.action_group_actions = {} - - self.double_map = double_map - self.layer_tree = layer_tree - self.follow_box = follow_box - self.result_box = result_box - self.message_box = message_box - - self.allways_enable = [] - self.init_enable = [] - self.dataload_enable = [] - - self.toolbar = None - self.menubar = None - self.status_bar = None - - def set_menus(self, menubar): - self.menubar = menubar - self.file_menu = menubar.addMenu('&文件') - # self.view_menu = menubar.addMenu('&视图') - self.basic_menu = menubar.addMenu('&基本工具') - self.preop_menu = menubar.addMenu('&预处理') - self.change_detection_menu = menubar.addMenu('&变化检测') - self.special_chagne_detec_menu = menubar.addMenu('&专题变化检测') - self.postop_menu = menubar.addMenu('&后处理') - self.eval_menu = menubar.addMenu('&评估') - self.help_menu = menubar.addMenu('&帮助') - - def set_toolbar(self, toolbar): - self.toolbar = toolbar - self.toolbar.setIconSize(QtCore.QSize(24, 24)) - - def set_status_bar(self, status_bar): - self.status_bar = status_bar - - def set_actions(self): - ''' - File menu - ''' - project_create = self.add_action(QAction('&工程创建', self.w_parent), 'File') - project_open = self.add_action(QAction('&打开工程', self.w_parent), 'File') - project_save = self.add_action(QAction('&保存工程', self.w_parent), 'File') - data_load = self.add_action(QAction('&数据加载', self.w_parent), 'File') - view_setting = self.add_action(QAction('&界面定制', self.w_parent), 'File') - exit_app = self.add_action(QAction('&退出', self.w_parent), 'File') - project_create.triggered.connect(self.project_create) - project_open.triggered.connect(self.project_open) - project_save.triggered.connect(self.project_save) - data_load.triggered.connect(self.data_load) - view_setting.triggered.connect(self.view_setting) - exit_app.triggered.connect(self.w_parent.close) - - self.allways_enable.append(project_create, project_open, exit_app, view_setting) - self.init_enable.append(project_save, data_load) - - self.file_menu.addAction(project_create) - self.file_menu.addAction(project_open) - self.file_menu.addAction(project_save) - self.file_menu.addAction(data_load) - self.file_menu.addAction(view_setting) - self.file_menu.addAction(exit_app) - - if self.toolbar is not None: - self.toolbar.addAction(project_create) - self.toolbar.addAction(project_open) - self.toolbar.addAction(project_save) - - ''' - Basic menu - ''' - grid_line = self.add_action(QAction('&网格线', self.w_parent), 'Basic') - grid_line.setCheckable(True) - grid_line.setChecked(True) - - zomm_in = self.add_action(QAction('&放大', self.w_parent), 'Basic') - zomm_out = self.add_action(QAction('&缩小', self.w_parent), 'Basic') - pan = self.add_action(QAction('&漫游', self.w_parent), 'Basic') - locate = self.add_action(QAction('&定位', self.w_parent), 'Basic') - - - - self.basic_menu.addAction(grid_line) - self.basic_menu.addAction(zomm_in) - self.basic_menu.addAction(zomm_out) - self.basic_menu.addAction(pan) - self.basic_menu.addAction(locate) - - ''' - Preop menu - ''' - morphology_filter = self.add_action(QAction('&形态学滤波', self.w_parent), 'filter') - lee_filter = self.add_action(QAction('&Lee滤波', self.w_parent), 'filter') - auto_filter = self.add_action(QAction('&自适应滤波-自主', self.w_parent), 'filter') - auto_filter_no_params = self.add_action(QAction('自动滤波(无参自适应滤波)-自主', self.w_parent), 'filter') - double_filter = self.add_action(QAction('&双边滤波', self.w_parent), 'filter') - align_action = self.add_action(QAction('&配准', self.w_parent), 'align') - filter_action_group = self.get_action_group('filter') - - filter_menu = self.preop_menu.addMenu('&滤波') - for action in filter_action_group.actions(): - filter_menu.addAction(action) - - # self.preop_menu.addActionGroup(filter_action_group) - self.preop_menu.addAction(align_action) - - if self.toolbar is not None: - self.toolbar.addAction(morphology_filter) - self.toolbar.addAction(lee_filter) - self.toolbar.addAction(auto_filter) - self.toolbar.addAction(auto_filter_no_params) - self.toolbar.addAction(double_filter) - - ''' - Change detection menu - ''' - diff_method = self.add_action(QAction('&差分法', self.w_parent), 'unsuper_change_detection') - log_diff = self.add_action(QAction('&对数差分法', self.w_parent), 'unsuper_change_detection') - lsts_ = self.add_action(QAction('&LSTS法', self.w_parent), 'unsuper_change_detection') - lhba = self.add_action(QAction('&LHBA法', self.w_parent), 'unsuper_change_detection') - aht = self.add_action(QAction('&AHT法', self.w_parent), 'unsuper_change_detection') - kpvd = self.add_action(QAction('&KPVD法', self.w_parent), 'unsuper_change_detection') - mohd = self.add_action(QAction('&MOHD法', self.w_parent), 'unsuper_change_detection') - sh = self.add_action(QAction('&SH法', self.w_parent), 'unsuper_change_detection') - cva = self.add_action(QAction('&CVA法', self.w_parent), 'unsuper_change_detection') - mls = self.add_action(QAction('&MLS法', self.w_parent), 'unsuper_change_detection') - pca_kmean = self.add_action(QAction('&PCA-KMean法', self.w_parent), 'unsuper_change_detection') - semi_fcm = self.add_action(QAction('&Semi-FCM法', self.w_parent), 'unsuper_change_detection') - mls_svm = self.add_action(QAction('&MLS-SVM法', self.w_parent), 'unsuper_change_detection') - cva_fcm = self.add_action(QAction('&CVA-FCM法', self.w_parent), 'unsuper_change_detection') - cva_emgmm = self.add_action(QAction('&CVA-EMGMM法', self.w_parent), 'unsuper_change_detection') - gwdm = self.add_action(QAction('&GWDM法', self.w_parent), 'unsuper_change_detection') - - mrf = self.add_action(QAction('&MRF法', self.w_parent), 'super_change_detection') - mad = self.add_action(QAction('&MAD法', self.w_parent), 'super_change_detection') - irmad = self.add_action(QAction('&IRMAD法', self.w_parent), 'super_change_detection') - - dcva = self.add_action(QAction('&DCVA法', self.w_parent), 'ai_change_detection') - dp_fcn = self.add_action(QAction('&DP-FCN法', self.w_parent), 'ai_change_detection') - rcnn = self.add_action(QAction('&RCNN法', self.w_parent), 'ai_change_detection') - - if self.toolbar is not None: - self.toolbar.addAction(diff_method) - self.toolbar.addAction(log_diff) - self.toolbar.addAction(lsts_) - self.toolbar.addAction(lhba) - - - unsuper_change_detection = self.get_action_group('unsuper_change_detection') - super_change_detection = self.get_action_group('super_change_detection') - ai_change_detection = self.get_action_group('ai_change_detection') - unsuper_menu = self.change_detection_menu.addMenu('&非监督') - for action in unsuper_change_detection.actions(): - unsuper_menu.addAction(action) - super_menu = self.change_detection_menu.addMenu('&监督') - for action in super_change_detection.actions(): - super_menu.addAction(action) - ai_menu = self.change_detection_menu.addMenu('&AI') - for action in ai_change_detection.actions(): - ai_menu.addAction(action) - - # self.change_detection_menu.addActionGroup(super_change_detection) - # self.change_detection_menu.addActionGroup(ai_change_detection) - - ''' - Special change detection menu - ''' - - water_change = self.add_action(QAction('&水体变化', self.w_parent), 'special_change_detection') - vegetation_change = self.add_action(QAction('&植被变化', self.w_parent), 'special_change_detection') - build_change = self.add_action(QAction('&房屋变化', self.w_parent), 'special_change_detection') - - self.special_chagne_detec_menu.addAction(water_change) - self.special_chagne_detec_menu.addAction(vegetation_change) - self.special_chagne_detec_menu.addAction(build_change) - - ''' - Postop menu - ''' - slide_window = self.add_action(QAction('&滑动窗口法', self.w_parent), 'noise_reduction') - density = self.add_action(QAction('&密度法', self.w_parent), 'noise_reduction') - - raster_export = self.add_action(QAction('&二值栅格数据导出', self.w_parent), 'export') - txt_pos_export = self.add_action(QAction('&兼容ArcMap的坐标Txt文件', self.w_parent), 'export') - render_export = self.add_action(QAction('&渲染图像导出', self.w_parent), 'export') - - noise_reduction = self.get_action_group('noise_reduction') - export = self.get_action_group('export') - - noise_reduction_menu = self.postop_menu.addMenu('&噪声抑制') - for action in noise_reduction.actions(): - noise_reduction_menu.addAction(action) - export_menu = self.postop_menu.addMenu('&导出') - for action in export.actions(): - export_menu.addAction(action) - - # self.postop_menu.addActionGroup(noise_reduction) - # self.postop_menu.addActionGroup(export) - - ''' - Evaluation menu - ''' - - evaluation = self.add_action(QAction('&评估', self.w_parent), 'evaluation') - self.eval_menu.addAction(evaluation) - - ''' - Help menu - ''' - about = self.add_action(QAction('&关于', self.w_parent), 'about') - about.triggered.connect(lambda : AboutDialog(self.w_parent).show()) - self.help_menu.addAction(about) - - self.message_box.info('Menu init finished') - self.message_box.info(self.actions.keys()) - for group in self.action_groups.keys(): - self.message_box.info('%s:' % (group)) - for action in self.action_groups[group].actions(): - action.setEnabled(False) - self.message_box.info('\t%s' % (action.text())) - - ''' - Enabled actions - ''' - about.setEnabled(True) - project_create.setEnabled(True) - project_open.setEnabled(True) - - Project().project_init.connect(self.project_init) - - if self.status_bar is not None: - corr_widget = QLabel(self.status_bar) - # corr_widget.setLineWidth(200) - corr_widget.setFixedWidth(200) - self.status_bar.addWidget(corr_widget) - scale_widget = QLabel(self.status_bar) - scale_widget.setFixedWidth(200) - self.status_bar.addWidget(scale_widget) - self.double_map.corr_changed.connect(corr_widget.setText) - self.double_map.scale_changed.connect(scale_widget.setText) - - def project_create(self): - project = Project() - if project.is_init: - project.save() - - projec_create = Create(self.w_parent) - if(projec_create.exec_()): - project.setup(os.path.join(projec_create.file, projec_create.name + '.prj')) - project.is_init = True - project.cell_size = projec_create.cell_size - - def project_init(self, state): - self.message_box.info('Project init') - for group in self.action_groups.keys(): - # self.message_box.info('%s:' % (group)) - for action in self.action_groups[group].actions(): - action.setEnabled(state) - # self.message_box.info('\t%s' % (action.text())) - - self.message_box.info('Project init finished') - - def project_open(self): - if Project().is_init: - Project().save() - - project_file = QFileDialog.getOpenFileName(self.w_parent, '打开工程', '.', '*.prj') - if project_file[0] != '': - Project().clear() - Project().setup(project_file[0]) - - def project_save(self): - if Project().is_init: - Project().save() - - def data_load(self): - if Project().is_init: - Project().save() - - def view_setting(self): - pass - - - def add_action(self, action, group=None): - if group is None: - self.actions[action.text()] = action - else: - if group not in self.action_groups: - self.action_groups[group] = QActionGroup(self.w_parent) - self.action_groups[group].setExclusive(True) - self.action_groups[group].addAction(action) - self.action_group_actions[group] = action - return action - - def get_action(self, action_name, group_name=None): - if action_name in self.actions: - return self.actions[action_name] - else: - if group_name is None: - return None - else: - if group_name not in self.action_group_actions: - return None - else: - group = self.action_group_actions[group_name] - for action in group.actions(): - if action.text() == action_name: - return action - return None - - def get_action_group(self, group_name): - return self.action_groups[group_name] - - def get_action_group_action(self, group_name): - return self.action_group_actions[group_name] - - def get_action_group_actions(self, group_name): - return self.action_group_actions[group_name].actions() - - def get_actions(self): - return self.actions \ No newline at end of file diff --git a/main.py b/main.py index 549e839..03e9abc 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,4 @@ -from mul.mulstart import MulStart +from rscder import MulStart import logging logging.basicConfig(level=logging.INFO, filename='log.txt', filemode='w', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') diff --git a/gui/about.py b/plugins/about/main.py similarity index 76% rename from gui/about.py rename to plugins/about/main.py index 718dee8..c84eb68 100644 --- a/gui/about.py +++ b/plugins/about/main.py @@ -1,6 +1,8 @@ -from PyQt5.QtWidgets import QDialog, QApplication, QLabel, QTextEdit, QVBoxLayout -from PyQt5.QtCore import Qt +from rscder.plugins.basic import BasicPlugin +from PyQt5.QtWidgets import QDialog, QAction, QApplication, QLabel, QTextEdit, QVBoxLayout +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QIcon class AboutDialog(QDialog): def __init__(self, parent=None): @@ -40,3 +42,17 @@ class AboutDialog(QDialog): self.layout.addWidget(self.label4) self.layout.addWidget(self.text) self.setLayout(self.layout) + + +class AboutPlugin(BasicPlugin): + + def set_action(self): + menu = self.ctx['help_menu'] + action = QAction('&关于', self.ctx['menubar']) + action.triggered.connect(self.on_about) + + menu.addAction(action) + + def on_about(self): + dialog = AboutDialog(self.ctx['main_window']) + dialog.show() \ No newline at end of file diff --git a/plugins/basic_change/main.py b/plugins/basic_change/main.py new file mode 100644 index 0000000..e24b5ea --- /dev/null +++ b/plugins/basic_change/main.py @@ -0,0 +1,8 @@ +from rscder.plugins.basic import BasicPlugin + +class BasicMethod(BasicPlugin): + + def set_action(self): + menubar = self.ctx['menubar'] + + \ No newline at end of file diff --git a/rscder/__init__.py b/rscder/__init__.py new file mode 100644 index 0000000..c34d767 --- /dev/null +++ b/rscder/__init__.py @@ -0,0 +1 @@ +from .mul.mulstart import MulStart \ No newline at end of file diff --git a/rscder/gui/actions.py b/rscder/gui/actions.py new file mode 100644 index 0000000..c698d0e --- /dev/null +++ b/rscder/gui/actions.py @@ -0,0 +1,382 @@ +import logging +import os +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.QtWidgets import QAction, QActionGroup, QLabel, QFileDialog +from rscder.gui.project import Create +from rscder.utils.project import Project +from rscder.gui.plugins import PluginDialog +class ActionManager(QtCore.QObject): + + def __init__(self, + double_map, + layer_tree, + follow_box, + result_box, + message_box, + parent=None): + super().__init__(parent) + self.w_parent = parent + self.actions = {} + self.action_groups = {} + self.action_group_actions = {} + + self.double_map = double_map + self.layer_tree = layer_tree + self.follow_box = follow_box + self.result_box = result_box + self.message_box = message_box + + self.allways_enable = [] + self.init_enable = [] + self.dataload_enable = [] + + self.toolbar = None + self.menubar = None + self.status_bar = None + + def set_menus(self, menubar): + self.menubar = menubar + self.file_menu = menubar.addMenu('&文件') + self.basic_menu = menubar.addMenu('&基本工具') + self.change_detection_menu = menubar.addMenu('&通用变化检测') + self.special_chagne_detec_menu = menubar.addMenu('&专题变化检测') + self.seg_chagne_detec_menu = menubar.addMenu('&分类后变化检测') + self.postop_menu = menubar.addMenu('&检测后处理') + self.view_menu = menubar.addMenu('&视图') + self.plugin_menu = menubar.addMenu('&插件') + self.help_menu = menubar.addMenu('&帮助') + + + def set_toolbar(self, toolbar): + self.toolbar = toolbar + self.toolbar.setIconSize(QtCore.QSize(24, 24)) + + def set_status_bar(self, status_bar): + self.status_bar = status_bar + + def set_actions(self): + ''' + File menu + ''' + project_create = self.add_action(QAction('&工程创建', self.w_parent), 'File') + project_open = self.add_action(QAction('&打开工程', self.w_parent), 'File') + project_save = self.add_action(QAction('&保存工程', self.w_parent), 'File') + data_load = self.add_action(QAction('&数据加载', self.w_parent), 'File') + view_setting = self.add_action(QAction('&界面定制', self.w_parent), 'File') + exit_app = self.add_action(QAction('&退出', self.w_parent), 'File') + project_create.triggered.connect(self.project_create) + project_open.triggered.connect(self.project_open) + project_save.triggered.connect(self.project_save) + data_load.triggered.connect(self.data_load) + view_setting.triggered.connect(self.view_setting) + exit_app.triggered.connect(self.w_parent.close) + + self.allways_enable.extend([project_create, project_open, exit_app, view_setting]) + self.init_enable.extend([project_save, data_load]) + + self.file_menu.addAction(project_create) + self.file_menu.addAction(project_open) + self.file_menu.addAction(project_save) + self.file_menu.addAction(data_load) + self.file_menu.addAction(view_setting) + self.file_menu.addAction(exit_app) + + if self.toolbar is not None: + self.toolbar.addAction(project_create) + self.toolbar.addAction(project_open) + self.toolbar.addAction(project_save) + + ''' + Basic menu + ''' + grid_line = self.add_action(QAction('&网格线', self.w_parent), 'Basic Line') + grid_line.setCheckable(True) + grid_line.setChecked(True) + + zomm_in = self.add_action(QAction('&放大', self.w_parent), 'Basic') + zomm_out = self.add_action(QAction('&缩小', self.w_parent), 'Basic') + pan = self.add_action(QAction('&漫游', self.w_parent), 'Basic') + locate = self.add_action(QAction('&定位', self.w_parent), 'Basic') + + pan.setCheckable(True) + pan.setChecked(True) + zomm_out.setCheckable(True) + zomm_out.setChecked(False) + zomm_in.setCheckable(True) + zomm_in.setChecked(False) + + self.double_map.connect_map_tool(pan, zomm_in, zomm_out) + self.double_map.connect_grid_show(grid_line) + + self.view_menu.addAction(grid_line) + self.view_menu.addSeparator() + self.view_menu.addAction(pan) + self.view_menu.addAction(zomm_in) + self.view_menu.addAction(zomm_out) + self.view_menu.addAction(locate) + + ''' + Plugin menu + ''' + plugin_list = self.add_action(QAction('&插件列表', self.w_parent), 'Plugin') + plugin_list.triggered.connect(self.plugin_list) + + self.plugin_menu.addAction(plugin_list) + # morphology_filter = self.add_action(QAction('&形态学滤波', self.w_parent), 'filter') + # lee_filter = self.add_action(QAction('&Lee滤波', self.w_parent), 'filter') + # auto_filter = self.add_action(QAction('&自适应滤波-自主', self.w_parent), 'filter') + # auto_filter_no_params = self.add_action(QAction('自动滤波(无参自适应滤波)-自主', self.w_parent), 'filter') + # double_filter = self.add_action(QAction('&双边滤波', self.w_parent), 'filter') + + # filter_action_group = self.get_action_group('filter') + + # filter_menu = self.basic_menu.addMenu('&滤波处理') + # for action in filter_action_group.actions(): + # filter_menu.addAction(action) + + # rgb2rgb = self.add_action(QAction('&光学-影像', self.w_parent), 'align') + # sar2sar = self.add_action(QAction('&SAR-影像', self.w_parent), 'align') + # multi_source = self.add_action(QAction('&多源影像', self.w_parent), 'align') + + # align_menu = self.basic_menu.addMenu('&图像配准') + # align_menu.addAction(rgb2rgb) + # align_menu.addAction(sar2sar) + # align_menu.addAction(multi_source) + + # cloud_menu = self.basic_menu.addMenu('&去云处理') + # defogging_menu = self.basic_menu.addMenu('&去雾处理') + + # # self.preop_menu.addActionGroup(filter_action_group) + # # self.basic_menu.addAction(align_action) + + # if self.toolbar is not None: + # self.toolbar.addAction(morphology_filter) + # self.toolbar.addAction(lee_filter) + # self.toolbar.addAction(auto_filter) + # self.toolbar.addAction(auto_filter_no_params) + # self.toolbar.addAction(double_filter) + + # ''' + # Change detection menu + # ''' + # diff_method = self.add_action(QAction('&差分法', self.w_parent), 'unsuper_change_detection') + # log_diff = self.add_action(QAction('&对数差分法', self.w_parent), 'unsuper_change_detection') + # lsts_ = self.add_action(QAction('&LSTS法', self.w_parent), 'unsuper_change_detection') + # lhba = self.add_action(QAction('&LHBA法', self.w_parent), 'unsuper_change_detection') + # aht = self.add_action(QAction('&AHT法', self.w_parent), 'unsuper_change_detection') + # kpvd = self.add_action(QAction('&KPVD法', self.w_parent), 'unsuper_change_detection') + # mohd = self.add_action(QAction('&MOHD法', self.w_parent), 'unsuper_change_detection') + # sh = self.add_action(QAction('&SH法', self.w_parent), 'unsuper_change_detection') + # cva = self.add_action(QAction('&CVA法', self.w_parent), 'unsuper_change_detection') + # mls = self.add_action(QAction('&MLS法', self.w_parent), 'unsuper_change_detection') + # pca_kmean = self.add_action(QAction('&PCA-KMean法', self.w_parent), 'unsuper_change_detection') + # semi_fcm = self.add_action(QAction('&Semi-FCM法', self.w_parent), 'unsuper_change_detection') + # mls_svm = self.add_action(QAction('&MLS-SVM法', self.w_parent), 'unsuper_change_detection') + # cva_fcm = self.add_action(QAction('&CVA-FCM法', self.w_parent), 'unsuper_change_detection') + # cva_emgmm = self.add_action(QAction('&CVA-EMGMM法', self.w_parent), 'unsuper_change_detection') + # gwdm = self.add_action(QAction('&GWDM法', self.w_parent), 'unsuper_change_detection') + + # mrf = self.add_action(QAction('&MRF法', self.w_parent), 'super_change_detection') + # mad = self.add_action(QAction('&MAD法', self.w_parent), 'super_change_detection') + # irmad = self.add_action(QAction('&IRMAD法', self.w_parent), 'super_change_detection') + + # dcva = self.add_action(QAction('&DCVA法', self.w_parent), 'ai_change_detection') + # dp_fcn = self.add_action(QAction('&DP-FCN法', self.w_parent), 'ai_change_detection') + # rcnn = self.add_action(QAction('&RCNN法', self.w_parent), 'ai_change_detection') + + # if self.toolbar is not None: + # self.toolbar.addAction(diff_method) + # self.toolbar.addAction(log_diff) + # self.toolbar.addAction(lsts_) + # self.toolbar.addAction(lhba) + + + # unsuper_change_detection = self.get_action_group('unsuper_change_detection') + # super_change_detection = self.get_action_group('super_change_detection') + # ai_change_detection = self.get_action_group('ai_change_detection') + # unsuper_menu = self.change_detection_menu.addMenu('&非监督') + # for action in unsuper_change_detection.actions(): + # unsuper_menu.addAction(action) + # super_menu = self.change_detection_menu.addMenu('&监督') + # for action in super_change_detection.actions(): + # super_menu.addAction(action) + # ai_menu = self.change_detection_menu.addMenu('&AI') + # for action in ai_change_detection.actions(): + # ai_menu.addAction(action) + + # # self.change_detection_menu.addActionGroup(super_change_detection) + # # self.change_detection_menu.addActionGroup(ai_change_detection) + + # ''' + # Special change detection menu + # ''' + + # water_change = self.add_action(QAction('&水体变化', self.w_parent), 'special_change_detection') + # vegetation_change = self.add_action(QAction('&植被变化', self.w_parent), 'special_change_detection') + # build_change = self.add_action(QAction('&房屋变化', self.w_parent), 'special_change_detection') + + # self.special_chagne_detec_menu.addAction(water_change) + # self.special_chagne_detec_menu.addAction(vegetation_change) + # self.special_chagne_detec_menu.addAction(build_change) + + # ''' + # Postop menu + # ''' + # slide_window = self.add_action(QAction('&滑动窗口法', self.w_parent), 'noise_reduction') + # density = self.add_action(QAction('&密度法', self.w_parent), 'noise_reduction') + + # raster_export = self.add_action(QAction('&二值栅格数据导出', self.w_parent), 'export') + # txt_pos_export = self.add_action(QAction('&兼容ArcMap的坐标Txt文件', self.w_parent), 'export') + # render_export = self.add_action(QAction('&渲染图像导出', self.w_parent), 'export') + + # noise_reduction = self.get_action_group('noise_reduction') + # export = self.get_action_group('export') + + # noise_reduction_menu = self.postop_menu.addMenu('&噪声抑制') + # for action in noise_reduction.actions(): + # noise_reduction_menu.addAction(action) + # export_menu = self.postop_menu.addMenu('&导出') + # for action in export.actions(): + # export_menu.addAction(action) + + # # self.postop_menu.addActionGroup(noise_reduction) + # # self.postop_menu.addActionGroup(export) + + # ''' + # Evaluation menu + # ''' + + # evaluation = self.add_action(QAction('&评估', self.w_parent), 'evaluation') + # self.postop_menu.addAction(evaluation) + + ''' + Help menu + ''' + # about = self.add_action(QAction('&关于', self.w_parent), 'about') + # about.triggered.connect(lambda : AboutDialog(self.w_parent).show()) + # self.help_menu.addAction(about) + + self.message_box.info('Menu init finished') + self.message_box.info(self.actions.keys()) + # for group in self.action_groups.keys(): + # self.message_box.info('%s:' % (group)) + # for action in self.action_groups[group].actions(): + # action.setEnabled(False) + # self.message_box.info('\t%s' % (action.text())) + + ''' + Enabled actions + ''' + # about.setEnabled(True) + project_create.setEnabled(True) + project_open.setEnabled(True) + + Project().project_init.connect(self.project_init) + + if self.status_bar is not None: + corr_widget = QLabel(self.status_bar) + # corr_widget.setLineWidth(200) + corr_widget.setFixedWidth(200) + self.status_bar.addWidget(corr_widget) + scale_widget = QLabel(self.status_bar) + scale_widget.setFixedWidth(200) + self.status_bar.addWidget(scale_widget) + self.double_map.corr_changed.connect(corr_widget.setText) + self.double_map.scale_changed.connect(scale_widget.setText) + + def plugin_list(self): + dialog = PluginDialog(self.w_parent) + dialog.show() + + def project_create(self): + project = Project() + + projec_create = Create(self.w_parent) + if(projec_create.exec_()): + if project.is_init: + project.save() + project.clear() + project.setup(os.path.join(projec_create.file, projec_create.name + '.prj')) + project.is_init = True + project.cell_size = projec_create.cell_size + project.max_memory = projec_create.max_memory + project.save() + self.message_box.info('Project created') + + def project_init(self, state): + self.message_box.info('Project init') + for group in self.action_groups.keys(): + # self.message_box.info('%s:' % (group)) + for action in self.action_groups[group].actions(): + action.setEnabled(state) + # self.message_box.info('\t%s' % (action.text())) + + self.message_box.info('Project init finished') + + def project_open(self): + if Project().is_init: + Project().save() + + project_file = QFileDialog.getOpenFileName(self.w_parent, '打开工程', '.', '*.prj') + if project_file[0] != '': + Project().clear() + Project().setup(project_file[0]) + + def project_save(self): + if Project().is_init: + Project().save() + + def data_load(self): + if Project().is_init: + Project().save() + + file_open = QFileDialog.getOpenFileNames(self.w_parent, '打开数据', Project().root, '*.*') + if file_open[0] != '': + if len(file_open[0]) != 2: + self.message_box.warning('请选择两个数据文件') + return + Project().add_layer(file_open[0][0], file_open[0][1]) + self.message_box.info('Data loaded') + + def view_setting(self): + pass + + + def add_action(self, action, group=None): + if group is None: + self.actions[action.text()] = action + else: + if group not in self.action_groups: + self.action_groups[group] = QActionGroup(self.w_parent) + self.action_groups[group].setExclusive(True) + self.action_groups[group].addAction(action) + self.action_group_actions[group] = action + return action + + def get_action(self, action_name, group_name=None): + if action_name in self.actions: + return self.actions[action_name] + else: + if group_name is None: + return None + else: + if group_name not in self.action_group_actions: + return None + else: + group = self.action_group_actions[group_name] + for action in group.actions(): + if action.text() == action_name: + return action + return None + + def get_action_group(self, group_name): + return self.action_groups[group_name] + + def get_action_group_action(self, group_name): + return self.action_group_actions[group_name] + + def get_action_group_actions(self, group_name): + return self.action_group_actions[group_name].actions() + + def get_actions(self): + return self.actions \ No newline at end of file diff --git a/gui/layertree.py b/rscder/gui/layertree.py similarity index 62% rename from gui/layertree.py rename to rscder/gui/layertree.py index 347b545..b15d59e 100644 --- a/gui/layertree.py +++ b/rscder/gui/layertree.py @@ -4,7 +4,7 @@ from PyQt5.QtCore import Qt,QModelIndex from PyQt5.QtGui import QStandardItemModel, QStandardItem from PyQt5.QtWidgets import (QTreeView, QTreeWidgetItem, QAbstractItemView, QHeaderView, QStyleFactory) -from utils.project import PairLayer +from rscder.utils.project import PairLayer, Project class LayerTree(QtWidgets.QWidget): @@ -18,13 +18,13 @@ class LayerTree(QtWidgets.QWidget): self.root=QTreeWidgetItem(self.tree) self.tree.setHeaderHidden(True) # self.tree.setHeaderLabels(['图层']) - self.root.setText(0,'Root') + self.root.setText(0,'图层') - child1=QTreeWidgetItem() - child1.setText(0,'child1') - child1.setCheckState(0,Qt.Checked) + # child1=QTreeWidgetItem() + # child1.setText(0,'child1') + # child1.setCheckState(0,Qt.Checked) - self.root.addChild(child1) + # self.root.addChild(child1) self.tree.expandAll() self.tree.addTopLevelItem(self.root) @@ -39,9 +39,25 @@ class LayerTree(QtWidgets.QWidget): def onClicked(self,index): print(index.row()) - def add_layer(self, layer:PairLayer): - pass - + def add_layer(self, layer:str): + layer:PairLayer = Project().layers[layer] + item1 = QtWidgets.QTreeWidgetItem(self.root) + item1.setText(0, layer.l1_name) + item1.setCheckState(0, Qt.Checked) + item2 = QtWidgets.QTreeWidgetItem(self.root) + item2.setText(0, layer.l2_name) + item2.setCheckState(0, Qt.Checked) + + item1.setData(0, Qt.UserRole, layer.id) + item2.setData(0, Qt.UserRole, layer.id) + self.tree.expandAll() + + def clear(self): + self.tree.clear() + self.root = QTreeWidgetItem(self.tree) + self.root.setText(0,'图层') + self.tree.addTopLevelItem(self.root) + def right_menu_show(self, position): rightMenu = QtWidgets.QMenu(self) # QAction = QtWidgets.QAction(self.menuBar1) diff --git a/gui/license.py b/rscder/gui/license.py similarity index 98% rename from gui/license.py rename to rscder/gui/license.py index 31b161d..058fc73 100644 --- a/gui/license.py +++ b/rscder/gui/license.py @@ -4,7 +4,7 @@ from PyQt5 import QtCore from PyQt5.QtGui import QIcon import os -from utils.license import LicenseHelper +from rscder.utils.license import LicenseHelper class License(QtWidgets.QDialog): diff --git a/gui/mainwindow.py b/rscder/gui/mainwindow.py similarity index 93% rename from gui/mainwindow.py rename to rscder/gui/mainwindow.py index d2ec7d3..358e33b 100644 --- a/gui/mainwindow.py +++ b/rscder/gui/mainwindow.py @@ -4,13 +4,13 @@ from PyQt5.QtCore import Qt, QSize from PyQt5.QtGui import QIcon from PyQt5 import QtGui from PyQtAds import QtAds -from actions.actions import ActionManager -from gui.layertree import LayerTree -from gui.mapcanvas import DoubleCanvas -from gui.messagebox import MessageBox -from gui.result import ResultTable -from utils import Settings -from utils.project import Project +from rscder.gui.actions import ActionManager +from rscder.gui.layertree import LayerTree +from rscder.gui.mapcanvas import DoubleCanvas +from rscder.gui.messagebox import MessageBox +from rscder.gui.result import ResultTable +from rscder.utils import Settings +from rscder.utils.project import Project class MainWindow(QMainWindow): @@ -41,7 +41,6 @@ class MainWindow(QMainWindow): self.action_manager.set_menus(self.menuBar()) self.action_manager.set_toolbar(self.toolbar) self.action_manager.set_status_bar(self.statusBar()) - self.action_manager.set_actions() self.resize(*Settings.General().size) diff --git a/gui/mapcanvas.py b/rscder/gui/mapcanvas.py similarity index 84% rename from gui/mapcanvas.py rename to rscder/gui/mapcanvas.py index 0a51fcf..9ef11a9 100644 --- a/gui/mapcanvas.py +++ b/rscder/gui/mapcanvas.py @@ -12,13 +12,15 @@ from PyQt5.QtWidgets import QMessageBox, QWidget, QHBoxLayout from PyQt5.QtGui import QColor, QDragEnterEvent, QDropEvent from qgis.core import QgsPointXY, QgsRasterLayer, QgsVectorLayer, QgsFeature, QgsGeometry, QgsCategorizedSymbolRenderer, QgsRendererCategory, QgsFillSymbol, QgsPalLayerSettings, QgsRuleBasedLabeling, QgsTextFormat -from qgis.gui import QgsMapCanvas -from qgis.core import QgsVectorLayerExporter, QgsVectorFileWriter, QgsProject, QgsField, QgsRasterFileWriter, QgsRasterPipe +from qgis.gui import QgsMapCanvas, QgsMapToolPan, QgsMapToolZoom +from qgis.core import QgsRectangle, QgsVectorFileWriter, QgsProject, QgsField, QgsRasterFileWriter, QgsRasterPipe import threading import tempfile import cv2 import os +from rscder.utils.project import PairLayer, Project + class DoubleCanvas(QWidget): corr_changed = pyqtSignal(str) scale_changed = pyqtSignal(str) @@ -35,6 +37,16 @@ class DoubleCanvas(QWidget): self.mapcanva1.update_coordinates_text.connect(self.corr_changed) self.mapcanva2.update_coordinates_text.connect(self.corr_changed) + def set_map1_extent(): + self.mapcanva1.set_extent(self.mapcanva2.extent()) + def set_map2_extent(): + self.mapcanva2.set_extent(self.mapcanva1.extent()) + + self.mapcanva1.extentsChanged.connect(set_map2_extent) + self.mapcanva2.extentsChanged.connect(set_map1_extent) + + self.set_pan_tool(True) + self.mapcanva1.update_scale_text.connect(self.scale_changed) self.mapcanva2.update_scale_text.connect(self.scale_changed) @@ -43,16 +55,117 @@ class DoubleCanvas(QWidget): layout.addWidget(self.mapcanva2) self.setLayout(layout) + self.grid_show = True + + def connect_grid_show(self, action): + def show_grid(_): + + self.grid_show = not self.grid_show + action.setChecked(self.grid_show) + if self.grid_show: + for layer in Project().layers.values(): + self.mapcanva1.add_grid_layer(layer.grid_layer.grid_layer) + self.mapcanva2.add_grid_layer(layer.grid_layer.grid_layer) + else: + self.mapcanva1.remove_grid_layer() + self.mapcanva2.remove_grid_layer() + + action.triggered.connect(show_grid) + + + def connect_map_tool(self, pan, zoom_in, zoom_out): + pan.triggered.connect(self.set_pan_tool) + zoom_in.triggered.connect(self.set_zoom_in) + zoom_out.triggered.connect( self.set_zoom_out) + + def set_pan_tool(self, s): + print('set pan tool') + if s: + self.mapcanva1.setMapTool(QgsMapToolPan(self.mapcanva1)) + self.mapcanva2.setMapTool(QgsMapToolPan(self.mapcanva2)) + + def set_zoom_in(self, s): + print('set zoom in') + if s: + self.mapcanva1.setMapTool(QgsMapToolZoom(self.mapcanva1, False)) + self.mapcanva2.setMapTool(QgsMapToolZoom(self.mapcanva2, False)) + + def set_zoom_out(self, s): + print('set zoom out') + if s: + self.mapcanva1.setMapTool(QgsMapToolZoom(self.mapcanva1, True)) + self.mapcanva2.setMapTool(QgsMapToolZoom(self.mapcanva2, True)) + + def add_layer(self, layer:str): + layer = Project().layers[layer] + if not self.mapcanva1.is_main and not self.mapcanva2.is_main: + self.mapcanva1.is_main = True + self.mapcanva1.add_layer(layer.l1) + self.mapcanva2.add_layer(layer.l2) + if self.grid_show: + self.mapcanva1.add_grid_layer(layer.grid_layer.grid_layer) + self.mapcanva2.add_grid_layer(layer.grid_layer.grid_layer) + + + def clear(self): + self.mapcanva1.clear() + self.mapcanva2.clear() class CanvasWidget(QgsMapCanvas): update_coordinates_text = pyqtSignal(str) update_scale_text = pyqtSignal(str) + def add_layer(self, layer) -> None: + self.layers.insert(0, layer) + self.setLayers(self.layers) + self.zoomToFeatureExtent(layer.extent()) + + def add_grid_layer(self, layer): + self.grid_layers.append(layer) + self.layers.insert(0, layer) + self.setLayers(self.layers) + + def remove_grid_layer(self): + layers = [] + for layer in self.layers: + if layer in self.grid_layers: + continue + layers.append(layer) + self.layers = layers + self.setLayers(self.layers) + + def enterEvent(self,e): + self.is_main = True + # print(e) + pass + + def leaveEvent(self, e): + self.is_main = False + pass + + def set_extent(self, extent:QgsRectangle): + ''' + Zoom to extent + ''' + # print(extent) + if self.is_main: + return + else: + self.zoomToFeatureExtent(extent) + + def clear(self) -> None: + self.setTheme('') + self.layers = [] + self.is_main = False + self.setLayers([]) + self.clearCache() + self.refresh() + def __init__(self, parent): super().__init__(parent) - self.current_raster_layer = None - self.current_vector_layer = None - + self.layers = [] + self.grid_layers = [] + self.is_main = False self.setCanvasColor(Qt.white) self.enableAntiAliasing(True) self.setAcceptDrops(False) diff --git a/gui/messagebox.py b/rscder/gui/messagebox.py similarity index 100% rename from gui/messagebox.py rename to rscder/gui/messagebox.py diff --git a/rscder/gui/plugins.py b/rscder/gui/plugins.py new file mode 100644 index 0000000..098c2d9 --- /dev/null +++ b/rscder/gui/plugins.py @@ -0,0 +1,97 @@ +from PyQt5.QtWidgets import * +from PyQt5.QtGui import QIcon, Qt +from rscder.utils.setting import Settings + +class PluginDialog(QDialog): + + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle('Plugins') + self.setWindowIcon(QIcon(":/icons/logo.svg")) + self.setMinimumWidth(800) + self.setMinimumHeight(600) + self.plugins = Settings.Plugin().plugins + + self.plugin_table = QTableWidget(len(self.plugins), 2, self) + self.plugin_table.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.plugin_table.setColumnWidth(0, 200) + self.plugin_table.setColumnWidth(1, 500) + self.plugin_table.setHorizontalHeaderLabels(['Name', 'Path', 'Enabled']) + self.plugin_table.setEditTriggers(QAbstractItemView.DoubleClicked) + self.plugin_table.cellDoubleClicked.connect(self.edit_plugin) + for idx, plugin in enumerate(self.plugins): + name_item = QTableWidgetItem(plugin['name']) + path_item = QTableWidgetItem(plugin['path']) + enabled_item = QTableWidgetItem() + enabled_item.setCheckState(Qt.Checked if plugin['enabled'] else Qt.Unchecked) + + self.plugin_table.setItem(idx, 0, name_item) + self.plugin_table.setItem(idx, 1, path_item) + self.plugin_table.setItem(idx, 2, enabled_item) + + self.add_button = QPushButton('Add', self) + self.add_button.clicked.connect(self.add_plugin) + self.remove_button = QPushButton('Remove', self) + self.remove_button.clicked.connect(self.remove_plugin) + self.save_button = QPushButton('Save', self) + self.save_button.clicked.connect(self.save_plugin) + self.cancel_button = QPushButton('Cancel', self) + self.cancel_button.clicked.connect(self.close) + + layout = QVBoxLayout(self) + layout.addWidget(self.plugin_table) + hlayout = QHBoxLayout() + hlayout.addWidget(self.add_button) + hlayout.addWidget(self.remove_button) + hlayout.addWidget(self.save_button) + hlayout.addWidget(self.cancel_button) + layout.addLayout(hlayout) + self.setLayout(layout) + self.has_change = False + + def add_plugin(self): + self.has_change = True + self.plugin_table.insertRow(self.plugin_table.rowCount()) + + def remove_plugin(self): + self.has_change = True + for row in self.plugin_table.selectedItems(): + self.plugin_table.removeRow(row.row()) + + # for idx in self.plugins + + def edit_plugin(self, row, column): + self.has_change = True + if column == 0: + self.plugin_table.item(row, column).setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) + elif column == 1: + open_file = QFileDialog.getOpenFileName(self, 'Open File', '', 'Python Files (*.py)') + if open_file[0]: + self.plugin_table.item(row, column).setText(open_file[0]) + else: + pass + elif column == 2: + self.plugin_table.item(row, column).setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) + # self.plugin_list.setFixedWidth(200) + + def save_plugin(self): + + plugins = [] + for idx in range(self.plugin_table.rowCount()): + name = self.plugin_table.item(idx, 0).text() + path = self.plugin_table.item(idx, 1).text() + enabled = self.plugin_table.item(idx, 2).checkState() == Qt.Checked + plugins.append({'name': name, 'path': path, 'enabled': enabled}) + Settings.Plugin().plugins = plugins + self.close() + + def closeEvent(self, event): + if self.has_change: + reply = QMessageBox.question(self, 'Message', "Do you want to save the changes?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) + if reply == QMessageBox.Yes: + self.save_plugin() + event.accept() + else: + event.accept() + else: + event.accept() \ No newline at end of file diff --git a/gui/project.py b/rscder/gui/project.py similarity index 52% rename from gui/project.py rename to rscder/gui/project.py index 9d28ae5..bd64658 100644 --- a/gui/project.py +++ b/rscder/gui/project.py @@ -1,7 +1,7 @@ -from PyQt5.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout, QLabel, QMessageBox -from PyQt5.QtGui import QIcon +from PyQt5.QtWidgets import QDialog, QFileDialog, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout, QLabel, QMessageBox +from PyQt5.QtGui import QIcon, QIntValidator from PyQt5.QtCore import Qt -from utils.setting import Settings +from rscder.utils.setting import Settings class Create(QDialog): def __init__(self, parent=None) -> None: @@ -21,12 +21,21 @@ class Create(QDialog): file_input.setToolTip('Project Dir') file_input.setReadOnly(True) file_input.setText(self.file) + self.file_input = file_input + + file_open = QPushButton('...', self) + file_open.setFixedWidth(30) + file_open.clicked.connect(self.open_file) + name_label = QLabel('Project Name:') name_label.setFixedWidth(100) name_input = QLineEdit() name_input.setPlaceholderText('Project Name') name_input.setToolTip('Project Name') + name_input.setText(self.name) + self.name_input = name_input + name_input_layout = QHBoxLayout() name_input_layout.addWidget(name_label) @@ -35,6 +44,7 @@ class Create(QDialog): file_input_layout = QHBoxLayout() file_input_layout.addWidget(file_label) file_input_layout.addWidget(file_input) + file_input_layout.addWidget(file_open) cell_size_label = QLabel('Cell Size:') cell_size_label.setFixedWidth(100) @@ -43,9 +53,16 @@ class Create(QDialog): cell_size_x_input = QLineEdit() cell_size_y_input = QLineEdit() cell_size_x_input.setPlaceholderText('Cell Size X') + cell_size_x_input.setValidator(QIntValidator()) cell_size_y_input.setPlaceholderText('Cell Size Y') + cell_size_y_input.setValidator(QIntValidator()) cell_size_x_input.setToolTip('Cell Size X') cell_size_y_input.setToolTip('Cell Size Y') + cell_size_x_input.setText(str(self.cell_size[0])) + cell_size_y_input.setText(str(self.cell_size[1])) + + self.cell_size_x_input = cell_size_x_input + self.cell_size_y_input = cell_size_y_input cell_input_layout = QHBoxLayout() cell_input_layout.addWidget(cell_size_label) @@ -54,8 +71,20 @@ class Create(QDialog): cell_input_layout.addWidget(cell_size_y_label) cell_input_layout.addWidget(cell_size_y_input) + max_memory_label = QLabel('Max Memory (MB):') + max_memory_label.setFixedWidth(100) + max_memory_input = QLineEdit() + max_memory_input.setPlaceholderText('Max Memory') + max_memory_input.setToolTip('Max Memory') + max_memory_input.setText(str(self.max_memory)) + max_memory_input.setValidator(QIntValidator()) + self.max_memory_input = max_memory_input + ok_button = QPushButton('OK') cancel_button = QPushButton('Cancel') + + ok_button.clicked.connect(self.ok) + cancel_button.clicked.connect(self.cancel) button_layout = QHBoxLayout() button_layout.setDirection(QHBoxLayout.RightToLeft) @@ -70,3 +99,29 @@ class Create(QDialog): self.setLayout(main_layout) + + def open_file(self): + file = QFileDialog.getExistingDirectory(self, 'Open Directory', self.file) + if file: + self.file = file + self.file_input.setText(self.file) + + def ok(self): + self.name = self.name_input.text() + self.max_memory = self.max_memory_input.text() + self.cell_size = (self.cell_size_x_input.text(), self.cell_size_y_input.text()) + if self.name == '': + QMessageBox.warning(self, 'Warning', 'Please input project name!') + return + if self.max_memory == '': + QMessageBox.warning(self, 'Warning', 'Please input max memory!') + return + if self.cell_size == ('', ''): + QMessageBox.warning(self, 'Warning', 'Please input cell size!') + return + self.max_memory = int(self.max_memory) + self.cell_size = (int(self.cell_size[0]), int(self.cell_size[1])) + self.accept() + + def cancel(self): + self.reject() \ No newline at end of file diff --git a/gui/result.py b/rscder/gui/result.py similarity index 94% rename from gui/result.py rename to rscder/gui/result.py index 81fd948..db9cc0d 100644 --- a/gui/result.py +++ b/rscder/gui/result.py @@ -4,7 +4,7 @@ from PyQt5.QtCore import Qt,QModelIndex from PyQt5.QtGui import QStandardItemModel, QStandardItem from PyQt5.QtWidgets import (QTreeView, QTreeWidgetItem, QAbstractItemView, QHeaderView, QStyleFactory) -from utils.project import PairLayer +from rscder.utils.project import PairLayer class ResultTable(QtWidgets.QWidget): @@ -34,6 +34,9 @@ class ResultTable(QtWidgets.QWidget): layout.addWidget(self.tree) self.setLayout(layout) + def clear(self): + pass + def onClicked(self,index): print(index.row()) diff --git a/gui/setting.py b/rscder/gui/setting.py similarity index 100% rename from gui/setting.py rename to rscder/gui/setting.py diff --git a/icons/assessment.svg b/rscder/icons/assessment.svg similarity index 100% rename from icons/assessment.svg rename to rscder/icons/assessment.svg diff --git a/icons/cancel.svg b/rscder/icons/cancel.svg similarity index 100% rename from icons/cancel.svg rename to rscder/icons/cancel.svg diff --git a/icons/clear.svg b/rscder/icons/clear.svg similarity index 100% rename from icons/clear.svg rename to rscder/icons/clear.svg diff --git a/icons/edit.svg b/rscder/icons/edit.svg similarity index 100% rename from icons/edit.svg rename to rscder/icons/edit.svg diff --git a/icons/exit.svg b/rscder/icons/exit.svg similarity index 100% rename from icons/exit.svg rename to rscder/icons/exit.svg diff --git a/icons/export.svg b/rscder/icons/export.svg similarity index 100% rename from icons/export.svg rename to rscder/icons/export.svg diff --git a/icons/font.svg b/rscder/icons/font.svg similarity index 100% rename from icons/font.svg rename to rscder/icons/font.svg diff --git a/icons/full.svg b/rscder/icons/full.svg similarity index 100% rename from icons/full.svg rename to rscder/icons/full.svg diff --git a/icons/load.svg b/rscder/icons/load.svg similarity index 100% rename from icons/load.svg rename to rscder/icons/load.svg diff --git a/icons/logo.svg b/rscder/icons/logo.svg similarity index 100% rename from icons/logo.svg rename to rscder/icons/logo.svg diff --git a/icons/model.svg b/rscder/icons/model.svg similarity index 100% rename from icons/model.svg rename to rscder/icons/model.svg diff --git a/icons/ok.svg b/rscder/icons/ok.svg similarity index 100% rename from icons/ok.svg rename to rscder/icons/ok.svg diff --git a/icons/outline.svg b/rscder/icons/outline.svg similarity index 100% rename from icons/outline.svg rename to rscder/icons/outline.svg diff --git a/icons/paint.svg b/rscder/icons/paint.svg similarity index 100% rename from icons/paint.svg rename to rscder/icons/paint.svg diff --git a/icons/pan.svg b/rscder/icons/pan.svg similarity index 100% rename from icons/pan.svg rename to rscder/icons/pan.svg diff --git a/icons/qt.svg b/rscder/icons/qt.svg similarity index 100% rename from icons/qt.svg rename to rscder/icons/qt.svg diff --git a/icons/settings.svg b/rscder/icons/settings.svg similarity index 100% rename from icons/settings.svg rename to rscder/icons/settings.svg diff --git a/icons/splash.png b/rscder/icons/splash.png similarity index 100% rename from icons/splash.png rename to rscder/icons/splash.png diff --git a/icons/start.svg b/rscder/icons/start.svg similarity index 100% rename from icons/start.svg rename to rscder/icons/start.svg diff --git a/icons/vector.svg b/rscder/icons/vector.svg similarity index 100% rename from icons/vector.svg rename to rscder/icons/vector.svg diff --git a/icons/zoomin.svg b/rscder/icons/zoomin.svg similarity index 100% rename from icons/zoomin.svg rename to rscder/icons/zoomin.svg diff --git a/icons/zoomout.svg b/rscder/icons/zoomout.svg similarity index 100% rename from icons/zoomout.svg rename to rscder/icons/zoomout.svg diff --git a/mul/mulexport.py b/rscder/mul/mulexport.py similarity index 100% rename from mul/mulexport.py rename to rscder/mul/mulexport.py diff --git a/mul/mulgrubcut.py b/rscder/mul/mulgrubcut.py similarity index 100% rename from mul/mulgrubcut.py rename to rscder/mul/mulgrubcut.py diff --git a/mul/mulstart.py b/rscder/mul/mulstart.py similarity index 100% rename from mul/mulstart.py rename to rscder/mul/mulstart.py diff --git a/rscder/plugins/basic.py b/rscder/plugins/basic.py new file mode 100644 index 0000000..b57bd36 --- /dev/null +++ b/rscder/plugins/basic.py @@ -0,0 +1,54 @@ +from PyQt5.QtCore import QObject, pyqtSignal +from rscder.utils.project import PairLayer + +class BasicPlugin(QObject): + ''' + 插件基类 + ctx: + layer_tree: layer tree + pair_canvas: pair canvas + message_box: message box + result_table: result table + project: project instance + mainwindow: mainwindow + toolbar: toolbar + statusbar: statusbar + menu: menu + file_menu: file menu + + + ''' + def __init__(self, ctx:dict) -> None: + super().__init__() + self.ctx = ctx + self.layer_tree = ctx['layer_tree'] + self.pair_canvas = ctx['pair_canvas'] + self.message_box = ctx['message_box'] + self.result_table = ctx['result_table'] + self.project = ctx['project'] + self.mainwindow = ctx['mainwindow'] + self.set_action() + self.project.layer_load.connect(self.on_data_load) + self.project.project_created.connect(self.setup) + + + def set_action(self): + ''' + When App start + ''' + pass + + def setup(self): + ''' + When project create + ''' + pass + + def get_layer(self, layer_id)-> PairLayer: + return self.project.layers[layer_id] + + def on_data_load(self, layer_id): + ''' + When data load + ''' + pass \ No newline at end of file diff --git a/rscder/plugins/loader.py b/rscder/plugins/loader.py new file mode 100644 index 0000000..18543fc --- /dev/null +++ b/rscder/plugins/loader.py @@ -0,0 +1,30 @@ +from rscder.utils.setting import Settings +from PyQt5.QtCore import QObject, pyqtSignal +from rscder.plugins.basic import BasicPlugin +import importlib +import os +import sys +import inspect + +class PluginLoader(QObject): + + plugin_loaded = pyqtSignal() + + def __init__(self, ctx): + self.ctx = ctx + + def load_plugin(self): + plugins = Settings.Plugin().plugins + for plugin in plugins: + name = plugin['name'] + path = plugin['path'] + try: + module = importlib.import_module(path) + for oname, obj in inspect.getmembers(module): + if inspect.isclass(obj) and issubclass(obj, BasicPlugin) and obj != BasicPlugin and obj != PluginLoader and oname == name: + obj(self.ctx) + except Exception as e: + self.ctx['message_box'].error(f'{name} load error: {e}') + # print(e) + + self.plugin_loaded.emit() \ No newline at end of file diff --git a/res.qrc b/rscder/res.qrc similarity index 100% rename from res.qrc rename to rscder/res.qrc diff --git a/utils/__init__.py b/rscder/utils/__init__.py similarity index 100% rename from utils/__init__.py rename to rscder/utils/__init__.py diff --git a/utils/license.py b/rscder/utils/license.py similarity index 100% rename from utils/license.py rename to rscder/utils/license.py diff --git a/rscder/utils/project.py b/rscder/utils/project.py new file mode 100644 index 0000000..26853a8 --- /dev/null +++ b/rscder/utils/project.py @@ -0,0 +1,262 @@ +import os +from pathlib import Path +from typing import Dict, List +import uuid +from osgeo import gdal, gdal_array +from utils.setting import Settings +from qgis.core import QgsRasterLayer, QgsLineSymbol, QgsSingleSymbolRenderer, QgsSimpleLineSymbolLayer, QgsVectorLayer, QgsCoordinateReferenceSystem, QgsFeature, QgsGeometry, QgsPointXY +from PyQt5.QtCore import QObject, pyqtSignal +from PyQt5.QtGui import QColor +import yaml +def singleton(cls): + _instance = {} + + def inner(*args, **kargs): + if cls not in _instance: + _instance[cls] = cls(*args, **kargs) + return _instance[cls] + return inner + +@singleton +class Project(QObject): + + project_init = pyqtSignal(bool) + + layer_load = pyqtSignal(str) + + def __init__(self, + parent=None): + super().__init__(parent) + self.is_init = False + self.cell_size = Settings.Project().cell_size + self.max_memory = Settings.Project().max_memory + self.max_threads = Settings.Project().max_threads + self.root = Settings.General().root + self.layers:Dict = dict() + # self.layers:List[PairLayer] = [] + + def connect(self, pair_canvas, + layer_tree, + message_box, + result_table): + self.pair_canvas = pair_canvas + self.layer_tree = layer_tree + self.message_box = message_box + self.result_table = result_table + + self.layer_load.connect(layer_tree.add_layer) + self.layer_load.connect(pair_canvas.add_layer) + # self.layer_load.connect(message_box.add_layer) + + + def setup(self, file=None): + self.is_init = True + self.file = file + if file is None: + self.file = Path(self.root)/'project'/'untitled.prj' + self.root = str(Path(self.file).parent) + dir_name = os.path.dirname(self.file) + if not os.path.exists(dir_name): + os.makedirs(dir_name, exist_ok=True) + if not os.path.exists(self.file): + with open(self.file, 'w') as f: + pass + else: + self.load() + # self.project_created.emit() + self.project_init.emit(True) + + def save(self): + data_dict = { + 'cell_size': self.cell_size, + 'max_memory': self.max_memory, + 'max_threads': self.max_threads, + 'root': self.root, + 'layers': [ layer.to_dict() for layer in self.layers.values() ], + 'results': [] + } + with open(self.file, 'w') as f: + yaml.safe_dump(data_dict, f) + # yaml.safe_dump(data_dict, open(self.file, 'w')) + + + def clear(self): + ''' + clear all layers + ''' + self.layers = dict() + self.layer_tree.clear() + self.pair_canvas.clear() + self.message_box.clear() + self.result_table.clear() + + def load(self): + with open(self.file, 'r') as f: + data = yaml.safe_load(f) + if data is None: + return + # data = yaml.safe_load(open(self.file, 'r')) + self.cell_size = data['cell_size'] + self.max_memory = data['max_memory'] + self.max_threads = data['max_threads'] + self.root = data['root'] + self.layers = dict() + for layer in data['layers']: + player = PairLayer.from_dict(layer) + if player.check(): + self.layers[player.id] = player + self.layer_load.emit(player.id) + + def add_layer(self, pth1, pth2): + self.root = str(Path(pth1).parent) + player = PairLayer(pth1, pth2, self.cell_size) + if player.check(): + # self.layers.append(player) + self.layers[player.id] = player + self.layer_load.emit(player.id) + else: + self.message_box.error(player.msg) + +class VectorLayer: + pass + +class GridLayer: + + def set_render(self): + symbol_layer = QgsSimpleLineSymbolLayer() + symbol_layer.setWidth(1) + symbol_layer.setColor(QColor.fromRgb(255,255,255, 100)) + + symbol = QgsLineSymbol() + symbol.changeSymbolLayer(0, symbol_layer) + + render = QgsSingleSymbolRenderer(symbol) + self.lines_layer.setRenderer(render) + + + def __init__(self, cell_size, ds): + self.cell_size = cell_size + self.ds = ds + + proj = ds.GetProjection() + geo = ds.GetGeoTransform() + self.proj = proj + self.geo = geo + self.x_size = ds.RasterXSize + self.y_size = ds.RasterYSize + + self.x_min = geo[0] + self.y_min = geo[3] + self.x_res = geo[1] + self.y_res = geo[5] + self.x_max = self.x_min + self.x_res * self.x_size + self.y_max = self.y_min + self.y_res * self.y_size + self.x_lines = [] + for xi in range(self.x_size // self.cell_size[0]): + self.x_lines.append(self.x_min + self.x_res * xi * self.cell_size[0]) + if self.x_lines[-1] == self.x_max: + self.x_lines.pop() + self.x_lines.append(self.x_max) + self.y_lines = [] + for yi in range(self.y_size // self.cell_size[1]): + self.y_lines.append(self.y_min + self.y_res * yi * self.cell_size[1]) + if self.y_lines[-1] == self.y_max: + self.y_lines.pop() + self.y_lines.append(self.y_max) + crs = QgsCoordinateReferenceSystem() + crs.createFromString('WKT:{}'.format(ds.GetProjection())) + # print(crs) + lines_layer = QgsVectorLayer('LineString?crs={}'.format(crs.toProj()), 'temp-grid-outline', "memory") + if not lines_layer.isValid(): + Project().message_box.error('Failed to create grid outline layer') + return + lines_layer.setLabelsEnabled(False) + lines_layer.startEditing() + features = [] + for x in self.x_lines: + line = QgsFeature() + line.setGeometry(QgsGeometry.fromPolylineXY([QgsPointXY(x, self.y_min), QgsPointXY(x, self.y_max)])) + features.append(line) + for y in self.y_lines: + line = QgsFeature() + line.setGeometry(QgsGeometry.fromPolylineXY([QgsPointXY(self.x_min, y), QgsPointXY(self.x_max, y)])) + features.append(line) + lines_layer.addFeatures(features) + lines_layer.commitChanges() + self.lines_layer = lines_layer + + self.set_render() + # self.x_lines = [ self.x_min + i * self.x_res for i in range(self.x_size) ] + + @property + def grid_layer(self): + return self.lines_layer + + def to_dict(self): + return { + 'cell_size': self.cell_size + } + + @staticmethod + def from_dict(data): + return GridLayer() + +class PairLayer: + + def to_dict(self): + return { + 'pth1': self.pth1, + 'pth2': self.pth2, + 'l1_name': self.l1_name, + 'l2_name': self.l2_name, + 'cell_size': self.cell_size, + } + + @staticmethod + def from_dict(data): + layer = PairLayer(data['pth1'], data['pth2'], data['cell_size']) + layer.l1_name = data['l1_name'] + layer.l2_name = data['l2_name'] + # layer.grid_layer = GridLayer.from_dict(data['grid_layer']) + return layer + def __init__(self, pth1, pth2, cell_size) -> None: + self.pth1 = pth1 + self.pth2 = pth2 + self.id = str(uuid.uuid1()) + + self.l1_name = os.path.basename(pth1) + self.l2_name = os.path.basename(pth2) + + self.cell_size = cell_size + + # self.grid_layer = GridLayer(cell_size) + + self.msg = '' + + def check(self): + if not os.path.exists(self.pth1): + self.msg = '图层1不存在' + return False + if not os.path.exists(self.pth2): + self.msg = '图层2不存在' + return False + + ds1 = gdal.Open(self.pth1) + ds2 = gdal.Open(self.pth2) + if ds1 is None or ds2 is None: + self.msg = '图层打开失败' + return False + + if ds1.RasterXSize != ds2.RasterXSize or ds1.RasterYSize != ds2.RasterYSize: + self.msg = '图层尺寸不一致' + return False + + self.grid_layer = GridLayer(self.cell_size, ds1) + + del ds1 + del ds2 + + self.l1 = QgsRasterLayer(self.pth1, self.l1_name) + self.l2 = QgsRasterLayer(self.pth2, self.l2_name) + + return True \ No newline at end of file diff --git a/utils/setting.py b/rscder/utils/setting.py similarity index 86% rename from utils/setting.py rename to rscder/utils/setting.py index 3dd71a1..0ea5673 100644 --- a/utils/setting.py +++ b/rscder/utils/setting.py @@ -2,7 +2,7 @@ from datetime import datetime import os from typing import Tuple from PyQt5.QtCore import QSettings -from utils.license import LicenseHelper +from rscder.utils.license import LicenseHelper class Settings(QSettings): @@ -16,6 +16,25 @@ class Settings(QSettings): def __exit__(self, *args, **kargs): self.endGroup() + class Plugin: + + PRE='plugin' + + @property + def root(self): + return './3rd' + + @property + def plugins(self): + with Settings(Settings.Plugin.PRE) as s: + return s.value('plugins', []) + + @plugins.setter + def plugins(self, value): + with Settings(Settings.Plugin.PRE) as s: + s.setValue('plugins', value) + + class Project: PRE= 'project' diff --git a/utils/menu.py b/utils/menu.py deleted file mode 100644 index fc0eefb..0000000 --- a/utils/menu.py +++ /dev/null @@ -1,11 +0,0 @@ -from functools import wraps - -def as_menu(D, name, icon=None, shortcut=None, tip=None, checkable=False, signal=None, callback=None, enabled=True): - - def func(f): - @wraps(f) - def wrapper(*args, **kwargs): - return f(*args, **kwargs) - D.addMenu(name, icon, shortcut, tip, checkable, signal, callback, enabled, wrapper) - return f - return func \ No newline at end of file diff --git a/utils/project.py b/utils/project.py deleted file mode 100644 index 59a0e73..0000000 --- a/utils/project.py +++ /dev/null @@ -1,121 +0,0 @@ -import os -from pathlib import Path -from osgeo import gdal, gdal_array -from utils.setting import Settings -from qgis.core import QgsRasterLayer -from PyQt5.QtCore import QObject, pyqtSignal - -def singleton(cls): - _instance = {} - - def inner(*args, **kargs): - if cls not in _instance: - _instance[cls] = cls(*args, **kargs) - return _instance[cls] - return inner - -@singleton -class Project(QObject): - - project_init = pyqtSignal(bool) - - def __init__(self, - parent=None): - super().__init__(parent) - self.is_init = False - self.cell_size = Settings.Project().cell_size - self.max_memory = Settings.Project().max_memory - self.max_threads = Settings.Project().max_threads - self.root = Settings.General().root - - def connect(self, pair_canvas, - layer_tree, - message_box, - result_table): - self.pair_canvas = pair_canvas - self.layer_tree = layer_tree - self.message_box = message_box - self.result_table = result_table - - def setup(self, file=None): - self.is_init = True - - if file is None: - self.file = Path(self.root)/'project'/'untitled.cdp' - dir_name = os.path.dirname(self.file) - if not os.path.exists(dir_name): - os.makedirs(dir_name, exist_ok=True) - if not self.file.exists(): - f = self.file.open('w') - f.close() - else: - self.load() - # self.project_created.emit() - self.project_init.emit(True) - - def save(self): - pass - - def clear(self): - ''' - clear all layers - ''' - self.layer_tree.clear() - self.pair_canvas.clear() - self.message_box.clear() - self.result_table.clear() - - def load(self): - pass - - def add_layer(self, pth1, pth2): - player = PairLayer(pth1, pth2) - if player.check(): - self.layer_tree.add_layer(player) - else: - self.message_box.show_message(player.msg) - -class VectorLayer: - pass - -class GridLayer: - pass - -class PairLayer: - - def __init__(self, pth1, pth2) -> None: - self.pth1 = pth1 - self.pth2 = pth2 - - self.l1_name = os.path.basename(pth1) - self.l2_name = os.path.basename(pth2) - - self.grid_layer = GridLayer() - - self.msg = '' - - def check(self): - if not os.path.exists(self.pth1): - self.msg = '图层1不存在' - return False - if not os.path.exists(self.pth2): - self.msg = '图层2不存在' - return False - - ds1 = gdal.Open(self.pth1) - ds2 = gdal.Open(self.pth2) - if ds1 is None or ds2 is None: - self.msg = '图层打开失败' - return False - - if ds1.RasterXSize != ds2.RasterXSize or ds1.RasterYSize != ds2.RasterYSize: - self.msg = '图层尺寸不一致' - return False - - del ds1 - del ds2 - - self.l1 = QgsRasterLayer(self.pth1, self.l1_name) - self.l2 = QgsRasterLayer(self.pth2, self.l2_name) - - return True \ No newline at end of file diff --git a/未命名.prj b/未命名.prj new file mode 100644 index 0000000..8f3903d --- /dev/null +++ b/未命名.prj @@ -0,0 +1,13 @@ +cell_size: &id001 +- 100 +- 100 +layers: +- cell_size: *id001 + l1_name: p122_r032_l5_20090915.tif + l2_name: p122_r032_l8_20170804.tif + pth1: F:/LZY_DATA/p122r032/p122_r032_l5_20090915.tif + pth2: F:/LZY_DATA/p122r032/p122_r032_l8_20170804.tif +max_memory: 100 +max_threads: 4 +results: [] +root: F:\LZY_DATA\p122r032