Skip to content

Commit

Permalink
0.1
Browse files Browse the repository at this point in the history
0.1
  • Loading branch information
fzahner authored Jun 21, 2022
2 parents 10af38a + 13915bf commit 36b108b
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 79 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
***

![](docs/screenshot.png)

Raster Cutter is a QGIS Plugin developed in Python and QT. It allows the user to select a raster layer from his project, set an extent and export the raster data within this extent to a `.jpg` or `.png` image file.

Additionally, the plugin can create a Worldfile and/or Lexocad sidecar file if desired. The plugin also supports re-projection into other Coordinate Reference Systems.
Expand Down
9 changes: 4 additions & 5 deletions metadata.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
[general]
name=Raster Cutter
qgisMinimumVersion=3.0
description=This Plugin allows the export of JPG files with a JPGL Sidecar file.
description=Export a raster layer as an image. Select extent, reproject, create Worldfile or Lexocad sidecar files.
version=0.1
author=IFS Institute for Software
email=feedback.ifs@ost.ch

about=Allows to export a JPG and corresponding JPGL File for use in LexoCAD.
about=This plugin allows the user to select a raster layer from his project, set an extent and export the raster data within this extent to a .jpg or .png image file. Additionally, the plugin can create a Worldfile and/or Lexocad sidecar file if desired. The plugin also supports re-projection into other Coordinate Reference Systems.

tracker=https://github.com/geometalab/qgis-raster-cutter/issues
repository=https://github.com/geometalab/qgis-raster-cutter
Expand All @@ -23,7 +23,7 @@ hasProcessingProvider=no
# changelog=

# Tags are comma separated with spaces allowed
tags=python
tags=python, raster, crs, gdal

homepage=https://github.com/geometalab/qgis-raster-cutter
category=Plugins
Expand All @@ -39,8 +39,7 @@ deprecated=False
# Check the documentation for more information.
# plugin_dependencies=

Category of the plugin: Raster, Vector, Database or Web
# category=
# Category of the plugin: Raster

# If the plugin can run on QGIS Server.
server=False
Expand Down
1 change: 1 addition & 0 deletions pyqgis.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ path %PATH%;%OSGEO4W_ROOT%\apps\qgis\bin
path %PATH%;%OSGEO4W_ROOT%\apps\grass\grass-7.4.0\lib
path %PATH%;C:\OSGeo4W3\apps\Qt5\bin
path %PATH%;C:\OSGeo4W3\apps\Python36\Scripts
path %PATH%;C:\Program Files\7-Zip

set PYTHONPATH=%PYTHONPATH%%OSGEO4W_ROOT%\apps\qgis\python

