Example #1
0
    def testSubsetAttributes(self):
        req = QgsFeatureRequest()
        self.assertFalse(req.subsetOfAttributes())
        self.assertFalse(req.flags() & QgsFeatureRequest.SubsetOfAttributes)

        req.setSubsetOfAttributes([1, 4])
        self.assertEqual(req.subsetOfAttributes(), [1, 4])
        self.assertTrue(req.flags() & QgsFeatureRequest.SubsetOfAttributes)

        req.setNoAttributes()
        self.assertEqual(req.subsetOfAttributes(), [])
        self.assertTrue(req.flags() & QgsFeatureRequest.SubsetOfAttributes)

        req.setSubsetOfAttributes([])
        self.assertFalse(req.subsetOfAttributes())
        self.assertTrue(req.flags() & QgsFeatureRequest.SubsetOfAttributes)

        req.setFlags(QgsFeatureRequest.Flags())
        f = QgsFields()
        f.append(QgsField('a', QVariant.String))
        f.append(QgsField('b', QVariant.String))
        f.append(QgsField('c', QVariant.String))
        req.setSubsetOfAttributes(['a', 'c'], f)
        self.assertEqual(req.subsetOfAttributes(), [0, 2])
        self.assertTrue(req.flags() & QgsFeatureRequest.SubsetOfAttributes)
 def select(self, mode):
     try:
         lay = self.iface.activeLayer()
         geo = self.geo_buffered
         req = QgsFeatureRequest()
         req.setFilterRect(geo.boundingBox())
         req.setNoAttributes()
         if mode == 0:
             selFeat = []
         else:
             selFeat = lay.selectedFeatureIds()
         for feat in lay.getFeatures(req):
             if geo.intersects(feat.geometry()):
                 if mode < 2:
                     selFeat.append(feat.id())
                 else:
                     try:
                         selFeat.remove(feat.id())
                     except:
                         pass
         lay.selectByIds(selFeat)
         self.gtomain.runcmd(self.config.get('tools', []))
     except Exception as e:
         self.info.err(e)
Example #3
0
    def get_plots_related_to_parcel(self,
                                    db,
                                    t_id,
                                    field_name=ID_FIELD,
                                    plot_layer=None,
                                    uebaunit_table=None):
        """
        :param db: DB Connector object
        :param t_id: parcel t_id
        :param field_name: The field name to get from DB for the matching features, use None for the QGIS internal ID
        :param plot_layer: Plot QGIS layer, in case it exists already in the caller
        :param uebaunit_table: UEBaunit QGIS table, in case it exists already in the caller
        :return: list of plot ids related to the parcel
        """
        required_layers = {
            PLOT_TABLE: {
                'name': PLOT_TABLE,
                'geometry': QgsWkbTypes.PolygonGeometry
            },
            UEBAUNIT_TABLE: {
                'name': UEBAUNIT_TABLE,
                'geometry': None
            }
        }

        if plot_layer is not None:
            del required_layers[PLOT_TABLE]
        if uebaunit_table is not None:
            del required_layers[UEBAUNIT_TABLE]

        if required_layers:
            res_layers = self.qgis_utils.get_layers(db,
                                                    required_layers,
                                                    load=True)

            if PLOT_TABLE in required_layers:
                plot_layer = res_layers[PLOT_TABLE]
                if plot_layer is None:
                    self.qgis_utils.message_emitted.emit(
                        QCoreApplication.translate(
                            "LADM_DATA",
                            "Plot layer couldn't be found... {}").format(
                                db.get_description()), Qgis.Warning)
                    return

            if UEBAUNIT_TABLE in required_layers:
                uebaunit_table = res_layers[UEBAUNIT_TABLE]
                if uebaunit_table is None:
                    self.qgis_utils.message_emitted.emit(
                        QCoreApplication.translate(
                            "LADM_DATA",
                            "UEBAUnit table couldn't be found... {}").format(
                                db.get_description()), Qgis.Warning)
                    return

        features = uebaunit_table.getFeatures(
            "{}={} AND {} IS NOT NULL".format(UEBAUNIT_TABLE_PARCEL_FIELD,
                                              t_id, UEBAUNIT_TABLE_PLOT_FIELD))

        plot_t_ids = list()
        for feature in features:
            plot_t_ids.append(feature[UEBAUNIT_TABLE_PLOT_FIELD])

        if field_name == ID_FIELD:
            return plot_t_ids

        plot_ids = list()
        if plot_t_ids:
            request = QgsFeatureRequest(
                QgsExpression("{} IN ({})".format(
                    ID_FIELD, ",".join([str(id) for id in plot_t_ids]))))

            field_found = False
            if field_name is None:  # We are only interested in the QGIS internal id, no need to get other fields
                request.setNoAttributes()
            else:
                field_found = plot_layer.fields().indexOf(field_name) != -1
                if field_found:
                    request.setSubsetOfAttributes([field_name],
                                                  plot_layer.fields())

            request.setFlags(QgsFeatureRequest.NoGeometry)
            features = plot_layer.getFeatures(request)

            for feature in features:
                if field_name is None:
                    plot_ids.append(feature.id())
                else:
                    if field_found:
                        plot_ids.append(feature[field_name])

        return plot_ids
