Example #1
0
def handleAlgorithmResults(alg, context, feedback=None, showResults=True):
    wrongLayers = []
    if feedback is None:
        feedback = QgsProcessingFeedback()
    feedback.setProgressText(QCoreApplication.translate('Postprocessing', 'Loading resulting layers'))
    i = 0
    for l, details in context.layersToLoadOnCompletion().items():
        if feedback.isCanceled():
            return False

        if len(context.layersToLoadOnCompletion()) > 2:
            # only show progress feedback if we're loading a bunch of layers
            feedback.setProgress(100 * i / float(len(context.layersToLoadOnCompletion())))

        try:
            layer = QgsProcessingUtils.mapLayerFromString(l, context)
            if layer is not None:
                if not ProcessingConfig.getSetting(ProcessingConfig.USE_FILENAME_AS_LAYER_NAME):
                    layer.setName(details.name)

                style = None
                if details.outputName:
                    style = RenderingStyles.getStyle(alg.id(), details.outputName)
                if style is None:
                    if layer.type() == QgsMapLayer.RasterLayer:
                        style = ProcessingConfig.getSetting(ProcessingConfig.RASTER_STYLE)
                    else:
                        if layer.geometryType() == QgsWkbTypes.PointGeometry:
                            style = ProcessingConfig.getSetting(ProcessingConfig.VECTOR_POINT_STYLE)
                        elif layer.geometryType() == QgsWkbTypes.LineGeometry:
                            style = ProcessingConfig.getSetting(ProcessingConfig.VECTOR_LINE_STYLE)
                        else:
                            style = ProcessingConfig.getSetting(ProcessingConfig.VECTOR_POLYGON_STYLE)
                if style:
                    layer.loadNamedStyle(style)

                details.project.addMapLayer(context.temporaryLayerStore().takeMapLayer(layer))

                if details.postProcessor():
                    details.postProcessor().postProcessLayer(layer, context, feedback)

            else:
                wrongLayers.append(str(l))
        except Exception:
            QgsMessageLog.logMessage(QCoreApplication.translate('Postprocessing', "Error loading result layer:") + "\n" + traceback.format_exc(), 'Processing', Qgis.Critical)
            wrongLayers.append(str(l))
        i += 1

    feedback.setProgress(100)

    if wrongLayers:
        msg = QCoreApplication.translate('Postprocessing', "The following layers were not correctly generated.")
        msg += "<ul>" + "".join(["<li>%s</li>" % lay for lay in wrongLayers]) + "</ul>"
        msg += QCoreApplication.translate('Postprocessing', "You can check the 'Log Messages Panel' in QGIS main window to find more information about the execution of the algorithm.")
        feedback.reportError(msg)

    return len(wrongLayers) == 0
Example #2
0
def handleAlgorithmResults(alg, context, feedback=None, showResults=True):
    wrongLayers = []
    if feedback is None:
        feedback = QgsProcessingFeedback()
    feedback.setProgressText(QCoreApplication.translate('Postprocessing', 'Loading resulting layers'))
    i = 0

    for l, details in context.layersToLoadOnCompletion().items():
        if feedback.isCanceled():
            return False

        if len(context.layersToLoadOnCompletion()) > 2:
            # only show progress feedback if we're loading a bunch of layers
            feedback.setProgress(100 * i / float(len(context.layersToLoadOnCompletion())))

        try:
            layer = QgsProcessingUtils.mapLayerFromString(l, context, typeHint=details.layerTypeHint)
            if layer is not None:
                set_layer_name(layer, details)

                style = None
                if details.outputName:
                    style = RenderingStyles.getStyle(alg.id(), details.outputName)
                if style is None:
                    if layer.type() == QgsMapLayer.RasterLayer:
                        style = ProcessingConfig.getSetting(ProcessingConfig.RASTER_STYLE)
                    else:
                        if layer.geometryType() == QgsWkbTypes.PointGeometry:
                            style = ProcessingConfig.getSetting(ProcessingConfig.VECTOR_POINT_STYLE)
                        elif layer.geometryType() == QgsWkbTypes.LineGeometry:
                            style = ProcessingConfig.getSetting(ProcessingConfig.VECTOR_LINE_STYLE)
                        else:
                            style = ProcessingConfig.getSetting(ProcessingConfig.VECTOR_POLYGON_STYLE)
                if style:
                    layer.loadNamedStyle(style)

                details.project.addMapLayer(context.temporaryLayerStore().takeMapLayer(layer))

                if details.postProcessor():
                    details.postProcessor().postProcessLayer(layer, context, feedback)

            else:
                wrongLayers.append(str(l))
        except Exception:
            QgsMessageLog.logMessage(QCoreApplication.translate('Postprocessing', "Error loading result layer:") + "\n" + traceback.format_exc(), 'Processing', Qgis.Critical)
            wrongLayers.append(str(l))
        i += 1

    feedback.setProgress(100)

    if wrongLayers:
        msg = QCoreApplication.translate('Postprocessing', "The following layers were not correctly generated.")
        msg += "<ul>" + "".join(["<li>%s</li>" % lay for lay in wrongLayers]) + "</ul>"
        msg += QCoreApplication.translate('Postprocessing', "You can check the 'Log Messages Panel' in QGIS main window to find more information about the execution of the algorithm.")
        feedback.reportError(msg)

    return len(wrongLayers) == 0
Example #3
0
def handle_algorithm_results(alg: QgsProcessingAlgorithm,
                             context: QgsProcessingContext,
                             feedback: QgsProcessingFeedback,
                             parameters={},
                             **kwargs) -> bool:
    """ Handle algorithms result layeri

        Insert result layers into destination project
    """
    wrongLayers = []
    for l, details in context.layersToLoadOnCompletion().items():
        if feedback.isCanceled():
            return False
        try:
            # Take as layer
            layer = QgsProcessingUtils.mapLayerFromString(
                l, context, typeHint=details.layerTypeHint)
            if layer is not None:
                # Set layer name to details name
                # This is because we enforce destination name beeing
                # the name from the parameter
                # see processing.io for how is handled layer destination naming
                layer.setName(details.name)
                LOGGER.debug("Layer name set to %s <details name: %s>",
                             layer.name(), details.name)

                _set_output_layer_style(l, layer, alg, details, context,
                                        parameters)

                # Add layer to destination project
                LOGGER.debug(
                    "Adding Map layer '%s' (outputName %s) to Qgs Project", l,
                    details.outputName)
                details.project.addMapLayer(
                    context.temporaryLayerStore().takeMapLayer(layer))

                # Handle post processing
                if details.postProcessor():
                    details.postProcessor().postProcessLayer(
                        layer, context, feedback)
            else:
                LOGGER.warning("No layer found for %s", l)
        except Exception:
            LOGGER.error("Processing: Error loading result layer:\n{}".format(
                traceback.format_exc()))
            wrongLayers.append(str(l))

    if wrongLayers:
        msg = "The following layers were not correctly generated:"
        msg += "\n".join("%s" % lay for lay in wrongLayers)
        msg += "You can check the log messages to find more information about the execution of the algorithm"
        feedback.reportError(msg)

    return len(wrongLayers) == 0
Example #4
0
def execute_in_place_run(alg,
                         parameters,
                         context=None,
                         feedback=None,
                         raise_exceptions=False):
    """Executes an algorithm modifying features in-place in the input layer.

    :param alg: algorithm to run
    :type alg: QgsProcessingAlgorithm
    :param parameters: parameters of the algorithm
    :type parameters: dict
    :param context: context, defaults to None
    :type context: QgsProcessingContext, optional
    :param feedback: feedback, defaults to None
    :type feedback: QgsProcessingFeedback, optional
    :param raise_exceptions: useful for testing, if True exceptions are raised, normally exceptions will be forwarded to the feedback
    :type raise_exceptions: boo, default to False
    :raises QgsProcessingException: raised when there is no active layer, or it cannot be made editable
    :return: a tuple with true if success and results
    :rtype: tuple
    """

    if feedback is None:
        feedback = QgsProcessingFeedback()
    if context is None:
        context = dataobjects.createContext(feedback)

    # Only feature based algs have sourceFlags
    try:
        if alg.sourceFlags(
        ) & QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks:
            context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck)
    except AttributeError:
        pass

    active_layer = parameters['INPUT']

    # Run some checks and prepare the layer for in-place execution by:
    # - getting the active layer and checking that it is a vector
    # - making the layer editable if it was not already
    # - selecting all features if none was selected
    # - checking in-place support for the active layer/alg/parameters
    # If one of the check fails and raise_exceptions is True an exception
    # is raised, else the execution is aborted and the error reported in
    # the feedback
    try:
        if active_layer is None:
            raise QgsProcessingException(tr("There is not active layer."))

        if not isinstance(active_layer, QgsVectorLayer):
            raise QgsProcessingException(
                tr("Active layer is not a vector layer."))

        if not active_layer.isEditable():
            if not active_layer.startEditing():
                raise QgsProcessingException(
                    tr("Active layer is not editable (and editing could not be turned on)."
                       ))

        if not alg.supportInPlaceEdit(active_layer):
            raise QgsProcessingException(
                tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."
                   ))
    except QgsProcessingException as e:
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing',
                                 Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True)
        return False, {}

    if not active_layer.selectedFeatureIds():
        active_layer.selectAll()

    # Make sure we are working on selected features only
    parameters['INPUT'] = QgsProcessingFeatureSourceDefinition(
        active_layer.id(), True)
    parameters['OUTPUT'] = 'memory:'

    req = QgsFeatureRequest(QgsExpression(r"$id < 0"))
    req.setFlags(QgsFeatureRequest.NoGeometry)
    req.setSubsetOfAttributes([])

    # Start the execution
    # If anything goes wrong and raise_exceptions is True an exception
    # is raised, else the execution is aborted and the error reported in
    # the feedback
    try:
        new_feature_ids = []

        active_layer.beginEditCommand(alg.displayName())

        # Checks whether the algorithm has a processFeature method
        if hasattr(alg, 'processFeature'):  # in-place feature editing
            # Make a clone or it will crash the second time the dialog
            # is opened and run
            alg = alg.create()
            if not alg.prepare(parameters, context, feedback):
                raise QgsProcessingException(
                    tr("Could not prepare selected algorithm."))
            # Check again for compatibility after prepare
            if not alg.supportInPlaceEdit(active_layer):
                raise QgsProcessingException(
                    tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."
                       ))
            field_idxs = range(len(active_layer.fields()))
            iterator_req = QgsFeatureRequest(active_layer.selectedFeatureIds())
            iterator_req.setInvalidGeometryCheck(
                context.invalidGeometryCheck())
            feature_iterator = active_layer.getFeatures(iterator_req)
            step = 100 / len(active_layer.selectedFeatureIds()
                             ) if active_layer.selectedFeatureIds() else 1
            for current, f in enumerate(feature_iterator):
                feedback.setProgress(current * step)
                if feedback.isCanceled():
                    break

                # need a deep copy, because python processFeature implementations may return
                # a shallow copy from processFeature
                input_feature = QgsFeature(f)
                new_features = alg.processFeature(input_feature, context,
                                                  feedback)
                new_features = QgsVectorLayerUtils.makeFeaturesCompatible(
                    new_features, active_layer)

                if len(new_features) == 0:
                    active_layer.deleteFeature(f.id())
                elif len(new_features) == 1:
                    new_f = new_features[0]
                    if not f.geometry().equals(new_f.geometry()):
                        active_layer.changeGeometry(f.id(), new_f.geometry())
                    if f.attributes() != new_f.attributes():
                        active_layer.changeAttributeValues(
                            f.id(), dict(zip(field_idxs, new_f.attributes())),
                            dict(zip(field_idxs, f.attributes())))
                    new_feature_ids.append(f.id())
                else:
                    active_layer.deleteFeature(f.id())
                    # Get the new ids
                    old_ids = set(
                        [f.id() for f in active_layer.getFeatures(req)])
                    if not active_layer.addFeatures(new_features):
                        raise QgsProcessingException(
                            tr("Error adding processed features back into the layer."
                               ))
                    new_ids = set(
                        [f.id() for f in active_layer.getFeatures(req)])
                    new_feature_ids += list(new_ids - old_ids)

            results, ok = {}, True

        else:  # Traditional 'run' with delete and add features cycle

            # There is no way to know if some features have been skipped
            # due to invalid geometries
            if context.invalidGeometryCheck(
            ) == QgsFeatureRequest.GeometrySkipInvalid:
                selected_ids = active_layer.selectedFeatureIds()
            else:
                selected_ids = []

            results, ok = alg.run(parameters, context, feedback)

            if ok:
                result_layer = QgsProcessingUtils.mapLayerFromString(
                    results['OUTPUT'], context)
                # TODO: check if features have changed before delete/add cycle

                new_features = []

                # Check if there are any skipped features
                if context.invalidGeometryCheck(
                ) == QgsFeatureRequest.GeometrySkipInvalid:
                    missing_ids = list(
                        set(selected_ids) - set(result_layer.allFeatureIds()))
                    if missing_ids:
                        for f in active_layer.getFeatures(
                                QgsFeatureRequest(missing_ids)):
                            if not f.geometry().isGeosValid():
                                new_features.append(f)

                active_layer.deleteFeatures(active_layer.selectedFeatureIds())

                for f in result_layer.getFeatures():
                    new_features.extend(
                        QgsVectorLayerUtils.makeFeaturesCompatible(
                            [f], active_layer))

                # Get the new ids
                old_ids = set([f.id() for f in active_layer.getFeatures(req)])
                if not active_layer.addFeatures(new_features):
                    raise QgsProcessingException(
                        tr("Error adding processed features back into the layer."
                           ))
                new_ids = set([f.id() for f in active_layer.getFeatures(req)])
                new_feature_ids += list(new_ids - old_ids)

        active_layer.endEditCommand()

        if ok and new_feature_ids:
            active_layer.selectByIds(new_feature_ids)
        elif not ok:
            active_layer.rollBack()

        return ok, results

    except QgsProcessingException as e:
        active_layer.endEditCommand()
        active_layer.rollBack()
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing',
                                 Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True)

    return False, {}
