完成联动

This commit is contained in:
copper 2022-05-12 15:38:56 +08:00
parent 0843a369be
commit da8d1f178e
9 changed files with 507 additions and 342 deletions

View File

@ -2,16 +2,60 @@ import math
import os
import pdb
from rscder.plugins.basic import BasicPlugin
from PyQt5.QtWidgets import QAction
from PyQt5.QtWidgets import QAction, QDialog, QHBoxLayout, QVBoxLayout, QPushButton
from PyQt5.QtCore import pyqtSignal
from rscder.utils.project import PairLayer, ResultLayer
from PyQt5.QtGui import QIcon
from rscder.utils.project import Project, PairLayer, ResultLayer
from rscder.gui.layercombox import LayerCombox
from osgeo import gdal, gdal_array
from threading import Thread
import numpy as np
class MyDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle('BasicChange')
self.setWindowIcon(QIcon(":/icons/logo.svg"))
self.setFixedWidth(500)
self.layer_select = LayerCombox(self)
self.layer_select.setFixedWidth(400)
# self.number_input = QLineEdit(self)
self.ok_button = QPushButton('OK', self)
self.ok_button.setIcon(QIcon(":/icons/ok.svg"))
self.ok_button.clicked.connect(self.on_ok)
self.cancel_button = QPushButton('Cancel', self)
self.cancel_button.setIcon(QIcon(":/icons/cancel.svg"))
self.cancel_button.clicked.connect(self.on_cancel)
self.button_layout = QHBoxLayout()
self.button_layout.addWidget(self.ok_button)
self.button_layout.addWidget(self.cancel_button)
self.main_layout = QVBoxLayout()
self.main_layout.addWidget(self.layer_select)
# self.main_layout.addWidget(self.number_input)
self.main_layout.addLayout(self.button_layout)
self.setLayout(self.main_layout)
def on_ok(self):
self.accept()
def on_cancel(self):
self.reject()
class BasicMethod(BasicPlugin):
message_send = pyqtSignal(str)
table_result_ok = pyqtSignal(str)
result_ok = pyqtSignal(dict)
@staticmethod
def info():
@ -33,34 +77,31 @@ class BasicMethod(BasicPlugin):
basic_diff_method.triggered.connect(self.basic_diff_alg)
self.message_send.connect(self.send_message)
self.table_result_ok.connect(self.on_table_result_ok)
self.result_ok.connect(self.on_result_ok)
# self.result_ok.connect(self.on_result_ok)
self.gap = 128
def on_data_load(self, layer_id):
self.basic_diff_method.setEnabled(True)
def send_message(self, s):
self.message_box.info(s)
def on_table_result_ok(self, s):
with open(s, 'r') as f:
lines = f.readlines()
data_lines = lines[1:]
def on_result_ok(self, data):
layer = Project().layers[data['layer_id']]
csv_result = ResultLayer('basic_diff_result', ResultLayer.POINT)
csv_result.load_file(data['csv_file'])
layer.results.append(csv_result)
self.layer_tree.update_layer(layer.id)
def run_basic_diff_alg(self, layer:PairLayer, out):
if len(data_lines) > 0:
data_table = []
for l in data_lines:
l = l.strip()
ls = l.split(',')
ls = [float(i) for i in ls]
data_table.append(ls)
result = ResultLayer(ResultLayer.POINT)
result.data = data_table
self.result_table.set_data(result)
pth1 = layer.pth1
pth2 = layer.pth2
cell_size = layer.cell_size
def run_basic_diff_alg(self, pth1, pth2, cell_size, out):
self.message_send.emit('开始计算差分法')
ds1 = gdal.Open(pth1)
@ -125,7 +166,7 @@ class BasicMethod(BasicPlugin):
self.message_send.emit('完成归一化概率')
self.message_send.emit('计算变化表格中...')
out_csv = os.path.join(out, 'diff_table.csv')
out_csv = os.path.join(out, '{}.csv'.format(int(np.random.rand() * 100000)))
xblocks = xsize // cell_size[0]
normal_in_ds = gdal.Open(out_normal_tif)
@ -151,7 +192,11 @@ class BasicMethod(BasicPlugin):
center_y = center_y * geo[5] + geo [3]
f.write(f'{center_x},{center_y},{block_data_xy.mean()},1\n')
self.table_result_ok.emit(out_csv)
self.result_ok.emit({
'layer_id': layer.id,
'csv_file': out_csv,
})
self.message_send.emit('完成计算变化表格')
@ -159,10 +204,13 @@ class BasicMethod(BasicPlugin):
def basic_diff_alg(self):
# layer_select =
layer:PairLayer = list(self.project.layers.values())[0]
img1 = layer.pth1
img2 = layer.pth2
layer = None
layer_select = MyDialog(self.mainwindow)
if(layer_select.exec_()):
layer = layer_select.layer_select.current_layer
else:
return
# layer:PairLayer = list(self.project.layers.values())[0]
if not layer.check():
return
@ -170,7 +218,7 @@ class BasicMethod(BasicPlugin):
if not os.path.exists(out_dir):
os.makedirs(out_dir, exist_ok=True)
t = Thread(target=self.run_basic_diff_alg, args=(img1, img2, layer.cell_size, out_dir))
t = Thread(target=self.run_basic_diff_alg, args=(layer, out_dir))
t.start()

