Skip to content

Commit

Permalink
0.3
Browse files Browse the repository at this point in the history
0.3
  • Loading branch information
fzahner authored Jun 27, 2022
2 parents c80678c + cda8bfc commit ef8b6dd
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 45 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,18 @@ Additionally, the plugin can create a Worldfile and/or Lexocad sidecar file if d

### Using this tool

*Work in Progress*
1. Start QGIS - install QGIS plugin 'Raster Cutter' if necessary - and open "New Project".
2. Set the QGIS project to the CRS "EPSG:2056" (= Swiss coordinate reference system CH/LV95) (bottom right).
3. If necessary, load background/base map (e.g. OpenStreetMap or MapGeoAdmin). Make sure that the CRS is still "EPSG:2056".
4. Load raster file/data source (= input layer) (WMS, WMTS/XYZ/TMS, GeoTIFF) and zoom to the desired section.
5. Open the dialog of the 'Raster Cutter' plugin and define the necessary parameters:
1. choose the input layer (if not already selected)
2. set the extent by clicking on "Map Canvas Extent".
3. Set the path and name of the output and the output format (GeoTIFF, PNG or JPG).
6. Set additional parameters if necessary (for advanced users): CRS and Output resolution.
7. finished (load and view with QGIS or Lexocad).

(Translated from https://md.coredump.ch/2H-jGnDTSbuBk7ai0xWIxA?view#Bedienungsanleitung)

### Set up local developement

Expand Down
Binary file modified docs/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
81 changes: 56 additions & 25 deletions raster_cutter.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from qgis.core import (QgsProject,
QgsMapLayer,
QgsCoordinateReferenceSystem,
QgsMapLayerProxyModel,
QgsTask,
Qgis,
QgsRasterLayer,
Expand Down Expand Up @@ -263,15 +264,17 @@ def run(self):
format_string = ""

# Set format string and format specific settings
# worldfile is always generated for non georeferenced types, as wished by stefan
if directory_url.endswith(".jpg"):
format_string = "JPEG"
# enables progressive jpg creation (https://gdal.org/drivers/raster/jpeg.html#creation-options)
# options_string += "-co PROGRESSIVE=ON, "
options_string += "-co WORLDFILE=YES, "
elif directory_url.endswith(".png"):
format_string = "PNG"

# worldfile is always generated, as wished by stefan
options_string += "-co WORLDFILE=YES, "
options_string += "-co WORLDFILE=YES, "
elif directory_url.endswith(".tif"):
pass

# create the task which contains the actual calculations and add the task to the task manager, starting it
# the task is saved in a global variable to avoid a bug (https://gis.stackexchange.com/questions/390652/qgstask-fromfunction-not-running-on-finished-method-unless-an-exception-is-raise)
Expand All @@ -285,9 +288,9 @@ def run(self):
options_string=options_string,
extent_win_string=get_extent_win(self),
generate_lexocad=self.dlg.lexocad_checkbox.isChecked(),
generate_worldfile=True,
add_to_map=self.dlg.add_to_map_checkbox.isChecked(),
target_resolution=get_target_resolution(self))
target_resolution=get_target_resolution(self),
resampling_method=get_resampling_method(self))
QgsApplication.taskManager().addTask(globals()['process_task'])
QgsMessageLog.logMessage('Starting process...', MESSAGE_CATEGORY, Qgis.Info)

Expand All @@ -297,8 +300,14 @@ def widget_init(self):
self.dlg.layer_combobox.setShowCrs(True)
self.dlg.lexocad_checkbox.toggled.connect(lambda: on_lexocad_toggled(self))
self.dlg.resolution_checkbox.toggled.connect(lambda: on_resolution_checkbox_toggled(self))
on_resolution_checkbox_toggled(self)
self.dlg.file_dest_field.fileChanged.connect(lambda: on_tif_selected(self))
self.dlg.button_box.helpRequested.connect(lambda: help_mode())
self.dlg.layer_combobox.setFilters(QgsMapLayerProxyModel.RasterLayer)

# also check states when dialog is opened
on_resolution_checkbox_toggled(self)
on_lexocad_toggled(self)
on_tif_selected(self)


# enables/disables x & y resolution spin boxes depending on resolution checkbox state
Expand All @@ -319,6 +328,16 @@ def on_lexocad_toggled(self):
else:
self.dlg.proj_selection.setEnabled(True)

def on_tif_selected(self):
# enables/disables the lexocad checkbox depending on if output file is a geotiff
path = self.dlg.file_dest_field.filePath()
filename, file_extension = os.path.splitext(path)
if file_extension == ".tif":
self.dlg.lexocad_checkbox.setChecked(False)
self.dlg.lexocad_checkbox.setEnabled(False)
else:
self.dlg.lexocad_checkbox.setEnabled(True)


# sets the layer dropdown to the selected layer in the QGIS layer manager, if one is selected
def select_current_layer(self):
Expand All @@ -336,21 +355,30 @@ def get_extent_win(self):
e = self.dlg.extent_box.outputExtent()
return f"{e.xMinimum()} {e.yMaximum()} {e.xMaximum()} {e.yMinimum()}"

def get_resampling_method(self):
if self.dlg.nearest_neighbour_radio_button.isChecked():
return "near"
elif self.dlg.cubic_spline_radio_button.isChecked():
return "cubicspline"
else:
error_message("Could not get resampling method.")