Example #5
0
def handleAlgorithmResults(alg, context, feedback=None, showResults=True):
    wrongLayers = []
    if feedback is None:
        feedback = QgsProcessingFeedback()
    feedback.setProgressText(
        QCoreApplication.translate('Postprocessing',
                                   'Loading resulting layers'))
    i = 0
    for l, details in context.layersToLoadOnCompletion().items():
        if feedback.isCanceled():
            return False

        if len(context.layersToLoadOnCompletion()) > 2:
            # only show progress feedback if we're loading a bunch of layers
            feedback.setProgress(
                100 * i / float(len(context.layersToLoadOnCompletion())))

        try:
            layer = QgsProcessingUtils.mapLayerFromString(l, context)
            if layer is not None:
                layer.setName(details.name)
                details.project.addMapLayer(
                    context.temporaryLayerStore().takeMapLayer(layer))
            else:
                name = details.name
                if ProcessingConfig.getSetting(
                        ProcessingConfig.USE_FILENAME_AS_LAYER_NAME):
                    name = os.path.basename(l)
                dataobjects.load(l, name, alg.crs,
                                 RenderingStyles.getStyle(alg.id(), l))
        except Exception:
            QgsMessageLog.logMessage(
                "Error loading result layer:\n" + traceback.format_exc(),
                'Processing', QgsMessageLog.CRITICAL)
            #wrongLayers.append(out.description())
            wrongLayers.append(l)
    # for out in alg.outputs:
    #     feedback.setProgress(100 * i / float(len(alg.outputs)))
    #     if out.flags() & QgsProcessingParameterDefinition.FlagHidden or not out.open:
    #         continue
    #     if isinstance(out, (OutputRaster, OutputVector, OutputTable)):
    #         try:
    #             layer = QgsProcessingUtils.mapLayerFromString(out.value, context)
    #             if layer:
    #                 layer.setName(out.description)
    #                 QgsProject.instance().addMapLayer(context.temporaryLayerStore().takeMapLayer(layer))
    #             else:
    #                 if ProcessingConfig.getSetting(
    #                         ProcessingConfig.USE_FILENAME_AS_LAYER_NAME):
    #                     name = os.path.basename(out.value)
    #                 else:
    #                     name = out.description
    #
    #                 isRaster = True if isinstance(out, OutputRaster) else False
    #                 dataobjects.load(out.value, name, alg.crs,
    #                                  RenderingStyles.getStyle(alg.id(), out.name),
    #                                  isRaster)
    #         except Exception:
    #             QgsMessageLog.logMessage("Error loading result layer:\n" + traceback.format_exc(), 'Processing', QgsMessageLog.CRITICAL)
    #             wrongLayers.append(out.description)
    #     elif isinstance(out, OutputHTML):
    #         resultsList.addResult(alg.icon(), out.description, out.value)
        i += 1

    QApplication.restoreOverrideCursor()
    if wrongLayers:
        msg = "The following layers were not correctly generated.<ul>"
        msg += "".join(["<li>%s</li>" % lay for lay in wrongLayers]) + "</ul>"
        msg += "You can check the log messages to find more information about the execution of the algorithm"
        feedback.reportError(msg)

    return len(wrongLayers) == 0
Example #6
0
def handleAlgorithmResults(alg, context, feedback=None, showResults=True, parameters={}):
    wrongLayers = []
    if feedback is None:
        feedback = QgsProcessingFeedback()
    feedback.setProgressText(QCoreApplication.translate('Postprocessing', 'Loading resulting layers'))
    i = 0

    for l, details in context.layersToLoadOnCompletion().items():
        if feedback.isCanceled():
            return False

        if len(context.layersToLoadOnCompletion()) > 2:
            # only show progress feedback if we're loading a bunch of layers
            feedback.setProgress(100 * i / float(len(context.layersToLoadOnCompletion())))
        try:
            layer = QgsProcessingUtils.mapLayerFromString(l, context, typeHint=details.layerTypeHint)
            if layer is not None:
                set_layer_name(layer, details)

                '''If running a model, the execution will arrive here when an algorithm that is part of
                that model is executed. We check if its output is a final otuput of the model, and
                adapt the output name accordingly'''
                outputName = details.outputName
                expcontext = QgsExpressionContext()
                scope = QgsExpressionContextScope()
                expcontext.appendScope(scope)
                for out in alg.outputDefinitions():
                    if out.name() not in parameters:
                        continue
                    outValue = parameters[out.name()]
                    if hasattr(outValue, "sink"):
                        outValue = outValue.sink.valueAsString(expcontext)[0]
                    else:
                        outValue = str(outValue)
                    if outValue == l:
                        outputName = out.name()
                        break
                style = None
                if outputName:
                    style = RenderingStyles.getStyle(alg.id(), outputName)
                if style is None:
                    if layer.type() == QgsMapLayer.RasterLayer:
                        style = ProcessingConfig.getSetting(ProcessingConfig.RASTER_STYLE)
                    else:
                        if layer.geometryType() == QgsWkbTypes.PointGeometry:
                            style = ProcessingConfig.getSetting(ProcessingConfig.VECTOR_POINT_STYLE)
                        elif layer.geometryType() == QgsWkbTypes.LineGeometry:
                            style = ProcessingConfig.getSetting(ProcessingConfig.VECTOR_LINE_STYLE)
                        else:
                            style = ProcessingConfig.getSetting(ProcessingConfig.VECTOR_POLYGON_STYLE)
                if style:
                    layer.loadNamedStyle(style)

                details.project.addMapLayer(context.temporaryLayerStore().takeMapLayer(layer))

                if details.postProcessor():
                    details.postProcessor().postProcessLayer(layer, context, feedback)

            else:
                wrongLayers.append(str(l))
        except Exception:
            QgsMessageLog.logMessage(QCoreApplication.translate('Postprocessing', "Error loading result layer:") + "\n" + traceback.format_exc(), 'Processing', Qgis.Critical)
            wrongLayers.append(str(l))
        i += 1

    feedback.setProgress(100)

    if wrongLayers:
        msg = QCoreApplication.translate('Postprocessing', "The following layers were not correctly generated.")
        msg += "<ul>" + "".join(["<li>%s</li>" % lay for lay in wrongLayers]) + "</ul>"
        msg += QCoreApplication.translate('Postprocessing', "You can check the 'Log Messages Panel' in QGIS main window to find more information about the execution of the algorithm.")
        feedback.reportError(msg)

    return len(wrongLayers) == 0