Example #4
0
def count_qgis_features(qgis_layer,
                        qgis_feature_request=None,
                        bbox_filter=None,
                        attribute_filters=None,
                        search_filter=None,
                        extra_expression=None,
                        extra_subset_string=None,
                        **kwargs):
    """Returns a list of QgsFeatures from the QGIS vector layer,
    with optional filter options.

    The API can be used in two distinct ways (that are not mutually exclusive):

    1. pass in a pre-configured QgsFeatureRequest instance
    2. pass a series of filter attributes and let this method configure
    the QgsFeatureRequest.

    :param qgis_layer: the QGIS vector layer instance
    :type qgis_layer: QgsVectorLayer
    :param qgis_feature_request: the QGIS feature request
    :type qgis_feature_request: QgsFeatureRequest, optional
    :param bbox_filter: BBOX filter in layer's CRS, defaults to None
    :type bbox_filter: QgsRectangle, optional
    :param attribute_filters: dictionary of attribute filters combined with AND, defaults to None
    :type attribute_filters: dict, optional
    :param search_filter: string filter for all fields
    :type search_filter: str, optional
    :param: exclude_fields: list of fields to exclude from returned data, '__all__' for no attributes
    :param: extra_expression: extra expression for filtering features
    :type: extra_expression: str, optional
    :param: extra_subset_string: extra subset string (provider side WHERE condition) for filtering features
    :type: extra_subset_string: str, optional
    :return: list of features
    :rtype: QgsFeature list
    """

    # Remove no_filters condition because featureCount()
    # is cached,  so it could fail on multi-processes deploy
    # ------------------------------------------------------

    # Fast track for no filters
    # no_filters = (attribute_filters is None
    #     and (bbox_filter is None or bbox_filter.isEmpty()) and
    #     attribute_filters is None and
    #     extra_expression is None and
    #     extra_subset_string is None)
    #
    # if qgis_feature_request is not None:
    #     no_filters = (no_filters and
    #         qgis_feature_request.filterRect().isEmpty() and
    #         qgis_feature_request.filterType() == QgsFeatureRequest.FilterNone)
    # else:
    if not qgis_feature_request:
        qgis_feature_request = QgsFeatureRequest()

    # if no_filters:
    #     # Try to patch a possible Oracle views QGIS featureCount bug.
    #     qgs_fc = qgis_layer.featureCount()
    #     if qgs_fc != -1:
    #         return qgis_layer.featureCount()

    qgis_feature_request.setNoAttributes()
    if qgis_feature_request.limit() != -1:
        qgis_feature_request = QgsFeatureRequest(qgis_feature_request)
        qgis_feature_request.setLimit(-1)

    return len(
        __get_qgis_features(
            qgis_layer,
            qgis_feature_request,
            bbox_filter,
            attribute_filters,
            search_filter,
            False,  #with_geometry,
            None,  #page,
            None,  #page_size,
            None,  #ordering,
            None,  #exclude_fields,
            extra_expression,
            extra_subset_string))
