From de30fbb57026efd7fb7fee8762acb4b79c3f5a7d Mon Sep 17 00:00:00 2001 From: Taku Fukada Date: Sat, 20 Jul 2024 15:43:46 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=9E=E3=82=A6=E3=82=B9=E3=82=AB=E3=83=BC?= =?UTF-8?q?=E3=82=BD=E3=83=AB=E4=BD=8D=E7=BD=AE=E3=81=AE=E3=83=A1=E3=83=83?= =?UTF-8?q?=E3=82=B7=E3=83=A5=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=99=E3=82=8B=E3=83=91=E3=83=8D=E3=83=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../algorithms/utils/gridsquare_to_box.py | 66 +++++++++- japanese_grids/panel.py | 114 ++++++++++++++++++ japanese_grids/plugin.py | 92 ++++++++------ 3 files changed, 231 insertions(+), 41 deletions(-) create mode 100644 japanese_grids/panel.py diff --git a/japanese_grids/algorithms/utils/gridsquare_to_box.py b/japanese_grids/algorithms/utils/gridsquare_to_box.py index b9b73bd..f1397d8 100644 --- a/japanese_grids/algorithms/utils/gridsquare_to_box.py +++ b/japanese_grids/algorithms/utils/gridsquare_to_box.py @@ -1,10 +1,14 @@ -from typing import Optional, Tuple +from typing import Optional, Tuple, TypedDict + +from .grid_square import AVAILABLE_PRIMARY_CODES _VALID_LENGTH = (4, 6, 8, 9, 10, 11) _VALID_QUAD = ("1", "2", "3", "4") LngLatBox = Tuple[float, float, float, float] +_MULTIPLIER = 30 # 浮動小数点誤差を回避するために内部的な経緯度にかける係数 + def grid_square_code_to_bbox( # noqa: C901 code: str, @@ -77,3 +81,63 @@ def grid_square_code_to_bbox( # noqa: C901 lat2 = lat + (lat10 + 0.015625) / 10 lng2 = lng + (lng10 + 0.015625) / 10 return (lng1, lat1 / 1.5, lng2, lat2 / 1.5) + + +class Codes(TypedDict): + primary: str + secondary: str + standard: str + half: str + quarter: str + eighth: str + + +def lnglat_to_grid_square_code(lng: float, lat: float) -> Optional[Codes]: + lat *= 1.5 + lat_r = int(lat) + lng_r = int(lng) + primary_code = f"{lat_r:02d}{lng_r-100:02d}" + if primary_code not in AVAILABLE_PRIMARY_CODES: + return None + + lat = (lat - lat_r) * 8 + lng = (lng - lng_r) * 8 + lat_r = int(lat) + lng_r = int(lng) + secondary_code = primary_code + f"{lat_r}{lng_r}" + + lat = (lat - lat_r) * 10 + lng = (lng - lng_r) * 10 + lat_r = int(lat) + lng_r = int(lng) + standard_code = secondary_code + f"{lat_r}{lng_r}" + + lat = (lat - lat_r) * 2 + lng = (lng - lng_r) * 2 + lat_r = int(lat) + lng_r = int(lng) + suffix = 1 + lat_r * 2 + lng_r + half_code = standard_code + str(suffix) + + lat = (lat - lat_r) * 2 + lng = (lng - lng_r) * 2 + lat_r = int(lat) + lng_r = int(lng) + suffix = 1 + lat_r * 2 + lng_r + quarter_code = half_code + str(suffix) + + lat = (lat - lat_r) * 2 + lng = (lng - lng_r) * 2 + lat_r = int(lat) + lng_r = int(lng) + suffix = 1 + lat_r * 2 + lng_r + eighth_code = quarter_code + str(suffix) + + return { + "primary": primary_code, + "secondary": secondary_code, + "standard": standard_code, + "half": half_code, + "quarter": quarter_code, + "eighth": eighth_code, + } diff --git a/japanese_grids/panel.py b/japanese_grids/panel.py new file mode 100644 index 0000000..ea09690 --- /dev/null +++ b/japanese_grids/panel.py @@ -0,0 +1,114 @@ +"""Coordinate Panel""" + +# Copyright (C) 2023 MIERUNE Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import re +from typing import Callable + +from qgis.core import QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject +from qgis.gui import QgisInterface, QgsMapMouseEvent, QgsMapTool +from qgis.PyQt.QtCore import Qt, QTimer +from qgis.PyQt.QtWidgets import ( + QDockWidget, + QHBoxLayout, + QLabel, + QLineEdit, + QVBoxLayout, + QWidget, +) + +from japanese_grids.algorithms.utils.gridsquare_to_box import lnglat_to_grid_square_code + + +class MapMouseMoveTool(QgsMapTool): + def __init__(self, canvas, callback: Callable[[float, float], None]): + super().__init__(canvas) + self._canvas = canvas + self._callback = callback + self._pressed = False + + def canvasPressEvent(self, event: QgsMapMouseEvent): + self._pressed = True + + def canvasReleaseEvent(self, event: QgsMapMouseEvent): + self._pressed = False + + def canvasMoveEvent(self, event: QgsMapMouseEvent): + if not self._pressed: + point = self.toMapCoordinates(event.pos()) + self._callback(point.x(), point.y()) + + +class CoordinatePanel: + def __init__(self, iface: QgisInterface): + self._iface = iface + self._setup() + self._current_coord = None + + def _handle_mousemove(self, x: float, y: float): + canvas = self._iface.mapCanvas() + source_crs = canvas.mapSettings().destinationCrs() + dest_crs = QgsCoordinateReferenceSystem(4326) # WGS 84 + transform = QgsCoordinateTransform(source_crs, dest_crs, QgsProject.instance()) + geog_point = transform.transform(x, y) + self._current_coord = geog_point + + (geog_lng, geog_lat) = (self._current_coord.x(), self._current_coord.y()) + self._coordinate_label.setText(f"緯度: {geog_lat:.5f}°, 経度: {geog_lng:.5f}°") + if code := lnglat_to_grid_square_code(geog_lng, geog_lat): + if m := re.match(r"(\d{4})(\d{4})(\d)(\d)(\d)", code["eighth"]): + self._line_edit.setText( + f"{m.group(1)}-{m.group(2)}-{m.group(3)}-{m.group(4)}-{m.group(5)}" + ) + else: + self._line_edit.setText("-") + + def _setup(self): + self._dock_widget = QDockWidget("地域メッシュコード", self._iface.mainWindow()) + + self._coordinate_label = QLabel("緯度: -.-°, 経度: -.-°") + layout = QVBoxLayout() + layout.addWidget(self._coordinate_label) + + self._line_edit = QLineEdit() + + self._line_edit.focusInEvent = lambda a0: QTimer.singleShot( + 1, self._line_edit.selectAll + ) + + h_layout = QHBoxLayout() + h_layout.addWidget(QLabel("コード:")) + h_layout.addWidget(self._line_edit) + layout.addLayout(h_layout) + + container = QWidget() + container.setLayout(layout) + container.setMaximumHeight(layout.totalSizeHint().height()) + self._dock_widget.setWidget(container) + + self._iface.addDockWidget(Qt.RightDockWidgetArea, self._dock_widget) + + canvas = self._iface.mapCanvas() + self._maptool = MapMouseMoveTool( + self._iface.mapCanvas(), self._handle_mousemove + ) + canvas.setMapTool(self._maptool) + + def teardown(self): + self._iface.removeDockWidget(self._dock_widget) + del self._dock_widget + del self._maptool diff --git a/japanese_grids/plugin.py b/japanese_grids/plugin.py index 7f615d5..3b812f6 100644 --- a/japanese_grids/plugin.py +++ b/japanese_grids/plugin.py @@ -22,6 +22,7 @@ from qgis.core import QgsApplication from qgis.gui import QgisInterface +from .panel import CoordinatePanel from .provider import JapanMeshProcessingProvider with contextlib.suppress(ImportError): @@ -39,47 +40,58 @@ def initGui(self): QgsApplication.processingRegistry().addProvider(self.provider) if self.iface: - # Add a button on the toolbar - tool_button = QToolButton() - icon = self.provider.icon() - default_action = QAction( - icon, "地域メッシュを作成", self.iface.mainWindow() - ) - default_action.triggered.connect( - lambda: execAlgorithmDialog("japanesegrid:creategridsquare", {}) - ) - tool_button.setDefaultAction(default_action) - - # ToolButton Menu - menu = QMenu() - tool_button.setMenu(menu) - tool_button.setPopupMode(QToolButton.MenuButtonPopup) - - action_grid_square = QAction( - icon, "地域メッシュを作成", self.iface.mainWindow() - ) - action_grid_square.triggered.connect( - lambda: execAlgorithmDialog("japanesegrid:creategridsquare", {}) - ) - menu.addAction(action_grid_square) - - action_legacy = QAction(icon, "国土基本図郭を作成", self.iface.mainWindow()) - action_legacy.triggered.connect( - lambda: execAlgorithmDialog("japanesegrid:createlegacygrid", {}) - ) - menu.addAction(action_legacy) - - action_estat = QAction( - icon, "地域メッシュ統計を読み込む", self.iface.mainWindow() - ) - action_estat.triggered.connect( - lambda: execAlgorithmDialog("japanesegrid:loadestatgridstats", {}) - ) - menu.addAction(action_estat) - - self.toolButtonAction = self.iface.addToolBarWidget(tool_button) + self.setup_algorithms_tool_button() + self._coord_panel = CoordinatePanel(self.iface) def unload(self): if self.iface: - self.iface.removeToolBarIcon(self.toolButtonAction) + self.teardown_algorithms_tool_button() + self._coord_panel.teardown() + QgsApplication.processingRegistry().removeProvider(self.provider) + + def setup_algorithms_tool_button(self): + """ツールバー上にアルゴリズムの呼び出しボタンを追加する""" + + # Add a button on the toolbar + tool_button = QToolButton() + icon = self.provider.icon() + default_action = QAction(icon, "地域メッシュを作成", self.iface.mainWindow()) + default_action.triggered.connect( + lambda: execAlgorithmDialog("japanesegrid:creategridsquare", {}) + ) + tool_button.setDefaultAction(default_action) + + # ToolButton Menu + menu = QMenu() + tool_button.setMenu(menu) + tool_button.setPopupMode(QToolButton.MenuButtonPopup) + + action_grid_square = QAction( + icon, "地域メッシュを作成", self.iface.mainWindow() + ) + action_grid_square.triggered.connect( + lambda: execAlgorithmDialog("japanesegrid:creategridsquare", {}) + ) + menu.addAction(action_grid_square) + + action_legacy = QAction(icon, "国土基本図郭を作成", self.iface.mainWindow()) + action_legacy.triggered.connect( + lambda: execAlgorithmDialog("japanesegrid:createlegacygrid", {}) + ) + menu.addAction(action_legacy) + + action_estat = QAction( + icon, "地域メッシュ統計を読み込む", self.iface.mainWindow() + ) + action_estat.triggered.connect( + lambda: execAlgorithmDialog("japanesegrid:loadestatgridstats", {}) + ) + menu.addAction(action_estat) + + self.toolButtonAction = self.iface.addToolBarWidget(tool_button) + + def teardown_algorithms_tool_button(self): + """ツールバー上のアルゴリズムの呼び出しボタンを削除する""" + + self.iface.removeToolBarIcon(self.toolButtonAction)