Important
Traducerea este un efort al comunității, la care puteți să vă alăturați. În prezent, această pagină este tradusă 46.46%.
6. Utilizarea straturilor vectoriale
Sugestie
Fragmentele de cod de pe această pagină necesită următoarele importuri, dacă vă aflați în afara consolei pyqgis:
1from qgis.core import (
2 QgsApplication,
3 QgsDataSourceUri,
4 QgsCategorizedSymbolRenderer,
5 QgsClassificationRange,
6 QgsPointXY,
7 QgsProject,
8 QgsExpression,
9 QgsField,
10 QgsFields,
11 QgsFeature,
12 QgsFeatureRequest,
13 QgsFeatureRenderer,
14 QgsGeometry,
15 QgsGraduatedSymbolRenderer,
16 QgsMarkerSymbol,
17 QgsMessageLog,
18 QgsRectangle,
19 QgsRendererCategory,
20 QgsRendererRange,
21 QgsSymbol,
22 QgsVectorDataProvider,
23 QgsVectorLayer,
24 QgsVectorFileWriter,
25 QgsWkbTypes,
26 QgsSpatialIndex,
27 QgsVectorLayerUtils
28)
29
30from qgis.core.additions.edit import edit
31
32from qgis.PyQt.QtGui import (
33 QColor,
34)
Această secțiune rezumă diferitele acțiuni care pot fi efectuate asupra straturilor vectoriale.
Most work here is based on the methods of the QgsVectorLayer
class.
6.1. Obținerea informaţiilor despre atribute
You can retrieve information about the fields associated with a vector layer
by calling fields()
on a
QgsVectorLayer
object:
vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
for field in vlayer.fields():
print(field.name(), field.typeName())
1ID Integer64
2fk_region Integer64
3ELEV Real
4NAME String
5USE String
The displayField()
and
mapTipTemplate()
methods provide
information on the field and template used in the Display Properties tab.
When you load a vector layer, a field is always chosen by QGIS as the
Display Name
, while the HTML Map Tip
is empty by default. With these
methods you can easily get both:
vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
print(vlayer.displayField())
NAME
Notă
If you change the Display Name
from a field to an expression, you have to
use displayExpression()
instead of displayField()
.
6.2. Iterații în straturile vectoriale
Iterating over the features in a vector layer is one of the most common tasks.
Below is an example of the simple basic code to perform this task and showing
some information about each feature. The layer
variable is assumed to have
a QgsVectorLayer
object.
1# "layer" is a QgsVectorLayer instance
2layer = iface.activeLayer()
3features = layer.getFeatures()
4
5for feature in features:
6 # retrieve every feature with its geometry and attributes
7 print("Feature ID: ", feature.id())
8 # fetch geometry
9 # show some information about the feature geometry
10 geom = feature.geometry()
11 geomSingleType = QgsWkbTypes.isSingleType(geom.wkbType())
12 if geom.type() == QgsWkbTypes.PointGeometry:
13 # the geometry type can be of single or multi type
14 if geomSingleType:
15 x = geom.asPoint()
16 print("Point: ", x)
17 else:
18 x = geom.asMultiPoint()
19 print("MultiPoint: ", x)
20 elif geom.type() == QgsWkbTypes.LineGeometry:
21 if geomSingleType:
22 x = geom.asPolyline()
23 print("Line: ", x, "length: ", geom.length())
24 else:
25 x = geom.asMultiPolyline()
26 print("MultiLine: ", x, "length: ", geom.length())
27 elif geom.type() == QgsWkbTypes.PolygonGeometry:
28 if geomSingleType:
29 x = geom.asPolygon()
30 print("Polygon: ", x, "Area: ", geom.area())
31 else:
32 x = geom.asMultiPolygon()
33 print("MultiPolygon: ", x, "Area: ", geom.area())
34 else:
35 print("Unknown or invalid geometry")
36 # fetch attributes
37 attrs = feature.attributes()
38 # attrs is a list. It contains all the attribute values of this feature
39 print(attrs)
40 # for this test only print the first feature
41 break
Feature ID: 1
Point: <QgsPointXY: POINT(7 45)>
[1, 'First feature']
6.3. Selectarea entităților
In QGIS desktop, features can be selected in different ways: the user can click on a feature, draw a rectangle on the map canvas or use an expression filter. Selected features are normally highlighted in a different color (default is yellow) to draw user’s attention on the selection.
Sometimes it can be useful to programmatically select features or to change the default color.
To select all the features, the selectAll()
method can be used:
# Get the active layer (must be a vector layer)
layer = iface.activeLayer()
layer.selectAll()
To select using an expression, use the selectByExpression()
method:
# Assumes that the active layer is points.shp file from the QGIS test suite
# (Class (string) and Heading (number) are attributes in points.shp)
layer = iface.activeLayer()
layer.selectByExpression('"Class"=\'B52\' and "Heading" > 10 and "Heading" <70', QgsVectorLayer.SetSelection)
To change the selection color you can use setSelectionColor()
method of
QgsMapCanvas
as shown in the following example:
iface.mapCanvas().setSelectionColor( QColor("red") )
To add features to the selected features list for a given layer, you
can call select()
passing to it the list of features IDs:
1selected_fid = []
2
3# Get the first feature id from the layer
4feature = next(layer.getFeatures())
5if feature:
6 selected_fid.append(feature.id())
7
8# Add that features to the selected list
9layer.select(selected_fid)
To clear the selection:
layer.removeSelection()
6.3.1. Accesarea atributelor
Attributes can be referred to by their name:
print(feature['name'])
First feature
Alternatively, attributes can be referred to by index. This is a bit faster than using the name. For example, to get the second attribute:
print(feature[1])
First feature
6.3.2. Parcurgerea entităților selectate
If you only need selected features, you can use the selectedFeatures()
method from the vector layer:
selection = layer.selectedFeatures()
for feature in selection:
# do whatever you need with the feature
pass
6.3.3. Parcurgerea unui subset de entități
If you want to iterate over a given subset of features in a layer, such as
those within a given area, you have to add a QgsFeatureRequest
object
to the getFeatures()
call. Here’s an example:
1areaOfInterest = QgsRectangle(450290,400520, 450750,400780)
2
3request = QgsFeatureRequest().setFilterRect(areaOfInterest)
4
5for feature in layer.getFeatures(request):
6 # do whatever you need with the feature
7 pass
For the sake of speed, the intersection is often done only using feature’s
bounding box. There is however a flag ExactIntersect
that makes sure that
only intersecting features will be returned:
request = QgsFeatureRequest().setFilterRect(areaOfInterest) \
.setFlags(QgsFeatureRequest.ExactIntersect)
With setLimit()
you can limit the number of requested features.
Here’s an example:
request = QgsFeatureRequest()
request.setLimit(2)
for feature in layer.getFeatures(request):
print(feature)
<qgis._core.QgsFeature object at 0x7f9b78590948>
<qgis._core.QgsFeature object at 0x7faef5881670>
Dacă aveți nevoie de un filtru pe bază de atribut în locul unuia spațial (sau în plus față de acesta), așa cum se arată în exemplele de mai sus, puteți construi un obiect QgsExpression
, pe care să-l transmiteți constructorului QgsFeatureRequest
. Iată un exemplu:
# The expression will filter the features where the field "location_name"
# contains the word "Lake" (case insensitive)
exp = QgsExpression('location_name ILIKE \'%Lake%\'')
request = QgsFeatureRequest(exp)
See Expresii, filtrarea și calculul valorilor for the details about the syntax supported by QgsExpression
.
Cererea poate fi utilizată pentru a defini datele cerute pentru fiecare entitate, astfel încât iteratorul să întoarcă toate entitățile, dar să returneze datele parțiale pentru fiecare dintre ele.
1# Only return selected fields to increase the "speed" of the request
2request.setSubsetOfAttributes([0,2])
3
4# More user friendly version
5request.setSubsetOfAttributes(['name','id'],layer.fields())
6
7# Don't return geometry objects to increase the "speed" of the request
8request.setFlags(QgsFeatureRequest.NoGeometry)
9
10# Fetch only the feature with id 45
11request.setFilterFid(45)
12
13# The options may be chained
14request.setFilterRect(areaOfInterest).setFlags(QgsFeatureRequest.NoGeometry).setFilterFid(45).setSubsetOfAttributes([0,2])
6.4. Modificarea straturilor vectoriale
Most vector data providers support editing of layer data. Sometimes they support
just a subset of possible editing actions. Use the capabilities()
function
to find out what set of functionality is supported.
caps = layer.dataProvider().capabilities()
# Check if a particular capability is supported:
if caps & QgsVectorDataProvider.DeleteFeatures:
print('The layer supports DeleteFeatures')
The layer supports DeleteFeatures
For a list of all available capabilities, please refer to the
API Documentation of QgsVectorDataProvider
.
To print layer’s capabilities textual description in a comma separated list you
can use capabilitiesString()
as in the following example:
1caps_string = layer.dataProvider().capabilitiesString()
2# Print:
3# 'Add Features, Delete Features, Change Attribute Values, Add Attributes,
4# Delete Attributes, Rename Attributes, Fast Access to Features at ID,
5# Presimplify Geometries, Presimplify Geometries with Validity Check,
6# Transactions, Curved Geometries'
Utilizând oricare dintre următoarele metode de editare a straturilor vectoriale, schimbările sunt efectuate direct în depozitul de date (un fișier, o bază de date etc). În cazul în care doriți să faceți doar schimbări temporare, treceți la secțiunea următoare, care explică efectuarea modifications with editing buffer.
Notă
If you are working inside QGIS (either from the console or from a plugin), it might be necessary to force a redraw of the map canvas in order to see the changes you’ve done to the geometry, to the style or to the attributes:
1# If caching is enabled, a simple canvas refresh might not be sufficient
2# to trigger a redraw and you must clear the cached image for the layer
3if iface.mapCanvas().isCachingEnabled():
4 layer.triggerRepaint()
5else:
6 iface.mapCanvas().refresh()
6.4.1. Adăugarea entităților
Create some QgsFeature
instances and pass a list of them to the provider
QgsVectorDataProvider
addFeatures()
method. It will return two values:
result (True
or False
) and
list of added features (their ID is set by the data store).
To set up the attributes of the feature, you can either initialize the feature passing a
QgsFields
object (you can obtain that from the
fields()
method of the vector layer)
or call initAttributes()
passing
the number of fields you want to be added.
1if caps & QgsVectorDataProvider.AddFeatures:
2 feat = QgsFeature(layer.fields())
3 feat.setAttributes([0, 'hello'])
4 # Or set a single attribute by key or by index:
5 feat.setAttribute('name', 'hello')
6 feat.setAttribute(0, 'hello')
7 feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(123, 456)))
8 (res, outFeats) = layer.dataProvider().addFeatures([feat])
6.4.2. Ștergerea entităților
To delete some features, just provide a list of their feature IDs.
if caps & QgsVectorDataProvider.DeleteFeatures:
res = layer.dataProvider().deleteFeatures([5, 10])
6.4.3. Modificarea entităților
It is possible to either change feature’s geometry or to change some attributes. The following example first changes values of attributes with index 0 and 1, then it changes the feature’s geometry.
1fid = 100 # ID of the feature we will modify
2
3if caps & QgsVectorDataProvider.ChangeAttributeValues:
4 attrs = { 0 : "hello", 1 : 123 }
5 layer.dataProvider().changeAttributeValues({ fid : attrs })
6
7if caps & QgsVectorDataProvider.ChangeGeometries:
8 geom = QgsGeometry.fromPointXY(QgsPointXY(111,222))
9 layer.dataProvider().changeGeometryValues({ fid : geom })
Sfat
Favor QgsVectorLayerEditUtils class for geometry-only edits
If you only need to change geometries, you might consider using
the QgsVectorLayerEditUtils
which provides some useful
methods to edit geometries (translate, insert or move vertex, etc.).
6.4.4. Modificarea straturi vectoriale prin editarea unui tampon de memorie
When editing vectors within QGIS application, you have to first start editing mode for a particular layer, then do some modifications and finally commit (or rollback) the changes. All the changes you make are not written until you commit them — they stay in layer’s in-memory editing buffer. It is possible to use this functionality also programmatically — it is just another method for vector layer editing that complements the direct usage of data providers. Use this option when providing some GUI tools for vector layer editing, since this will allow user to decide whether to commit/rollback and allows the usage of undo/redo. When changes are committed, all changes from the editing buffer are saved to data provider.
The methods are similar to the ones we have seen in the provider, but they are
called on the QgsVectorLayer
object instead.
For these methods to work, the layer must be in editing mode. To start the editing mode,
use the startEditing()
method.
To stop editing, use the commitChanges()
or rollBack()
methods.
The first one will commit all your changes to the data source, while the second
one will discard them and will not modify the data source at all.
To find out whether a layer is in editing mode, use the isEditable()
method.
Here you have some examples that demonstrate how to use these editing methods.
1from qgis.PyQt.QtCore import QVariant
2
3feat1 = feat2 = QgsFeature(layer.fields())
4fid = 99
5feat1.setId(fid)
6
7# add two features (QgsFeature instances)
8layer.addFeatures([feat1,feat2])
9# delete a feature with specified ID
10layer.deleteFeature(fid)
11
12# set new geometry (QgsGeometry instance) for a feature
13geometry = QgsGeometry.fromWkt("POINT(7 45)")
14layer.changeGeometry(fid, geometry)
15# update an attribute with given field index (int) to a given value
16fieldIndex =1
17value ='My new name'
18layer.changeAttributeValue(fid, fieldIndex, value)
19
20# add new field
21layer.addAttribute(QgsField("mytext", QVariant.String))
22# remove a field
23layer.deleteAttribute(fieldIndex)
In order to make undo/redo work properly, the above mentioned calls have to be wrapped into undo commands. (If you do not care about undo/redo and want to have the changes stored immediately, then you will have easier work by editing with data provider.)
Here is how you can use the undo functionality:
1layer.beginEditCommand("Feature triangulation")
2
3# ... call layer's editing methods ...
4
5if problem_occurred:
6 layer.destroyEditCommand()
7 # ... tell the user that there was a problem
8 # and return
9
10# ... more editing ...
11
12layer.endEditCommand()
The beginEditCommand()
method will create an internal „active” command and will
record subsequent changes in vector layer. With the call to endEditCommand()
the command is pushed onto the undo stack and the user will be able to undo/redo
it from GUI. In case something went wrong while doing the changes, the
destroyEditCommand()
method will remove the command and rollback all
changes done while this command was active.
De asemenea, puteți utiliza expresia with edit(layer)
- pentru a încorpora într-un bloc de cod semantic, pentru commit și rollback, așa cum se arată în exemplul de mai jos:
with edit(layer):
feat = next(layer.getFeatures())
feat[0] = 5
layer.updateFeature(feat)
This will automatically call commitChanges()
in the end.
If any exception occurs, it will rollBack()
all the changes.
In case a problem is encountered within commitChanges()
(when the method
returns False) a QgsEditError
exception will be raised.
6.4.5. Adăugarea și eliminarea câmpurilor
Pentru a adăuga câmpuri (atribute), trebuie să specificați o listă de definiții pentru acestea. Pentru ștergerea de câmpuri e suficientă furnizarea unei liste de indecși pentru câmpuri.
1from qgis.PyQt.QtCore import QVariant
2
3if caps & QgsVectorDataProvider.AddAttributes:
4 res = layer.dataProvider().addAttributes(
5 [QgsField("mytext", QVariant.String),
6 QgsField("myint", QVariant.Int)])
7
8if caps & QgsVectorDataProvider.DeleteAttributes:
9 res = layer.dataProvider().deleteAttributes([0])
1# Alternate methods for removing fields
2# first create temporary fields to be removed (f1-3)
3layer.dataProvider().addAttributes([QgsField("f1",QVariant.Int),QgsField("f2",QVariant.Int),QgsField("f3",QVariant.Int)])
4layer.updateFields()
5count=layer.fields().count() # count of layer fields
6ind_list=list((count-3, count-2)) # create list
7
8# remove a single field with an index
9layer.dataProvider().deleteAttributes([count-1])
10
11# remove multiple fields with a list of indices
12layer.dataProvider().deleteAttributes(ind_list)
După adăugarea sau eliminarea câmpurilor din furnizorul de date, câmpurile stratului trebuie să fie actualizate, deoarece modificările nu se propagă automat.
layer.updateFields()
Sfat
Directly save changes using with
based command
Using with edit(layer):
the changes will be committed automatically
calling commitChanges()
at the end. If any exception occurs, it will
rollBack()
all the changes. See Modificarea straturi vectoriale prin editarea unui tampon de memorie.
6.5. Crearea unui index spațial
Indecșii spațiali pot îmbunătăți dramatic performanța codului dvs, în cazul în care este nevoie să interogați frecvent un strat vectorial. Imaginați-vă, de exemplu, că scrieți un algoritm de interpolare, și că, pentru o anumită locație, trebuie să aflați cele mai apropiate 10 puncte dintr-un strat, în scopul utilizării acelor puncte în calculul valorii interpolate. Fără un index spațial, singura modalitate pentru QGIS de a găsi cele 10 puncte, este de a calcula distanța tuturor punctelor față de locația specificată și apoi de a compara aceste distanțe. Această sarcină poate fi mare consumatoare de timp, mai ales în cazul în care trebuie să fie repetată pentru mai multe locații. Dacă pentru stratul respectiv există un index spațial, operațiunea va fi mult mai eficientă.
Gândiți-vă la un strat fără index spațial ca la o carte de telefon în care numerele de telefon nu sunt ordonate sau indexate. Singura modalitate de a afla numărul de telefon al unei anumite persoane este de a citi toate numerele, începând cu primul, până când îl găsiți.
Indecșii spațiali nu sunt creați în mod implicit pentru un strat QGIS vectorial, dar îi puteți genera cu ușurință. Iată ce trebuie să faceți:
create spatial index using the
QgsSpatialIndex
class:index = QgsSpatialIndex()
add features to index — index takes
QgsFeature
object and adds it to the internal data structure. You can create the object manually or use one from a previous call to the provider’sgetFeatures()
method.index.addFeature(feat)
alternatively, you can load all features of a layer at once using bulk loading
index = QgsSpatialIndex(layer.getFeatures())
o dată ce ați introdus valori în indexul spațial, puteți efectua unele interogări
1# returns array of feature IDs of five nearest features 2nearest = index.nearestNeighbor(QgsPointXY(25.4, 12.7), 5) 3 4# returns array of IDs of features which intersect the rectangle 5intersect = index.intersects(QgsRectangle(22.5, 15.3, 23.1, 17.2))
You can also use the QgsSpatialIndexKDBush
spatial index. This index is similar to the standard QgsSpatialIndex
but:
supports only single point features
is static (no additional features can be added to the index after the construction)
is much faster!
allows direct retrieval of the original feature’s points, without requiring additional feature requests
supports true distance based searches, i.e. return all points within a radius from a search point
6.6. The QgsVectorLayerUtils class
The QgsVectorLayerUtils
class contains
some very useful methods that you can use with vector layers.
For example the createFeature()
method prepares a QgsFeature
to be added to
a vector layer keeping all the eventual constraints and default values of each
field:
vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
feat = QgsVectorLayerUtils.createFeature(vlayer)
The getValues()
method allows
you to quickly get the values of a field or expression:
1vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
2# select only the first feature to make the output shorter
3vlayer.selectByIds([1])
4val = QgsVectorLayerUtils.getValues(vlayer, "NAME", selectedOnly=True)
5print(val)
(['AMBLER'], True)
6.7. Creating Vector Layers
There are several ways to generate a vector layer dataset:
the
QgsVectorFileWriter
class: A convenient class for writing vector files to disk, using either a static call towriteAsVectorFormatV3()
which saves the whole vector layer or creating an instance of the class and issue calls to inheritedaddFeature()
. This class supports all the vector formats that GDAL supports (GeoPackage, Shapefile, GeoJSON, KML and others).the
QgsVectorLayer
class: instantiates a data provider that interprets the supplied path (url) of the data source to connect to and access the data. It can be used to create temporary, memory-based layers (memory
) and connect to GDAL vector datasets (ogr
), databases (postgres
,spatialite
,mysql
,mssql
) and more (wfs
,gpx
,delimitedtext
…).
6.7.1. From an instance of QgsVectorFileWriter
1# SaveVectorOptions contains many settings for the writer process
2save_options = QgsVectorFileWriter.SaveVectorOptions()
3transform_context = QgsProject.instance().transformContext()
4# Write to a GeoPackage (default)
5error = QgsVectorFileWriter.writeAsVectorFormatV3(layer,
6 "testdata/my_new_file.gpkg",
7 transform_context,
8 save_options)
9if error[0] == QgsVectorFileWriter.NoError:
10 print("success!")
11else:
12 print(error)
1# Write to an ESRI Shapefile format dataset using UTF-8 text encoding
2save_options = QgsVectorFileWriter.SaveVectorOptions()
3save_options.driverName = "ESRI Shapefile"
4save_options.fileEncoding = "UTF-8"
5transform_context = QgsProject.instance().transformContext()
6error = QgsVectorFileWriter.writeAsVectorFormatV3(layer,
7 "testdata/my_new_shapefile",
8 transform_context,
9 save_options)
10if error[0] == QgsVectorFileWriter.NoError:
11 print("success again!")
12else:
13 print(error)
1# Write to an ESRI GDB file
2save_options = QgsVectorFileWriter.SaveVectorOptions()
3save_options.driverName = "FileGDB"
4# if no geometry
5save_options.overrideGeometryType = QgsWkbTypes.Unknown
6save_options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer
7save_options.layerName = 'my_new_layer_name'
8transform_context = QgsProject.instance().transformContext()
9gdb_path = "testdata/my_example.gdb"
10error = QgsVectorFileWriter.writeAsVectorFormatV3(layer,
11 gdb_path,
12 transform_context,
13 save_options)
14if error[0] == QgsVectorFileWriter.NoError:
15 print("success!")
16else:
17 print(error)
You can also convert fields to make them compatible with different formats by
using the FieldValueConverter
.
For example, to convert array variable types (e.g. in Postgres) to a text type,
you can do the following:
1LIST_FIELD_NAME = 'xxxx'
2
3class ESRIValueConverter(QgsVectorFileWriter.FieldValueConverter):
4
5 def __init__(self, layer, list_field):
6 QgsVectorFileWriter.FieldValueConverter.__init__(self)
7 self.layer = layer
8 self.list_field_idx = self.layer.fields().indexFromName(list_field)
9
10 def convert(self, fieldIdxInLayer, value):
11 if fieldIdxInLayer == self.list_field_idx:
12 return QgsListFieldFormatter().representValue(layer=vlayer,
13 fieldIndex=self.list_field_idx,
14 config={},
15 cache=None,
16 value=value)
17 else:
18 return value
19
20 def fieldDefinition(self, field):
21 idx = self.layer.fields().indexFromName(field.name())
22 if idx == self.list_field_idx:
23 return QgsField(LIST_FIELD_NAME, QVariant.String)
24 else:
25 return self.layer.fields()[idx]
26
27converter = ESRIValueConverter(vlayer, LIST_FIELD_NAME)
28opts = QgsVectorFileWriter.SaveVectorOptions()
29opts.fieldValueConverter = converter
A destination CRS may also be specified — if a valid instance of
QgsCoordinateReferenceSystem
is passed as the fourth parameter, the layer is transformed to that CRS.
For valid driver names please call the supportedFiltersAndFormats()
method
or consult the supported formats by OGR — you
should pass the value in the „Code” column as the driver name.
Optionally you can set whether to export only selected features, pass further
driver-specific options for creation or tell the writer not to create attributes…
There are a number of other (optional) parameters; see the QgsVectorFileWriter
documentation for details.
6.7.2. Directly from features
1from qgis.PyQt.QtCore import QVariant
2
3# define fields for feature attributes. A QgsFields object is needed
4fields = QgsFields()
5fields.append(QgsField("first", QVariant.Int))
6fields.append(QgsField("second", QVariant.String))
7
8""" create an instance of vector file writer, which will create the vector file.
9Arguments:
101. path to new file (will fail if exists already)
112. field map
123. geometry type - from WKBTYPE enum
134. layer's spatial reference (instance of
14 QgsCoordinateReferenceSystem)
155. coordinate transform context
166. save options (driver name for the output file, encoding etc.)
17"""
18
19crs = QgsProject.instance().crs()
20transform_context = QgsProject.instance().transformContext()
21save_options = QgsVectorFileWriter.SaveVectorOptions()
22save_options.driverName = "ESRI Shapefile"
23save_options.fileEncoding = "UTF-8"
24
25writer = QgsVectorFileWriter.create(
26 "testdata/my_new_shapefile.shp",
27 fields,
28 QgsWkbTypes.Point,
29 crs,
30 transform_context,
31 save_options
32)
33
34if writer.hasError() != QgsVectorFileWriter.NoError:
35 print("Error when creating shapefile: ", writer.errorMessage())
36
37# add a feature
38fet = QgsFeature()
39
40fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
41fet.setAttributes([1, "text"])
42writer.addFeature(fet)
43
44# delete the writer to flush features to disk
45del writer
6.7.3. From an instance of QgsVectorLayer
Among all the data providers supported by the QgsVectorLayer
class, let’s focus on the memory-based layers.
Memory provider is intended to be used mainly by plugin or 3rd party app
developers. It does not store data on disk, allowing developers to use it as a
fast backend for some temporary layers.
Furnizorul suportă câmpuri de tip string, int sau double.
The memory provider also supports spatial indexing, which is enabled by calling
the provider’s createSpatialIndex()
function. Once the spatial index is
created you will be able to iterate over features within smaller regions faster
(since it’s not necessary to traverse all the features, only those in specified
rectangle).
A memory provider is created by passing "memory"
as the provider string to
the QgsVectorLayer
constructor.
The constructor also takes a URI defining the geometry type of the layer,
one of: "Point"
, "LineString"
, "Polygon"
, "MultiPoint"
,
"MultiLineString"
, "MultiPolygon"
or "None"
.
URI poate specifica, de asemenea, sistemul de referință și coordonate, câmpurile, precum și indexarea furnizorului de memorie. Sintaxa este:
- crs=definiție
Specifies the coordinate reference system, where definition may be any of the forms accepted by
QgsCoordinateReferenceSystem.createFromString()
- index=yes
Specificați dacă furnizorul va utiliza un index spațial.
- field=nume:tip(lungime,precizie)
Specificați un atribut al stratului. Atributul are un nume și, opțional, un tip (integer, double sau string), lungime și precizie. Pot exista mai multe definiții de câmp.
Următorul exemplu de URI încorporează toate aceste opțiuni
"Point?crs=epsg:4326&field=id:integer&field=name:string(20)&index=yes"
Următorul exemplu de cod ilustrează crearea și popularea unui furnizor de memorie
1from qgis.PyQt.QtCore import QVariant
2
3# create layer
4vl = QgsVectorLayer("Point", "temporary_points", "memory")
5pr = vl.dataProvider()
6
7# add fields
8pr.addAttributes([QgsField("name", QVariant.String),
9 QgsField("age", QVariant.Int),
10 QgsField("size", QVariant.Double)])
11vl.updateFields() # tell the vector layer to fetch changes from the provider
12
13# add a feature
14fet = QgsFeature()
15fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
16fet.setAttributes(["Johny", 2, 0.3])
17pr.addFeatures([fet])
18
19# update layer's extent when new features have been added
20# because change of extent in provider is not propagated to the layer
21vl.updateExtents()
În cele din urmă, să verificăm dacă totul a mers bine
1# show some stats
2print("fields:", len(pr.fields()))
3print("features:", pr.featureCount())
4e = vl.extent()
5print("extent:", e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum())
6
7# iterate over features
8features = vl.getFeatures()
9for fet in features:
10 print("F:", fet.id(), fet.attributes(), fet.geometry().asPoint())
fields: 3
features: 1
extent: 10.0 10.0 10.0 10.0
F: 1 ['Johny', 2, 0.3] <QgsPointXY: POINT(10 10)>
6.8. Aspectul (simbologia) straturilor vectoriale
Când un strat vector este randat, aspectul datelor este dat de render și de simbolurile asociate stratului. Simbolurile sunt clase care au grijă de reprezentarea vizuală a tuturor entităților, în timp ce un render determină ce simbol va fi folosit doar pentru anumite entități.
The renderer for a given layer can be obtained as shown below:
renderer = layer.renderer()
Și cu acea referință, să explorăm un pic
print("Type:", renderer.type())
Type: singleSymbol
There are several known renderer types available in the QGIS core library:
Tipul |
Clasa |
Descrierea |
---|---|---|
singleSymbol |
Asociază tuturor entităților același simbol |
|
categorizedSymbol |
Asociază entităților un simbol diferit, în funcție de categorie |
|
graduatedSymbol |
Asociază fiecărei entități un simbol diferit pentru fiecare gamă de valori |
There might be also some custom renderer types, so never make an assumption
there are just these types. You can query the application’s QgsRendererRegistry
to find out currently available renderers:
print(QgsApplication.rendererRegistry().renderersList())
['nullSymbol', 'singleSymbol', 'categorizedSymbol', 'graduatedSymbol', 'RuleRenderer', 'pointDisplacement', 'pointCluster', 'mergedFeatureRenderer', 'invertedPolygonRenderer', 'heatmapRenderer', '25dRenderer', 'embeddedSymbol']
Este posibilă obținerea conținutului renderului sub formă de text — lucru util pentru depanare
renderer.dump()
SINGLE: MARKER SYMBOL (1 layers) color 190,207,80,255
6.8.1. Render cu Simbol Unic
You can get the symbol used for rendering by calling symbol()
method and
change it with setSymbol()
method (note for C++ devs: the renderer takes
ownership of the symbol.)
You can change the symbol used by a particular vector layer by calling
setSymbol()
passing an instance of the appropriate symbol instance.
Symbols for point, line and polygon layers can be created by calling
the createSimple()
function of the corresponding classes
QgsMarkerSymbol
, QgsLineSymbol
and
QgsFillSymbol
.
The dictionary passed to createSimple()
sets the style properties of the
symbol.
For example you can replace the symbol used by a particular point layer
by calling setSymbol()
passing an instance of a QgsMarkerSymbol
,
as in the following code example:
symbol = QgsMarkerSymbol.createSimple({'name': 'square', 'color': 'red'})
layer.renderer().setSymbol(symbol)
# show the change
layer.triggerRepaint()
nume
: indică forma markerului, aceasta putând fi oricare dintre următoarele:
circle
square
cross
rectangle
diamond
pentagon
triangle
equilateral_triangle
star
regular_star
arrow
filled_arrowhead
x
To get the full list of properties for the first symbol layer of a symbol instance you can follow the example code:
print(layer.renderer().symbol().symbolLayers()[0].properties())
{'angle': '0', 'cap_style': 'square', 'color': '255,0,0,255', 'horizontal_anchor_point': '1', 'joinstyle': 'bevel', 'name': 'square', 'offset': '0,0', 'offset_map_unit_scale': '3x:0,0,0,0,0,0', 'offset_unit': 'MM', 'outline_color': '35,35,35,255', 'outline_style': 'solid', 'outline_width': '0', 'outline_width_map_unit_scale': '3x:0,0,0,0,0,0', 'outline_width_unit': 'MM', 'scale_method': 'diameter', 'size': '2', 'size_map_unit_scale': '3x:0,0,0,0,0,0', 'size_unit': 'MM', 'vertical_anchor_point': '1'}
This can be useful if you want to alter some properties:
1# You can alter a single property...
2layer.renderer().symbol().symbolLayer(0).setSize(3)
3# ... but not all properties are accessible from methods,
4# you can also replace the symbol completely:
5props = layer.renderer().symbol().symbolLayer(0).properties()
6props['color'] = 'yellow'
7props['name'] = 'square'
8layer.renderer().setSymbol(QgsMarkerSymbol.createSimple(props))
9# show the changes
10layer.triggerRepaint()
6.8.2. Render cu Simboluri Categorisite
When using a categorized renderer, you can query and set the attribute that is used for classification: use the
classAttribute()
and setClassAttribute()
methods.
Pentru a obține o listă de categorii
1categorized_renderer = QgsCategorizedSymbolRenderer()
2# Add a few categories
3cat1 = QgsRendererCategory('1', QgsMarkerSymbol(), 'category 1')
4cat2 = QgsRendererCategory('2', QgsMarkerSymbol(), 'category 2')
5categorized_renderer.addCategory(cat1)
6categorized_renderer.addCategory(cat2)
7
8for cat in categorized_renderer.categories():
9 print("{}: {} :: {}".format(cat.value(), cat.label(), cat.symbol()))
1: category 1 :: <qgis._core.QgsMarkerSymbol object at 0x7f378ffcd9d8>
2: category 2 :: <qgis._core.QgsMarkerSymbol object at 0x7f378ffcd9d8>
Where value()
is the value used for discrimination between categories,
label()
is a text used for category description and symbol()
method
returns the assigned symbol.
The renderer usually stores also original symbol and color ramp which were used
for the classification: sourceColorRamp()
and sourceSymbol()
methods.
6.8.3. Render cu Simboluri Graduale
Acest render este foarte similar cu renderul cu simbol clasificat, descris mai sus, dar în loc de o singură valoare de atribut per clasă el lucrează cu intervale de valori, putând fi, astfel, utilizat doar cu atribute numerice.
Pentru a afla mai multe despre gamele utilizate în render
1graduated_renderer = QgsGraduatedSymbolRenderer()
2# Add a few categories
3graduated_renderer.addClassRange(QgsRendererRange(QgsClassificationRange('class 0-100', 0, 100), QgsMarkerSymbol()))
4graduated_renderer.addClassRange(QgsRendererRange(QgsClassificationRange('class 101-200', 101, 200), QgsMarkerSymbol()))
5
6for ran in graduated_renderer.ranges():
7 print("{} - {}: {} {}".format(
8 ran.lowerValue(),
9 ran.upperValue(),
10 ran.label(),
11 ran.symbol()
12 ))
0.0 - 100.0: class 0-100 <qgis._core.QgsMarkerSymbol object at 0x7f8bad281b88>
101.0 - 200.0: class 101-200 <qgis._core.QgsMarkerSymbol object at 0x7f8bad281b88>
you can again use the
classAttribute()
(to find the classification attribute name),
sourceSymbol()
and sourceColorRamp()
methods.
Additionally there is the mode()
method which determines how the ranges were created:
using equal intervals, quantiles or some other method.
Dacă doriți să creați propriul render cu simbol gradual, puteți face acest lucru așa cum este ilustrat în fragmentul de mai jos (care creează un simplu aranjament cu două clase)
1from qgis.PyQt import QtGui
2
3myVectorLayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
4myTargetField = 'ELEV'
5myRangeList = []
6myOpacity = 1
7# Make our first symbol and range...
8myMin = 0.0
9myMax = 50.0
10myLabel = 'Group 1'
11myColour = QtGui.QColor('#ffee00')
12mySymbol1 = QgsSymbol.defaultSymbol(myVectorLayer.geometryType())
13mySymbol1.setColor(myColour)
14mySymbol1.setOpacity(myOpacity)
15myRange1 = QgsRendererRange(myMin, myMax, mySymbol1, myLabel)
16myRangeList.append(myRange1)
17#now make another symbol and range...
18myMin = 50.1
19myMax = 100
20myLabel = 'Group 2'
21myColour = QtGui.QColor('#00eeff')
22mySymbol2 = QgsSymbol.defaultSymbol(
23 myVectorLayer.geometryType())
24mySymbol2.setColor(myColour)
25mySymbol2.setOpacity(myOpacity)
26myRange2 = QgsRendererRange(myMin, myMax, mySymbol2, myLabel)
27myRangeList.append(myRange2)
28myRenderer = QgsGraduatedSymbolRenderer('', myRangeList)
29myClassificationMethod = QgsApplication.classificationMethodRegistry().method("EqualInterval")
30myRenderer.setClassificationMethod(myClassificationMethod)
31myRenderer.setClassAttribute(myTargetField)
32
33myVectorLayer.setRenderer(myRenderer)
6.8.4. Lucrul cu Simboluri
For representation of symbols, there is QgsSymbol
base class with
three derived classes:
QgsMarkerSymbol
— pentru entitățile de tip punctQgsLineSymbol
— pentru entitățile liniareQgsFillSymbol
— pentru entitățile poligonale
Every symbol consists of one or more symbol layers (classes derived from
QgsSymbolLayer
). The symbol layers do the actual rendering, the
symbol class itself serves only as a container for the symbol layers.
Having an instance of a symbol (e.g. from a renderer), it is possible to
explore it: the type()
method says whether it is a
marker, line or fill symbol. There is a dump()
method which returns a brief description of the symbol. To get a list of symbol
layers:
marker_symbol = QgsMarkerSymbol()
for i in range(marker_symbol.symbolLayerCount()):
lyr = marker_symbol.symbolLayer(i)
print("{}: {}".format(i, lyr.layerType()))
0: SimpleMarker
To find out symbol’s color use color()
method
and setColor()
to
change its color. With marker symbols additionally you can query for the symbol
size and rotation with the size()
and angle()
methods. For line symbols
the width()
method returns the line width.
Dimensiunea și lățimea sunt în milimetri, în mod implicit, iar unghiurile sunt în grade.
6.8.4.1. Lucrul cu Straturile Simbolului
As said before, symbol layers (subclasses of QgsSymbolLayer
)
determine the appearance of the features. There are several basic symbol layer
classes for general use. It is possible to implement new symbol layer types and
thus arbitrarily customize how features will be rendered. The layerType()
method uniquely identifies the symbol layer class — the basic and default
ones are SimpleMarker
, SimpleLine
and SimpleFill
symbol layers types.
You can get a complete list of the types of symbol layers you can create for a given symbol layer class with the following code:
1from qgis.core import QgsSymbolLayerRegistry
2myRegistry = QgsApplication.symbolLayerRegistry()
3myMetadata = myRegistry.symbolLayerMetadata("SimpleFill")
4for item in myRegistry.symbolLayersForType(QgsSymbol.Marker):
5 print(item)
1AnimatedMarker
2EllipseMarker
3FilledMarker
4FontMarker
5GeometryGenerator
6MaskMarker
7RasterMarker
8SimpleMarker
9SvgMarker
10VectorField
The QgsSymbolLayerRegistry
class manages
a database of all available symbol layer types.
To access symbol layer data, use its properties()
method that returns a
key-value dictionary of properties which determine the appearance. Each symbol
layer type has a specific set of properties that it uses. Additionally, there
are the generic methods color()
, size()
, angle()
and
width()
,
with their setter counterparts. Of course size and angle are available only for
marker symbol layers and width for line symbol layers.
6.8.4.2. Crearea unor Tipuri Personalizate de Straturi pentru Simboluri
Imaginați-vă că ați dori să personalizați modul în care se randează datele. Vă puteți crea propria dvs. clasă de strat de simbol, care va desena entitățile exact așa cum doriți. Iată un exemplu de marker care desenează cercuri roșii cu o rază specificată
1from qgis.core import QgsMarkerSymbolLayer
2from qgis.PyQt.QtGui import QColor
3
4class FooSymbolLayer(QgsMarkerSymbolLayer):
5
6 def __init__(self, radius=4.0):
7 QgsMarkerSymbolLayer.__init__(self)
8 self.radius = radius
9 self.color = QColor(255,0,0)
10
11 def layerType(self):
12 return "FooMarker"
13
14 def properties(self):
15 return { "radius" : str(self.radius) }
16
17 def startRender(self, context):
18 pass
19
20 def stopRender(self, context):
21 pass
22
23 def renderPoint(self, point, context):
24 # Rendering depends on whether the symbol is selected (QGIS >= 1.5)
25 color = context.selectionColor() if context.selected() else self.color
26 p = context.renderContext().painter()
27 p.setPen(color)
28 p.drawEllipse(point, self.radius, self.radius)
29
30 def clone(self):
31 return FooSymbolLayer(self.radius)
The layerType()
method determines
the name of the symbol layer; it has to be unique among all symbol layers.
The properties()
method is used
for persistence of attributes. The clone()
method must return a copy of the symbol layer with
all attributes being exactly the same. Finally there are rendering methods:
startRender()
is called before
rendering the first feature, stopRender()
when the rendering is done, and renderPoint()
is called to do the rendering.
The coordinates of the point(s) are already transformed to the output coordinates.
For polylines and polygons the only difference would be in the rendering
method: you would use
renderPolyline()
which receives a list of lines,
while renderPolygon()
receives a list of points on the outer ring as the
first parameter and a list of inner rings (or None) as a second parameter.
De obicei, este convenabilă adăugarea unui GUI pentru setarea atributelor tipului de strat pentru simboluri, pentru a permite utilizatorilor să personalizeze aspectul: în exemplul de mai sus, putem lăsa utilizatorul să seteze raza cercului. Codul de mai jos implementează un astfel de widget
1from qgis.gui import QgsSymbolLayerWidget
2
3class FooSymbolLayerWidget(QgsSymbolLayerWidget):
4 def __init__(self, parent=None):
5 QgsSymbolLayerWidget.__init__(self, parent)
6
7 self.layer = None
8
9 # setup a simple UI
10 self.label = QLabel("Radius:")
11 self.spinRadius = QDoubleSpinBox()
12 self.hbox = QHBoxLayout()
13 self.hbox.addWidget(self.label)
14 self.hbox.addWidget(self.spinRadius)
15 self.setLayout(self.hbox)
16 self.connect(self.spinRadius, SIGNAL("valueChanged(double)"), \
17 self.radiusChanged)
18
19 def setSymbolLayer(self, layer):
20 if layer.layerType() != "FooMarker":
21 return
22 self.layer = layer
23 self.spinRadius.setValue(layer.radius)
24
25 def symbolLayer(self):
26 return self.layer
27
28 def radiusChanged(self, value):
29 self.layer.radius = value
30 self.emit(SIGNAL("changed()"))
This widget can be embedded into the symbol properties dialog. When the symbol
layer type is selected in symbol properties dialog, it creates an instance of
the symbol layer and an instance of the symbol layer widget. Then it calls
the setSymbolLayer()
method to
assign the symbol layer to the widget. In that
method the widget should update the UI to reflect the attributes of the symbol
layer. The symbolLayer()
method
is used to retrieve the symbol layer again
by the properties dialog to use it for the symbol.
On every change of attributes, the widget should emit the changed()
signal
to let the properties dialog update the symbol preview.
Acum mai lipsește doar liantul final: pentru a face QGIS conștient de aceste noi clase. Acest lucru se face prin adăugarea stratului simbol la registru. Este posibilă utilizarea stratului simbol, de asemenea, fără a-l adăuga la registru, dar unele funcționalități nu vor fi disponibile: de exemplu, încărcarea de fișiere de proiect cu straturi simbol personalizate sau incapacitatea de a edita atributele stratului în GUI.
Va trebui să creăm metadate pentru stratul simbolului
1from qgis.core import QgsSymbol, QgsSymbolLayerAbstractMetadata, QgsSymbolLayerRegistry
2
3class FooSymbolLayerMetadata(QgsSymbolLayerAbstractMetadata):
4
5 def __init__(self):
6 super().__init__("FooMarker", "My new Foo marker", QgsSymbol.Marker)
7
8 def createSymbolLayer(self, props):
9 radius = float(props["radius"]) if "radius" in props else 4.0
10 return FooSymbolLayer(radius)
11
12fslmetadata = FooSymbolLayerMetadata()
QgsApplication.symbolLayerRegistry().addSymbolLayerType(fslmetadata)
You should pass layer type (the same as returned by the layer) and symbol type
(marker/line/fill) to the constructor of the parent class. The createSymbolLayer()
method
takes care of creating an instance of symbol layer with attributes specified in
the props dictionary. And there is the createSymbolLayerWidget()
method which
returns the settings widget for this symbol layer type.
Ultimul pas este de a adăuga acest strat simbol la registru — și am încheiat.
6.8.5. Crearea renderelor Personalizate
Ar putea fi utilă crearea unei noi implementări de render, dacă doriți să personalizați regulile de selectare a simbolurilor pentru randarea entităților. Unele cazuri de utilizare: simbolul să fie determinat de o combinație de câmpuri, dimensiunea simbolurilor să depindă în funcție de scara curentă, etc
Urmatorul cod prezintă o simplă randare personalizată, care creează două simboluri de tip marker și apoi alege aleatoriu unul dintre ele pentru fiecare entitate
1import random
2from qgis.core import QgsWkbTypes, QgsSymbol, QgsFeatureRenderer
3
4
5class RandomRenderer(QgsFeatureRenderer):
6 def __init__(self, syms=None):
7 super().__init__("RandomRenderer")
8 self.syms = syms if syms else [
9 QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.Point)),
10 QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.Point))
11 ]
12
13 def symbolForFeature(self, feature, context):
14 return random.choice(self.syms)
15
16 def startRender(self, context, fields):
17 super().startRender(context, fields)
18 for s in self.syms:
19 s.startRender(context, fields)
20
21 def stopRender(self, context):
22 super().stopRender(context)
23 for s in self.syms:
24 s.stopRender(context)
25
26 def usedAttributes(self, context):
27 return []
28
29 def clone(self):
30 return RandomRenderer(self.syms)
The constructor of the parent QgsFeatureRenderer
class needs a renderer name (which has to be unique among renderers). The
symbolForFeature()
method
is the one that decides what symbol will be used for a particular feature.
startRender()
and stopRender()
take care of initialization/finalization
of symbol rendering. The usedAttributes()
method can return a list of field names that the renderer expects to be present.
Finally, the clone()
method
should return a copy of the renderer.
Like with symbol layers, it is possible to attach a GUI for configuration of
the renderer. It has to be derived from QgsRendererWidget
.
The following sample code creates a button that allows the user to set the
first symbol
1from qgis.gui import QgsRendererWidget, QgsColorButton
2
3
4class RandomRendererWidget(QgsRendererWidget):
5 def __init__(self, layer, style, renderer):
6 super().__init__(layer, style)
7 if renderer is None or renderer.type() != "RandomRenderer":
8 self.r = RandomRenderer()
9 else:
10 self.r = renderer
11 # setup UI
12 self.btn1 = QgsColorButton()
13 self.btn1.setColor(self.r.syms[0].color())
14 self.vbox = QVBoxLayout()
15 self.vbox.addWidget(self.btn1)
16 self.setLayout(self.vbox)
17 self.btn1.colorChanged.connect(self.setColor1)
18
19 def setColor1(self):
20 color = self.btn1.color()
21 if not color.isValid(): return
22 self.r.syms[0].setColor(color)
23
24 def renderer(self):
25 return self.r
The constructor receives instances of the active layer (QgsVectorLayer
), the global style (QgsStyle
) and the current renderer. If there is no
renderer or the renderer has different type, it will be replaced with our new
renderer, otherwise we will use the current renderer (which has already the
type we need). The widget contents should be updated to show current state of
the renderer. When the renderer dialog is accepted, the widget’s renderer()
method is called to get the current
renderer — it will be assigned to the layer.
Ultimul bit lipsă este cel al metadatelor renderului și înregistrarea în registru, altfel încărcarea straturilor cu renderul nu va funcționa, iar utilizatorul nu va fi capabil să-l selecteze din lista de rendere. Să finalizăm exemplul nostru de RandomRenderer
1from qgis.core import (
2 QgsRendererAbstractMetadata,
3 QgsRendererRegistry,
4 QgsApplication
5)
6
7class RandomRendererMetadata(QgsRendererAbstractMetadata):
8
9 def __init__(self):
10 super().__init__("RandomRenderer", "Random renderer")
11
12 def createRenderer(self, element):
13 return RandomRenderer()
14
15 def createRendererWidget(self, layer, style, renderer):
16 return RandomRendererWidget(layer, style, renderer)
17
18rrmetadata = RandomRendererMetadata()
QgsApplication.rendererRegistry().addRenderer(rrmetadata)
Similarly as with symbol layers, abstract metadata constructor awaits renderer
name, name visible for users and optionally name of renderer’s icon.
The createRenderer()
method passes a QDomElement
instance that can be
used to restore the renderer’s state from the DOM tree. The createRendererWidget()
method creates the configuration widget. It does not have to be present or can
return None
if the renderer does not come with GUI.
To associate an icon with the renderer you can assign it in
the QgsRendererAbstractMetadata
constructor as a third (optional)
argument — the base class constructor in the RandomRendererMetadata __init__()
function becomes
QgsRendererAbstractMetadata.__init__(self,
"RandomRenderer",
"Random renderer",
QIcon(QPixmap("RandomRendererIcon.png", "png")))
The icon can also be associated at any later time using the setIcon()
method
of the metadata class. The icon can be loaded from a file (as shown above) or
can be loaded from a Qt resource
(PyQt5 includes .qrc compiler for Python).
6.9. Lecturi suplimentare
DE EFECTUAT:
creating/modifying symbols
working with style (
QgsStyle
)working with color ramps (
QgsColorRamp
)exploring symbol layer and renderer registries