Example #5
0
def __get_qgis_features(qgis_layer,
                        qgis_feature_request=None,
                        bbox_filter=None,
                        attribute_filters=None,
                        search_filter=None,
                        with_geometry=True,
                        page=None,
                        page_size=None,
                        ordering=None,
                        exclude_fields=None,
                        extra_expression=None,
                        extra_subset_string=None):
    """Private implementation for count and get"""

    if qgis_feature_request is None:
        qgis_feature_request = QgsFeatureRequest()

    if exclude_fields is not None:
        if exclude_fields == '__all__':
            qgis_feature_request.setNoAttributes()
        else:
            qgis_feature_request.setSubsetOfAttributes([
                name for name in qgis_layer.fields().names()
                if name not in exclude_fields
            ], qgis_layer.fields())

    expression_parts = []

    if extra_expression is not None:
        expression_parts.append(extra_expression)

    if not with_geometry:
        qgis_feature_request.setFlags(QgsFeatureRequest.NoGeometry)

    if bbox_filter is not None:
        assert isinstance(bbox_filter, QgsRectangle)
        qgis_feature_request.setFilterRect(bbox_filter)

    # Ordering
    if ordering is not None:
        ascending = True
        if ordering.startswith('-'):
            ordering = ordering[1:]
            ascending = False
        order_by = QgsFeatureRequest.OrderBy(
            [QgsFeatureRequest.OrderByClause('"%s"' % ordering, ascending)])
        qgis_feature_request.setOrderBy(order_by)

    # Search
    if search_filter is not None:
        exp_template = '"{field_name}" ILIKE \'%' + search_filter.replace(
            '\'', '\\\'') + '%\''
        exp_parts = []
        for f in qgis_layer.fields():
            exp_parts.append(
                exp_template.format(field_name=f.name().replace('"', '\\"')))
        expression_parts.append(' OR '.join(exp_parts))

    # Attribute filters
    if attribute_filters is not None:
        exp_parts = []
        for field_name, field_value in attribute_filters.items():
            exp_parts.append('"{field_name}" ILIKE \'%{field_value}%\''.format(
                field_name=field_name.replace('"', '\\"'),
                field_value=str(field_value).replace('\'', '\\\'')))
        expression_parts.append(' AND '.join(exp_parts))

    offset = 0
    feature_count = qgis_layer.featureCount()

    if page is not None and page_size is not None:
        page_size = int(page_size)
        page = int(page)
        offset = page_size * (page - 1)
        feature_count = page_size * page
        # Set to max, without taking filters into account
        qgis_feature_request.setLimit(feature_count)
    else:
        page_size = None  # make sure it's none

    # Fetch features
    if expression_parts:
        qgis_feature_request.combineFilterExpression(
            '(' + ') AND ('.join(expression_parts) + ')')

    logger.debug(
        'Fetching features from layer {layer_name} - filter expression: {filter} - BBOX: {bbox}'
        .format(layer_name=qgis_layer.name(),
                filter=qgis_feature_request.filterExpression(),
                bbox=qgis_feature_request.filterRect()))

    features = []

    original_subset_string = qgis_layer.subsetString()
    if extra_subset_string is not None:
        subset_string = original_subset_string
        if subset_string:
            qgis_layer.setSubsetString(
                "({original_subset_string}) AND ({extra_subset_string})".
                format(original_subset_string=original_subset_string,
                       extra_subset_string=extra_subset_string))
        else:
            qgis_layer.setSubsetString(extra_subset_string)

    iterator = qgis_layer.getFeatures(qgis_feature_request)

    try:
        for _ in range(offset):
            next(iterator)
        if page_size is not None:
            for __ in range(page_size):
                features.append(next(iterator))
        else:
            while True:
                features.append(next(iterator))
    except StopIteration:
        pass

    if extra_subset_string is not None:
        qgis_layer.setSubsetString(original_subset_string)

    return features