class CartogramWorkOrchestratorMixIn:
    """Manage the tasks of the plugin’s workers."""
    def __init__(self):
        """Manage the tasks of the plugin’s workers."""
        super(CartogramWorkOrchestratorMixIn, self).__init__()
        self.add_processing_provider()

    def add_processing_provider(self):
        self.provider = CartogramProcessingProvider()
        QgsApplication.processingRegistry().addProvider(self.provider)

    def cancel_task(self):
        self.disable_cancel_button()
        self.task.cancel()

    def is_task_running(self):
        try:
            return self.task.isActive()
        except (AttributeError, RuntimeError):  # (no self.task)
            return False

    def remove_processing_provider(self):
        QgsApplication.processingRegistry().removeProvider(self.provider)

    def sample_layer(self):
        source_layer = QgsVectorLayer(
            os.path.join(self.plugin_dir, "data", "Austria_PopulationByNUTS2.gml"),
            ""
        )

        # (empty) memory layer
        sample_layer = QgsVectorLayer(
            QgsWkbTypes.geometryDisplayString(source_layer.geometryType())
            + "?crs=" + source_layer.crs().authid()
            + "&index=yes",
            "Austria_Population_NUTS2_20170101",
            "memory"
        )
        sample_layer_data_provider = sample_layer.dataProvider()
        sample_layer_data_provider.addAttributes(source_layer.fields().toList())
        sample_layer.updateFields()
        sample_layer_data_provider.addFeatures(list(source_layer.getFeatures()))

        sample_layer.loadNamedStyle(
            os.path.join(self.plugin_dir, "data", "Austria_PopulationByNUTS2.qml")
        )

        sample_layer.setTitle("Austria: Population by NUTS2 regions, 1 Jan 2017")
        sample_layer.setShortName("Austria_Population_NUTS2_20170101")
        sample_layer.setAbstract(
            "Austria’s population by NUTS2 region, as of 1 Jan 2017 \n"
            + "\n"
            + "Data sources: \n"
            + "    http://ec.europa.eu/eurostat/web/gisco/geodata/"
            + "reference-data/administrative-units-statistical-units/"
            + "nuts#nuts13 \n"
            + "    http://www.statistik.at/web_de/statistiken/"
            + "menschen_und_gesellschaft/bevoelkerung/"
            + "bevoelkerungsstand_und_veraenderung/"
            + "bevoelkerung_zu_jahres-_quartalsanfang/index.html"
        )

        return sample_layer

    def start_task(self, input_layer, field, max_iterations, max_average_error):
        self.context = QgsProcessingContext()
        self.feedback = QgsProcessingFeedback()
        self.task = QgsProcessingAlgRunnerTask(
            QgsApplication.processingRegistry().algorithmById("cartogram3:compute_cartogram"),
            {
                "INPUT": input_layer,
                "FIELD": field,
                "MAX_ITERATIONS": max_iterations,
                "MAX_AVERAGE_ERROR": max_average_error,
                "OUTPUT": "memory:"
            },
            self.context,
            self.feedback
        )
        self.task.executed.connect(self.task_finished)
        self.feedback.progressChanged.connect(self.update_progress)
        QgsApplication.taskManager().addTask(self.task)

    def task_finished(self, successful, results={}):
        if successful:
            output_layer = self.context.getMapLayer(results["OUTPUT"])
            if output_layer and output_layer.isValid():
                layer = self.context.takeResultLayer(output_layer.id())
                self.add_result_layer_to_map_canvas(layer, results["FIELD"])
                self.feedback.pushInfo(
                    (
                        "Finished computing cartogram for layer {:s} on field {:s} "
                        + "after {:d} iterations with {:.2n}% residual error."
                    ).format(
                        self.input_layer.name(),
                        results["FIELD"],
                        results["ITERATIONS"],
                        results["RESIDUAL_AVERAGE_ERROR"]
                    )
                )
        else:
            if self.feedback.isCanceled():
                self.feedback.pushWarning("User canceled cartogram computation")
            self.feedback.reportError("Failed to compute cartogram")

        self.clean_up_ui()  # remove progress bar

    def update_progress(self, progress):
        self.update_progress_bar(progress)
Example #8
0
    def mergeDataSources2Vrt(self, dataSources, outFile, union=False, relative=False,
                             schema=False, feedback=None):
        '''Function to do the work of merging datasources in a single vrt format

        @param data_sources: Array of path strings
        @param outfile: the output vrt file to generate
        @param relative: Write relative flag. DOES NOT relativise paths. They have to be already relative
        @param schema: Schema flag
        @return: vrt in string format
        '''
        if feedback is None:
            feedback = QgsProcessingFeedback()

        vrt = '<OGRVRTDataSource>'
        if union:
            vrt += '<OGRVRTUnionLayer name="UnionedLayer">'

        total = 100.0 / len(dataSources) if dataSources else 1
        for current, layer in enumerate(dataSources):
            if feedback.isCanceled():
                break

            feedback.setProgress(int(current * total))

            inFile = layer.source()
            srcDS = ogr.Open(inFile, 0)
            if srcDS is None:
                raise QgsProcessingException(
                    self.tr('Invalid datasource: {}'.format(inFile)))

            if schema:
                inFile = '@dummy@'

            for layer in srcDS:
                layerDef = layer.GetLayerDefn()
                layerName = layerDef.GetName()

                vrt += '<OGRVRTLayer name="{}">'.format(self.XmlEsc(layerName))
                vrt += '<SrcDataSource relativeToVRT="{}" shared="{}">{}</SrcDataSource>'.format(1 if relative else 0, not schema, self.XmlEsc(inFile))
                if schema:
                    vrt += '<SrcLayer>@dummy@</SrcLayer>'
                else:
                    vrt += '<SrcLayer>{}</SrcLayer>'.format(self.XmlEsc(layerName))

                vrt += '<GeometryType>{}</GeometryType>'.format(self.GeomType2Name(layerDef.GetGeomType()))

                crs = layer.GetSpatialRef()
                if crs is not None:
                    vrt += '<LayerSRS>{}</LayerSRS>'.format(self.XmlEsc(crs.ExportToWkt()))

                # Process all the fields.
                for fieldIdx in range(layerDef.GetFieldCount()):
                    fieldDef = layerDef.GetFieldDefn(fieldIdx)
                    vrt += '<Field name="{}" type="{}"'.format(self.XmlEsc(fieldDef.GetName()), self.fieldType2Name(fieldDef.GetType()))
                    if not schema:
                        vrt += ' src="{}"'.format(self.XmlEsc(fieldDef.GetName()))
                    if fieldDef.GetWidth() > 0:
                        vrt += ' width="{}"'.format(fieldDef.GetWidth())
                    if fieldDef.GetPrecision() > 0:
                        vrt += ' precision="{}"'.format(fieldDef.GetPrecision())
                    vrt += '/>'

                vrt += '</OGRVRTLayer>'

            srcDS.Destroy()

        if union:
            vrt += '</OGRVRTUnionLayer>'

        vrt += '</OGRVRTDataSource>'

        #TODO: pretty-print XML

        if outFile is not None:
            with codecs.open(outFile, 'w') as f:
                f.write(vrt)

        return vrt
