# -*- coding: utf-8 -*-
# ***********************************************************************
# License:
# 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.
#
# ***********************************************************************
from math import ceil, floor
from statistics import mean
from qgis.core import (QgsProcessing, QgsProcessingUtils, QgsFeature, QgsRectangle, QgsPointXY, QgsGeometry,
QgsMarkerSymbol, QgsLineSymbol, QgsField,
QgsProject, QgsLayerTreeLayer,
QgsVectorLayer,
QgsFeatureSink,
QgsProcessingException,
QgsProcessingAlgorithm,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink,
QgsProcessingParameterDistance,
QgsProcessingParameterNumber,
QgsProcessingParameterEnum,
QgsProcessingParameterVectorDestination,
)
from qgis.PyQt.QtCore import QCoreApplication, QVariant
# from qgis import processing
import processing
[docs]
class SeismicGridsAlgorithm(QgsProcessingAlgorithm):
"""
This is an example algorithm that takes a vector layer and
creates a new identical one.
It is meant to be used as an example of how to create your own
algorithms and explain methods and variables used to do it. An
algorithm like this will be available in all elements, and there
is not need for additional work.
All Processing algorithms should extend the QgsProcessingAlgorithm
class.
"""
# Constants used to refer to parameters and outputs. They will be
# used when calling the algorithm from another algorithm, or when
# calling from the QGIS console.
INPUT_R = 'INPUT_R'
INPUT_S = 'INPUT_S'
OUTPUT_SL = 'OUTPUT_SL'
OUTPUT_RL = 'OUTPUT_RL'
OUTPUT_RP = 'OUTPUT_RP'
RPI = 'RPI'
RLI = 'RLI'
SLI = 'SLI'
AZIMUTH = 'AZIMUTH'
STAGGER = 'STAGGER'
_shift_by_x = None
_shift_by_y = None
_min_x_for_halo = None
_max_x_for_halo = None
receiver_points_id = None
receiver_lines_id = None
source_lines_id = None
[docs]
def tr(self, string):
"""
Returns a translatable string with the self.tr() function.
"""
return QCoreApplication.translate('Processing', string)
[docs]
def createInstance(self):
return SeismicGridsAlgorithm()
[docs]
def name(self):
"""
Returns the algorithm name, used for identifying the algorithm. This
string should be fixed for the algorithm, and must not be localised.
The name should be unique within each provider. Names should contain
lowercase alphanumeric characters only and no spaces or other
formatting characters.
"""
return 'seismicgrids'
[docs]
def displayName(self):
"""
Returns the translated algorithm name, which should be used for any
user-visible display of the algorithm name.
"""
return self.tr('1 Generate Seismic Grids')
[docs]
def group(self):
"""
Returns the name of the group this algorithm belongs to. This string
should be localised.
"""
return self.tr('Seismic scripts')
[docs]
def groupId(self):
"""
Returns the unique ID of the group this algorithm belongs to. This
string should be fixed for the algorithm, and must not be localised.
The group id should be unique within each provider. Group id should
contain lowercase alphanumeric characters only and no spaces or other
formatting characters.
"""
return 'seismicscripts'
[docs]
def shortHelpString(self):
"""
Returns a localised short helper string for the algorithm. This string
should provide a basic description about what the algorithm does and the
parameters and outputs associated with it..
"""
return self.tr("""
This tool generates a seismic grid based on the polygons in the specified layers, combined with your input parameters.
The process creates three temporary layers in your current QGIS project:
a. Source lines: A grid within the source polygon, with lines spaced according to the Source Line Interval (SLI, in meters).
b. Receiver lines: A grid within the receiver polygon, with lines spaced according to the Receiver Line Interval (RLI, in meters).
c. Receiver points: Points placed along the receiver lines at intervals defined by the Receiver Point Interval (RPI, in meters). These are organized either:
- Linearly (if STAGGER is set to NO), or
- Triangularly (if STAGGER is set to YES).
""")
[docs]
def initAlgorithm(self, config=None):
"""
Here we define the inputs and output of the algorithm, along
with some other properties.
"""
# INPUTS & parameters
self.addParameter(
QgsProcessingParameterFeatureSource(self.INPUT_R, self.tr('Receiver polygon'),
[QgsProcessing.TypeVectorPolygon]))
self.addParameter(
QgsProcessingParameterFeatureSource(self.INPUT_S, self.tr('Source polygon'),
[QgsProcessing.TypeVectorPolygon]))
self.addParameter(
QgsProcessingParameterDistance('RLI', self.tr('RLI'), defaultValue=400.0, parentParameterName='INPUT_R'))
self.addParameter(
QgsProcessingParameterDistance('RPI', self.tr('RPI'), defaultValue=400.0, parentParameterName='INPUT_R'))
self.addParameter(
QgsProcessingParameterDistance('SLI', self.tr('SLI'), defaultValue=50.0, parentParameterName='INPUT_R'))
self.addParameter(
QgsProcessingParameterNumber('AZIMUTH', self.tr('AZIMUTH'), QgsProcessingParameterNumber.Double,
defaultValue=0.0, minValue=-360, maxValue=360))
self.addParameter(
QgsProcessingParameterEnum('STAGGER', self.tr('STAGGER'), ['YES', 'NO'])
)
# OUTPUTS
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_RP, self.tr('RP')))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_SL, self.tr('Source lines')))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_RL, self.tr('Receiver lines')))
[docs]
def processAlgorithm(self, parameters, context, feedback):
"""
Here is where the processing itself takes place.
"""
# r_poly = self.parameterAsSource(parameters, self.INPUT_R, context)
# if r_poly is None:
# raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT_R))
# feedback.pushInfo(r_poly.crs())
# Send some information to the user
# feedback.pushInfo('Test')
self.receiver_points_id, receiver_points = self.process_receiver_points(parameters, context, feedback)
self.receiver_lines_id = self.process_receiver_lines(receiver_points, parameters, context, feedback)
self.source_lines_id = self.process_source_lines(parameters, context, feedback)
return {self.OUTPUT_RP: self.receiver_points_id, self.OUTPUT_RL: self.receiver_lines_id, self.OUTPUT_SL: self.source_lines_id}
[docs]
def process_receiver_points(self, parameters, context, feedback):
r_poly = self.parameterAsSource(parameters, self.INPUT_R, context)
if r_poly is None:
raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT_R))
if r_poly.featureCount() > 1:
raise Exception('Receiver polygon should have only one feature')
r_poly_feature = next(r_poly.getFeatures()) # should have only one feature
centroid = r_poly_feature.geometry().centroid().asPoint().toString()
rotated_r_poly = processing.run('native:rotatefeatures', {
'INPUT': parameters[self.INPUT_R],
'ANGLE': -self.parameterAsDouble(parameters, self.AZIMUTH, context),
'ANCHOR': centroid,
'OUTPUT': 'memory:RPRotatedRPoly',
}, context=context, feedback=feedback)['OUTPUT']
rotated_s_poly = processing.run('native:rotatefeatures', {
'INPUT': parameters[self.INPUT_S],
'ANGLE': -self.parameterAsDouble(parameters, self.AZIMUTH, context),
'ANCHOR': centroid,
'OUTPUT': 'memory:RPRotatedSPoly',
}, context=context, feedback=feedback)['OUTPUT']
crs = QgsProject.instance().crs().authid()
extent = self._get_grid_initial_extent(rotated_s_poly, rotated_r_poly, parameters, context, feedback)
extent_string = '{},{},{},{} [{}]'.format(extent.xMinimum(), extent.xMaximum(), extent.yMinimum(),
extent.yMaximum(), crs)
gridded_points = processing.run('qgis:creategrid', {
'TYPE': 0,
'EXTENT': extent_string,
'HSPACING': self.parameterAsDouble(parameters, self.RLI, context),
'VSPACING': self.parameterAsDouble(parameters, self.RPI, context),
'CRS': 'ProjectCrs',
'OUTPUT': 'memory:RPGrid',
}, context=context, feedback=feedback)['OUTPUT']
# Compute amounts by which to shift the grids so that it is centered in x and y in the middle of the
# receiver area
min_x_r_poly = rotated_r_poly.extent().xMinimum()
max_x_r_poly = rotated_r_poly.extent().xMaximum()
min_y_r_poly = rotated_r_poly.extent().yMinimum()
max_y_r_poly = rotated_r_poly.extent().yMaximum()
rli = self.parameterAsDouble(parameters, self.RLI, context)
rpi = self.parameterAsDouble(parameters, self.RPI, context)
width_of_r_poly = max_x_r_poly - min_x_r_poly
gap_each_end_x = (width_of_r_poly - rli * floor(width_of_r_poly / rli)) / 2
height_of_r_poly = max_y_r_poly - min_y_r_poly
gap_each_end_y = (height_of_r_poly - rpi * floor(height_of_r_poly / rpi)) / 2
self._shift_by_x = gap_each_end_x
self._shift_by_y = -gap_each_end_y
translated_points = processing.run('native:translategeometry', {
'INPUT': gridded_points,
'DELTA_X': self._shift_by_x,
'DELTA_Y': self._shift_by_y,
'DELTA_Z': 0,
'DELTA_M': 0,
'OUTPUT': 'memory:RPGridShifted',
}, context=context, feedback=feedback)['OUTPUT']
del gridded_points
if self.parameterAsEnum(parameters, self.STAGGER, context) == 0: # 0 = "YES", 1 = "NO"
min_x = translated_points.extent().xMinimum()
every_other_line = processing.run('native:extractbyexpression', {
'INPUT': translated_points,
# 'EXPRESSION': 'round(($x_at(0) - {}), 0) % {} = 0'.format(min_x,
# 2 * round(self.parameterAsDouble(parameters, self.RLI,
# context))),
'EXPRESSION': 'round(($x_at(0) - {}) / {}) % 2 = 0'.format(min_x,
self.parameterAsDouble(parameters, self.RLI,
context)),
'OUTPUT': 'memory:RPLinesToStagger',
'FAIL_OUTPUT': 'memory:',
}, context=context, feedback=feedback)['OUTPUT']
translated_every_other = processing.run('native:translategeometry', {
'INPUT': every_other_line,
'DELTA_X': 0,
'DELTA_Y': self.parameterAsDouble(parameters, self.RPI, context) / 2,
'DELTA_Z': 0,
'DELTA_M': 0,
'OUTPUT': 'memory:RPTranslatedEveryOther',
}, context=context, feedback=feedback)['OUTPUT']
non_translated_lines = processing.run('native:extractbyexpression', {
'INPUT': translated_points,
'EXPRESSION': 'round(($x_at(0) - {}) / {}) % 2 <> 0'.format(min_x,
self.parameterAsDouble(parameters,
self.RLI, context)),
'OUTPUT': 'memory:RPLinesNoStagger',
'FAIL_OUTPUT': 'memory:',
}, context=context, feedback=feedback)['OUTPUT']
translated_points = processing.run('native:mergevectorlayers', {
'LAYERS': [translated_every_other, non_translated_lines],
'CRS': 'ProjectCrs',
'OUTPUT': 'memory:RPMergedWithStagger',
}, context=context, feedback=feedback)['OUTPUT']
clipped_points = processing.run('native:clip', {
'INPUT': translated_points,
'OVERLAY': rotated_r_poly,
'OUTPUT': 'memory:RPClipped',
}, context=context, feedback=feedback)['OUTPUT']
del translated_points
single_points = processing.run('native:multiparttosingleparts', {
'INPUT': clipped_points,
'OUTPUT': 'memory:RPSingle',
}, context=context, feedback=feedback)['OUTPUT']
del clipped_points
def _add_companion_to_lonely_points(points_layer, parameters, context):
# Check if some points are alone on their lines, and if so add a second one to have a line at least RLI long
classified_by_left_value = {}
for feature in points_layer.getFeatures():
if feature['left'] in classified_by_left_value:
classified_by_left_value[feature['left']] = None
else:
classified_by_left_value[feature['left']] = feature
lonely_points = [f for l, f in classified_by_left_value.items() if f is not None]
for point in lonely_points:
as_point = point.geometry().asPoint()
new_point = QgsPointXY(as_point)
new_point.setY(new_point.y() + self.parameterAsDouble(parameters, self.RPI, context))
new_feature = QgsFeature()
new_feature.setGeometry(QgsGeometry.fromPointXY(new_point))
single_points.dataProvider().addFeatures([new_feature])
_add_companion_to_lonely_points(single_points, parameters, context)
# Adding line & point numbers
min_x = single_points.extent().xMinimum()
min_y = single_points.extent().yMinimum()
clipped_points_with_line_nb = processing.run('qgis:fieldcalculator', {
'INPUT': single_points,
'FIELD_NAME': 'Line',
'FIELD_TYPE': 1,
'FIELD_LENGTH': 5,
'FIELD_PRECISION': 0,
'NEW_FIELD': True,
'FORMULA': '1001 + round(($x - {})/{})'.format(min_x, self.parameterAsDouble(parameters, self.RLI, context)),
'OUTPUT': 'memory:RPwithX',
}, context=context, feedback=feedback)['OUTPUT']
del single_points
clipped_points_with_point_nb = processing.run('qgis:fieldcalculator', {
'INPUT': clipped_points_with_line_nb,
'FIELD_NAME': 'Point',
'FIELD_TYPE': 1,
'FIELD_LENGTH': 5,
'FIELD_PRECISION': 0,
'NEW_FIELD': True,
'FORMULA': '1001 + round(($y - {})/{}, 0)'.format(min_y, self.parameterAsDouble(parameters, self.RPI, context)),
'OUTPUT': 'memory:RPwithXY',
}, context=context, feedback=feedback)['OUTPUT']
del clipped_points_with_line_nb
# Removing unused fields
layer = clipped_points_with_point_nb
provider = layer.dataProvider()
attribute_indexes = [provider.fieldNameIndex('top'),
provider.fieldNameIndex('bottom'),
provider.fieldNameIndex('left'),
provider.fieldNameIndex('right'),
provider.fieldNameIndex('id'),
provider.fieldNameIndex('layer'),
provider.fieldNameIndex('path'),
]
attribute_indexes = [i for i in attribute_indexes if i >= 0] # get rid of -1, i.e. non-existing fields
provider.deleteAttributes(attribute_indexes)
layer.updateFields()
# Compute min and max x used later for source halo calculations
self._min_x_for_halo = layer.extent().xMinimum()
self._max_x_for_halo = layer.extent().xMaximum()
receiver_points = processing.run('native:rotatefeatures', {
'INPUT': layer,
'ANGLE': self.parameterAsDouble(parameters, self.AZIMUTH, context),
'ANCHOR': centroid,
'OUTPUT': 'memory:RPOutput',
}, context=context, feedback=feedback)['OUTPUT']
del clipped_points_with_point_nb
del layer
(receiver_points_sink, self.receiver_points_id) = self.parameterAsSink(parameters, self.OUTPUT_RP, context,
receiver_points.fields(),
receiver_points.wkbType(),
receiver_points.sourceCrs())
for feature in receiver_points.getFeatures():
receiver_points_sink.addFeature(feature, QgsFeatureSink.FastInsert)
return self.receiver_points_id, receiver_points
[docs]
def process_receiver_lines(self, receiver_points, parameters, context, feedback):
paths_from_points = processing.run('qgis:pointstopath', {
'INPUT': receiver_points,
'ORDER_FIELD': 'Point',
'GROUP_FIELD': 'Line',
'OUTPUT': 'memory:RLPaths',
}, context=context, feedback=feedback)['OUTPUT']
simplified = processing.run('native:simplifygeometries', {
'INPUT': paths_from_points,
'METHOD': 0,
'TOLERANCE': 1,
'OUTPUT': 'memory:RLOutput'
}, context=context, feedback=feedback)['OUTPUT']
del paths_from_points
receiver_lines = self.extend_lines_and_add_coords(simplified, parameters, context, feedback)
(receiver_lines_sink, self.receiver_lines_id) = self.parameterAsSink(parameters, self.OUTPUT_RL, context,
receiver_lines.fields(),
receiver_lines.wkbType(),
receiver_lines.sourceCrs())
for feature in receiver_lines.getFeatures():
receiver_lines_sink.addFeature(feature, QgsFeatureSink.FastInsert)
return self.receiver_lines_id
[docs]
def process_source_lines(self, parameters, context, feedback):
r_poly = self.parameterAsSource(parameters, self.INPUT_R, context)
if r_poly is None:
raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT_R))
r_poly_feature = next(r_poly.getFeatures())
centroid = r_poly_feature.geometry().centroid().asPoint()
rotated_r_poly = processing.run('native:rotatefeatures', {
'INPUT': parameters[self.INPUT_R],
'ANGLE': -self.parameterAsDouble(parameters, self.AZIMUTH, context),
'ANCHOR': centroid,
'OUTPUT': 'memory:RPRotatedRPoly',
}, context=context, feedback=feedback)['OUTPUT']
rotated_s_poly = processing.run('native:rotatefeatures', {
'INPUT': parameters[self.INPUT_S],
'ANGLE': -self.parameterAsDouble(parameters, self.AZIMUTH, context),
'ANCHOR': centroid,
'OUTPUT': 'memory:SLRotatedSPoly',
}, context=context, feedback=feedback)['OUTPUT']
crs = QgsProject.instance().crs().authid()
extent = self._get_grid_initial_extent(rotated_s_poly, rotated_r_poly, parameters, context, feedback)
extent_string = '{},{},{},{} [{}]'.format(extent.xMinimum() - 2000, extent.xMaximum(), extent.yMinimum(),
extent.yMaximum() + 2000, crs)
gridded_lines = processing.run('qgis:creategrid', {
'TYPE': 1,
'EXTENT': extent_string,
'HSPACING': self.parameterAsDouble(parameters, self.SLI, context),
'VSPACING': 1000, # will delete
'CRS': 'ProjectCrs',
'OUTPUT': 'memory:SLGrid',
}, context=context, feedback=feedback)['OUTPUT']
with_vertical_lines_selected = processing.run('qgis:selectbyexpression', {
'INPUT': gridded_lines,
'EXPRESSION': 'top <> bottom',
'METHOD': 0,
'OUTPUT': 'memory:SLVerticalSelection',
}, context=context, feedback=feedback)['OUTPUT']
del gridded_lines
vertical_lines_only = processing.run('native:saveselectedfeatures', {
'INPUT': with_vertical_lines_selected,
'OUTPUT': 'memory:',
}, context=context, feedback=feedback)['OUTPUT']
del with_vertical_lines_selected
lines_translated_by_half_interval = processing.run('native:translategeometry', {
'INPUT': vertical_lines_only,
'DELTA_X': self._shift_by_x + self.parameterAsDouble(parameters, self.SLI, context) / 2,
'DELTA_Y': self._shift_by_y,
'DELTA_Z': 0,
'DELTA_M': 0,
'OUTPUT': 'memory:SPVerticalOnly',
}, context=context, feedback=feedback)['OUTPUT']
del vertical_lines_only
clipped_lines = processing.run('native:clip', {
'INPUT': lines_translated_by_half_interval,
'OVERLAY': rotated_s_poly,
'OUTPUT': 'memory:',
}, context=context, feedback=feedback)['OUTPUT']
del lines_translated_by_half_interval
clipped_lines = self._compute_source_halos(clipped_lines, parameters, context)
# Adding line number
min_x = clipped_lines.extent().xMinimum()
adding_line_number = processing.run('qgis:fieldcalculator', {
'INPUT': clipped_lines,
'FIELD_NAME': 'Line',
'FIELD_TYPE': 1,
'FIELD_LENGTH': 5,
'FIELD_PRECISION': 0,
'NEW_FIELD': True,
'FORMULA': '10001 + round(($x_at(0) - {})/{})'.format(min_x, self.parameterAsDouble(parameters, self.SLI,
context)),
'OUTPUT': 'memory:SLwithLine',
}, context=context, feedback=feedback)['OUTPUT']
del clipped_lines
adding_line_number = self._compute_average_stagger_between_sl(adding_line_number, context, feedback)
rotated_lines = processing.run('native:rotatefeatures', {
'INPUT': adding_line_number,
'ANGLE': self.parameterAsDouble(parameters, self.AZIMUTH, context),
'ANCHOR': centroid,
'OUTPUT': 'memory:SLRotated',
}, context=context, feedback=feedback)['OUTPUT']
del adding_line_number
extended_with_coords = self.extend_lines_and_add_coords(rotated_lines, parameters, context, feedback)
del rotated_lines
source_lines = processing.run('native:simplifygeometries', {
'INPUT': extended_with_coords,
'METHOD': 0,
'TOLERANCE': 1,
'OUTPUT': 'memory:SLOutput',
}, context=context, feedback=feedback)['OUTPUT']
del extended_with_coords
(source_lines_sink, self.source_lines_id) = self.parameterAsSink(parameters, self.OUTPUT_SL, context,
source_lines.fields(),
source_lines.wkbType(),
source_lines.sourceCrs())
for feature in sorted(source_lines.getFeatures(), key=lambda f: f['Line']):
source_lines_sink.addFeature(feature, QgsFeatureSink.FastInsert)
return self.source_lines_id
def _compute_source_halos(self, sl_layer, parameters, context):
sli = self.parameterAsDouble(parameters, self.SLI, context)
sl_min_x = sl_layer.extent().xMinimum()
sl_max_x = sl_layer.extent().xMaximum()
halo_start = self._min_x_for_halo - sl_min_x + sli / 2
halo_end = sl_max_x - self._max_x_for_halo + sli / 2
return sl_layer # for persistence
def _compute_average_stagger_between_sl(self, vertical_sl_layer, context, feedback):
single_lines = processing.run('native:multiparttosingleparts', {
'INPUT': vertical_sl_layer,
'OUTPUT': 'memory:SLSingle',
}, context=context, feedback=feedback)['OUTPUT']
# Compute average stagger between source lines (before rotating)
previous_start_y = None
previous_end_y = None
staggers = []
for feature in sorted(single_lines.getFeatures(), key=lambda f: f['Line']):
geom = feature.geometry()
line = geom.asPolyline()
start_point = line[0]
end_point = line[-1]
start_y = start_point.y()
end_y = end_point.y()
if previous_start_y is not None and previous_end_y is not None:
staggers.append(abs(start_y - previous_start_y))
staggers.append(abs(end_y - previous_end_y))
previous_start_y = start_y
previous_end_y = end_y
average_stagger = mean(staggers)
return vertical_sl_layer # for persistence
[docs]
def extend_lines_and_add_coords(self, lines, parameters, context, feedback):
extended = processing.run('native:extendlines', {
'INPUT': lines,
'START_DISTANCE': 1,
'END_DISTANCE': 1,
'OUTPUT': 'memory:ExtendedLines',
}, context=context, feedback=feedback)['OUTPUT']
with_added_fields = processing.run('qgis:fieldcalculator', {
'INPUT': extended,
'FIELD_NAME': 'X_start',
'FIELD_TYPE': 0,
'FIELD_LENGTH': 15,
'FIELD_PRECISION': 3,
'NEW_FIELD': True,
'FORMULA': '$x_at(0)',
'OUTPUT': 'memory:LinesWithXStart',
}, context=context, feedback=feedback)['OUTPUT']
with_added_fields = processing.run('qgis:fieldcalculator', {
'INPUT': with_added_fields,
'FIELD_NAME': 'Y_start',
'FIELD_TYPE': 0,
'FIELD_LENGTH': 15,
'FIELD_PRECISION': 3,
'NEW_FIELD': True,
'FORMULA': '$y_at(0)',
'OUTPUT': 'memory:LinesWithYStart',
}, context=context, feedback=feedback)['OUTPUT']
with_added_fields = processing.run('qgis:fieldcalculator', {
'INPUT': with_added_fields,
'FIELD_NAME': 'X_end',
'FIELD_TYPE': 0,
'FIELD_LENGTH': 15,
'FIELD_PRECISION': 3,
'NEW_FIELD': True,
'FORMULA': '$x_at(-1)',
'OUTPUT': 'memory:LinesWithXEnd',
}, context=context, feedback=feedback)['OUTPUT']
with_added_fields = processing.run('qgis:fieldcalculator', {
'INPUT': with_added_fields,
'FIELD_NAME': 'Y_end',
'FIELD_TYPE': 0,
'FIELD_LENGTH': 15,
'FIELD_PRECISION': 3,
'NEW_FIELD': True,
'FORMULA': '$y_at(-1)',
'OUTPUT': 'memory:LinesWithYEnd',
}, context=context, feedback=feedback)['OUTPUT']
# Removing unused fields
layer = with_added_fields
provider = layer.dataProvider()
attribute_indexes = [provider.fieldNameIndex('top'),
provider.fieldNameIndex('bottom'),
provider.fieldNameIndex('left'),
provider.fieldNameIndex('right'),
provider.fieldNameIndex('id'),
provider.fieldNameIndex('begin'),
provider.fieldNameIndex('end'),
]
attribute_indexes = [i for i in attribute_indexes if i >= 0] # get rid of -1, i.e. non-existing fields
provider.deleteAttributes(attribute_indexes)
layer.updateFields()
return layer
def _get_grid_initial_extent(self, rotated_source_poly, rotated_receiver_poly, parameters, context, feedback):
s_extent = rotated_source_poly.extent()
r_extent = rotated_receiver_poly.extent()
rli = self.parameterAsDouble(parameters, self.RLI, context)
rpi = self.parameterAsDouble(parameters, self.RPI, context)
# 0.1m to ensure no clipping of first line of receivers
x_min = r_extent.xMinimum() - max(0, ceil((r_extent.xMinimum() - s_extent.xMinimum())/rli)) * rli + 0.1
x_max = r_extent.xMaximum() + max(0, ceil((s_extent.xMaximum() - r_extent.xMaximum())/rli)) * rli
y_min = r_extent.yMinimum() - max(0, ceil((r_extent.yMinimum() - s_extent.yMinimum())/rpi)) * rpi
y_max = r_extent.yMaximum() + max(0, ceil((s_extent.yMaximum() - r_extent.yMaximum())/rpi)) * rpi - 0.1
grid_initial_extent = QgsRectangle(x_min, y_min, x_max, y_max)
return grid_initial_extent
[docs]
def postProcessAlgorithm(self, context, feedback):
ids_for_feature_count = []
layer = QgsProcessingUtils.mapLayerFromString(self.receiver_lines_id, context)
symbol = QgsLineSymbol.createSimple({'color': 'blue', 'width': '0.25'})
layer.renderer().setSymbol(symbol)
layer.triggerRepaint()
ids_for_feature_count.append(layer.id())
layer = QgsProcessingUtils.mapLayerFromString(self.source_lines_id, context)
symbol = QgsLineSymbol.createSimple({'color': '230,68,95', 'width': '0.15'})
layer.renderer().setSymbol(symbol)
layer.triggerRepaint()
ids_for_feature_count.append(layer.id())
layer = QgsProcessingUtils.mapLayerFromString(self.receiver_points_id, context)
symbol = QgsMarkerSymbol.createSimple({'name': 'circle', 'color': 'blue', 'size': '1', 'outline_style': 'no'})
layer.renderer().setSymbol(symbol)
layer.triggerRepaint()
ids_for_feature_count.append(layer.id())
return {self.OUTPUT_RP: self.receiver_points_id, self.OUTPUT_RL: self.receiver_lines_id, self.OUTPUT_SL: self.source_lines_id}