View File

@ -1,247 +0,0 @@
usage: pyinstaller [-h] [-v] [-D] [-F] [--specpath DIR] [-n NAME]
[--add-data <SRC;DEST or SRC:DEST>]
[--add-binary <SRC;DEST or SRC:DEST>] [-p DIR]
[--hidden-import MODULENAME]
[--collect-submodules MODULENAME]
[--collect-data MODULENAME] [--collect-binaries MODULENAME]
[--collect-all MODULENAME] [--copy-metadata PACKAGENAME]
[--recursive-copy-metadata PACKAGENAME]
[--additional-hooks-dir HOOKSPATH]
[--runtime-hook RUNTIME_HOOKS] [--exclude-module EXCLUDES]
[--key KEY] [--splash IMAGE_FILE]
[-d {all,imports,bootloader,noarchive}] [-s] [--noupx]
[--upx-exclude FILE] [-c] [-w]
[-i <FILE.ico or FILE.exe,ID or FILE.icns or "NONE">]
[--disable-windowed-traceback] [--version-file FILE]
[-m <FILE or XML>] [-r RESOURCE] [--uac-admin]
[--uac-uiaccess] [--win-private-assemblies]
[--win-no-prefer-redirects]
[--osx-bundle-identifier BUNDLE_IDENTIFIER]
[--target-architecture ARCH] [--codesign-identity IDENTITY]
[--osx-entitlements-file FILENAME] [--runtime-tmpdir PATH]
[--bootloader-ignore-signals] [--distpath DIR]
[--workpath WORKPATH] [-y] [--upx-dir UPX_DIR] [-a]
[--clean] [--log-level LEVEL]
scriptname [scriptname ...]
positional arguments:
scriptname name of scriptfiles to be processed or exactly one
.spec-file. If a .spec-file is specified, most options
are unnecessary and are ignored.
optional arguments:
-h, --help show this help message and exit
-v, --version Show program version info and exit.
--distpath DIR Where to put the bundled app (default: .\dist)
--workpath WORKPATH Where to put all the temporary work files, .log, .pyz
and etc. (default: .\build)
-y, --noconfirm Replace output directory (default:
SPECPATH\dist\SPECNAME) without asking for
confirmation
--upx-dir UPX_DIR Path to UPX utility (default: search the execution
path)
-a, --ascii Do not include unicode encoding support (default:
included if available)
--clean Clean PyInstaller cache and remove temporary files
before building.
--log-level LEVEL Amount of detail in build-time console messages. LEVEL
may be one of TRACE, DEBUG, INFO, WARN, ERROR,
CRITICAL (default: INFO).
What to generate:
-D, --onedir Create a one-folder bundle containing an executable
(default)
-F, --onefile Create a one-file bundled executable.
--specpath DIR Folder to store the generated spec file (default:
current directory)
-n NAME, --name NAME Name to assign to the bundled app and spec file
(default: first script's basename)
What to bundle, where to search:
--add-data <SRC;DEST or SRC:DEST>
Additional non-binary files or folders to be added to
the executable. The path separator is platform
specific, ``os.pathsep`` (which is ``;`` on Windows
and ``:`` on most unix systems) is used. This option
can be used multiple times.
--add-binary <SRC;DEST or SRC:DEST>
Additional binary files to be added to the executable.
See the ``--add-data`` option for more details. This
option can be used multiple times.
-p DIR, --paths DIR A path to search for imports (like using PYTHONPATH).
Multiple paths are allowed, separated by ``';'``, or
use this option multiple times. Equivalent to
supplying the ``pathex`` argument in the spec file.
--hidden-import MODULENAME, --hiddenimport MODULENAME
Name an import not visible in the code of the
script(s). This option can be used multiple times.
--collect-submodules MODULENAME
Collect all submodules from the specified package or
module. This option can be used multiple times.
--collect-data MODULENAME, --collect-datas MODULENAME
Collect all data from the specified package or module.
This option can be used multiple times.
--collect-binaries MODULENAME
Collect all binaries from the specified package or
module. This option can be used multiple times.
--collect-all MODULENAME
Collect all submodules, data files, and binaries from
the specified package or module. This option can be
used multiple times.
--copy-metadata PACKAGENAME
Copy metadata for the specified package. This option
can be used multiple times.
--recursive-copy-metadata PACKAGENAME
Copy metadata for the specified package and all its
dependencies. This option can be used multiple times.
--additional-hooks-dir HOOKSPATH
An additional path to search for hooks. This option
can be used multiple times.
--runtime-hook RUNTIME_HOOKS
Path to a custom runtime hook file. A runtime hook is
code that is bundled with the executable and is
executed before any other code or module to set up
special features of the runtime environment. This
option can be used multiple times.
--exclude-module EXCLUDES
Optional module or package (the Python name, not the
path name) that will be ignored (as though it was not
found). This option can be used multiple times.
--key KEY The key used to encrypt Python bytecode.
--splash IMAGE_FILE (EXPERIMENTAL) Add an splash screen with the image
IMAGE_FILE to the application. The splash screen can
show progress updates while unpacking.
How to generate:
-d {all,imports,bootloader,noarchive}, --debug {all,imports,bootloader,noarchive}
Provide assistance with debugging a frozen
application. This argument may be provided multiple
times to select several of the following options.
- all: All three of the following options.
- imports: specify the -v option to the underlying
Python interpreter, causing it to print a message
each time a module is initialized, showing the
place (filename or built-in module) from which it
is loaded. See
https://docs.python.org/3/using/cmdline.html#id4.
- bootloader: tell the bootloader to issue progress
messages while initializing and starting the
bundled app. Used to diagnose problems with
missing imports.
- noarchive: instead of storing all frozen Python
source files as an archive inside the resulting
executable, store them as files in the resulting
output directory.
-s, --strip Apply a symbol-table strip to the executable and
shared libs (not recommended for Windows)
--noupx Do not use UPX even if it is available (works
differently between Windows and *nix)
--upx-exclude FILE Prevent a binary from being compressed when using upx.
This is typically used if upx corrupts certain
binaries during compression. FILE is the filename of
the binary without path. This option can be used
multiple times.
Windows and Mac OS X specific options:
-c, --console, --nowindowed
Open a console window for standard i/o (default). On
Windows this option will have no effect if the first
script is a '.pyw' file.
-w, --windowed, --noconsole
Windows and Mac OS X: do not provide a console window
for standard i/o. On Mac OS X this also triggers
building an OS X .app bundle. On Windows this option
will be set if the first script is a '.pyw' file. This
option is ignored in *NIX systems.
-i <FILE.ico or FILE.exe,ID or FILE.icns or "NONE">, --icon <FILE.ico or FILE.exe,ID or FILE.icns or "NONE">
FILE.ico: apply that icon to a Windows executable.
FILE.exe,ID, extract the icon with ID from an exe.
FILE.icns: apply the icon to the .app bundle on Mac OS
X. Use "NONE" to not apply any icon, thereby making
the OS to show some default (default: apply
PyInstaller's icon)
--disable-windowed-traceback
Disable traceback dump of unhandled exception in
windowed (noconsole) mode (Windows and macOS only),
and instead display a message that this feature is
disabled.
Windows specific options:
--version-file FILE add a version resource from FILE to the exe
-m <FILE or XML>, --manifest <FILE or XML>
add manifest FILE or XML to the exe
-r RESOURCE, --resource RESOURCE
Add or update a resource to a Windows executable. The
RESOURCE is one to four items,
FILE[,TYPE[,NAME[,LANGUAGE]]]. FILE can be a data file
or an exe/dll. For data files, at least TYPE and NAME
must be specified. LANGUAGE defaults to 0 or may be
specified as wildcard * to update all resources of the
given TYPE and NAME. For exe/dll files, all resources
from FILE will be added/updated to the final
executable if TYPE, NAME and LANGUAGE are omitted or
specified as wildcard *.This option can be used
multiple times.
--uac-admin Using this option creates a Manifest which will
request elevation upon application restart.
--uac-uiaccess Using this option allows an elevated application to
work with Remote Desktop.
Windows Side-by-side Assembly searching options (advanced):
--win-private-assemblies
Any Shared Assemblies bundled into the application
will be changed into Private Assemblies. This means
the exact versions of these assemblies will always be
used, and any newer versions installed on user
machines at the system level will be ignored.
--win-no-prefer-redirects
While searching for Shared or Private Assemblies to
bundle into the application, PyInstaller will prefer
not to follow policies that redirect to newer
versions, and will try to bundle the exact versions of
the assembly.
Mac OS X specific options:
--osx-bundle-identifier BUNDLE_IDENTIFIER
Mac OS X .app bundle identifier is used as the default
unique program name for code signing purposes. The
usual form is a hierarchical name in reverse DNS
notation. For example:
com.mycompany.department.appname (default: first
script's basename)
--target-architecture ARCH, --target-arch ARCH
Target architecture (macOS only; valid values: x86_64,
arm64, universal2). Enables switching between
universal2 and single-arch version of frozen
application (provided python installation supports the
target architecture). If not target architecture is
not specified, the current running architecture is
targeted.
--codesign-identity IDENTITY
Code signing identity (macOS only). Use the provided
identity to sign collected binaries and generated
executable. If signing identity is not provided, ad-
hoc signing is performed instead.
--osx-entitlements-file FILENAME
Entitlements file to use when code-signing the
collected binaries (macOS only).
Rarely used special options:
--runtime-tmpdir PATH
Where to extract libraries and support files in
`onefile`-mode. If this option is given, the
bootloader will ignore any temp-folder location
defined by the run-time OS. The ``_MEIxxxxxx``-folder
will be created here. Please use this option only if
you know what you are doing.
--bootloader-ignore-signals
Tell the bootloader to ignore signals rather than
forwarding them to the child process. Useful in
situations where e.g. a supervisor process signals
both the bootloader and child (e.g. via a process
group) to avoid signalling the child twice.

0
rscder/gui/__init__.py Normal file
View File

22
rscder/gui/layercombox.py Normal file
View File

@ -0,0 +1,22 @@
from PyQt5.QtWidgets import QComboBox
from rscder.utils.project import Project
class LayerCombox(QComboBox):
def __init__(self, parent=None):
super().__init__(parent)
self.addItem('---', None)
for layer in Project().layers.values():
self.addItem(layer.name, layer.id)
self.currentIndexChanged.connect(self.on_changed)
self.current_layer = None
def on_changed(self, index):
if index == 0:
self.current_layer = None
else:
self.current_layer = Project().layers[self.itemData(index)]

View File

@ -1,7 +1,8 @@
import pdb
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt,QModelIndex
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QCursor
from PyQt5.QtWidgets import (QTreeView, QTreeWidgetItem, QAbstractItemView, QHeaderView, QStyleFactory)
from rscder.gui.actions import get_action_manager
@ -9,6 +10,15 @@ from rscder.utils.project import PairLayer, Project
class LayerTree(QtWidgets.QWidget):
LAYER_TOOT = 0
SUB_RASTER = 1
RESULT = 2
LEFT_RASTER = 0
RIGHT_RASTER = 1
GRID = 3
tree_changed = QtCore.pyqtSignal(str)
result_clicked = QtCore.pyqtSignal(str, int)
def __init__(self, parent=None):
super().__init__(parent)
# self.tree_view = QTreeView(self)
@ -16,7 +26,8 @@ class LayerTree(QtWidgets.QWidget):
self.tree.setColumnCount(1)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.right_menu_show)
self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
self.tree.customContextMenuRequested.connect(self.right_menu_show)
self.root=QTreeWidgetItem(self.tree)
self.tree.setHeaderHidden(True)
# self.tree.setHeaderLabels(['图层'])
@ -31,35 +42,122 @@ class LayerTree(QtWidgets.QWidget):
self.tree.addTopLevelItem(self.root)
self.tree.clicked.connect(self.onClicked)
# self.tree.clicked.connect(self.onClicked)
self.tree.itemClicked.connect(self.onItemClicked)
self.tree.itemChanged.connect(self.onItemChanged)
layout = QtWidgets.QGridLayout()
layout.addWidget(self.tree)
self.setLayout(layout)
self.setLayoutDirection(Qt.LeftToRight)
self.is_in_add_layer = False
def onClicked(self,index):
print(index.row())
item = self.tree.currentItem()
def onItemClicked(self, item:QtWidgets.QTreeWidgetItem, column):
if item == self.root:
return
layer_id = str(item.data(0, Qt.UserRole))
layer = Project().layers[layer_id]
print(layer.l1_name)
print(layer.l2_name)
root = item
if item.data(0, Qt.UserRole) != LayerTree.LAYER_TOOT:
root = item.parent()
if item.data(0, Qt.UserRole) == LayerTree.LAYER_TOOT:
return
layer = Project().layers[root.data(0, Qt.UserRole + 1)]
Project().current_layer = layer
if item.data(0, Qt.UserRole) == LayerTree.RESULT:
# result = layer.results[item.data(0, Qt.UserRole + 1)]
self.result_clicked.emit(layer.id, item.data(0, Qt.UserRole + 1))
def onItemChanged(self, item:QtWidgets.QTreeWidgetItem, column):
if self.is_in_add_layer:
return
if item == self.root:
return
root = item
if item.data(0, Qt.UserRole) != LayerTree.LAYER_TOOT:
root = item.parent()
layer = Project().layers[root.data(0, Qt.UserRole + 1)]
if item.data(0, Qt.UserRole) == LayerTree.LAYER_TOOT:
layer.enable = item.checkState(0) == Qt.Checked
if item.data(0, Qt.UserRole) == LayerTree.SUB_RASTER:
if item.data(0, Qt.UserRole + 1) == LayerTree.LEFT_RASTER:
layer.l1_enable = item.checkState(0) == Qt.Checked
elif item.data(0, Qt.UserRole + 1) == LayerTree.RIGHT_RASTER:
layer.l2_enable = item.checkState(0) == Qt.Checked
if item.data(0, Qt.UserRole) == LayerTree.RESULT:
layer.results[item.data(0, Qt.UserRole + 1)].enable = item.checkState(0) == Qt.Checked
if item.data(0, Qt.UserRole) == LayerTree.GRID:
layer.grid_enable = item.checkState(0) == Qt.Checked
self.tree_changed.emit(layer.id)
def add_layer(self, layer:str):
# self.tree.it
self.is_in_add_layer = True
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)
item_root = QtWidgets.QTreeWidgetItem(self.root)
item_root.setText(0,layer.name)
item_root.setData(0, Qt.UserRole, LayerTree.LAYER_TOOT)
item_root.setData(0, Qt.UserRole + 1, layer.id)
item_root.setCheckState(0, Qt.Checked if layer.enable else Qt.Unchecked)
item1.setData(0, Qt.UserRole, layer.id)
item2.setData(0, Qt.UserRole, layer.id)
self.add_sub_layer(item_root, layer)
self.is_in_add_layer = False
def add_sub_layer(self, item_root, layer:PairLayer):
# print(item_root.text(0))
# print(layer.results.__len__())
grid_item = QtWidgets.QTreeWidgetItem(item_root)
grid_item.setText(0,'格网')
grid_item.setData(0, Qt.UserRole, LayerTree.GRID)
grid_item.setCheckState(0, Qt.Checked if layer.grid_enable else Qt.Unchecked)
item1 = QtWidgets.QTreeWidgetItem(item_root)
item1.setText(0, layer.l1_name)
item1.setCheckState(0, Qt.Checked if layer.l1_enable else Qt.Unchecked)
item1.setData(0, Qt.UserRole, LayerTree.SUB_RASTER)
item1.setData(0, Qt.UserRole + 1, LayerTree.LEFT_RASTER)
item2 = QtWidgets.QTreeWidgetItem(item_root)
item2.setText(0, layer.l2_name)
item2.setCheckState(0, Qt.Checked if layer.l2_enable else Qt.Unchecked)
item1.setData(0, Qt.UserRole, LayerTree.SUB_RASTER)
item1.setData(0, Qt.UserRole + 1, LayerTree.RIGHT_RASTER)
for ri, item in enumerate(layer.results):
item_result = QtWidgets.QTreeWidgetItem(item_root)
item_result.setText(0, item.name)
item_result.setCheckState(0, Qt.Checked if item.enable else Qt.Unchecked)
item_result.setData(0, Qt.UserRole, LayerTree.RESULT)
item_result.setData(0, Qt.UserRole + 1, ri)
self.tree.expandAll()
def update_layer(self, layer:str):
self.is_in_add_layer = True
layer:PairLayer = Project().layers[layer]
layer_root = None
# pdb.set_trace()
for idx in range(self.root.childCount()):
item_root = self.root.child(idx)
if item_root.data(0, Qt.UserRole) == LayerTree.LAYER_TOOT:
if item_root.data(0, Qt.UserRole + 1) == layer.id:
layer_root = item_root
break
print(layer_root.text(0))
if layer_root is None:
self.add_layer(layer.id)
return
layer_root.setText(0,layer.name)
while layer_root.childCount() > 0:
layer_root.removeChild(layer_root.child(0))
self.add_sub_layer(layer_root, layer)
self.is_in_add_layer = False
def clear(self):
self.tree.clear()
@ -70,17 +168,37 @@ class LayerTree(QtWidgets.QWidget):
def right_menu_show(self, position):
rightMenu = QtWidgets.QMenu(self)
# QAction = QtWidgets.QAction(self.menuBar1)
item = self.tree.currentItem()
item = self.tree.itemAt(position)
action_manager = get_action_manager()
actions = []
if item == self.root:
data_load_action = action_manager.get_action('&数据加载', 'File')
actions.append(data_load_action)
data_load_action = action_manager.get_action('&数据加载', 'File')
actions.append(data_load_action)
if item is None:
print('nothing')
else:
pass
if item == self.root:
pass
elif item.data(0, Qt.UserRole) == LayerTree.LAYER_TOOT:
actions.append(QtWidgets.QAction('&缩放至该图层', self))
actions.append(QtWidgets.QAction('&重命名', self))
actions.append(QtWidgets.QAction('&删除', self))
elif item.data(0, Qt.UserRole) == LayerTree.SUB_RASTER:
actions.append(QtWidgets.QAction('&缩放至该图层', self))
actions.append(QtWidgets.QAction('&重命名', self))
actions.append(QtWidgets.QAction('&删除', self))
elif item.data(0, Qt.UserRole) == LayerTree.RESULT:
actions.append(QtWidgets.QAction('&缩放至该图层', self))
actions.append(QtWidgets.QAction('&重命名', self))
actions.append(QtWidgets.QAction('&导出', self))
actions.append(QtWidgets.QAction('&删除', self))
for action in actions:
rightMenu.addAction(action)
rightMenu.exec_(self.mapToGlobal(position))
rightMenu.exec_(QCursor.pos())

View File

@ -33,6 +33,11 @@ class MainWindow(QMainWindow):
self.layer_tree,
self.message_box,
self.result_box)
self.layer_tree.tree_changed.connect(self.double_map.layer_changed)
self.layer_tree.result_clicked.connect(self.result_box.on_result)
self.result_box.on_item_click.connect(self.double_map.zoom_to_result)
self.result_box.on_item_changed.connect(Project().change_result)
self.action_manager = ActionManager(
self.double_map,
self.layer_tree,

View File

@ -66,8 +66,9 @@ class DoubleCanvas(QWidget):
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)
if layer.grid_enable:
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()
@ -99,16 +100,48 @@ class DoubleCanvas(QWidget):
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:
layer:PairLayer = Project().layers[layer]
if not layer.enable:
return
self.clear()
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:
if layer.l1_enable:
self.mapcanva1.add_layer(layer.l1)
if layer.l2_enable:
self.mapcanva2.add_layer(layer.l2)
if layer.grid_enable and self.grid_show:
self.mapcanva1.add_grid_layer(layer.grid_layer.grid_layer)
self.mapcanva2.add_grid_layer(layer.grid_layer.grid_layer)
for r in layer.results:
if r.enable:
self.mapcanva1.add_layer(r.layer)
self.mapcanva2.add_layer(r.layer)
# self.mapcanva1.set_extent(layer.l1.extent())
self.mapcanva1.refresh()
self.mapcanva2.refresh()
def zoom_to_result(self, xydict:dict):
x = xydict['x']
y = xydict['y']
if Project().current_layer is not None:
layer = Project().current_layer
else:
layer = Project().layers[list(Project().layers.keys())[0]]
extent = QgsRectangle(x - layer.cell_size[0] * layer.xres, y - layer.cell_size[1] * layer.yres, x + layer.cell_size[0] * layer.xres, y + layer.cell_size[1] * layer.yres)
self.mapcanva1.set_extent(extent)
self.mapcanva2.set_extent(extent)
def zoom_to_layer(self, layer:str):
layer:PairLayer = Project().layers[layer]
self.mapcanva1.set_extent(layer.l1.extent())
self.mapcanva2.set_extent(layer.l2.extent())
def layer_changed(self, layer:str):
self.add_layer(layer)
def clear(self):
self.mapcanva1.clear()
self.mapcanva2.clear()