Example #9
0
    def processAlgorithm(self,  # type: ignore
                         parameters: Dict[str, Any],
                         context: QgsProcessingContext,
                         feedback: QgsProcessingFeedback) -> Dict[str, Any]:
        resident_count = self.parameterAsInt(parameters, self.RESIDENT_COUNT, context)
        employee_count = self.parameterAsInt(parameters, self.EMPLOYEE_COUNT, context)
        ssa_value = self.parameterAsDouble(parameters, self.SSA, context)

        resident_employee_count = resident_count + employee_count
        if resident_employee_count <= 0:
            raise QgsProcessingException('Sum of resident and employee count can not equal 0 or less')

        if ssa_value < 0 or ssa_value > 1:
            raise QgsProcessingException('SSA value needs to be between 0 and 1 or less')

        outputs = {}

        # USL Clip Raster
        alg_params = {
            'NO_DATA_VALUE': parameters[self.NO_DATA_VALUE],
            'RASTER': parameters[self.RASTER],
            'VECTOR': parameters[self.VECTOR],
            'CLIPPED_RASTER': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['UslClipRaster'] = processing.run('usl:usl_clip_raster', alg_params, context=context,
                                                  feedback=feedback, is_child_algorithm=True)
        if feedback.isCanceled():
            return {}

        # USL SI Calculator
        alg_params = {
            'BUILD_UP_VALUE': parameters[self.BUILD_UP_VALUE],
            'CLIPPED_RASTER': outputs['UslClipRaster']['CLIPPED_RASTER'],
            'NO_DATA_VALUE': parameters[self.NO_DATA_VALUE],
            'RADIUS': constants.RADIUS_VALUE,
            'RASTER': parameters[self.RASTER],
            'SI_RASTER': parameters[self.OUTPUT_RASTER]
        }
        outputs['UslSiCalculator'] = processing.run('usl:usl_si_calculator', alg_params, context=context,
                                                    feedback=feedback, is_child_algorithm=True)
        if feedback.isCanceled():
            return {}

        # USL DIS Calculator
        alg_params = {
            'SI_RASTER': outputs['UslSiCalculator']['SI_RASTER']
        }
        outputs['UslDisCalculator'] = processing.run('usl:usl_dis_calculator', alg_params, context=context,
                                                     feedback=feedback, is_child_algorithm=True)

        if feedback.isCanceled():
            return {}

        # USL LUP Calculator
        alg_params = {
            'BUILD_UP_VALUE': parameters[self.BUILD_UP_VALUE],
            'CLIPPED_RASTER': outputs['UslClipRaster']['CLIPPED_RASTER'],
            'EMPLOYEE_COUNT': employee_count,
            'RESIDENT_COUNT': resident_count
        }
        outputs['UslLupCalculator'] = processing.run('usl:usl_lup_calculator', alg_params, context=context,
                                                     feedback=feedback, is_child_algorithm=True)

        if feedback.isCanceled():
            return {}

        # USL WUP Calculator
        alg_params = {
            'DIS': outputs['UslDisCalculator']['DIS'],
            'LUP': outputs['UslLupCalculator']['LUP'],
            'SSA': ssa_value
        }
        outputs['UslWupCalculator'] = processing.run('usl:usl_wup_calculator', alg_params, context=context,
                                                     feedback=feedback, is_child_algorithm=True)
        feedback.pushInfo('WUP,DIS,LUP')
        feedback.pushInfo(outputs['UslWupCalculator']['WUP'], outputs['UslDisCalculator']['DIS'], outputs['UslLupCalculator']['LUP'])

        return {self.OUTPUT: outputs['UslWupCalculator']['WUP']}
Example #10
0
    def processAlgorithm(self, parameters, context,
                         feedback: QgsProcessingFeedback):
        # Input / Output
        source = self.parameterAsSource(parameters, self.INPUT, context)
        (sink, dest_id) = self.create_output_sink(parameters, context,
                                                  source.sourceCrs())

        # Filter parameters
        observation_type_indices = self.parameterAsEnums(
            parameters, self.OBSERVATION_TYPE, context)
        observation_types = list(
            map(lambda i: self.OBSERVATION_TYPES[i][0],
                observation_type_indices))

        from_date = None
        from_date_string = self.parameterAsString(parameters, self.FROM_DATE,
                                                  context)
        if from_date_string:
            from_date = datetime.fromisoformat(from_date_string)

        to_date = None
        to_date_string = self.parameterAsString(parameters, self.TO_DATE,
                                                context)
        if to_date_string:
            to_date = datetime.fromisoformat(to_date_string)

        fire_connection_string = self.settings.value("fire_connection_string")
        fireDb = FireDb(fire_connection_string, debug=True)

        features = list(source.getFeatures())
        total_num_features = len(features)
        total_num_features_processed = 0
        # for current, feature in enumerate(features):
        for feature in features:
            if feedback.isCanceled():
                return {}
            wkt = feature.geometry().asWkt().upper()
            geometry = Geometry(wkt)
            observations = fireDb.hent_observationer_naer_geometri(
                geometri=geometry, afstand=0, tidfra=from_date, tidtil=to_date)

            pid_list = self.get_pids_from_observations(observations)
            geometriobjekter = self.get_geometriobjekter_from_pids(
                fireDb, pid_list)
            idents = self.get_idents_from_pids(fireDb, pid_list)

            feedback.setProgressText(
                "Fandt {antal} observationer".format(antal=len(observations)))
            feedback.setProgressText("Fandt {antal} geometriobjekter".format(
                antal=len(geometriobjekter)))
            feedback.setProgressText(
                "Fandt {antal} idents".format(antal=len(idents)))
            for current, observation in enumerate(observations):
                observation_type_id = observation.observationstypeid
                if observation_type_id in observation_types:
                    feature = self.create_feature_from_observation(
                        observation, geometriobjekter, idents, feedback)
                    if feature:
                        sink.addFeature(feature, QgsFeatureSink.FastInsert)
            total_num_features_processed = total_num_features_processed + 1
            feedback.setProgress(total_num_features_processed /
                                 total_num_features)
            if feedback.isCanceled():
                return {}

        apply_theme = self.parameterAsBool(parameters, self.APPLY_THEME,
                                           context)
        if apply_theme:
            style_file = os.path.join(os.path.dirname(__file__), "..",
                                      "styles", "observation.qml")
            alg_params = {"INPUT": dest_id, "STYLE": style_file}
            processing.run(
                "qgis:setstyleforvectorlayer",
                alg_params,
                context=context,
                feedback=feedback,
                is_child_algorithm=True,
            )

        return {self.OUTPUT: dest_id}
Example #11
0
def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=None, raise_exceptions=False):
    """Executes an algorithm modifying features in-place in the input layer.

    The input layer must be editable or an exception is raised.

    :param alg: algorithm to run
    :type alg: QgsProcessingAlgorithm
    :param active_layer: the editable layer
    :type active_layer: QgsVectoLayer
    :param parameters: parameters of the algorithm
    :type parameters: dict
    :param context: context, defaults to None
    :param context: QgsProcessingContext, optional
    :param feedback: feedback, defaults to None
    :param feedback: QgsProcessingFeedback, optional
    :raises QgsProcessingException: raised when the layer is not editable or the layer cannot be found in the current project
    :return: a tuple with true if success and results
    :rtype: tuple
    """

    if feedback is None:
        feedback = QgsProcessingFeedback()
    if context is None:
        context = dataobjects.createContext(feedback)

    if active_layer is None or not active_layer.isEditable():
        raise QgsProcessingException(tr("Layer is not editable or layer is None."))

    if not alg.supportInPlaceEdit(active_layer):
        raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))

    parameters['OUTPUT'] = 'memory:'

    try:
        new_feature_ids = []

        active_layer.beginEditCommand(alg.displayName())

        req = QgsFeatureRequest(QgsExpression(r"$id < 0"))
        req.setFlags(QgsFeatureRequest.NoGeometry)
        req.setSubsetOfAttributes([])

        # Checks whether the algorithm has a processFeature method
        if hasattr(alg, 'processFeature'):  # in-place feature editing
            # Make a clone or it will crash the second time the dialog
            # is opened and run
            alg = alg.create()
            if not alg.prepare(parameters, context, feedback):
                raise QgsProcessingException(tr("Could not prepare selected algorithm."))
            # Check again for compatibility after prepare
            if not alg.supportInPlaceEdit(active_layer):
                raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))
            field_idxs = range(len(active_layer.fields()))
            feature_iterator = active_layer.getFeatures(QgsFeatureRequest(active_layer.selectedFeatureIds())) if parameters['INPUT'].selectedFeaturesOnly else active_layer.getFeatures()
            step = 100 / len(active_layer.selectedFeatureIds()) if active_layer.selectedFeatureIds() else 1
            for current, f in enumerate(feature_iterator):
                feedback.setProgress(current * step)
                if feedback.isCanceled():
                    break

                # need a deep copy, because python processFeature implementations may return
                # a shallow copy from processFeature
                input_feature = QgsFeature(f)
                new_features = alg.processFeature(input_feature, context, feedback)
                new_features = QgsVectorLayerUtils.makeFeaturesCompatible(new_features, active_layer)
                if len(new_features) == 0:
                    active_layer.deleteFeature(f.id())
                elif len(new_features) == 1:
                    new_f = new_features[0]
                    if not f.geometry().equals(new_f.geometry()):
                        active_layer.changeGeometry(f.id(), new_f.geometry())
                    if f.attributes() != new_f.attributes():
                        active_layer.changeAttributeValues(f.id(), dict(zip(field_idxs, new_f.attributes())), dict(zip(field_idxs, f.attributes())))
                    new_feature_ids.append(f.id())
                else:
                    active_layer.deleteFeature(f.id())
                    # Get the new ids
                    old_ids = set([f.id() for f in active_layer.getFeatures(req)])
                    if not active_layer.addFeatures(new_features):
                        raise QgsProcessingException(tr("Error adding processed features back into the layer."))
                    new_ids = set([f.id() for f in active_layer.getFeatures(req)])
                    new_feature_ids += list(new_ids - old_ids)

            results, ok = {}, True

        else:  # Traditional 'run' with delete and add features cycle
            results, ok = alg.run(parameters, context, feedback)

            if ok:
                result_layer = QgsProcessingUtils.mapLayerFromString(results['OUTPUT'], context)
                # TODO: check if features have changed before delete/add cycle
                active_layer.deleteFeatures(active_layer.selectedFeatureIds())
                new_features = []
                for f in result_layer.getFeatures():
                    new_features.extend(QgsVectorLayerUtils.
                                        makeFeaturesCompatible([f], active_layer))

                # Get the new ids
                old_ids = set([f.id() for f in active_layer.getFeatures(req)])
                if not active_layer.addFeatures(new_features):
                    raise QgsProcessingException(tr("Error adding processed features back into the layer."))
                new_ids = set([f.id() for f in active_layer.getFeatures(req)])
                new_feature_ids += list(new_ids - old_ids)

        active_layer.endEditCommand()

        if ok and new_feature_ids:
            active_layer.selectByIds(new_feature_ids)
        elif not ok:
            active_layer.rollBack()

        return ok, results

    except QgsProcessingException as e:
        active_layer.endEditCommand()
        active_layer.rollBack()
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)))

    return False, {}
Example #12
0
    def processAlgorithm(self, parameters: Dict[str, Any],
                         context: QgsProcessingContext,
                         feedback: QgsProcessingFeedback) -> Union[dict, Dict[str, Any]]:
        result = dict()

        canalsfield: str = self.parameterAsFields(parameters, self.CANALSFIELD, context)[0]
        pointsfield: str = self.parameterAsFields(parameters, self.POINTSFIELD, context)[0]

        typefield: str = self.parameterAsFields(parameters, self.TYPEFIELD, context)[0]
        typevalue: str = self.parameterAsString(parameters, self.TYPEVALUE, context)

        tolerancepoints: float = self.parameterAsDouble(parameters, self.TOLERANCEPOINTS, context)
        tolerancecanals: float = self.parameterAsDouble(parameters, self.TOLERANCECANALS, context)

        model_feedback = QgsProcessingMultiStepFeedback(3, feedback)

        if feedback.isCanceled():
            return result

        snappedcanals_out_name = 'native:splitwithlines_1:{}'.format(self.SNAPPEDCANALS)
        intersect_out_name = 'native:fieldcalculator_1:INTERSECTIONS'
        points_with_uuid_name = 'qgis:advancedpythonfieldcalculator_1:{}'.format(self.POINTSWITHUUID)

        proc_result = processing.run(self.snap_lines.model(),
                                     {
                                         'CANALS': parameters[self.CANALS],
                                         'DELIVERYPOINTS': parameters[self.DELIVERYPOINTS],
                                         'TOLERANCECANALS': tolerancecanals,
                                         'TYPEFIELD': typefield,
                                         'TYPEVALUE': typevalue,
                                         'VERBOSE_LOG': True,
                                         snappedcanals_out_name: 'TEMPORARY_OUTPUT',
                                         intersect_out_name: 'TEMPORARY_OUTPUT',
                                         points_with_uuid_name: 'TEMPORARY_OUTPUT',
                                     },
                                     context=context,
                                     feedback=model_feedback,
                                     is_child_algorithm=True)
        # TODO SNAPPEDCANALS добавить в result в конце, когда они будут порезаны по пересечениям

        print("Main processing complete")

        model_feedback.setCurrentStep(1)

        if feedback.isCanceled():
            return result

        snapped_canals: QgsVectorLayer = context.takeResultLayer(
            proc_result[snappedcanals_out_name])

        intersections: QgsVectorLayer = context.takeResultLayer(
            proc_result[intersect_out_name])

        points_with_uuid: QgsVectorLayer = context.takeResultLayer(
            proc_result[points_with_uuid_name])

        (snapped_canals_sink, snapped_canals_id) = self.parameterAsSink(parameters, self.SNAPPEDCANALS,
                                                                        context, snapped_canals.fields(),
                                                                        snapped_canals.wkbType(),
                                                                        snapped_canals.sourceCrs())

        (points_with_uuid_sink, points_with_uuid_id) = self.parameterAsSink(parameters, self.POINTSWITHUUID,
                                                                            context, points_with_uuid.fields(),
                                                                            points_with_uuid.wkbType(),
                                                                            points_with_uuid.sourceCrs())

        snapped_canals_sink.addFeatures(snapped_canals.getFeatures())
        points_with_uuid_sink.addFeatures(points_with_uuid.getFeatures())

        result.update({
            self.SNAPPEDCANALS: snapped_canals_id,
            self.POINTSWITHUUID: points_with_uuid_id,
        })

        snapped_points_layers = []

        for line_name in points_with_uuid.uniqueValues(points_with_uuid.fields().indexFromName(pointsfield)):
            line_expression = QgsExpression().createFieldEqualityExpression(canalsfield, line_name)
            line_request = QgsFeatureRequest()
            line_request.setFilterExpression(line_expression)

            point_expression = QgsExpression().createFieldEqualityExpression(pointsfield, line_name)
            point_request = QgsFeatureRequest()
            point_request.setFilterExpression(point_expression)

            subset_snapped_canals = snapped_canals.materialize(line_request, model_feedback)
            subset_points_with_uuid = points_with_uuid.materialize(point_request, model_feedback)

            snapped_points_layers.append(
                processing.run("native:snapgeometries",
                               {
                                   'INPUT': subset_points_with_uuid,
                                   'REFERENCE_LAYER': subset_snapped_canals,
                                   'TOLERANCE': tolerancepoints,
                                   'BEHAVIOR': 1,
                                   'OUTPUT': 'TEMPORARY_OUTPUT'
                               },
                               context=context,
                               # feedback=model_feedback,
                               is_child_algorithm=True)['OUTPUT']
            )

        print("Cycle complete")

        model_feedback.setCurrentStep(2)

        if feedback.isCanceled():
            return result

        snapped_points_layers.append(intersections)

        snapped_points_id = processing.run("native:mergevectorlayers",
                                           {
                                               'LAYERS': snapped_points_layers,
                                               'CRS': points_with_uuid.crs(),
                                               'OUTPUT': 'TEMPORARY_OUTPUT'
                                           },
                                           context=context,
                                           feedback=model_feedback,
                                           is_child_algorithm=True)['OUTPUT']

        snapped_points: QgsVectorLayer = context.takeResultLayer(snapped_points_id)

        (snapped_points_sink, snapped_points_id) = self.parameterAsSink(parameters, self.SNAPPEDPOINTS,
                                                                        context, snapped_points.fields(),
                                                                        snapped_points.wkbType(),
                                                                        snapped_points.sourceCrs())

        snapped_points_sink.addFeatures(snapped_points.getFeatures())

        print("Vectors merged")

        model_feedback.setCurrentStep(3)

        if feedback.isCanceled():
            return result

        result.update({
            self.SNAPPEDPOINTS: snapped_points_id,
        })

        print(result)
        return result
