diff --git a/scale.py b/scale.py new file mode 100644 index 0000000..b1f920f --- /dev/null +++ b/scale.py @@ -0,0 +1,121 @@ +from qgis.core import ( + QgsProject, + QgsRectangle, + QgsPoint, + QgsScaleCalculator, + QgsCoordinateReferenceSystem, + QgsCoordinateTransform, +) +from qgis.utils import iface + + +def get_scale_from_canvas() -> float: + """get scale from map canvas. + For web mercator projection (EPSG:3857) case, + calculate scale with map extent correction according to scale factor""" + + if QgsProject.instance().crs().authid() == "EPSG:3857": + canvas = iface.mapCanvas() + # get map canvas center coordinates in geographic + transform = QgsCoordinateTransform( + canvas.mapSettings().destinationCrs(), + QgsCoordinateReferenceSystem("EPSG:4326"), + QgsProject.instance(), + ) + center_geographic = transform.transform(canvas.center()) + center_point = QgsPoint(center_geographic.x(), center_geographic.y()) + + # calculate scale_factor from center_point + # https://en.wikipedia.org/wiki/Mercator_projection#Scale_factor + scale_factor_x = ( + QgsProject.instance().crs().factors(center_point).parallelScale() + ) + scale_factor_y = ( + QgsProject.instance().crs().factors(center_point).meridionalScale() + ) + + # determine extension corrected with scale factor + extent = canvas.extent() + delta_x = (extent.width() * scale_factor_x) - extent.width() + delta_y = (extent.height() * scale_factor_y) - extent.height() + corrected_extent = QgsRectangle( + extent.xMinimum() - delta_x / 2, + extent.yMinimum() - delta_y / 2, + extent.xMaximum() + delta_x / 2, + extent.yMaximum() + delta_y / 2, + ) + + # calculate scale based on corrected map extent + scale_calculator = QgsScaleCalculator( + canvas.mapSettings().outputDpi(), canvas.mapUnits() + ) + return scale_calculator.calculate(corrected_extent, canvas.size().width()) + else: + return iface.mapCanvas().scale() + + +def set_map_extent_from(scale: float, crs: str): + """Calculate map extent according to scale and crs + input: scale and crs + action: in case of webmercator update map canvas extent with correction + according to scale factor + for other cases update map canvas with zoom to scale action + """ + if crs == "EPSG:3857": + # calculate extent with scale factor correction + + canvas = iface.mapCanvas() + canvas_width_px = canvas.width() + canvas_height_px = canvas.height() + canvas_dpi = canvas.mapSettings().outputDpi() + + # Get the center point of the canvas extent + transform = QgsCoordinateTransform( + canvas.mapSettings().destinationCrs(), + QgsCoordinateReferenceSystem("EPSG:4326"), + QgsProject.instance(), + ) + center_geographic = transform.transform(canvas.center()) + center_point = QgsPoint(center_geographic.x(), center_geographic.y()) + + # calculate scale_factor from center_point + # https://en.wikipedia.org/wiki/Mercator_projection#Scale_factor + scale_factor_x = ( + QgsProject.instance().crs().factors(center_point).parallelScale() + ) + scale_factor_y = ( + QgsProject.instance().crs().factors(center_point).meridionalScale() + ) + + # Calculate map units per pixel + meter_per_inch = 0.0254 # 0.0254m in 1 inch + map_units_per_pixel = (meter_per_inch / canvas_dpi) * scale + + # Calculate extent width and height in map units + extent_width_map_units = canvas_width_px * map_units_per_pixel / scale_factor_x + extent_height_map_units = ( + canvas_height_px * map_units_per_pixel / scale_factor_y + ) + + # Calculate the corrected extent + canvas_center = canvas.extent().center() + corrected_extent = QgsRectangle( + canvas_center.x() - extent_width_map_units / 2, + canvas_center.y() - extent_height_map_units / 2, + canvas_center.x() + extent_width_map_units / 2, + canvas_center.y() + extent_height_map_units / 2, + ) + + # update map canvas + canvas.setExtent(corrected_extent) + canvas.refresh() + + # Fix setExtent bug + # Re-set extent if canvas scale is set same as Webmercator scale + if round(iface.mapCanvas().scale()) == scale: + canvas.setExtent(corrected_extent) + canvas.refresh() + + else: + # zoom to scale for other crs + iface.mapCanvas().zoomScale(scale) diff --git a/ui/main_dialog.py b/ui/main_dialog.py index ac8fb3a..823588c 100644 --- a/ui/main_dialog.py +++ b/ui/main_dialog.py @@ -22,7 +22,8 @@ from translator.vector.label import generate_label_vector from ui.progress_dialog import ProgressDialog from translator.thread import ProcessingThread -from utils import write_json, get_tempdir, get_scale +from utils import write_json, get_tempdir +from scale import get_scale_from_canvas, set_map_extent_from class MainDialog(QDialog): @@ -50,6 +51,12 @@ def init_ui(self): ] ) + # perform update_ui_scale when it's true + # become false when UI scale widget is edited by user + self.enable_update_ui_scale = True + + # set canvas scale when user input scale in ui + self.ui.scale_widget.scaleChanged.connect(self._zoom_canvas_from_scale) # calculate export scale and show to ui self._update_ui_scale() # update export scale shown in ui when change map extent @@ -143,7 +150,7 @@ def _run(self): self.ui.mExtentGroupBox.outputExtent().xMaximum(), self.ui.mExtentGroupBox.outputExtent().yMaximum(), ], - "scale": get_scale(), + "scale": get_scale_from_canvas(), "layers": layers_processed_successfully, # layer_0,2,5.. "assets_path": "assets", } @@ -279,4 +286,31 @@ def _process_node_recursive(self, node, parent_node): self._process_node_recursive(child, item) def _update_ui_scale(self): - self.ui.label_scale_value.setText(str(get_scale())) + # do not update when enable_update_ui_scale is False + # in case of canvas is calculated from scale widget + if not self.enable_update_ui_scale: + return + + # disable auto ui scale update + try: + self.ui.scale_widget.scaleChanged.disconnect() + except TypeError: + # when signal is not connected + pass + + # update ui scale + self.ui.scale_widget.setScale(get_scale_from_canvas()) + # reactivate auto ui scale update + self.ui.scale_widget.scaleChanged.connect(self._zoom_canvas_from_scale) + + def _zoom_canvas_from_scale(self): + # disable temporary scale auto-calculation when extent changed + self.enable_update_ui_scale = False + + # update canvas + set_map_extent_from( + scale=self.ui.scale_widget.scale(), crs=QgsProject.instance().crs().authid() + ) + + # reactive scale auto-calculation when extent changed + self.enable_update_ui_scale = True diff --git a/ui/main_dialog.ui b/ui/main_dialog.ui index 51e7274..42bd288 100644 --- a/ui/main_dialog.ui +++ b/ui/main_dialog.ui @@ -43,10 +43,7 @@ - - - - + @@ -55,22 +52,19 @@ - - - - 1: - - - - - - 1 + + + true + + + + @@ -109,6 +103,11 @@ QWidget
qgsfilewidget.h
+ + QgsScaleWidget + QWidget +
qgsscalewidget.h
+
diff --git a/utils.py b/utils.py index ee9f7aa..d0d8f62 100644 --- a/utils.py +++ b/utils.py @@ -6,12 +6,6 @@ from qgis.core import ( QgsRenderContext, QgsUnitTypes, - QgsProject, - QgsRectangle, - QgsPoint, - QgsScaleCalculator, - QgsCoordinateReferenceSystem, - QgsCoordinateTransform, ) from qgis.utils import iface @@ -61,48 +55,3 @@ def get_tempdir(output_dir: str) -> str: os.mkdir(os.path.join(output_dir, temp_dir_path)) return os.path.join(output_dir, temp_dir_path) - - -def get_scale() -> float: - """get scale from map canvas. - For web mercator projection (EPSG:3857) case, - calculate scale with map extent correction according to scale factor""" - - if QgsProject.instance().crs().authid() == "EPSG:3857": - canvas = iface.mapCanvas() - # get map canvas center coordinates in geographic - transform = QgsCoordinateTransform( - canvas.mapSettings().destinationCrs(), - QgsCoordinateReferenceSystem("EPSG:4326"), - QgsProject.instance(), - ) - center_geographic = transform.transform(canvas.center()) - center_point = QgsPoint(center_geographic.x(), center_geographic.y()) - - # calculate scale_factor from center_point - # https://en.wikipedia.org/wiki/Mercator_projection#Scale_factor - scale_factor_x = ( - QgsProject.instance().crs().factors(center_point).parallelScale() - ) - scale_factor_y = ( - QgsProject.instance().crs().factors(center_point).meridionalScale() - ) - - # determine extension corrected with scale factor - extent = canvas.extent() - delta_x = (extent.width() * scale_factor_x) - extent.width() - delta_y = (extent.height() * scale_factor_y) - extent.height() - corrected_extent = QgsRectangle( - extent.xMinimum() - delta_x / 2, - extent.yMinimum() - delta_y / 2, - extent.xMaximum() + delta_x / 2, - extent.yMaximum() + delta_y / 2, - ) - - # calculate scale based on corrected map extent - scale_calculator = QgsScaleCalculator( - canvas.mapSettings().outputDpi(), canvas.mapUnits() - ) - return scale_calculator.calculate(corrected_extent, canvas.size().width()) - else: - return iface.mapCanvas().scale()