Using QGIS v2.18, I have been attempting to combine (pipe?) two Processing Toolbox algorithms into one script. Random Extract as the first and Differential Privacy as the second. Through my weeks (literally), of research and seeking any examples I can find, I understand you can have only one output so I have been trying to code the output of the first algorithm as the input of the 2nd algorithm with no success.
It appears one must denote the first process with an [‘OUTPUT’]
but as to where/how this is to be inserted is my failure. Not sure whether this should be inserted under def defineCharacteristics(self):
or under def processAlgorithm(self, progress):
Both scripts work correctly on their own. What I do not understand is why I can’t simply change:
INPUT_LAYER = 'INPUT_LAYER'
to INPUT_LAYER = 'OUTPUT'
or change
inputFilename = self.getParameterValue(self.INPUT_LAYER)
to
inputFilename = self.getParameterValue(self.OUTPUT)
.
FYI - I am using all the imports that are found in the two scripts
class Random_Point_MakerAlgorithm(GeoAlgorithm):
# from Random Extract constants
INPUT = 'INPUT'
OUTPUT = 'OUTPUT'
METHOD = 'METHOD'
NUMBER = 'NUMBER'
"""
Differential Privacy algorithm implementing the method outlined in:
Andrés, M.E. et al., 2013. Geo-indistinguishability. In the 2013 ACM SIGSAC
conference. New York, New York, USA: ACM Press, pp. 901–914.
"""
# 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.
# Differencial Privacy constants
OUTPUT_LAYER = 'OUTPUT_LAYER'
INPUT_LAYER = 'INPUT_LAYER'
PROTECTION_DISTANCE = 'PROTECTION_DISTANCE'
NINETY_FIVE_DISTANCE = 'NINETY_FIVE_DISTANCE'
LIMIT_NINETY_FIVE = 'LIMIT_NINETY_FIVE'
def getIcon(self):
"""Get the icon.
"""
return DifferentialPrivacyUtils.getIcon()
def defineCharacteristics(self):
self.methods = [self.tr('Percentage of selected features'),
self.tr('Number of selected features')]
self.addParameter(ParameterVector(self.INPUT,
self.tr('Input Points Layer'), [ParameterVector.VECTOR_TYPE_POINT]))
self.addParameter(ParameterSelection(self.METHOD,
self.tr('Processing Method'), self.methods, 0))
self.addParameter(ParameterNumber(self.NUMBER,
self.tr('Percentage or Number of selected features'), 0, None, 50))
# This line below I usually disable to work on having only one output
self.addOutput(OutputVector(self.OUTPUT, self.tr('Extracted Points')))
"""Here we define the inputs and output of the algorithm, along
with some other properties.
"""
# The name that the user will see in the toolbox
self.name = 'Diff Privacy points'
# The branch of the toolbox under which the algorithm will appear
self.group = 'Vector'
# We add the input vector layer. It can have any kind of geometry
# It is a mandatory (not optional) one, hence the False argument
# This line berlow I usually comment out to pull in the OUTPUT layer of the
# Random Extracted layer to continue as the input.
self.addParameter(ParameterVector(self.INPUT_LAYER,
self.tr('Input layer'), [ParameterVector.VECTOR_TYPE_POINT], False))
self.addParameter(ParameterNumber(
self.PROTECTION_DISTANCE,
self.tr('Protection distance (projected units)'),
minValue=0.,
default=0.000200
))
self.addParameter(ParameterBoolean(
self.LIMIT_NINETY_FIVE,
"Limit the distance moved to the 95% confidence interval",
default=False
))
# We add a vector layer as output
self.addOutput(OutputVector(self.OUTPUT_LAYER,
self.tr('Anonymized Features')))
self.addOutput(OutputNumber(
self.NINETY_FIVE_DISTANCE,
"95% confidence distance for offset"
))
def processAlgorithm(self, progress):
"""Here is where the Random Extract processing itself takes place."""
filename = self.getParameterValue(self.INPUT)
layer = dataobjects.getObjectFromUri(filename)
method = self.getParameterValue(self.METHOD)
features = vector.features(layer)
featureCount = len(features)
value = int(self.getParameterValue(self.NUMBER))
if method == 0:
if value > featureCount:
raise GeoAlgorithmExecutionException(
self.tr('Selected number is greater than feature count. '
'Choose a lower value and try again.'))
else:
if value > 100:
raise GeoAlgorithmExecutionException(
self.tr("Percentage can't be greater than 100. Set a "
"different value and try again."))
value = int(round(value / 100.0000, 4) * featureCount)
selran = random.sample(xrange(featureCount), value)
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
layer.pendingFields().toList(), layer.wkbType(), layer.crs())
total = 100.0 / featureCount if featureCount > 0 else 1
for i, feat in enumerate(features):
if i in selran:
writer.addFeature(feat)
progress.setPercentage(int(i * total))
del writer
"""Differenctial Privacy processing below."""
# The first thing to do is retrieve the values of the parameters
# entered by the user
inputFilename = self.getParameterValue(self.INPUT_LAYER)
radius = float(self.getParameterValue(
self.PROTECTION_DISTANCE))
base_epsilon = float(ProcessingConfig.getSetting(
DifferentialPrivacyUtils.DIFFERENTIAL_EPSILON))
limit_nine_five = self.getParameterValue(self.LIMIT_NINETY_FIVE)
# scale should be 1 / epsilon where epsilon is some base epsilon constant / chosen radius
r_generator = gamma(2., scale=radius / base_epsilon)
theta_generator = uniform(scale=2 * np.pi)
output = self.getOutputValue(self.OUTPUT_LAYER)
# Input layers vales are always a string with its location.
# That string can be converted into a QGIS object (a
# QgsVectorLayer in this case) using the
# processing.getObjectFromUri() method.
vectorLayer = dataobjects.getObjectFromUri(inputFilename)
# And now we can process
# First we create the output layer. The output value entered by
# the user is a string containing a filename, so we can use it
# directly
settings = QSettings()
systemEncoding = settings.value('/UI/encoding', 'System')
provider = vectorLayer.dataProvider()
writer = QgsVectorFileWriter(output, systemEncoding,
provider.fields(),
provider.geometryType(), provider.crs())
# Now we take the features from input layer and add them to the
# output. Method features() returns an iterator, considering the
# selection that might exist in layer and the configuration that
# indicates should algorithm use only selected features or all
# of them
nine_five_distance = r_generator.ppf(0.95)
features = vector.features(vectorLayer)
for f in features:
r = r_generator.rvs()
if limit_nine_five and r > nine_five_distance:
r = nine_five_distance
theta = theta_generator.rvs()
g = f.geometryAndOwnership()
g.translate(np.cos(theta) * r, np.sin(theta) * r)
f.setGeometry(g)
writer.addFeature(f)
ProcessingLog.addToLog(
ProcessingLog.LOG_INFO,
"95% confiedence distance: {}".format(nine_five_distance)
)
self.setOutputValue(self.NINETY_FIVE_DISTANCE, nine_five_distance)
-
does the comment on gis.stackexchange.com/questions/287251/… help?Ian Turton– Ian Turton2020年06月08日 07:25:30 +00:00Commented Jun 8, 2020 at 7:25
-
I don't believe I need/use a batch process to achieve my results (but I could be wrong). From other examples I have read up on it seems a modification to a line of code works but I cannot figure out how it should be written or placed in what order. Here's a few I found: gis.stackexchange.com/questions/280740/… or gis.stackexchange.com/questions/280254/…Clutch– Clutch2020年06月08日 16:55:30 +00:00Commented Jun 8, 2020 at 16:55