Example #13
0
def generate_od_routes(
    network_layer: QgsVectorLayer,
    origin_layer: QgsVectorLayer,
    poi_layer: QgsVectorLayer,
    size_field: str,
    class_field: str,
    work_layer: QgsVectorLayer = None,
    work_size_field: str = None,
    school_layer: QgsVectorLayer = None,
    school_size_field: str = None,
    origin_weight_field: str = None,
    socio_data=None,
    health_data=None,
    diversity_data=None,
    join_on: str = None,
    max_distance: int = 25000,
    return_layer: bool = True,
    return_raw: bool = False,
    feedback: QgsProcessingFeedback = None,
) -> QgsVectorLayer:
    """
    Shortest path algorithm based on Dijkstra's algorithm

    :param network_layer: road network
    :param points_layer: combined from to points
    :param relations_data: tabular from to id data
    :param origin_field: name of from field
    :param destination_field: name of to field
    :param max_distance: maximum distance/cost
    :param crs: output layer crs
    """

    if not network_layer.wkbType() & QgsWkbTypes.LineString:
        raise Exception('Network layer must be of type LineString')
    crs = network_layer.crs()

    ## prepare graph
    director = QgsVectorLayerDirector(
        source=network_layer,
        directionFieldId=-1,
        directDirectionValue='',
        reverseDirectionValue='',
        bothDirectionValue='',
        defaultDirection=QgsVectorLayerDirector.DirectionBoth,
    )
    # First strategy is for actual shortest distance calculation
    director.addStrategy(QgsNetworkDistanceStrategy())  # 0
    # Second strategy is a hack to be able to recover the edge id
    director.addStrategy(SaveFidStrategy())  # 1
    builder = QgsGraphBuilder(crs)

    ## spatial index

    poi_sidx = QgsSpatialIndex(poi_layer)
    work_sidx = QgsSpatialIndex(work_layer)
    school_sidx = QgsSpatialIndex(school_layer)

    ## prepare points
    orig_n = len(origin_layer)
    poi_n = len(poi_layer)
    work_n = len(work_layer) if work_layer else 0
    school_n = len(school_layer) if school_layer else 0
    dest_n = poi_n + work_n + school_n

    orig_points = [None] * orig_n
    orig_sizes = np.zeros(orig_n, dtype=float)
    if socio_data:
        orig_socio = np.zeros(orig_n, dtype=float)
    dest_points = [None] * dest_n
    dest_sizes = [None] * dest_n
    dest_fids = [None] * dest_n
    dest_cats = [None] * dest_n

    orig_id_field = 'deso'
    for i, feat in enumerate(origin_layer.getFeatures()):
        orig_points[i] = feat.geometry().asPoint()
        orig_sizes[i] = feat[size_field] * (
            feat[origin_weight_field] if origin_weight_field else 1
        )

        if socio_data:
            orig_socio[i] = socio_data[
                feat[orig_id_field]
            ]  # FIXME: check if all origins have data

    if socio_data:
        orig_socio = orig_socio / np.mean(orig_socio)
        orig_sizes *= orig_socio

    for i, feat in enumerate(poi_layer.getFeatures()):
        dest_points[i] = feat.geometry().asPoint()
        dest_fids[i] = feat.id()
        dest_sizes[i] = 1  # TODO: dest size
        dest_cats[i] = poi_class_map.get(feat[class_field])

    if work_layer:
        for i, feat in enumerate(work_layer.getFeatures(), start=poi_n):
            dest_points[i] = feat.geometry().asPoint()
            dest_fids[i] = feat.id()
            dest_sizes[i] = feat[work_size_field]  # TODO: dest size
            dest_cats[i] = 'work'

    if school_layer:
        for i, feat in enumerate(school_layer.getFeatures(), start=(poi_n + work_n)):
            dest_points[i] = feat.geometry().asPoint()
            dest_fids[i] = feat.id()
            dest_sizes[i] = feat[school_size_field]  # TODO: dest size
            dest_cats[i] = 'school'

    # points = [origin.point for origin in origins_data] + [
    #    dest.point for dest in dests_data
    # ]

    if feedback is None:
        feedback = QgsProcessingFeedback()

        def progress(p):
            if int(10 * p % 100) == 0:
                print(f'{int(p):#3d}%')

        feedback.progressChanged.connect(progress)

    with timing('build network graph'):
        tied_points = director.makeGraph(
            builder, orig_points + dest_points, feedback=feedback
        )
        graph = builder.graph()

    orig_tied_points = tied_points[:orig_n]
    dest_tied_points = tied_points[orig_n:]

    poi_tied_points = dest_tied_points[:poi_n]
    work_tied_points = dest_tied_points[poi_n : poi_n + work_n]
    school_tied_points = dest_tied_points[poi_n + work_n :]

    dest_fid_to_tied_points = dict(zip(dest_fids, enumerate(dest_tied_points)))

    poi_fid_to_tied_points = dict(zip(dest_fids[:poi_n], enumerate(poi_tied_points)))
    work_fid_to_tied_points = dict(
        zip(dest_fids[poi_n : poi_n + work_n], enumerate(work_tied_points, start=poi_n))
    )
    school_fid_to_tied_points = dict(
        zip(
            dest_fids[poi_n + work_n :],
            enumerate(school_tied_points, start=poi_n + work_n),
        )
    )

    orig_dests = [None] * orig_n
    for i, point in enumerate(orig_points):
        orig_dests[i] = (
            [
                poi_fid_to_tied_points[fid]
                for fid in poi_sidx.nearestNeighbor(
                    point, neighbors=MAX_NEIGHBORS, maxDistance=max_distance
                )
            ]
            + [
                work_fid_to_tied_points[fid]
                for fid in work_sidx.nearestNeighbor(
                    point, neighbors=MAX_NEIGHBORS, maxDistance=max_distance
                )
            ]
            + [
                school_fid_to_tied_points[fid]
                for fid in school_sidx.nearestNeighbor(
                    point, neighbors=MAX_NEIGHBORS, maxDistance=max_distance
                )
            ]
        )

    step = 100.0 / orig_n
    time_dijkstra = 0.0
    time_find = 0.0
    time_route = 0.0
    with timing('calculate connecting routes'):
        routes = []
        # for i, (origin_fid, dest_fids) in enumerate(od_data):
        for i, (orig_point, dests) in enumerate(zip(orig_tied_points, orig_dests)):
            origin_vertex_id = graph.findVertex(orig_point)

            # Calculate the tree and cost using the distance strategy (#0)
            ts = time()
            (tree, cost) = QgsGraphAnalyzer.dijkstra(graph, origin_vertex_id, 0)
            time_dijkstra += time() - ts

            for j, dest_point in dests:
                if feedback.isCanceled():
                    return
                if dest_sizes[j] <= 0:
                    continue
                category = dest_cats[j]
                if category is None:
                    continue
                ts = time()
                dest_vertex_id = graph.findVertex(dest_point)
                time_find += time() - ts
                if tree[dest_vertex_id] != -1 and (
                    cost[dest_vertex_id] <= MAX_DISTANCE_M
                    or MAX_DISTANCE_M <= 0  # TODO: enable skipping max distance
                ):
                    route_distance = cost[dest_vertex_id]
                    # route_points = [graph.vertex(dest_vertex_id).point()]
                    cur_vertex_id = dest_vertex_id
                    route_fids = []
                    # Iterate the graph from dest to origin saving the edges
                    ts = time()
                    while cur_vertex_id != origin_vertex_id:
                        cur_edge = graph.edge(tree[cur_vertex_id])
                        # Here we recover the edge id through strategy #1
                        route_fids.append(cur_edge.cost(1))
                        cur_vertex_id = cur_edge.fromVertex()
                        # route_points.append(graph.vertex(cur_vertex_id).point())
                    time_route += time() - ts

                    # route_points.reverse()
                    # route_geom = QgsGeometry.fromPolylineXY(route_points))

                    # Hack to remove duplicate fids
                    route_fids = list(
                        dict.fromkeys(route_fids)
                    )  # NOTE: requires python >= 3.7 for ordered dict FIXME: add python version check
                    route_fids.reverse()

                    # Calc
                    # TODO: Move to matrix and vectorize calculation using numpy
                    gravity_value = poi_gravity_values[category]
                    bike_params = mode_params_bike[category]
                    ebike_params = mode_params_ebike[category]

                    # NOTE: we include dest size in decay here
                    decay = dest_sizes[j] * math.exp(
                        gravity_value * route_distance / 1000.0
                    )
                    p_bike = sigmoid(*bike_params, route_distance)
                    p_ebike = sigmoid(*ebike_params, route_distance)

                    # TODO: use namedtuple or dataclass
                    routes.append(
                        Route(
                            i,
                            j,
                            category,
                            route_distance,
                            decay,
                            p_bike,
                            p_ebike,
                            route_fids,
                        )
                    )
            feedback.setProgress(i * step)

        print(f'dijkstra took: {time_dijkstra:#1.2f} sec')
        print(f'find vertex took: {time_find:#1.2f} sec')
        print(f'route took: {time_route:#1.2f} sec')

    with timing('post process routes'):
        alpha_bike = 0.8
        alpha_ebke = 0.2

        decay_sums = {cat: defaultdict(float) for cat in poi_categories}
        bike_values = {cat: defaultdict(float) for cat in poi_categories}
        ebike_values = {cat: defaultdict(float) for cat in poi_categories}

        for route in routes:
            # NOTE: dest size is included in decay
            decay_sums[route.cat][route.i] += route.decay
        for route in routes:
            decay_sum = decay_sums[route.cat][route.i]
            # TODO: add T_p and alpha_m
            T_p = trip_generation[route.cat]
            bike_value = (
                T_p
                * alpha_bike
                * orig_sizes[route.i]
                * route.p_bike
                * route.decay
                / decay_sum
            )
            ebike_value = (
                T_p
                * alpha_ebke
                * orig_sizes[route.i]
                * route.p_ebike
                * route.decay
                / decay_sum
            )
            for fid in route.net_fids:
                bike_values[route.cat][fid] += float(bike_value)
                ebike_values[route.cat][fid] += float(ebike_value)

    # FIXME: Un-kludge this
    with timing('create result features'):
        fields = get_fields()

        segments = []
        for feature in network_layer.getFeatures():
            fid = feature.id()
            segment = QgsFeature(fields)
            segment.setGeometry(QgsGeometry(feature.geometry()))

            segment['network_fid'] = fid
            flow = 0.0
            for cat in poi_categories:
                bike_field = f'{cat}_bike_value'
                ebike_field = f'{cat}_ebike_value'

                flow_bike = segment[bike_field] = bike_values[cat].get(fid)
                flow_ebke = segment[ebike_field] = ebike_values[cat].get(fid)

                if flow_bike is not None:
                    flow += flow_bike
                if flow_ebke is not None:
                    flow += flow_ebke

            segment['flow'] = flow
            segment['lts'] = feature['lts']
            segment['vgu'] = feature['vgu']
            segment['R'] = flow * feature['ratio']
            segments.append(segment)

    if not return_layer:
        if return_raw:
            return segments, bike_values, ebike_values
        return segments

    with timing('create result layer'):
        output_layer = QgsVectorLayer(
            f'LineString?crs={crs.toWkt()}', 'segments', 'memory'
        )
        with edit(output_layer):
            for field in fields:
                output_layer.addAttribute(field)
            output_layer.addFeatures(segments, flags=QgsFeatureSink.FastInsert)

    return output_layer
