def get_layer_metadata(self,lyr=None): ''' builds a metadata dict of the current layer to be stored in summary sheet ''' if not lyr: lyr = self.lyr #fields = collections.OrderedDict() fields = "" for field in lyr.fields().toList(): fields += field.name()+'_'+QVariant.typeToName(field.type())+'|'+str(field.length())+'|'+str(field.precision())+' ' #metadata = collections.OrderedDict() metadata = [ ['layer_name', lyr.name(),], ['gdrive_id', self.service_sheet.spreadsheetId,], ['geometry_type', self.geom_types[lyr.geometryType()],], ['features', "'%s" % str(lyr.featureCount()),], ['extent', self.wgs84_extent(lyr.extent()).asWktCoordinates(),], #['fields', fields,], ['abstract', lyr.abstract(),], ['srid', lyr.crs().authid(),], ['proj4_def', "'%s" % lyr.crs().toProj4(),], ] return metadata
def get_metadata(self, instance, qgs_maplayer): """Build metadata for layer by QgsMapLayer instance :param instance: qdjango Layer model instance :param qgs_maplayer: QgsMapLayer instance :return: dict """ metadata = {} metadata['title'] = instance.title # try to build by qgs_layer metadata['attributes'] = [] if instance.database_columns: for f in qgs_maplayer.fields(): attribute = {} attribute['name'] = f.name() # attribute['editType'] = f.editType() attribute['typeName'] = f.typeName() attribute['comment'] = f.comment() attribute['length'] = f.length() attribute['precision'] = f.precision() attribute['type'] = QVariant.typeToName(f.type()) attribute['alias'] = f.alias() metadata['attributes'].append(attribute) # FIXME: ask to elapso where to find CRS getprojectsettings layer list. metadata['crs'] = [] metadata['dataurl'] = {} if qgs_maplayer.dataUrl() != '': metadata['dataurl']['onlineresource'] = qgs_maplayer.dataUrl() metadata['metadataurl'] = {} if qgs_maplayer.metadataUrl() != '': metadata['metadataurl'][ 'onlineresource'] = qgs_maplayer.metadataUrl() metadata['metadataurl']['type'] = qgs_maplayer.metadataUrlType() metadata['metadataurl']['format'] = qgs_maplayer.metadataUrlFormat( ) metadata['attribution'] = {} if qgs_maplayer.attribution() != '': metadata['attribution']['title'] = qgs_maplayer.attribution() if qgs_maplayer.attributionUrl() != '': metadata['attribution'][ 'onlineresource'] = qgs_maplayer.attributionUrl() # add abstract if qgs_maplayer.abstract() != '': metadata['abstract'] = qgs_maplayer.abstract() return metadata
def _set_add_edit_user_field_choices(self): """ Set choices for add_user_field select and edit_user_field select """ touse = [] fields = self.layer.qgis_layer.fields() for f in fields: type = QVariant.typeToName(f.type()).upper() if type == 'QSTRING': # and f.length() > 200: touse.append(f.name()) self.fields['edit_user_field'].choices = \ self.fields['add_user_field'].choices = [(None, '--------')] + [(f, f) for f in touse]
def setValue(self, value): if type(value) != dict: return destinationFields = QgsFields() expressions = {} for field_def in value: f = QgsField( field_def.get('name'), field_def.get('type', QVariant.Invalid), field_def.get( QVariant.typeToName(field_def.get('type', QVariant.Invalid))), field_def.get('length', 0), field_def.get('precision', 0)) try: expressions[f.name()] = field_def['expressions'] except AttributeError: pass destinationFields.append(f) if len(destinationFields): self.fieldsView.setDestinationFields(destinationFields, expressions)
def generate_model_doc(): # NOQA C901 global TEMPLATE markdown_all = TEMPLATE files = os.listdir(resources_path('data_models')) mermaid_md = '```mermaid\n' mermaid_md += 'classDiagram\n' mermaid_field_md = '' for csv_file in files: if csv_file.endswith('csvt'): continue table_name = csv_file.replace('.csv', '') if table_name == 'metadata': continue md = '' # Ajout de la geom if MAPPING[table_name][0] is not None: field_md = TEMPLATE_FIELDS.format( id='', name='*geom*', type=MAPPING[table_name][0], alias='', ) md += field_md mermaid_field_md += '{} : geom {}\n'.format( table_name, MAPPING[table_name][0], ) mermaid_md += table_name + '\n' pretty_name = table_name.replace('_', ' ') pretty_name = pretty_name.title() csv_path = resources_path('data_models', '{}.csv'.format(table_name)) csv = QgsVectorLayer(csv_path, table_name, 'ogr') for i, field in enumerate(csv.getFeatures()): display_name = mermaid_display_name = field['name'] if display_name == 'id': display_name = '**' + display_name + '**' mermaid_display_name += ' PK' # if display_name.endswith('_id'): # display_name = '[{title} FK](#{anchor})'.format( # title=display_name, # anchor=slug(find_relation(field['name'], table_name)) # ) # mermaid_display_name += ' FK' field_md = TEMPLATE_FIELDS.format( id=field['idx'], name=display_name, type=QVariant.typeToName(int(field['type'])), alias=field['alias'], ) md += field_md if i < 10: mermaid_field_md += '{} : {}\n'.format( table_name, mermaid_display_name, ) elif i == 10: mermaid_field_md += '{} : ...\n'.format(table_name) markdown = TEMPLATE_TABLE.format(name=pretty_name, fields=md) markdown_all += markdown for relation in relations: mermaid_md += '{} <|-- {}\n'.format( relation['referencedLayer'], relation['referencingLayer'], ) mermaid_md += mermaid_field_md mermaid_md += '```' markdown_all = markdown_all.format(relationships=mermaid_md) output_file = join(PATH, 'README.md') output_file = '/home/etienne/dev/python/dsvi/docs/model.md' text_file = open(output_file, "w+") text_file.write(markdown_all) text_file.close()
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
def mapLayerAttributesFromQgisLayer(qgis_layer, **kwargs): """ map QGIS layer's simple and direct field to Attributes for client editing system only concrete field not virtual field and many2many """ # Set fields to exclude fieldsToExclude = kwargs['exclude'] if 'exclude' in kwargs else [] toRes = OrderedDict() fields = qgis_layer.fields() data_provider = qgis_layer.dataProvider() field_index = 0 pk_attributes = qgis_layer.primaryKeyAttributes() # Determine if we are using an old and bugged version of QGIS IS_QGIS_3_10 = Qgis.QGIS_VERSION.startswith('3.10') # FIXME: find better way for layer join 1:1 managment for field in fields: if field.name() not in fieldsToExclude and field.name( ) in kwargs['fields']: #internal_typename = field.typeName().split('(')[0] internal_typename = QVariant.typeToName(field.type()).upper() if internal_typename in FIELD_TYPES_MAPPING: # Get constraints and default clause to define if the field is editable # or set editable property by kwargs. # Only consider "strong" constraints constraints = qgis_layer.fieldConstraints(field_index) not_null = bool(constraints & QgsFieldConstraints.ConstraintNotNull) and \ field.constraints().constraintStrength( QgsFieldConstraints.ConstraintNotNull) == QgsFieldConstraints.ConstraintStrengthHard unique = bool(constraints & QgsFieldConstraints.ConstraintUnique) and \ field.constraints().constraintStrength( QgsFieldConstraints.ConstraintUnique) == QgsFieldConstraints.ConstraintStrengthHard has_expression = bool(constraints & QgsFieldConstraints.ConstraintExpression) and \ field.constraints().constraintStrength( QgsFieldConstraints.ConstraintExpression) == QgsFieldConstraints.ConstraintStrengthHard default_clause = data_provider.defaultValueClause(field_index) # default value for editing from qgis_layer if 'default' not in kwargs: default_value = qgis_layer.defaultValue(field_index) if qgis_layer.defaultValue(field_index) \ else None else: default_value = kwargs['default'] if isinstance(default_value, QDate) or isinstance( default_value, QDateTime): try: default_value = default_value.toString( kwargs['fields'][field.name()]['input']['options'] ['formats'][0]['displayformat']) except Exception as e: default_value = '' expression = '' if has_expression: expression = field.constraints().constraintExpression() # Check for defaultValueDefinition with expression # 2021/10/04 snippet by Alessandro Pasotti (elpaso) has_default_value_expression = False if field.defaultValueDefinition().expression() != '': exp = QgsExpression( field.defaultValueDefinition().expression()) has_default_value_expression = exp.rootNode().nodeType( ) != QgsExpressionNode.ntLiteral if not_null and unique and default_clause or has_default_value_expression: editable = False else: editable = kwargs['fields'][field.name()]['editable'] # remove editable from kwargs: del (kwargs['fields'][field.name()]['editable']) comment = field.comment() if field.comment() else field.name() fieldType = FIELD_TYPES_MAPPING[internal_typename] if IS_QGIS_3_10: is_pk = unique and default_clause and not_null else: is_pk = (field_index in pk_attributes) toRes[field.name()] = editingFormField( field.name(), required=not_null, fieldLabel=comment, type=fieldType, inputType=FORM_FIELDS_MAPPING[fieldType], editable=editable, default_clause=default_clause, unique=unique, expression=expression, pk=is_pk, default=default_value) # add upload url to image type if module is set if 'editing' in settings.G3WADMIN_LOCAL_MORE_APPS: if fieldType == FIELD_TYPE_IMAGE: toRes[field.name()].update( {'uploadurl': reverse('editing-upload')}) # update with fields configs data if 'fields' in kwargs and field.name() in kwargs['fields']: deepupdate(toRes[field.name()], kwargs['fields'][field.name()]) if fieldType == FIELD_TYPE_BOOLEAN: toRes[field.name()]['input']['options']['values'] = [{ 'checked': True, 'value': True }, { 'checked': False, 'value': False }] field_index += 1 return toRes
def mapLayerAttributesFromQgisLayer(qgis_layer, **kwargs): """ map QGIS layer's simple and direct field to Attributes for client editing system only concrete field not virtual field and many2many """ fieldsToExclude = kwargs[ 'fieldsToExclude'] if 'fieldsToExclude' in kwargs else [] toRes = OrderedDict() fields = qgis_layer.fields() data_provider = qgis_layer.dataProvider() # exclude if set: if 'exclude' in kwargs: _fieldsMapped = [] for field in fields: if field.name not in kwargs['exclude']: _fieldsMapped.append(field) fields = _fieldsMapped field_index = 0 pk_attributes = qgis_layer.primaryKeyAttributes() # Determine if we are using an old and bugged version of QGIS IS_QGIS_3_10 = Qgis.QGIS_VERSION.startswith('3.10') # FIXME: find better way for layer join 1:1 managment for field in fields: if field.name() not in fieldsToExclude and field.name( ) in kwargs['fields']: #internal_typename = field.typeName().split('(')[0] internal_typename = QVariant.typeToName(field.type()).upper() if internal_typename in FIELD_TYPES_MAPPING: # Get constraints and default clause to define if the field is editable # or set editable property by kwargs. # Only consider "strong" constraints constraints = qgis_layer.fieldConstraints(field_index) not_null = bool(constraints & QgsFieldConstraints.ConstraintNotNull) and \ field.constraints().constraintStrength( QgsFieldConstraints.ConstraintNotNull) == QgsFieldConstraints.ConstraintStrengthHard unique = bool(constraints & QgsFieldConstraints.ConstraintUnique) and \ field.constraints().constraintStrength( QgsFieldConstraints.ConstraintUnique) == QgsFieldConstraints.ConstraintStrengthHard has_expression = bool(constraints & QgsFieldConstraints.ConstraintExpression) and \ field.constraints().constraintStrength( QgsFieldConstraints.ConstraintExpression) == QgsFieldConstraints.ConstraintStrengthHard default_clause = data_provider.defaultValueClause(field_index) expression = '' if has_expression: expression = field.constraints().constraintExpression() if not_null and unique and default_clause: editable = False else: editable = kwargs['fields'][field.name()]['editable'] # remove editable from kwargs: del (kwargs['fields'][field.name()]['editable']) comment = field.comment() if field.comment() else field.name() fieldType = FIELD_TYPES_MAPPING[internal_typename] if IS_QGIS_3_10: is_pk = unique and default_clause and not_null else: is_pk = (field_index in pk_attributes) toRes[field.name()] = editingFormField( field.name(), required=not_null, fieldLabel=comment, type=fieldType, inputType=FORM_FIELDS_MAPPING[fieldType], editable=editable, default_clause=default_clause, unique=unique, expression=expression, pk=is_pk) # add upload url to image type if module is set if 'editing' in settings.G3WADMIN_LOCAL_MORE_APPS: if fieldType == FIELD_TYPE_IMAGE: toRes[field.name()].update( {'uploadurl': reverse('editing-upload')}) if fieldType == FIELD_TYPE_BOOLEAN: toRes[field.name()]['input']['options'].update({ 'values': [{ 'key': _('Yes'), 'value': True }, { 'key': 'No', 'value': False }] }) # update with fields configs data if 'fields' in kwargs and field.name() in kwargs['fields']: deepupdate(toRes[field.name()], kwargs['fields'][field.name()]) field_index += 1 return toRes
def add_geojson_features(geojson, project, qgis_layer_id=None, connection_id=None, new_layer_name=None, name=None, style=None): """Add geojson features to a destination layer, the layer can be specified by passing a QgsVectorLayer instance or by specifying a connection and a new layer name plus the QDjango project for the new layer. The connection may assume the special values `__shapefile__`, `__spatialite__` or `__geopackage__` for the creation of new OGR files of the corresponding type. The creation of the new layer may raise an exception is a layer with the same name as new_layer_name already exists. For already existing layers the `qgis_layer_id` can be used, provided that the layer belongs to the current `project`. Returns the qgis_layer_id :param geojson: new features in GeoJSON format :type geojson: str :param project: QDjango Project instance for the new or the existing layer :type project: Project instance :param layer_id: optional, QGIS layer id :type layer_id: QGIS layer id :param connection: 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 :raises Exception: raise on error :rtype: str """ # Additional fields that are not returned by the service as feature attributes json_data = json.loads(geojson) if name is None: name = "Isochrone %s" % QDateTime.currentDateTime().toString( Qt.ISODateWithMs) metadata = { 'range_type': json_data['metadata']['query']['range_type'], 'name': name, # 'timestamp': json_data['metadata']['timestamp'], // Not supported } for f in json_data['features']: f['properties'].update(metadata) geojson = json.dumps(json_data) fields = QgsJsonUtils.stringToFields(geojson) # Patch timestamp type to DateTime // Not supported # fields.remove(fields.lookupField('timestamp')) #fields.append(QgsField('timestamp', QVariant.DateTime)) # Create the new layer if connection_id is not None: def _write_to_ogr(destination_path, new_layer_name, driverName=None): """Writes features to new or existing OGR layer""" tmp_dir = QTemporaryDir() tmp_path = os.path.join(tmp_dir.path(), 'isochrone.json') with open(tmp_path, 'w+') as f: f.write(geojson) tmp_layer = QgsVectorLayer(tmp_path, 'tmp_isochrone', 'ogr') if not tmp_layer.isValid(): raise Exception( _('Cannot create temporary layer for isochrone result.')) # Note: shp attribute names are max 10 chars long save_options = QgsVectorFileWriter.SaveVectorOptions() if driverName is not None: save_options.driverName = driverName save_options.layerName = new_layer_name save_options.fileEncoding = 'utf-8' # This is nonsense to me: if the file does not exist the actionOnExistingFile # should be ignored instead of raising an error, probable QGIS bug if os.path.exists(destination_path): # Check if the layer already exists layer_exists = QgsVectorFileWriter.targetLayerExists( destination_path, new_layer_name) if layer_exists: raise Exception( _('Cannot save isochrone result to destination layer: layer already exists (use "qgis_layer_id" instead)!' )) save_options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer error_code, error_message = QgsVectorFileWriter.writeAsVectorFormatV2( tmp_layer, destination_path, project.qgis_project.transformContext(), save_options) if error_code != QgsVectorFileWriter.NoError: raise Exception( _('Cannot save isochrone result to destination layer: ') + error_message) layer_uri = destination_path if driverName != 'ESRI Shapefile': layer_uri += '|layername=' + new_layer_name provider = 'ogr' return layer_uri, provider destination_path = None if connection_id == '__shapefile__': # new shapefile driverName = 'ESRI Shapefile' extension = 'shp' elif connection_id == '__spatialite__': # new sqlite driverName = 'SpatiaLite' extension = 'sqlite' elif connection_id == '__geopackage__': # new gpkg driverName = 'GPKG' extension = 'gpkg' else: # Add new table to an existing DB connection try: connection = get_db_connections( project.qgis_project)[connection_id] except: raise Exception(_('Wrong connection id.')) if connection['provider'] == 'ogr': destination_path = connection_id driverName = 'GPKG' if destination_path.lower().endswith( '.gpkg') else 'SpatiaLite' else: driverName = None # Create a new file/layer if driverName is not None: new_layer_name = os.path.basename(new_layer_name) if destination_path is None: # new files! destination_path = os.path.join( settings.DATASOURCE_PATH, "{}.{}".format(new_layer_name, extension)) i = 0 while os.path.exists(destination_path): i += 1 destination_path = os.path.join( settings.DATASOURCE_PATH, "{}_{}.{}".format(new_layer_name, i, extension)) layer_uri, provider = _write_to_ogr(destination_path, new_layer_name, driverName) # Create a new DB table else: assert connection['provider'] != 'ogr' md = QgsProviderRegistry.instance().providerMetadata( connection['provider']) if not md: raise Exception( _('Error creating destination layer connection.')) conn = md.createConnection(connection_id, {}) try: conn.createVectorTable(connection['schema'], new_layer_name, fields, QgsWkbTypes.Polygon, QgsCoordinateReferenceSystem(4326), False, {'geometryColumn': 'geom'}) except QgsProviderConnectionException as ex: raise Exception( _('Error creating destination layer: ') + str(ex)) uri = QgsDataSourceUri(conn.uri()) uri.setTable(new_layer_name) uri.setSchema(connection['schema']) uri.setSrid('4326') uri.setGeometryColumn('geom') provider = connection['provider'] layer_uri = uri.uri() # Now reload the new layer and add it to the project qgis_layer = QgsVectorLayer(layer_uri, new_layer_name, provider) if not qgis_layer.isValid(): raise Exception( _('Error creating destination layer: layer is not valid!')) qgis_layer_id = qgis_layer.id() with QgisProjectFileLocker(project) as project: apply_style(qgis_layer, style, True, name) project.qgis_project.addMapLayers([qgis_layer]) root = project.qgis_project.layerTreeRoot() if qgis_layer_id not in root.findLayerIds(): # Append layer at the end root.insertLayer(-1, qgis_layer) if not project.update_qgis_project(): raise Exception( _('Error saving the destination layer: could not write project!' )) # Retrieve the layer again because saving the project deleted it qgis_layer = project.qgis_project.mapLayer(qgis_layer_id) # Create Layer object instance, created = Layer.objects.get_or_create( qgs_layer_id=qgis_layer_id, project=project, defaults={ 'origname': new_layer_name, 'name': new_layer_name, 'title': new_layer_name, 'is_visible': True, 'layer_type': provider, 'srid': 4326, 'datasource': layer_uri, 'geometrytype': 'Polygon', # TODO: make this a property of the Layer object 'database_columns': str([{ 'name': f.name(), 'type': QVariant.typeToName(f.type()).upper(), 'label': f.displayName() } for f in qgis_layer.fields()]), }) if not created: raise Exception( _('Error adding destination Layer to the project: layer already exists.' )) # for OGR (already filled with features) returns the id of the new layer if driverName is not None: return qgis_layer_id # Append to an existing layer qgis_layer = project.qgis_project.mapLayer(qgis_layer_id) if qgis_layer is None: raise Exception( _('Error opening destination layer %s: layer not found in QGIS project!' % qgis_layer_id)) features = QgsJsonUtils.stringToFeatureList(geojson, fields) compatible_features = [] for f in features: compatible_features.extend( QgsVectorLayerUtils.makeFeatureCompatible(f, qgis_layer)) if qgis_layer.crs().isValid( ) and qgis_layer.crs() != QgsCoordinateReferenceSystem(4326): ct = QgsCoordinateTransform(QgsCoordinateReferenceSystem(4326), qgis_layer.crs(), project.qgis_project) for f in compatible_features: geom = f.geometry() if geom.transform(ct) != QgsGeometry.Success: raise Exception( _('Error transforming geometry from 4326 to destination layer CRS.' )) f.setGeometry(geom) if len(compatible_features) == 0: raise Exception(_('No compatible features returned from isochrone.')) if not qgis_layer.startEditing(): raise Exception(_('Destination layer is not editable.')) # Add features to the layer if not qgis_layer.addFeatures(compatible_features): raise Exception(_('Error adding features to the destination layer.')) if not qgis_layer.commitChanges(): raise Exception( _('Error committing features to the destination layer.')) if style is not None: with QgisProjectFileLocker(project) as project: apply_style(qgis_layer, style, False, name) project.update_qgis_project() return qgis_layer_id