Example #6
0
def feature_validator(feature, layer):
    """Validate a QGIS feature by checking QGIS fields constraints

    The logic here is to:
    - if geometry is not None check if geometry type matches the layer type
    - loop through the fields and check for constraints:
        - NOT NULL, skip the next check if this fails
        - UNIQUE (only if not NULL)
        - EXPRESSION (QgsExpression configured in the form), always evaluated,
          even in case of NULLs

    Note: only hard constraints are checked!

    :param feature: QGIS feature
    :type feature: QgsFeature
    :param layer: QGIS layer
    :type layer: QgsVectorLayer
    :return: a dictionary of errors for each field + geometry
    :rtype: dict
    """

    errors = dict()
    geometry = feature.geometry()

    data_provider = layer.dataProvider()

    def _has_default_value(field_index, field):
        return (
            # Provider level
            data_provider.defaultValueClause(field_index)
            or data_provider.defaultValue(field_index)
            or field.defaultValueDefinition().isValid())

    # Check geometry type
    if not geometry.isNull() and geometry.wkbType() != layer.wkbType():
        if not (geometry.wkbType() == QgsWkbTypes.Point25D
                and layer.wkbType() == QgsWkbTypes.PointZ
                or geometry.wkbType() == QgsWkbTypes.Polygon25D
                and layer.wkbType() == QgsWkbTypes.PolygonZ
                or geometry.wkbType() == QgsWkbTypes.LineString25D
                and layer.wkbType() == QgsWkbTypes.LineStringZ
                or geometry.wkbType() == QgsWkbTypes.MultiPoint25D
                and layer.wkbType() == QgsWkbTypes.MultiPointZ
                or geometry.wkbType() == QgsWkbTypes.MultiPolygon25D
                and layer.wkbType() == QgsWkbTypes.MultiPolygonZ
                or geometry.wkbType() == QgsWkbTypes.MultiLineString25D
                and layer.wkbType() == QgsWkbTypes.MultiLineStringZ):

            errors['geometry'] = _(
                'Feature geometry type %s does not match layer type: %s') % (
                    QgsWkbTypes.displayString(geometry.wkbType()),
                    QgsWkbTypes.displayString(layer.wkbType()))

    def _set_error(field_name, error):
        if not field_name in errors:
            errors[field_name] = []
        errors[field_name].append(error)

    # Check fields "hard" constraints
    for field_index in range(layer.fields().count()):

        field = layer.fields().field(field_index)

        # check if fields is a join field:
        if layer.fields().fieldOrigin(field_index) == QgsFields.OriginJoin:
            continue

        # Check not null first, if it fails skip other tests (unique and expression)
        value = feature.attribute(field.name())
        # If there is a default value we assume it's not NULL (we cannot really know at this point
        # what will be the result of the default value clause evaluation, it might even be provider-side
        if (value is None or value
                == QVariant()) and not _has_default_value(field_index, field):
            not_null = (field.constraints().constraintOrigin(
                QgsFieldConstraints.ConstraintNotNull) !=
                        QgsFieldConstraints.ConstraintOriginNotSet
                        and field.constraints().constraintStrength(
                            QgsFieldConstraints.ConstraintNotNull)
                        == QgsFieldConstraints.ConstraintStrengthHard)
            if not_null:
                _set_error(field.name(), _('Field value must be NOT NULL'))
                continue

        value = feature.attribute(field_index)

        # Skip if NULL, not sure if we want to continue in this case but it seems pointless
        # to check for unique or type compatibility on NULLs
        if value is not None and value != QVariant():

            if not QVariant(value).convert(field.type()):
                _set_error(
                    field.name(),
                    _('Field value \'%s\' cannot be converted to %s') %
                    (value, QVariant.typeToName(field.type())))

            unique = (field.constraints().constraintOrigin(
                QgsFieldConstraints.ConstraintUnique) !=
                      QgsFieldConstraints.ConstraintOriginNotSet
                      and field.constraints().constraintStrength(
                          QgsFieldConstraints.ConstraintUnique)
                      == QgsFieldConstraints.ConstraintStrengthHard)

            if unique:
                # Search for features, excluding self if it's an update
                request = QgsFeatureRequest()
                request.setNoAttributes()
                request.setFlags(QgsFeatureRequest.NoGeometry)
                request.setLimit(2)
                if field.isNumeric():
                    request.setFilterExpression(
                        '"%s" = %s' %
                        (field.name().replace('"', '\\"'), value))
                elif field.type() == QVariant.String:
                    request.setFilterExpression(
                        '"%s" = \'%s\'' % (field.name().replace(
                            '"', '\\"'), value.replace("'", "\\'")))
                elif field.type() == QVariant.Date:
                    request.setFilterExpression(
                        'to_date("%s") = \'%s\'' % (field.name().replace(
                            '"', '\\"'), value.toString(Qt.ISODate)))
                elif field.type() == QVariant.DateTime:
                    request.setFilterExpression(
                        'to_datetime("{field_name}") = \'{date_time_string}\' OR to_datetime("{field_name}") = \'{date_time_string}.000\''
                        .format(field_name=field.name().replace('"', '\\"'),
                                date_time_string=value.toString(Qt.ISODate)))
                elif field.type(
                ) == QVariant.Bool:  # This does not make any sense, but still
                    request.setFilterExpression(
                        '"%s" = %s' % (field.name().replace(
                            '"', '\\"'), 'true' if value else 'false'))
                else:  # All the other formats: let's convert to string and hope for the best
                    request.setFilterExpression(
                        '"%s" = \'%s\'' %
                        (field.name().replace('"', '\\"'), value.toString()))

                # Exclude same feature by id
                found = [
                    f.id() for f in layer.getFeatures(request)
                    if f.id() != feature.id()
                ]
                if len(found) > 0:
                    _set_error(field.name(), _('Field value must be UNIQUE'))

        # Check for expressions, even in case of NULL because expressions may want to check for combined
        # conditions on multiple fields
        expression = (field.constraints().constraintOrigin(
            QgsFieldConstraints.ConstraintExpression) !=
                      QgsFieldConstraints.ConstraintOriginNotSet
                      and field.constraints().constraintStrength(
                          QgsFieldConstraints.ConstraintExpression)
                      == QgsFieldConstraints.ConstraintStrengthHard)
        if expression:
            constraints = field.constraints()
            expression = constraints.constraintExpression()
            description = constraints.constraintDescription()
            value = feature.attribute(field_index)
            exp = QgsExpression(expression)
            context = QgsExpressionContextUtils.createFeatureBasedContext(
                feature, layer.fields())
            context.appendScopes(
                QgsExpressionContextUtils.globalProjectLayerScopes(layer))
            if not bool(exp.evaluate(context)):
                if not description:
                    description = _('Expression check violation')
                _set_error(field.name(),
                           _("%s Expression: %s") % (description, expression))

    return errors