Expand Down
111 changes: 62 additions & 49 deletions raster_cutter.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,15 @@
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction
from qgis.core import (QgsProcessingParameterDefinition,
QgsProcessingParameters,
QgsProject,
QgsMapSettings,
from qgis.core import (QgsProject,
QgsMapLayer,
QgsRectangle,
QgsMapRendererCustomPainterJob,
QgsCoordinateReferenceSystem,
QgsCoordinateTransform,
QgsReferencedRectangle,
QgsProcessingContext,
QgsTaskManager,
QgsTask,
QgsProcessingAlgRunnerTask,
Qgis,
QgsProcessingFeedback,
QgsApplication,
QgsMessageLog,
QgsTaskManager,
QgsMessageLog,
QgsProcessingAlgRunnerTask,
QgsApplication,
QgsProcessingContext,
QgsProcessingFeedback,
QgsProject)

# Initialize Qt resources from file resources.py
Expand All @@ -59,12 +44,14 @@
from .raster_cutter_dialog import RasterCutterDialog
from .tooltips import *
import os.path
from osgeo import gdal, ogr
from osgeo import gdal

from PIL import Image # for reading dimensions of image

MESSAGE_CATEGORY = 'Raster Cutter'

gdal.UseExceptions()


# TODO help button
# TODO imports?
Expand Down Expand Up @@ -200,7 +187,7 @@ def initGui(self):
icon_path = ':/plugins/raster_cutter/icon.png'
self.add_action(
icon_path,
text=self.tr(u'Export JPG + JPGL'),
text=self.tr(u'Save raster image'),
callback=self.run,
parent=self.iface.mainWindow())

Expand All @@ -223,10 +210,9 @@ def run(self):
if self.first_start:
self.first_start = False
self.dlg = RasterCutterDialog()
self.dlg.file_dest_field.setFilePath(os.path.expanduser("~")) # set path to user home
self.dlg.file_dest_field.setFilePath(os.path.expanduser("~/Documents/raster.jpg")) # set path to user home
widget_init(self)


layers = [layer for layer in QgsProject.instance().mapLayers().values()]
if layers: # if there are layers in the project, we can set extent box extents and crs's
# extentbox init
Expand All @@ -236,9 +222,11 @@ def run(self):
self.dlg.proj_selection.setCrs(self.dlg.layer_combobox.currentLayer().crs())
self.dlg.extent_box.setCurrentExtent(currentExtent=self.iface.mapCanvas().extent(),
currentCrs=QgsProject.instance().crs())
on_lexocad_toggeled(self) # check if checkbox is still checked and apply CRS if needed (this ensures CRS is always correct)
on_lexocad_toggeled(
self) # check if checkbox is still checked and apply CRS if needed (this ensures CRS is always correct)
globals()['self'] = self # for throwing an error without having to pass it around
add_tooltips(self)
select_current_layer(self)

# show the dialog
self.dlg.show()
Expand All @@ -254,7 +242,7 @@ def run(self):
error_message(error)
return
src, error = open_dataset(data_provider)
x_res, y_res=src.RasterXSize, src.RasterYSize
x_res, y_res = src.RasterXSize, src.RasterYSize
print(str(x_res) + " " + str(y_res))
if error is not None:
error_message(error)
Expand Down Expand Up @@ -287,7 +275,7 @@ def run(self):
extent_win_string=get_extent_win(self),
generate_lexocad=self.dlg.lexocad_checkbox.isChecked(),
generate_worldfile=self.dlg.worldfile_checkbox.isChecked(),
target_res={"x": self.dlg.x_res_box.value(), "y": self.dlg.x_res_box.value()})
target_res=get_target_res(self))
QgsApplication.taskManager().addTask(process_task)
QgsMessageLog.logMessage('Starting process...', MESSAGE_CATEGORY, Qgis.Info)

Expand All @@ -296,28 +284,44 @@ def widget_init(self):
# input layer init
self.dlg.layer_combobox.setShowCrs(True)
self.dlg.lexocad_checkbox.toggled.connect(lambda: on_lexocad_toggeled(self))
self.dlg.layer_combobox.setLayer(self.iface.layerTreeView().selectedLayers()[0]) # select the selected layer in the dropdown
self.dlg.res_checkbox.toggled.connect(lambda: on_resolution_checkbox_toggled(self))
on_resolution_checkbox_toggled(self)
self.dlg.button_box.helpRequested.connect(lambda: help_mode())


def on_resolution_checkbox_toggled(self):
if self.dlg.res_checkbox.isChecked():
self.dlg.x_res_box.setEnabled(True)
self.dlg.y_res_box.setEnabled(True)
else:
self.dlg.x_res_box.setEnabled(False)
self.dlg.y_res_box.setEnabled(False)


def on_lexocad_toggeled(self):
if self.dlg.lexocad_checkbox.isChecked():
self.dlg.proj_selection.setEnabled(False)
self.dlg.proj_selection.setCrs(QgsCoordinateReferenceSystem.fromEpsgId(2056))
else:
self.dlg.proj_selection.setEnabled(True)

def select_current_layer(self):
# sets the layer dropdown to the selected layer in the QGIS layer manager, if one is selected
if self.iface.layerTreeView().selectedLayers():
self.dlg.layer_combobox.setLayer(
self.iface.layerTreeView().selectedLayers()[0]) # select the selected layer in the dropdown

def get_target_projection(self):
return self.dlg.proj_selection.crs()


def get_extent_win(self):
e = self.dlg.extent_box.outputExtent()
return "%d %d %d %d" % (e.xMinimum(), e.yMaximum(), e.xMaximum(), e.yMinimum())
return f"{e.xMinimum()} {e.yMaximum()} {e.xMaximum()} {e.yMinimum()}"