Example #14
0
def handleAlgorithmResults(alg,
                           context,
                           feedback=None,
                           showResults=True,
                           parameters={}):
    wrongLayers = []
    if feedback is None:
        feedback = QgsProcessingFeedback()
    feedback.setProgressText(
        QCoreApplication.translate('Postprocessing',
                                   'Loading resulting layers'))
    i = 0

    for l, details in context.layersToLoadOnCompletion().items():
        if feedback.isCanceled():
            return False

        if len(context.layersToLoadOnCompletion()) > 2:
            # only show progress feedback if we're loading a bunch of layers
            feedback.setProgress(
                100 * i / float(len(context.layersToLoadOnCompletion())))
        try:
            layer = QgsProcessingUtils.mapLayerFromString(
                l, context, typeHint=details.layerTypeHint)
            if layer is not None:
                set_layer_name(layer, details)
                '''If running a model, the execution will arrive here when an algorithm that is part of
                that model is executed. We check if its output is a final otuput of the model, and
                adapt the output name accordingly'''
                outputName = details.outputName
                expcontext = QgsExpressionContext()
                scope = QgsExpressionContextScope()
                expcontext.appendScope(scope)
                for out in alg.outputDefinitions():
                    if out.name() not in parameters:
                        continue
                    outValue = parameters[out.name()]
                    if hasattr(outValue, "sink"):
                        outValue = outValue.sink.valueAsString(expcontext)[0]
                    else:
                        outValue = str(outValue)
                    if outValue == l:
                        outputName = out.name()
                        break
                style = None
                if outputName:
                    style = RenderingStyles.getStyle(alg.id(), outputName)
                if style is None:
                    if layer.type() == QgsMapLayerType.RasterLayer:
                        style = ProcessingConfig.getSetting(
                            ProcessingConfig.RASTER_STYLE)
                    else:
                        if layer.geometryType() == QgsWkbTypes.PointGeometry:
                            style = ProcessingConfig.getSetting(
                                ProcessingConfig.VECTOR_POINT_STYLE)
                        elif layer.geometryType() == QgsWkbTypes.LineGeometry:
                            style = ProcessingConfig.getSetting(
                                ProcessingConfig.VECTOR_LINE_STYLE)
                        else:
                            style = ProcessingConfig.getSetting(
                                ProcessingConfig.VECTOR_POLYGON_STYLE)
                if style:
                    layer.loadNamedStyle(style)

                details.project.addMapLayer(
                    context.temporaryLayerStore().takeMapLayer(layer))

                if details.postProcessor():
                    details.postProcessor().postProcessLayer(
                        layer, context, feedback)

            else:
                wrongLayers.append(str(l))
        except Exception:
            QgsMessageLog.logMessage(
                QCoreApplication.translate(
                    'Postprocessing', "Error loading result layer:") + "\n" +
                traceback.format_exc(), 'Processing', Qgis.Critical)
            wrongLayers.append(str(l))
        i += 1

    feedback.setProgress(100)

    if wrongLayers:
        msg = QCoreApplication.translate(
            'Postprocessing',
            "The following layers were not correctly generated.")
        msg += "<ul>" + "".join(["<li>%s</li>" % lay
                                 for lay in wrongLayers]) + "</ul>"
        msg += QCoreApplication.translate(
            'Postprocessing',
            "You can check the 'Log Messages Panel' in QGIS main window to find more information about the execution of the algorithm."
        )
        feedback.reportError(msg)

    return len(wrongLayers) == 0
Example #15
0
def handleAlgorithmResults(alg, context, feedback=None, showResults=True):
    wrongLayers = []
    if feedback is None:
        feedback = QgsProcessingFeedback()
    feedback.setProgressText(QCoreApplication.translate('Postprocessing', 'Loading resulting layers'))
    i = 0
    for l, details in context.layersToLoadOnCompletion().items():
        if feedback.isCanceled():
            return False

        if len(context.layersToLoadOnCompletion()) > 2:
            # only show progress feedback if we're loading a bunch of layers
            feedback.setProgress(100 * i / float(len(context.layersToLoadOnCompletion())))

        try:
            layer = QgsProcessingUtils.mapLayerFromString(l, context)
            if layer is not None:
                layer.setName(details.name)
                details.project.addMapLayer(context.temporaryLayerStore().takeMapLayer(layer))
            else:
                name = details.name
                if ProcessingConfig.getSetting(
                        ProcessingConfig.USE_FILENAME_AS_LAYER_NAME):
                    name = os.path.basename(l)
                dataobjects.load(l, name, alg.crs,
                                 RenderingStyles.getStyle(alg.id(), l))
        except Exception:
            QgsMessageLog.logMessage("Error loading result layer:\n" + traceback.format_exc(), 'Processing', QgsMessageLog.CRITICAL)
            #wrongLayers.append(out.description())
            wrongLayers.append(l)
    # for out in alg.outputs:
    #     feedback.setProgress(100 * i / float(len(alg.outputs)))
    #     if out.flags() & QgsProcessingParameterDefinition.FlagHidden or not out.open:
    #         continue
    #     if isinstance(out, (OutputRaster, OutputVector, OutputTable)):
    #         try:
    #             layer = QgsProcessingUtils.mapLayerFromString(out.value, context)
    #             if layer:
    #                 layer.setName(out.description)
    #                 QgsProject.instance().addMapLayer(context.temporaryLayerStore().takeMapLayer(layer))
    #             else:
    #                 if ProcessingConfig.getSetting(
    #                         ProcessingConfig.USE_FILENAME_AS_LAYER_NAME):
    #                     name = os.path.basename(out.value)
    #                 else:
    #                     name = out.description
    #
    #                 isRaster = True if isinstance(out, OutputRaster) else False
    #                 dataobjects.load(out.value, name, alg.crs,
    #                                  RenderingStyles.getStyle(alg.id(), out.name),
    #                                  isRaster)
    #         except Exception:
    #             QgsMessageLog.logMessage("Error loading result layer:\n" + traceback.format_exc(), 'Processing', QgsMessageLog.CRITICAL)
    #             wrongLayers.append(out.description)
    #     elif isinstance(out, OutputHTML):
    #         resultsList.addResult(alg.icon(), out.description, out.value)
        i += 1

    QApplication.restoreOverrideCursor()
    if wrongLayers:
        msg = "The following layers were not correctly generated.<ul>"
        msg += "".join(["<li>%s</li>" % lay for lay in wrongLayers]) + "</ul>"
        msg += "You can check the log messages to find more information about the execution of the algorithm"
        feedback.reportError(msg)

    return len(wrongLayers) == 0