View File

@ -4,7 +4,7 @@ from PyQt5.QtCore import Qt,QModelIndex, pyqtSignal
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import (QTableWidgetItem, QTableWidget, QAbstractItemView, QHeaderView, QStyleFactory)
from rscder.utils.project import PairLayer, ResultLayer
from rscder.utils.project import PairLayer, Project, ResultLayer
class ResultTable(QtWidgets.QWidget):
@ -29,11 +29,15 @@ class ResultTable(QtWidgets.QWidget):
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.tablewidget)
self.setLayout(layout)
self.result = None
self.is_in_set_data = False
def clear(self):
pass
self.tablewidget.clear()
def onChanged(self, row, col):
if self.is_in_set_data:
return
if col == 3:
item_idx = row
item_status = self.tablewidget.item(row, col).checkState() == Qt.Checked
@ -41,7 +45,8 @@ class ResultTable(QtWidgets.QWidget):
self.tablewidget.item(row, col).setBackground(Qt.yellow)
else:
self.tablewidget.item(row, col).setBackground(Qt.green)
self.on_item_changed.emit({'idx':item_idx, 'status':item_status})
# print(item_idx, item_status)
self.result.update({'row':item_idx, 'value':item_status})
def onClicked(self, row, col):
if col == 3:
@ -50,9 +55,18 @@ class ResultTable(QtWidgets.QWidget):
def onDoubleClicked(self, row, col):
x = self.tablewidget.item(row, 0).text()
y = self.tablewidget.item(row, 1).text()
self.on_item_click.emit({'x':x, 'y':y})
self.on_item_click.emit({'x':float(x), 'y':float(y)})
def on_result(self, layer_id, result_id):
self.is_in_set_data = True
result = Project().layers[layer_id].results[result_id]
self.result = result
self.clear()
self.set_data(result)
def set_data(self, data:ResultLayer):
self.is_in_set_data = True
if data.layer_type != ResultLayer.POINT:
return
self.tablewidget.setRowCount(len(data.data))
# print(len(data.data))
self.tablewidget.setVerticalHeaderLabels([ str(i+1) for i in range(len(data.data))])
@ -73,5 +87,4 @@ class ResultTable(QtWidgets.QWidget):
self.tablewidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.tablewidget.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.is_in_set_data = False