def process(task, src, directory_url, dest_srs, format_string, extent_win_string, options_string, generate_lexocad: bool,
def process(task, src, directory_url, dest_srs, format_string, extent_win_string, options_string,
generate_lexocad: bool,
generate_worldfile: bool, target_res: {"x": float, "y": float}):
QgsMessageLog.logMessage('Cropping raster...', MESSAGE_CATEGORY, Qgis.Info)
cropped = crop('/vsimem/cropped.tif', src, extent_win_string, dest_srs)
Expand All @@ -337,6 +341,7 @@ def process(task, src, directory_url, dest_srs, format_string, extent_win_string
src = None
warped = None
manage_files(generate_lexocad, generate_worldfile, directory_url)
QgsMessageLog.logMessage('Done!', MESSAGE_CATEGORY, Qgis.Info)
return translated


Expand All @@ -351,7 +356,9 @@ def manage_files(generate_lexocad, generate_worldfile, dir_url):


def open_dataset(data_provider):
if data_provider.name() == "wms":
QgsMessageLog.logMessage(data_provider.name(), MESSAGE_CATEGORY, Qgis.Info)
QgsMessageLog.logMessage(data_provider.dataSourceUri(), MESSAGE_CATEGORY, Qgis.Info)
if data_provider.name() == "wms" or "xyz":
args = data_provider.dataSourceUri().split("&")
for arg in args:
if arg.find("url=") is not -1:
Expand All @@ -368,17 +375,16 @@ def open_dataset(data_provider):

return gdal.Open(gdal_string, gdal.GA_ReadOnly), None


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))
return gdal.Translate(out, src, options="-projwin %s, -projwin_srs %s, -outsize 2000 0, -r bilinear" % (
extent_win_string, extent_srs))


def warp(out, src, dst_srs, extent_win_string, target_res):
QgsMessageLog.logMessage(dst_srs, MESSAGE_CATEGORY, Qgis.Info)
options_string = "-t_srs %s, " % dst_srs
# options_string += "-te " + extent_win_string + ", "
# options_string += "-te_srs EPSG:4326, " # TODO Remove
# options_string += "-ts 3000 0, "
options_string += "-tr %s %s" % (target_res['x'], target_res['y'])
QgsMessageLog.logMessage(options_string, MESSAGE_CATEGORY, Qgis.Info)
if target_res['x'] > 0 and target_res['y'] > 0: # if no custom target res is defined, these should both be 0
options_string += "-tr %s %s" % (target_res['x'], target_res['y'])
return gdal.Warp(out, src, options=options_string)


Expand Down Expand Up @@ -412,25 +418,21 @@ def generate_lexocad_files(directoryUrl):
xMinimum = float(lines[4])
yMinimum = float(lines[5]) - height
with open(directoryUrl + "l", 'w') as f:
f.write(
str(xMinimum) + "\n" +
str(yMinimum) + "\n" +
str(float(width)) + "\n" +
str(float(height)) + "\n" +
"\n" +
"# cadwork swisstopo" + "\n" +
"# " + str(xMinimum) + " " + str(yMinimum) + "\n" +
"# " + str(width) + " " + str(height) + "\n" +
"# projection: EPSG:2056 - CH1903+ / LV95"
)
f.write(f"{str(xMinimum)}"
f"{str(yMinimum)}"
f"str(float(width))"
f"str(float(height))"
f"# cadwork swisstopo"
f"# {str(xMinimum)} {str(yMinimum)}"
f"# {str(width)} {str(height)}"
f"# projection: EPSG:2056 - CH1903+ / LV95"
)


def delete_world_file(directory_url):
worldfile_path = get_worldfile_url_from_dir(directory_url)
if os.path.exists(worldfile_path):
os.remove(worldfile_path)
else:
raise Exception("The file does not exist")


def get_worldfile_url_from_dir(directory_url):
Expand All @@ -439,12 +441,22 @@ def get_worldfile_url_from_dir(directory_url):
worldfile_path = directory_url[:index]
worldfile_path += ".wld"
else:
raise Exception("Could not find . in path")
raise Exception("Could not find generate worldfile file name")
return worldfile_path


def pre_launch_checks():
pass


def get_target_res(self):
if self.dlg.res_checkbox.isChecked():
return {'x': self.dlg.x_res_box.value(),
'y': self.dlg.y_res_box.value()}
else:
return {'x': 0, 'y': 0}


def pre_process_checks(layer, data_provider):
if layer.type() is QgsMapLayer.VectorLayer:
return "Provided Layer is a vector layer. Please select a raster layer."
Expand All @@ -454,6 +466,7 @@ def pre_process_checks(layer, data_provider):
return "Please select a valid raster layer."
return None


def error_message(message):
self = globals()['self']
QgsMessageLog.logMessage(message, MESSAGE_CATEGORY, Qgis.Critical)
Expand All @@ -463,4 +476,4 @@ def error_message(message):
def help_mode():
QWhatsThis.enterWhatsThisMode()
self = globals()['self']
print(self.dlg.extent_box.currentCrs())
print(self.dlg.extent_box.currentCrs())
Loading

0 comments on commit 36b108b

Please sign in to comment.