Example #7
0
def isochrone_from_layer(input_qgis_layer_id,
                         profile,
                         params,
                         project_id,
                         qgis_layer_id,
                         connection_id,
                         new_layer_name,
                         name,
                         style,
                         process_info=None):
    """Generate isochrones asynchronously from an input QGIS point layer.
    This function must be run as an asynchronous task.

    Expected params (dict):

        {
            "locations" null,  // <-- will be populated in batches by the function
            "range_type":"time",
            "range":[480],
            "interval":60,
            "location_type":"start",
            "attributes":[
                "area",
                "reachfactor",
                "total_pop"
            ]
        }

    * "range": Maximum range value of the analysis in seconds for time and metres for distance.
               Alternatively a comma separated list of specific single range values.

    * "locations": The locations to use for the route as an array of longitude/latitude pairs

    Returns:

        - in case of errors:

        {
            'result': False,
            'error': 'error message'
        }

        - in case of success

        {
            'result': True,
            'qgis_layer_id': qgis_layer_id
        }

    :param input_qgis_layer_id: QGIS layer ID of the points layer which contains the locations for the isochrones
    :type input_qgis_layer_id: str
    :param profile: ORS profile (such as `driving-car`)
    :type profile: str
    :param params: ORS params
    :type profile: str
    :param project_id: QDjango Project pk for the new or the existing layer
    :type project_id: int
    :param qgis_layer_id: optional, QGIS layer id
    :type qgis_layer_id: QGIS layer id
    :param connection_id: optional, connection id or the special value `__shapefile__`, `__spatialite__` or `__geopackage__`
    :type connection: str
    :param new_layer_name: optional, name of the new layer
    :type new_layer_name: str
    :param name: optional, name of the isochrone, default to current datetime
    :type name: str
    :param style: optional, dictionary with style properties: example {'color': [100, 50, 123], 'transparency': 0.5, 'stroke_width: 3 }
    :type style: dict
    :param process_info: optional Huey process information
    :type process_info: ProcessInfo
    :raises Exception: raise on error
    :rtype: dict

    """

    project = get_object_or_404(Project, pk=project_id)

    # Loop through features from the layer
    input_layer = project.qgis_project.mapLayer(input_qgis_layer_id)

    # Check preconditions
    assert input_layer is not None and input_layer.geometryType(
    ) == QgsWkbTypes.PointGeometry

    # Store range
    rang = params['range']

    req = QgsFeatureRequest()
    req.setNoAttributes()

    ct = QgsCoordinateTransform(input_layer.crs(),
                                QgsCoordinateReferenceSystem(4326),
                                project.qgis_project.transformContext())

    # Collect point batches
    points = []

    def _process_batch(points, qgis_layer_id):
        #import time; time.sleep(20)
        params['locations'] = points
        # Note: passing a range list is mutually exclusive
        # with "interval"
        result = isochrone(profile, params)
        if result.status_code != status.HTTP_200_OK:
            jcontent = json.loads(result.content.decode('utf-8'))
            try:
                error_message = jcontent['error']['message']
            except (KeyError, TypeError):
                error_message = jcontent['error']
            raise Exception(
                _('Error generating isochrone: %s.') % error_message)
        return add_geojson_features(result.content.decode('utf-8'), project,
                                    qgis_layer_id, connection_id,
                                    new_layer_name, name, style)

    feature_count = input_layer.featureCount()

    if process_info is not None:
        process_info.update_total(feature_count)

    for f in input_layer.getFeatures(req):
        g = f.geometry()
        if ct.isValid():
            g.transform(ct)
        for point in g.constParts():
            points.append([point.x(), point.y()])
            if len(points) >= ORS_MAX_LOCATIONS:
                qgis_layer_id = _process_batch(points, qgis_layer_id)
                connection_id = None
                style = None
                points = []

        if process_info is not None:
            process_info.update(n=1)

    if len(points) > 0:
        qgis_layer_id = _process_batch(points, qgis_layer_id)

    if qgis_layer_id is None:
        raise Exception(
            _('Unknown error adding results to the destination layer.'))

    return {'qgis_layer_id': qgis_layer_id}