Example #16
0
def execute_in_place_run(alg, parameters, context=None, feedback=None, raise_exceptions=False):
    """Executes an algorithm modifying features in-place in the input layer.

    :param alg: algorithm to run
    :type alg: QgsProcessingAlgorithm
    :param parameters: parameters of the algorithm
    :type parameters: dict
    :param context: context, defaults to None
    :type context: QgsProcessingContext, optional
    :param feedback: feedback, defaults to None
    :type feedback: QgsProcessingFeedback, optional
    :param raise_exceptions: useful for testing, if True exceptions are raised, normally exceptions will be forwarded to the feedback
    :type raise_exceptions: boo, default to False
    :raises QgsProcessingException: raised when there is no active layer, or it cannot be made editable
    :return: a tuple with true if success and results
    :rtype: tuple
    """

    if feedback is None:
        feedback = QgsProcessingFeedback()
    if context is None:
        context = dataobjects.createContext(feedback)

    # Only feature based algs have sourceFlags
    try:
        if alg.sourceFlags() & QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks:
            context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck)
    except AttributeError:
        pass

    active_layer = parameters['INPUT']

    # Run some checks and prepare the layer for in-place execution by:
    # - getting the active layer and checking that it is a vector
    # - making the layer editable if it was not already
    # - selecting all features if none was selected
    # - checking in-place support for the active layer/alg/parameters
    # If one of the check fails and raise_exceptions is True an exception
    # is raised, else the execution is aborted and the error reported in
    # the feedback
    try:
        if active_layer is None:
            raise QgsProcessingException(tr("There is not active layer."))

        if not isinstance(active_layer, QgsVectorLayer):
            raise QgsProcessingException(tr("Active layer is not a vector layer."))

        if not active_layer.isEditable():
            if not active_layer.startEditing():
                raise QgsProcessingException(tr("Active layer is not editable (and editing could not be turned on)."))

        if not alg.supportInPlaceEdit(active_layer):
            raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))
    except QgsProcessingException as e:
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True)
        return False, {}

    if not active_layer.selectedFeatureIds():
        active_layer.selectAll()

    # Make sure we are working on selected features only
    parameters['INPUT'] = QgsProcessingFeatureSourceDefinition(active_layer.id(), True)
    parameters['OUTPUT'] = 'memory:'

    req = QgsFeatureRequest(QgsExpression(r"$id < 0"))
    req.setFlags(QgsFeatureRequest.NoGeometry)
    req.setSubsetOfAttributes([])

    # Start the execution
    # If anything goes wrong and raise_exceptions is True an exception
    # is raised, else the execution is aborted and the error reported in
    # the feedback
    try:
        new_feature_ids = []

        active_layer.beginEditCommand(alg.displayName())

        # Checks whether the algorithm has a processFeature method
        if hasattr(alg, 'processFeature'):  # in-place feature editing
            # Make a clone or it will crash the second time the dialog
            # is opened and run
            alg = alg.create()
            if not alg.prepare(parameters, context, feedback):
                raise QgsProcessingException(tr("Could not prepare selected algorithm."))
            # Check again for compatibility after prepare
            if not alg.supportInPlaceEdit(active_layer):
                raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))
            field_idxs = range(len(active_layer.fields()))
            iterator_req = QgsFeatureRequest(active_layer.selectedFeatureIds())
            iterator_req.setInvalidGeometryCheck(context.invalidGeometryCheck())
            feature_iterator = active_layer.getFeatures(iterator_req)
            step = 100 / len(active_layer.selectedFeatureIds()) if active_layer.selectedFeatureIds() else 1
            for current, f in enumerate(feature_iterator):
                feedback.setProgress(current * step)
                if feedback.isCanceled():
                    break

                # need a deep copy, because python processFeature implementations may return
                # a shallow copy from processFeature
                input_feature = QgsFeature(f)
                new_features = alg.processFeature(input_feature, context, feedback)
                new_features = QgsVectorLayerUtils.makeFeaturesCompatible(new_features, active_layer)

                if len(new_features) == 0:
                    active_layer.deleteFeature(f.id())
                elif len(new_features) == 1:
                    new_f = new_features[0]
                    if not f.geometry().equals(new_f.geometry()):
                        active_layer.changeGeometry(f.id(), new_f.geometry())
                    if f.attributes() != new_f.attributes():
                        active_layer.changeAttributeValues(f.id(), dict(zip(field_idxs, new_f.attributes())), dict(zip(field_idxs, f.attributes())))
                    new_feature_ids.append(f.id())
                else:
                    active_layer.deleteFeature(f.id())
                    # Get the new ids
                    old_ids = set([f.id() for f in active_layer.getFeatures(req)])
                    if not active_layer.addFeatures(new_features):
                        raise QgsProcessingException(tr("Error adding processed features back into the layer."))
                    new_ids = set([f.id() for f in active_layer.getFeatures(req)])
                    new_feature_ids += list(new_ids - old_ids)

            results, ok = {}, True

        else:  # Traditional 'run' with delete and add features cycle

            # There is no way to know if some features have been skipped
            # due to invalid geometries
            if context.invalidGeometryCheck() == QgsFeatureRequest.GeometrySkipInvalid:
                selected_ids = active_layer.selectedFeatureIds()
            else:
                selected_ids = []

            results, ok = alg.run(parameters, context, feedback)

            if ok:
                result_layer = QgsProcessingUtils.mapLayerFromString(results['OUTPUT'], context)
                # TODO: check if features have changed before delete/add cycle

                new_features = []

                # Check if there are any skipped features
                if context.invalidGeometryCheck() == QgsFeatureRequest.GeometrySkipInvalid:
                    missing_ids = list(set(selected_ids) - set(result_layer.allFeatureIds()))
                    if missing_ids:
                        for f in active_layer.getFeatures(QgsFeatureRequest(missing_ids)):
                            if not f.geometry().isGeosValid():
                                new_features.append(f)

                active_layer.deleteFeatures(active_layer.selectedFeatureIds())

                for f in result_layer.getFeatures():
                    new_features.extend(QgsVectorLayerUtils.
                                        makeFeaturesCompatible([f], active_layer))

                # Get the new ids
                old_ids = set([f.id() for f in active_layer.getFeatures(req)])
                if not active_layer.addFeatures(new_features):
                    raise QgsProcessingException(tr("Error adding processed features back into the layer."))
                new_ids = set([f.id() for f in active_layer.getFeatures(req)])
                new_feature_ids += list(new_ids - old_ids)

        active_layer.endEditCommand()

        if ok and new_feature_ids:
            active_layer.selectByIds(new_feature_ids)
        elif not ok:
            active_layer.rollBack()

        return ok, results

    except QgsProcessingException as e:
        active_layer.endEditCommand()
        active_layer.rollBack()
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True)

    return False, {}
Example #17
0
    def runGdal(commands, feedback=None):
        if feedback is None:
            feedback = QgsProcessingFeedback()
        envval = os.getenv('PATH')
        # We need to give some extra hints to get things picked up on OS X
        isDarwin = False
        try:
            isDarwin = platform.system() == 'Darwin'
        except IOError:  # https://travis-ci.org/m-kuhn/QGIS#L1493-L1526
            pass
        if isDarwin and os.path.isfile(os.path.join(QgsApplication.prefixPath(), "bin", "gdalinfo")):
            # Looks like there's a bundled gdal. Let's use it.
            os.environ['PATH'] = "{}{}{}".format(os.path.join(QgsApplication.prefixPath(), "bin"), os.pathsep, envval)
            os.environ['DYLD_LIBRARY_PATH'] = os.path.join(QgsApplication.prefixPath(), "lib")
        else:
            # Other platforms should use default gdal finder codepath
            settings = QgsSettings()
            path = settings.value('/GdalTools/gdalPath', '')
            if not path.lower() in envval.lower().split(os.pathsep):
                envval += '{}{}'.format(os.pathsep, path)
                os.putenv('PATH', envval)

        fused_command = ' '.join([str(c) for c in commands])
        QgsMessageLog.logMessage(fused_command, 'Processing', Qgis.Info)
        feedback.pushInfo(GdalUtils.tr('GDAL command:'))
        feedback.pushCommandInfo(fused_command)
        feedback.pushInfo(GdalUtils.tr('GDAL command output:'))

        loglines = [GdalUtils.tr('GDAL execution console output')]

        def on_stdout(ba):
            val = ba.data().decode('UTF-8')
            # catch progress reports
            if val == '100 - done.':
                on_stdout.progress = 100
                feedback.setProgress(on_stdout.progress)
            elif val in ('0', '10', '20', '30', '40', '50', '60', '70', '80', '90'):
                on_stdout.progress = int(val)
                feedback.setProgress(on_stdout.progress)
            elif val == '.':
                on_stdout.progress += 2.5
                feedback.setProgress(on_stdout.progress)

            on_stdout.buffer += val
            if on_stdout.buffer.endswith('\n') or on_stdout.buffer.endswith('\r'):
                # flush buffer
                feedback.pushConsoleInfo(on_stdout.buffer.rstrip())
                loglines.append(on_stdout.buffer.rstrip())
                on_stdout.buffer = ''

        on_stdout.progress = 0
        on_stdout.buffer = ''

        def on_stderr(ba):
            val = ba.data().decode('UTF-8')
            on_stderr.buffer += val

            if on_stderr.buffer.endswith('\n') or on_stderr.buffer.endswith('\r'):
                # flush buffer
                feedback.reportError(on_stderr.buffer.rstrip())
                loglines.append(on_stderr.buffer.rstrip())
                on_stderr.buffer = ''

        on_stderr.buffer = ''

        command, *arguments = QgsRunProcess.splitCommand(fused_command)
        proc = QgsBlockingProcess(command, arguments)
        proc.setStdOutHandler(on_stdout)
        proc.setStdErrHandler(on_stderr)

        res = proc.run(feedback)
        if feedback.isCanceled() and res != 0:
            feedback.pushInfo(GdalUtils.tr('Process was canceled and did not complete'))
        elif not feedback.isCanceled() and proc.exitStatus() == QProcess.CrashExit:
            raise QgsProcessingException(GdalUtils.tr('Process was unexpectedly terminated'))
        elif res == 0:
            feedback.pushInfo(GdalUtils.tr('Process completed successfully'))
        elif proc.processError() == QProcess.FailedToStart:
            raise QgsProcessingException(GdalUtils.tr('Process {} failed to start. Either {} is missing, or you may have insufficient permissions to run the program.').format(command, command))
        else:
            feedback.reportError(GdalUtils.tr('Process returned error code {}').format(res))

        return loglines
Example #18
0
    def mergeDataSources2Vrt(self,
                             dataSources,
                             outFile,
                             union=False,
                             relative=False,
                             schema=False,
                             feedback=None):
        '''Function to do the work of merging datasources in a single vrt format

        @param data_sources: Array of path strings
        @param outfile: the output vrt file to generate
        @param relative: Write relative flag. DOES NOT relativise paths. They have to be already relative
        @param schema: Schema flag
        @return: vrt in string format
        '''
        if feedback is None:
            feedback = QgsProcessingFeedback()

        vrt = '<OGRVRTDataSource>'
        if union:
            vrt += '<OGRVRTUnionLayer name="UnionedLayer">'

        total = 100.0 / len(dataSources) if dataSources else 1
        for current, layer in enumerate(dataSources):
            if feedback.isCanceled():
                break

            feedback.setProgress(int(current * total))

            inFile = layer.source()
            srcDS = ogr.Open(inFile, 0)
            if srcDS is None:
                raise QgsProcessingException(
                    self.tr('Invalid datasource: {}'.format(inFile)))

            if schema:
                inFile = '@dummy@'

            for layer in srcDS:
                layerDef = layer.GetLayerDefn()
                layerName = layerDef.GetName()

                vrt += '<OGRVRTLayer name="{}">'.format(self.XmlEsc(layerName))
                vrt += '<SrcDataSource relativeToVRT="{}" shared="{}">{}</SrcDataSource>'.format(
                    1 if relative else 0, not schema, self.XmlEsc(inFile))
                if schema:
                    vrt += '<SrcLayer>@dummy@</SrcLayer>'
                else:
                    vrt += '<SrcLayer>{}</SrcLayer>'.format(
                        self.XmlEsc(layerName))

                vrt += '<GeometryType>{}</GeometryType>'.format(
                    self.GeomType2Name(layerDef.GetGeomType()))

                crs = layer.GetSpatialRef()
                if crs is not None:
                    vrt += '<LayerSRS>{}</LayerSRS>'.format(
                        self.XmlEsc(crs.ExportToWkt()))

                # Process all the fields.
                for fieldIdx in range(layerDef.GetFieldCount()):
                    fieldDef = layerDef.GetFieldDefn(fieldIdx)
                    vrt += '<Field name="{}" type="{}"'.format(
                        self.XmlEsc(fieldDef.GetName()),
                        self.fieldType2Name(fieldDef.GetType()))
                    if not schema:
                        vrt += ' src="{}"'.format(
                            self.XmlEsc(fieldDef.GetName()))
                    if fieldDef.GetWidth() > 0:
                        vrt += ' width="{}"'.format(fieldDef.GetWidth())
                    if fieldDef.GetPrecision() > 0:
                        vrt += ' precision="{}"'.format(
                            fieldDef.GetPrecision())
                    vrt += '/>'

                vrt += '</OGRVRTLayer>'

            srcDS.Destroy()

        if union:
            vrt += '</OGRVRTUnionLayer>'

        vrt += '</OGRVRTDataSource>'

        #TODO: pretty-print XML

        if outFile is not None:
            with codecs.open(outFile, 'w') as f:
                f.write(vrt)

        return vrt