# this is where all calculations actually happen
def process(task, src, iface, directory_url, dest_srs, format_string, extent_win_string, options_string,
generate_lexocad: bool,
generate_worldfile: bool, add_to_map: bool, target_resolution: {"x": float, "y": float}):
add_to_map: bool, target_resolution: {"x": float, "y": float}, resampling_method):
# Crop raster, so that only the needed parts are reprojected, saving processing time
QgsMessageLog.logMessage('Cropping raster (possibly downloading)...', MESSAGE_CATEGORY, Qgis.Info)
cropped = crop('/vsimem/cropped.tif', src, extent_win_string, dest_srs)
cropped = crop('/vsimem/cropped.tif', src, extent_win_string, dest_srs, resampling_method)
if task.isCanceled(): # check if task was cancelled between each step
stopped(task)
return None

# reproject and set resolution
QgsMessageLog.logMessage('Warping raster...', MESSAGE_CATEGORY, Qgis.Info)
warped = warp('/vsimem/warped.tif', cropped, dest_srs, extent_win_string, target_resolution)
warped = warp('/vsimem/warped.tif', cropped, dest_srs, target_resolution, resampling_method)
if task.isCanceled():
stopped(task)
return None
Expand All @@ -375,20 +403,19 @@ def process(task, src, iface, directory_url, dest_srs, format_string, extent_win
file_name_no_ext, file_ext = os.path.splitext(file)
file_name = f"{file_name_no_ext} cropped"

manage_files(generate_lexocad, generate_worldfile, directory_url)
manage_files(generate_lexocad, add_to_map, directory_url)

return {"ds": translated, "iface": iface, "path": translated.GetDescription(), "file_name": file_name}


# generate lexocad file and delete worldfile if wanted
def manage_files(generate_lexocad, generate_worldfile, dir_url):
if not generate_worldfile and not generate_lexocad:
return
QgsMessageLog.logMessage("Creating sidecar files", MESSAGE_CATEGORY, Qgis.Info)
# generate lexocad file and delete worldfile and .aux.xml if needed
def manage_files(generate_lexocad: bool, add_to_map: bool, dir_url):
QgsMessageLog.logMessage("Managing sidecar files", MESSAGE_CATEGORY, Qgis.Info)
if generate_lexocad:
generate_lexocad_files(dir_url)
if not generate_worldfile and generate_lexocad:
delete_world_file(dir_url)
if not add_to_map:
delete_aux_xml_file(dir_url)

delete_tms_xml() # is only necessary if layer was XYZ, but executes always


Expand Down Expand Up @@ -441,19 +468,23 @@ def delete_tms_xml():
if os.path.exists(temp_file_path):
os.remove(temp_file_path)

def delete_aux_xml_file(path):
aux_xml_file_path = path + '.aux.xml'
os.remove(aux_xml_file_path)


def get_file_path(file_name):
return os.path.join(os.path.dirname(__file__), file_name)

def crop(out, src, extent_win_string, extent_srs):
return gdal.Translate(out, src, options="-projwin %s, -projwin_srs %s, -outsize 2000 0, -r bilinear" % (
extent_win_string, extent_srs))

def crop(out, src, extent_win_string, extent_srs, resampling_method):
return gdal.Translate(out, src, options=f"-projwin {extent_win_string}, -projwin_srs {extent_srs}, -outsize 2000 0, -r {resampling_method}")

def warp(out, src, dst_srs, extent_win_string, target_resolution):
options_string = "-t_srs %s, " % dst_srs
def warp(out, src, dst_srs, target_resolution, resampling_method):
options_string = f"-t_srs {dst_srs}, "
if target_resolution['x'] > 0 and target_resolution[
'y'] > 0: # if no custom target res is defined, these should both be 0
options_string += "-tr %s %s" % (target_resolution['x'], target_resolution['y'])
options_string += f"-tr {target_resolution['x']} {target_resolution['y']}, "
options_string += f"-r {resampling_method}"
return gdal.Warp(out, src, options=options_string)


Expand Down Expand Up @@ -557,7 +588,7 @@ def error_message(message):
self = globals()['self']
QgsMessageLog.logMessage(message, MESSAGE_CATEGORY, Qgis.Critical)
self.iface.messageBar().pushMessage("Error", message, level=Qgis.Critical)
raise Exception(message)
# raise Exception(message)


# enter WhatsThis mode
Expand Down
75 changes: 65 additions & 10 deletions raster_cutter_dialog_base.ui
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>610</width>
<height>479</height>
<height>488</height>
</rect>
</property>
<property name="windowTitle">
Expand All @@ -24,9 +24,9 @@
<property name="geometry">
<rect>
<x>20</x>
<y>440</y>
<y>450</y>
<width>581</width>
<height>41</height>
<height>31</height>
</rect>
</property>
<property name="orientation">
Expand All @@ -42,7 +42,7 @@
<x>19</x>
<y>19</y>
<width>581</width>
<height>401</height>
<height>424</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
Expand Down Expand Up @@ -124,7 +124,7 @@
<item>
<widget class="QgsFileWidget" name="file_dest_field">
<property name="filter">
<string>PNG (*.png);;JPG (*.jpg)</string>
<string>PNG (*.png);;JPG (*.jpg);;GeoTIFF (*.tif)</string>
</property>
<property name="storageMode">
<enum>QgsFileWidget::SaveFile</enum>
Expand Down Expand Up @@ -278,26 +278,81 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="resampling_algorithm_label">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="text">
<string>Resampling algorithm</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="nearest_neighbour_radio_button">
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Nearest Neighbour</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="cubic_spline_radio_button">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>100</height>
</size>
</property>
<property name="text">
<string>Cubic Spline</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QCheckBox" name="add_to_map_checkbox">
<property name="geometry">
<rect>
<x>130</x>
<x>150</x>
<y>450</y>
<width>170</width>
<height>20</height>
<width>150</width>
<height>31</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>170</width>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>170</width>
<width>150</width>
<height>16777215</height>
</size>
</property>
Expand Down
Loading

0 comments on commit ef8b6dd

Please sign in to comment.