View File

@ -2,9 +2,10 @@ import os
from pathlib import Path
from typing import Dict, List
import uuid
import numpy as np
from osgeo import gdal, gdal_array
from rscder.utils.setting import Settings
from qgis.core import QgsRasterLayer, QgsLineSymbol, QgsSingleSymbolRenderer, QgsSimpleLineSymbolLayer, QgsVectorLayer, QgsCoordinateReferenceSystem, QgsFeature, QgsGeometry, QgsPointXY
from qgis.core import QgsRasterLayer, QgsMarkerSymbol, QgsPalLayerSettings, QgsRuleBasedLabeling, QgsTextFormat, QgsLineSymbol, QgsSingleSymbolRenderer, QgsSimpleLineSymbolLayer, QgsVectorLayer, QgsCoordinateReferenceSystem, QgsFeature, QgsGeometry, QgsPointXY
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QColor
import yaml
@ -32,6 +33,7 @@ class Project(QObject):
self.root = str(Path(Settings.General().root)/'default')
self.file_mode = Project.ABSOLUTE_MODE
self.layers:Dict[str, PairLayer] = dict()
self.current_layer = None
def connect(self, pair_canvas,
layer_tree,
@ -44,8 +46,17 @@ class Project(QObject):
self.layer_load.connect(layer_tree.add_layer)
self.layer_load.connect(pair_canvas.add_layer)
# self.layer_load.connect(message_box.add_layer)
# self.layer_load.connect(message_box.add_layer)
#
def change_result(self, layer_id, result_id, data):
if layer_id in self.layers:
result = self.layers[layer_id].results[result_id]
if result.layer_type == ResultLayer.POINT:
result.update(data)
elif result.layer_type == ResultLayer.RASTER:
pass
def setup(self, path = None, name = None):
self.is_init = True
if path is not None:
@ -71,7 +82,6 @@ class Project(QObject):
'max_threads': self.max_threads,
'root': self.root,
'layers': [ layer.to_dict(None if self.file_mode == Project.ABSOLUTE_MODE else self.root) for layer in self.layers.values() ],
'results': []
}
with open(self.file, 'w') as f:
yaml.safe_dump(data_dict, f)
@ -89,22 +99,25 @@ class Project(QObject):
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, None if self.file_mode == Project.ABSOLUTE_MODE else self.root)
if player.check():
self.layers[player.id] = player
self.layer_load.emit(player.id)
try:
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, None if self.file_mode == Project.ABSOLUTE_MODE else self.root)
if player.check():
self.layers[player.id] = player
self.layer_load.emit(player.id)
except Exception as e:
self.message_box.error(str(e))
self.clear()
def add_layer(self, pth1, pth2):
# self.root = str(Path(pth1).parent)
@ -205,9 +218,142 @@ class ResultLayer:
POINT = 0
RASTER = 1
def __init__(self, layer_type):
def __init__(self, name, layer_type = POINT):
self.layer_type = layer_type
self.data = []
self.data = None
self.layer = None
self.name = name
self.path = None
self.wkt = None
self.enable = False
def update(self, data):
if self.layer_type == ResultLayer.POINT:
row = data['row']
value = data['value']
self.data[row][-1] = value
self.update_point_layer()
elif self.layer_type == ResultLayer.RASTER:
pass
def load_file(self, path):
self.path = path
if self.layer_type == ResultLayer.POINT:
self.load_point_file()
elif self.layer_type == ResultLayer.RASTER:
self.load_raster_file()
else:
raise Exception('Unknown layer type')
def format_point_layer(self, layer):
layer.setLabelsEnabled(True)
lyr = QgsPalLayerSettings()
lyr.enabled = True
lyr.fieldName = 'id'
lyr.placement = QgsPalLayerSettings.OverPoint
lyr.textNamedStyle = 'Medium'
text_format = QgsTextFormat()
text_format.color = QColor('#ffffff')
text_format.background().color = QColor('#000000')
text_format.buffer().setEnabled(True)
text_format.buffer().setSize(1)
text_format.buffer().setOpacity(0.5)
lyr.setFormat(text_format)
root = QgsRuleBasedLabeling.Rule(QgsPalLayerSettings())
rule = QgsRuleBasedLabeling.Rule(lyr)
rule.setDescription('label')
root.appendChild(rule)
#Apply label configuration
rules = QgsRuleBasedLabeling(root)
layer.setLabeling(rules)
def set_render(self, layer):
symbol = QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '5'})
render = QgsSingleSymbolRenderer(symbol)
layer.setRenderer(render)
def load_point_file(self):
data = np.loadtxt(self.path, delimiter=',', skiprows=1)
if data is None:
return
self.data = data
self.make_point_layer()
def make_point_layer(self):
if self.wkt is not None:
crs = QgsCoordinateReferenceSystem()
crs.createFromString('WKT:{}'.format(self.wkt))
else:
crs = QgsCoordinateReferenceSystem()
uri = 'Point?crs={}'.format(crs.toProj())
layer = QgsVectorLayer(uri, self.name, "memory")
if not layer.isValid():
Project().message_box.error('Failed to create layer')
return
self.format_point_layer(layer)
layer.startEditing()
features = []
for i, d in enumerate(self.data):
point = QgsFeature(i)
point.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(d[0], d[1])))
# point.setAttribute('id', i)
features.append(point)
layer.addFeatures(features)
layer.commitChanges()
self.set_render(layer)
self.layer = layer
def update_point_layer(self):
if self.layer is None:
return
self.layer.startEditing()
add_features = []
delete_features = []
for i, d in enumerate(self.data):
feature = self.layer.getFeature(i+1)
if d[-1]:
if feature is None:
feature = QgsFeature(i)
feature.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(d[0], d[1])))
# feature.setAttribute('id', i)
add_features.append(feature)
else:
if feature is not None:
delete_features.append(feature.id())
if len(add_features) > 0:
self.layer.addFeatures(add_features)
if len(delete_features) > 0:
self.layer.deleteFeatures(delete_features)
self.layer.commitChanges()
def load_raster_file(self):
ds = gdal.Open(self.path)
if ds is None:
return
self.layer = QgsRasterLayer(self.path, self.name)
@staticmethod
def from_dict(data, root = None):
result = ResultLayer(data['name'], data['layer_type'])
result.wkt = data['wkt']
if root is not None:
result.load_file(str(Path(root) / data['path']))
else:
result.load_file(data['path'])
return result
def to_dict(self, root=None):
return {
'name': self.name,
'layer_type': self.layer_type,
'wkt': self.wkt,
'path': self.path if root is None else str(Path(self.path).relative_to(root))
}
# def load_file(self, path):
class PairLayer:
@ -219,14 +365,19 @@ class PairLayer:
'l1_name': self.l1_name,
'l2_name': self.l2_name,
'cell_size': self.cell_size,
'results': [r.to_dict(root) for r in self.results],
'name': self.name
}
else:
return {
'pth1': relative_path(self.pth1, root),
'pth2': relative_path(self.pth2, root),
'name': self.name,
'l1_name': self.l1_name,
'l2_name': self.l2_name,
'cell_size': self.cell_size,
'results': [r.to_dict(root) for r in self.results]
}
@staticmethod
@ -237,23 +388,37 @@ class PairLayer:
layer = PairLayer(os.path.join(root, data['pth1']), os.path.join(root, data['pth2']), data['cell_size'])
layer.l1_name = data['l1_name']
layer.l2_name = data['l2_name']
layer.name = data['name']
for r in data['results']:
layer.results.append(ResultLayer.from_dict(r, root))
# 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.enable = True
self.l1_enable = True
self.l2_enable = True
self.grid_enable = True
self.id = str(uuid.uuid1())
self.name = '{}-{}'.format(os.path.basename(pth1), os.path.basename(pth2))
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 = ''
self.checked = False
self.xsize = 0
self.ysize = 0
self.xres = 0
self.yres = 0
self.wkt = None
self.results:List[ResultLayer] = []
def check(self):
if self.checked:
return self.checked
@ -274,6 +439,14 @@ class PairLayer:
self.msg = '图层尺寸不一致'
return False
self.xsize = ds1.RasterXSize
self.ysize = ds1.RasterYSize
self.xres = ds1.GetGeoTransform()[1]
self.yres = ds1.GetGeoTransform()[5]
self.wkt = ds1.GetProjection()
self.grid_layer = GridLayer(self.cell_size, ds1)
del ds1