Example #19
0
    def processAlgorithm(
            self, parameters: Dict[str, Any], context: QgsProcessingContext,
            feedback: QgsProcessingFeedback) -> Union[dict, Dict[str, Any]]:

        # Словарь в котором будут сохраняться результаты работы алгоритма
        result = dict()

        # Получаем переданные на вход параметры
        source: QgsFeatureSource = self.parameterAsSource(
            parameters, self.SAMPLES, context)

        if self.MOSAIC in parameters and parameters[self.MOSAIC]:
            whole_mosaic: Optional[
                QgsRasterLayer] = self.parameterAsRasterLayer(
                    parameters, self.MOSAIC, context)
        else:
            whole_mosaic = None

        horresolution: float = self.parameterAsDouble(parameters,
                                                      self.HORRESOLUTION,
                                                      context)
        vertresolution: float = self.parameterAsDouble(parameters,
                                                       self.VERTRESOLUTION,
                                                       context)
        width: int = self.parameterAsInt(parameters, self.WIDTH, context)
        height: int = self.parameterAsInt(parameters, self.HEIGHT, context)
        dest_crs: QgsCoordinateReferenceSystem = self.parameterAsCrs(
            parameters, self.CRS, context)

        if self.FOLDER in parameters and parameters[self.FOLDER]:
            folder: Optional[str] = self.parameterAsFileOutput(
                parameters, self.FOLDER, context)
        else:
            folder = None

        saveonesample: bool = self.parameterAsBoolean(parameters,
                                                      self.SAVEONESAMPLE,
                                                      context)

        step = 0
        model_feedback = QgsProcessingMultiStepFeedback(4, feedback)
        if feedback.isCanceled():
            return result

        # Запускаем загруженный из файла алгоритм, который создает слои INTERSECTION и GRID
        temp_grid_id = processing.run(
            self.grid_model.model(), {
                'CRS':
                dest_crs,
                'SAMPLES':
                parameters[self.SAMPLES],
                'HORRESOLUTION':
                horresolution,
                'VERTRESOLUTION':
                vertresolution,
                'HEIGHT':
                height,
                'WIDTH':
                width,
                'VERBOSE_LOG':
                True,
                'native:renametablefield_1:{}'.format(self.GRID):
                'TEMPORARY_OUTPUT'
            },
            context=context,
            feedback=model_feedback,
            is_child_algorithm=True)['native:renametablefield_1:{}'.format(
                self.GRID)]

        step += 1
        model_feedback.setCurrentStep(step)
        if feedback.isCanceled():
            return result

        temp_grid: QgsVectorLayer = context.takeResultLayer(temp_grid_id)

        if whole_mosaic and folder:
            # Извлекаем екстент из мозаики и любую (первую) точку из грида, чтобы по ним вычислить смещение
            mosaic_extent: QgsRectangle = whole_mosaic.extent()
            first_point: QgsPoint = temp_grid.getFeatures().__next__(
            ).geometry().vertices().__next__()

            # вычисляем смещения и двигаем грид
            translated_grid_id = processing.run(
                "native:translategeometry", {
                    'INPUT': temp_grid,
                    'DELTA_X': (mosaic_extent.xMaximum() - first_point.x()) %
                    horresolution,
                    'DELTA_Y': (mosaic_extent.yMaximum() - first_point.y()) %
                    vertresolution,
                    'DELTA_Z': 0,
                    'DELTA_M': 0,
                    'OUTPUT': 'TEMPORARY_OUTPUT'
                },
                context=context,
                feedback=model_feedback,
                is_child_algorithm=True)['OUTPUT']

            translated_grid: QgsVectorLayer = context.takeResultLayer(
                translated_grid_id)
        else:
            translated_grid_id = temp_grid_id
            translated_grid = temp_grid

        step += 1
        model_feedback.setCurrentStep(step)
        if feedback.isCanceled():
            return result

        temp_intersection_id = processing.run(
            self.intersection_model.model(), {
                'CRS':
                dest_crs,
                'SAMPLES':
                parameters[self.SAMPLES],
                'GRID':
                translated_grid,
                'VERBOSE_LOG':
                True,
                'native:intersection_1:{}'.format(self.INTERSECTION):
                'TEMPORARY_OUTPUT'
            },
            context=context,
            feedback=model_feedback,
            is_child_algorithm=True)['native:intersection_1:{}'.format(
                self.INTERSECTION)]

        temp_intersection: QgsVectorLayer = context.takeResultLayer(
            temp_intersection_id)

        # Создаем выходные слои, записываем в них features и сохраняем с словарь результатов result
        (intersection,
         intersection_id) = self.parameterAsSink(parameters, self.INTERSECTION,
                                                 context,
                                                 temp_intersection.fields(),
                                                 temp_intersection.wkbType(),
                                                 temp_intersection.sourceCrs())

        (grid, grid_id) = self.parameterAsSink(parameters, self.GRID, context,
                                               translated_grid.fields(),
                                               translated_grid.wkbType(),
                                               translated_grid.sourceCrs())

        intersection.addFeatures(temp_intersection.getFeatures())
        grid.addFeatures(translated_grid.getFeatures())

        result.update({self.INTERSECTION: intersection_id, self.GRID: grid_id})

        step += 1
        model_feedback.setCurrentStep(step)
        if feedback.isCanceled():
            return result

        # Поля для выходного слоя RLE
        rle_fields = QgsFields()
        rle_fields.append(QgsField("Image_Label", QVariant.String))
        rle_fields.append(QgsField("EncodedPixels", QVariant.String))

        # Выходной слой RLE
        (rle, rle_id) = self.parameterAsSink(parameters, self.RLE, context,
                                             rle_fields,
                                             translated_grid.wkbType(),
                                             QgsCoordinateReferenceSystem())
        rle: QgsFeatureSink

        store: QgsMapLayerStore = QgsProject.instance().layerStore()

        # Добавляем слои в хранилище слоев проекта, чтобы их id видел QgsProcessingFeatureSourceDefinition
        store.addMapLayer(temp_intersection)
        store.addMapLayer(translated_grid)

        # Индикатор для сохранения одного растра с sample
        first = True

        # Цикл по каждому тайлу из GRID
        for current, tile_feat in enumerate(translated_grid.getFeatures()):

            tile_feat: QgsFeature

            if feedback.isCanceled():
                return result

            # для каждого тайла создаем запрос по которому будут запрошены соответствующие feature из INTERSECTION
            expression = QgsExpression().createFieldEqualityExpression(
                "tile_id", tile_feat["tile_id"])
            request = QgsFeatureRequest()
            request.setFilterExpression(expression)

            # Цикл по всем feature в INTERSECTION
            for samle_feat in temp_intersection.getFeatures(request):
                samle_feat: QgsFeature

                # выделяем в слое INTERSECTION текущую feature, чтобы только ее передать в алгоритм обрезки растра
                temp_intersection.selectByIds([samle_feat.id()])

                # Превращаем текущую feature из INTERSECTION в растр, экстент растра задаем по текущему тайлу
                bin_raster = processing.run("gdal:rasterize", {
                    'BURN':
                    1,
                    'DATA_TYPE':
                    0,
                    'EXTENT':
                    tile_feat.geometry().boundingBox(),
                    'EXTRA':
                    '',
                    'FIELD':
                    '',
                    'HEIGHT':
                    vertresolution,
                    'INIT':
                    None,
                    'INPUT':
                    QgsProcessingFeatureSourceDefinition(
                        temp_intersection.id(), selectedFeaturesOnly=True),
                    'INVERT':
                    False,
                    'NODATA':
                    0,
                    'OPTIONS':
                    'NBITS=1',
                    'OUTPUT':
                    'TEMPORARY_OUTPUT',
                    'UNITS':
                    1,
                    'WIDTH':
                    horresolution
                },
                                            context=context,
                                            feedback=model_feedback,
                                            is_child_algorithm=True)['OUTPUT']

                # Получаем результат работы алгоритма в виде растра
                bin_raster: QgsRasterLayer = QgsProcessingUtils.mapLayerFromString(
                    bin_raster, context)

                if first and folder and saveonesample:
                    first = False
                    save_raster(
                        bin_raster, folder,
                        str(tile_feat["tile_id"]).zfill(5) + '_sample.tif')

                # Получаем строку RLE
                raster_rle_string = rle_encode(
                    convertRasterToNumpyArray(bin_raster))

                # Записываем строку с описанием RLE в слой RLE
                feat = QgsFeature(rle_fields)
                feat["Image_Label"] = tile_feat["tile_id"]
                feat["EncodedPixels"] = raster_rle_string
                rle.addFeatures([feat])

                if feedback.isCanceled():
                    return result

            # если указаны мозаика и выходная папка, то обрезаем мозаику по текущему тайлу GRID
            if whole_mosaic and folder:

                if feedback.isCanceled():
                    return result

                # выделяем в слое GRID текущий тайл, чтобы только его передать в алгоритм обрезки растра
                translated_grid.selectByIds([tile_feat.id()])

                mosaic_tile_temp_raster = processing.run(
                    "gdal:cliprasterbymasklayer",
                    {
                        'ALPHA_BAND':
                        False,
                        'CROP_TO_CUTLINE':
                        True,
                        'DATA_TYPE':
                        0,
                        'EXTRA':
                        '',
                        'INPUT':
                        whole_mosaic,
                        'KEEP_RESOLUTION':
                        False,
                        # 'MASK': single_grid,
                        'MASK':
                        QgsProcessingFeatureSourceDefinition(
                            translated_grid.id(), selectedFeaturesOnly=True),
                        'MULTITHREADING':
                        False,
                        'NODATA':
                        None,
                        'OPTIONS':
                        '',
                        # 'OUTPUT': 'TEMPORARY_OUTPUT',
                        'OUTPUT':
                        os.path.join(folder,
                                     str(tile_feat["tile_id"]).zfill(5)) +
                        '.tif',
                        'SET_RESOLUTION':
                        True,
                        'SOURCE_CRS':
                        None,
                        'TARGET_CRS':
                        None,
                        'X_RESOLUTION':
                        abs(whole_mosaic.rasterUnitsPerPixelX()),
                        'Y_RESOLUTION':
                        abs(whole_mosaic.rasterUnitsPerPixelX())
                    },
                    context=context,
                    feedback=model_feedback,
                    is_child_algorithm=True)["OUTPUT"]

                if feedback.isCanceled():
                    return result

        store.removeMapLayer(temp_intersection)
        store.removeMapLayer(translated_grid)

        step += 1
        model_feedback.setCurrentStep(step)
        if feedback.isCanceled():
            return result

        result.update({self.RLE: rle_id})
        return result