벡터 레이어 사용

이 장에서는 벡터 레이어에 대해 할 수 있는 여러 가지 작업들을 소개합니다.

Retrieving information about attributes

You can retrieve information about the fields associated with a vector layer by calling pendingFields() on a QgsVectorLayer instance:

# "layer" is a QgsVectorLayer instance
for field in layer.pendingFields():
    print field.name(), field.typeName()

주석

Starting from QGIS 2.12 there is also a fields() in QgsVectorLayer which is an alias to pendingFields().

피처 선택

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 can be useful to programmatically select features or to change the default color.

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 add features to the selected features list for a given layer, you can call setSelectedFeatures() passing to it the list of features IDs:

# Get the active layer (must be a vector layer)
layer = iface.activeLayer()
# Get the first feature from the layer
feature = layer.getFeatures().next()
# Add this features to the selected list
layer.setSelectedFeatures([feature.id()])

To clear the selection, just pass an empty list:

layer.setSelectedFeatures([])

벡터 레이어 상의 반복 작업

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

iter = layer.getFeatures()
for feature in iter:
    # retrieve every feature with its geometry and attributes
    # fetch geometry
    geom = feature.geometry()
    print "Feature ID %d: " % feature.id()

    # show some information about the feature
    if geom.type() == QGis.Point:
        x = geom.asPoint()
        print "Point: " + str(x)
    elif geom.type() == QGis.Line:
        x = geom.asPolyline()
        print "Line: %d points" % len(x)
    elif geom.type() == QGis.Polygon:
        x = geom.asPolygon()
        numPts = 0
        for ring in x:
            numPts += len(ring)
        print "Polygon: %d rings with %d points" % (len(x), numPts)
    else:
        print "Unknown"

    # fetch attributes
    attrs = feature.attributes()

    # attrs is a list. It contains all the attribute values of this feature
    print attrs

속성에 접근하기

Attributes can be referred to by their name.

print feature['name']

Alternatively, attributes can be referred to by index. This is will be a bit faster than using the name. For example, to get the first attribute:

print feature[0]

선택된 피처에 대한 반복 작업

if you only need selected features, you can use the selectedFeatures() method from vector layer:

selection = layer.selectedFeatures()
print len(selection)
for feature in selection:
    # do whatever you need with the feature

Another option is the Processing features() method:

import processing
features = processing.features(layer)
for feature in features:
    # do whatever you need with the feature

By default, this will iterate over all the features in the layer, in case there is no selection, or over the selected features otherwise. Note that this behavior can be changed in the Processing options to ignore selections.

피처 부분 집합에 대한 반복 작업

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

request = QgsFeatureRequest()
request.setFilterRect(areaOfInterest)
for feature in layer.getFeatures(request):
    # do whatever you need with the feature

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):
    # loop through only 2 features

If you need an attribute-based filter instead (or in addition) of a spatial one like shown in the examples above, you can build an QgsExpression object and pass it to the QgsFeatureRequest constructor. Here’s an example

# 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 표현식을 이용한 속성값의 필터링 및 계산 for the details about the syntax supported by QgsExpression.

request를 각 피처에서 검색된 데이터를 정의하는 데 사용할 수 있기에, 반복 작업자가 모든 피처를 순회하긴 하지만, 각 피처에 대한 부분적인 데이터를 반환합니다.

# Only return selected fields
request.setSubsetOfAttributes([0,2])
# More user friendly version
request.setSubsetOfAttributes(['name','id'],layer.pendingFields())
# Don't return geometry objects
request.setFlags(QgsFeatureRequest.NoGeometry)

참고

Speed features request

If you only need a subset of the attributes or you don’t need the geometry information, you can significantly increase the speed of the features request by using QgsFeatureRequest.NoGeometry flag or specifying a subset of attributes (possibly empty) like shown in the example above.

벡터 레이어 수정

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:
caps & QgsVectorDataProvider.DeleteFeatures
# Print 2 if DeleteFeatures is supported

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:

caps_string = layer.dataProvider().capabilitiesString()
# Print:
# u'Add Features, Delete Features, Change Attribute Values,
# Add Attributes, Delete Attributes, Create Spatial Index,
# Fast Access to Features at ID, Change Geometries,
# Simplify Geometries with topological validation'

벡터 레이어 편집을 위한 다음 방법 가운데 어떤 것을 사용하든, 변경 사항은 기저 데이터 저장소(파일, 데이터베이스 등등)에 직접 반영됩니다. 일시적으로만 변경하고자 할 경우, 편집 버퍼로 수정 하는 방법을 설명하는 다음 단계로 넘어가십시오.

주석

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:

# If caching is enabled, a simple canvas refresh might not be sufficient
# to trigger a redraw and you must clear the cached image for the layer
if iface.mapCanvas().isCachingEnabled():
    layer.setCacheImage(None)
else:
    iface.mapCanvas().refresh()

피처 추가

Create some QgsFeature instances and pass a list of them to provider’s addFeatures() method. It will return two values: result (true/false) and list of added features (their ID is set by the data store).

To set up the attributes you can either initialize the feature passing a QgsFields instance or call initAttributes() passing the number of fields you want to be added.

if caps & QgsVectorDataProvider.AddFeatures:
    feat = QgsFeature(layer.pendingFields())
    feat.setAttributes([0, 'hello'])
    # Or set a single attribute by key or by index:
    feat.setAttribute('name', 'hello')
    feat.setAttribute(0, 'hello')
    feat.setGeometry(QgsGeometry.fromPoint(QgsPoint(123, 456)))
    (res, outFeats) = layer.dataProvider().addFeatures([feat])

피처 삭제

To delete some features, just provide a list of their feature IDs

if caps & QgsVectorDataProvider.DeleteFeatures:
    res = layer.dataProvider().deleteFeatures([5, 10])

피처 수정

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

fid = 100   # ID of the feature we will modify

if caps & QgsVectorDataProvider.ChangeAttributeValues:
    attrs = { 0 : "hello", 1 : 123 }
    layer.dataProvider().changeAttributeValues({ fid : attrs })

if caps & QgsVectorDataProvider.ChangeGeometries:
    geom = QgsGeometry.fromPoint(QgsPoint(111,222))
    layer.dataProvider().changeGeometryValues({ fid : geom })

참고

Favor QgsVectorLayerEditUtils class for geometry-only edits

If you only need to change geometries, you might consider using the QgsVectorLayerEditUtils which provides some of useful methods to edit geometries (translate, insert or move vertex etc.).

참고

Directly save changes using with based command

Using with edit(layer): the changes will be commited automatically calling commitChanges() at the end. If any exception occurs, it will rollBack() all the changes. See 편집 버퍼로 벡터 레이어 수정.

항목 추가 및 제거

필드(속성)를 추가하려면, 필드를 정의한 리스트를 지정해야 합니다. 필드를 삭제하려면 필드 인덱스 목록만 넘겨주면 됩니다.

from PyQt4.QtCore import QVariant

if caps & QgsVectorDataProvider.AddAttributes:
    res = layer.dataProvider().addAttributes(
        [QgsField("mytext", QVariant.String),
        QgsField("myint", QVariant.Int)])

if caps & QgsVectorDataProvider.DeleteAttributes:
    res = layer.dataProvider().deleteAttributes([0])

데이터 제공자에서 필드를 추가 또는 제거한 다음 레이어의 필드를 업데이트해야 합니다. 이는 변경 사항이 자동적으로 반영되지 않기 때문입니다.

layer.updateFields()

편집 버퍼로 벡터 레이어 수정

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 do 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 committing changes, all changes from the editing buffer are saved to data provider.

To find out whether a layer is in editing mode, use isEditable() — the editing functions work only when the editing mode is turned on. Usage of editing functions

from PyQt4.QtCore import QVariant

# add two features (QgsFeature instances)
layer.addFeatures([feat1,feat2])
# delete a feature with specified ID
layer.deleteFeature(fid)

# set new geometry (QgsGeometry instance) for a feature
layer.changeGeometry(fid, geometry)
# update an attribute with given field index (int) to given value (QVariant)
layer.changeAttributeValue(fid, fieldIndex, value)

# add new field
layer.addAttribute(QgsField("mytext", QVariant.String))
# remove a field
layer.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.) How to use the undo functionality

layer.beginEditCommand("Feature triangulation")

# ... call layer's editing methods ...

if problem_occurred:
  layer.destroyEditCommand()
  return

# ... more editing ...

layer.endEditCommand()

The beginEditCommand() 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.

To start editing mode, there is startEditing() method, to stop editing there are commitChanges() and rollBack() — however normally you should not need these methods and leave this functionality to be triggered by the user.

You can also use the with edit(layer)-statement to wrap commit and rollback into a more semantic code block as shown in the example below:

with edit(layer):
  feat = layer.getFeatures().next()
  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.

공간 인덱스 사용

벡터 레이어에 대해 자주 쿼리를 해야 할 경우 공간 인덱스를 사용하면 코드 실행 속도를 획기적으로 향상시킬 수 있습니다. 여러분이 보간 알고리듬을 작성하는데, 보간값을 계산하기 위해 주어진 위치에서 가장 가까운 포인트 레이어의 포인트 10개를 알아내야 한다고 한번 상상해보십시오. 공간 인덱스가 없다면, QGIS가 그 포인트 10개를 찾는 유일한 방법은 해당 위치에서 모든 포인트까지의 거리를 각각 계산한 다음 그 거리들을 비교하는 것입니다. 특히 이 작업을 몇 군데의 위치에 대해 반복해야 할 경우 시간이 아주 오래 걸릴 수 있습니다. 레이어가 공간 인덱스를 가지고 있다면, 훨씬 효율적으로 작업할 수 있습니다.

공간 인덱스가 없는 레이어를 전화번호가 정렬되지도 색인되지도 않은 전화번호부라고 생각해보십시오. 어떤 사람의 전화번호를 찾으려면 처음부터 그 번호를 찾을 때까지 읽을 수밖에 없습니다.

Spatial indexes are not created by default for a QGIS vector layer, but you can create them easily. This is what you have to do:

  • create spatial index — the following code creates an empty index

    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 previous call to provider’s nextFeature()

    index.insertFeature(feat)
    
  • alternatively, you can load all features of a layer at once using bulk loading

    index = QgsSpatialIndex(layer.getFeatures())
    
  • 공간 인덱스가 채워지면 쿼리를 해보십시오.

    # returns array of feature IDs of five nearest features
    nearest = index.nearestNeighbor(QgsPoint(25.4, 12.7), 5)
    
    # returns array of IDs of features which intersect the rectangle
    intersect = index.intersects(QgsRectangle(22.5, 15.3, 23.1, 17.2))
    

Writing Vector Layers

You can write vector layer files using QgsVectorFileWriter class. It supports any other kind of vector file that OGR supports (shapefiles, GeoJSON, KML and others).

There are two possibilities how to export a vector layer:

  • from an instance of QgsVectorLayer

    error = QgsVectorFileWriter.writeAsVectorFormat(layer, "my_shapes.shp", "CP1250", None, "ESRI Shapefile")
    
    if error == QgsVectorFileWriter.NoError:
        print "success!"
    
    error = QgsVectorFileWriter.writeAsVectorFormat(layer, "my_json.json", "utf-8", None, "GeoJSON")
    if error == QgsVectorFileWriter.NoError:
        print "success again!"
    

    The third parameter specifies output text encoding. Only some drivers need this for correct operation - shapefiles are one of those — however in case you are not using international characters you do not have to care much about the encoding. The fourth parameter that we left as None may specify destination CRS — if a valid instance of QgsCoordinateReferenceSystem is passed, the layer is transformed to that CRS.

    For valid driver names please 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 — look into the documentation for full syntax.

  • directly from features

    from PyQt4.QtCore import QVariant
    
    # define fields for feature attributes. A QgsFields object is needed
    fields = QgsFields()
    fields.append(QgsField("first", QVariant.Int))
    fields.append(QgsField("second", QVariant.String))
    
    """ create an instance of vector file writer, which will create the vector file.
    Arguments:
    1. path to new file (will fail if exists already)
    2. encoding of the attributes
    3. field map
    4. geometry type - from WKBTYPE enum
    5. layer's spatial reference (instance of
       QgsCoordinateReferenceSystem) - optional
    6. driver name for the output file """
    writer = QgsVectorFileWriter("my_shapes.shp", "CP1250", fields, QGis.WKBPoint, None, "ESRI Shapefile")
    
    if writer.hasError() != QgsVectorFileWriter.NoError:
        print "Error when creating shapefile: ",  w.errorMessage()
    
    # add a feature
    fet = QgsFeature()
    fet.setGeometry(QgsGeometry.fromPoint(QgsPoint(10,10)))
    fet.setAttributes([1, "text"])
    writer.addFeature(fet)
    
    # delete the writer to flush features to disk
    del writer
    

Memory Provider

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.

이 제공자는 문자열(string), 정수(int), 더블형 실수(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.

이 생성자 역시 URI 형식으로 지정되며, 지오메트리 유형으로 "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", 또는 "MultiPolygon" 이 가능합니다.

이 URI로 좌표계, 항목, 메모리기반 제공자의 인덱스 작업도 설정할 수 있습니다. 문법은 다음과 같습니다.

crs=definition
Specifies the coordinate reference system, where definition may be any of the forms accepted by QgsCoordinateReferenceSystem.createFromString()
index=yes

제공자가 공간 인덱스를 사용하도록 설정합니다.

field=name:type(length,precision)

레이어의 속성을 설정합니다. 속성의 명칭은 필수적이며, 선택적으로 유형(정수, 더블형 실수, 문자 스트링), 길이 및 정밀도를 설정할 수 있습니다. 여러 개의 속성 항목을 정의할 수도 있습니다.

다음은 이 모든 옵션들을 포함하는 URI의 예시입니다.

"Point?crs=epsg:4326&field=id:integer&field=name:string(20)&index=yes"

다음은 메모리기반 제공자를 생성하고 값을 채우는 코드의 예시입니다.

from PyQt4.QtCore import QVariant

# create layer
vl = QgsVectorLayer("Point", "temporary_points", "memory")
pr = vl.dataProvider()

# add fields
pr.addAttributes([QgsField("name", QVariant.String),
                    QgsField("age",  QVariant.Int),
                    QgsField("size", QVariant.Double)])
vl.updateFields() # tell the vector layer to fetch changes from the provider

# add a feature
fet = QgsFeature()
fet.setGeometry(QgsGeometry.fromPoint(QgsPoint(10,10)))
fet.setAttributes(["Johny", 2, 0.3])
pr.addFeatures([fet])

# update layer's extent when new features have been added
# because change of extent in provider is not propagated to the layer
vl.updateExtents()

마지막으로, 모든 작업이 성공적이었는지 확인해봅시다.

# show some stats
print "fields:", len(pr.fields())
print "features:", pr.featureCount()
e = layer.extent()
print "extent:", e.xMiniminum(), e.yMinimum(), e.xMaximum(), e.yMaximum()

# iterate over features
f = QgsFeature()
features = vl.getFeatures()
for f in features:
    print "F:", f.id(), f.attributes(), f.geometry().asPoint()

벡터 레이어의 표현(심볼)

벡터 레이어를 렌더링할 때, 레이어와 관련된 렌더러심볼 이 데이터의 표현을 결정합니다. 심볼은 피처의 시각적 표현을 그리는 일을 담당하는 클래스이며, 렌더러는 특정 피처에 대해 어떤 심볼을 적용할지 결정합니다.

The renderer for a given layer can obtained as shown below:

renderer = layer.rendererV2()

그리고 이를 참고해 조금 더 나가봅시다.

print "Type:", rendererV2.type()

There are several known renderer types available in QGIS core library:

유형

클래스

설명

singleSymbol QgsSingleSymbolRendererV2

모든 피처를 동일한 심볼로 렌더링합니다.

categorizedSymbol QgsCategorizedSymbolRendererV2

피처를 각 카테고리에 대해 서로 다른 심볼로 렌더링합니다.

graduatedSymbol QgsGraduatedSymbolRendererV2

피처를 값의 범위에 따라 서로 다른 심볼로 렌더링합니다.

There might be also some custom renderer types, so never make an assumption there are just these types. You can query QgsRendererV2Registry singleton to find out currently available renderers:

print QgsRendererV2Registry.instance().renderersList()
# Print:
[u'singleSymbol',
u'categorizedSymbol',
u'graduatedSymbol',
u'RuleRenderer',
u'pointDisplacement',
u'invertedPolygonRenderer',
u'heatmapRenderer']

텍스트 형식으로 렌더러의 내용을 덤프받을 수도 있습니다. 디버깅 작업에 유용할 수 있습니다.

print rendererV2.dump()

단일 심볼 렌더러

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 QgsMarkerSymbolV2, QgsLineSymbolV2 and QgsFillSymbolV2.

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 QgsMarkerSymbolV2 as in the following code example:

symbol = QgsMarkerSymbolV2.createSimple({'name': 'square', 'color': 'red'})
layer.rendererV2().setSymbol(symbol)

name 은 마커의 형태를 의미하며, 다음 중 어느 것이라도 가능합니다.

  • 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 simbol instance you can follow the example code:

print layer.rendererV2().symbol().symbolLayers()[0].properties()
# Prints
{u'angle': u'0',
u'color': u'0,128,0,255',
u'horizontal_anchor_point': u'1',
u'name': u'circle',
u'offset': u'0,0',
u'offset_map_unit_scale': u'0,0',
u'offset_unit': u'MM',
u'outline_color': u'0,0,0,255',
u'outline_style': u'solid',
u'outline_width': u'0',
u'outline_width_map_unit_scale': u'0,0',
u'outline_width_unit': u'MM',
u'scale_method': u'area',
u'size': u'2',
u'size_map_unit_scale': u'0,0',
u'size_unit': u'MM',
u'vertical_anchor_point': u'1'}

This can be useful if you want to alter some properties:

# You can alter a single property...
layer.rendererV2().symbol().symbolLayer(0).setName('square')
# ... but not all properties are accessible from methods,
# you can also replace the symbol completely:
props = layer.rendererV2().symbol().symbolLayer(0).properties()
props['color'] = 'yellow'
props['name'] = 'square'
layer.rendererV2().setSymbol(QgsMarkerSymbolV2.createSimple(props))

카테고리 심볼 렌더러

You can query and set attribute name which is used for classification: use classAttribute() and setClassAttribute() methods.

카테고리 목록을 얻는 방법은 다음과 같습니다.

for cat in rendererV2.categories():
    print "%s: %s :: %s" % (cat.value().toString(), cat.label(), str(cat.symbol()))

Where value() is the value used for discrimination between categories, label() is a text used for category description and symbol() method returns assigned symbol.

The renderer usually stores also original symbol and color ramp which were used for the classification: sourceColorRamp() and sourceSymbol() methods.

등급 심볼 렌더러

이 렌더러는 앞에서 설명한 카테고리 심볼 렌더러와 매우 비슷하지만, 범주 당 하나의 속성을 할당하는 대신 값의 범위에 따라 할당하기 때문에 숫자 속성에만 사용할 수 있습니다.

이 렌더러가 사용하는 범위에 대해 상세히 조회하려면 다음과 같은 방법을 사용합니다.

for ran in rendererV2.ranges():
    print "%f - %f: %s %s" % (
        ran.lowerValue(),
        ran.upperValue(),
        ran.label(),
        str(ran.symbol())
      )

범주화 속성명을 알아내는 데 classAttribute() 함수를 사용할 수 있습니다. 또 mode() 함수를 사용하면 어떻게 범위를 생성할지 – 일정한 간격, 수량 또는 다른 방법으로 – 설정할 수 있습니다.

사용자 정의 등급 심볼 렌더러를 생성하고자 할 경우 다음 예시에서처럼 하면 됩니다. (이 예시에서는 범주 2개인 간단한 배열을 생성합니다.)

from qgis.core import *

myVectorLayer = QgsVectorLayer(myVectorPath, myName, 'ogr')
myTargetField = 'target_field'
myRangeList = []
myOpacity = 1
# Make our first symbol and range...
myMin = 0.0
myMax = 50.0
myLabel = 'Group 1'
myColour = QtGui.QColor('#ffee00')
mySymbol1 = QgsSymbolV2.defaultSymbol(myVectorLayer.geometryType())
mySymbol1.setColor(myColour)
mySymbol1.setAlpha(myOpacity)
myRange1 = QgsRendererRangeV2(myMin, myMax, mySymbol1, myLabel)
myRangeList.append(myRange1)
#now make another symbol and range...
myMin = 50.1
myMax = 100
myLabel = 'Group 2'
myColour = QtGui.QColor('#00eeff')
mySymbol2 = QgsSymbolV2.defaultSymbol(
     myVectorLayer.geometryType())
mySymbol2.setColor(myColour)
mySymbol2.setAlpha(myOpacity)
myRange2 = QgsRendererRangeV2(myMin, myMax, mySymbol2 myLabel)
myRangeList.append(myRange2)
myRenderer = QgsGraduatedSymbolRendererV2('', myRangeList)
myRenderer.setMode(QgsGraduatedSymbolRendererV2.EqualInterval)
myRenderer.setClassAttribute(myTargetField)

myVectorLayer.setRendererV2(myRenderer)
QgsMapLayerRegistry.instance().addMapLayer(myVectorLayer)

심볼 다루기

For representation of symbols, there is QgsSymbolV2 base class with three derived classes:

  • QgsMarkerSymbolV2 — for point features
  • QgsLineSymbolV2 — for line features
  • QgsFillSymbolV2 — for polygon features

Every symbol consists of one or more symbol layers (classes derived from QgsSymbolLayerV2). 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: 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

for i in xrange(symbol.symbolLayerCount()):
    lyr = symbol.symbolLayer(i)
    print "%d: %s" % (i, lyr.layerType())

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 size() and angle() methods, for line symbols there is width() method returning line width.

기본적으로 크기 및 두께는 밀리미터 단위이며, 각도는 도 단위입니다.

심볼 레이어 다루기

As said before, symbol layers (subclasses of QgsSymbolLayerV2) 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 like this

from qgis.core import QgsSymbolLayerV2Registry
myRegistry = QgsSymbolLayerV2Registry.instance()
myMetadata = myRegistry.symbolLayerMetadata("SimpleFill")
for item in myRegistry.symbolLayersForType(QgsSymbolV2.Marker):
    print item

Output

EllipseMarker
FontMarker
SimpleMarker
SvgMarker
VectorField

QgsSymbolLayerV2Registry 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 generic methods color(), size(), angle(), width() with their setter counterparts. Of course size and angle is available only for marker symbol layers and width for line symbol layers.

사용자 지정 심볼 레이어 유형 생성

데이터를 어떻게 렌더링할지 사용자 지정하고 싶다고 상상해보십시오. 사용자가 원하는 방식대로 피처를 그리는, 자신만의 심볼 레이어 클래스를 생성할 수 있습니다. 다음은 지정된 반지름으로 빨간색 원을 그리는 마커의 예시 코드입니다.

class FooSymbolLayer(QgsMarkerSymbolLayerV2):

  def __init__(self, radius=4.0):
      QgsMarkerSymbolLayerV2.__init__(self)
      self.radius = radius
      self.color = QColor(255,0,0)

  def layerType(self):
     return "FooMarker"

  def properties(self):
      return { "radius" : str(self.radius) }

  def startRender(self, context):
    pass

  def stopRender(self, context):
      pass

  def renderPoint(self, point, context):
      # Rendering depends on whether the symbol is selected (QGIS >= 1.5)
      color = context.selectionColor() if context.selected() else self.color
      p = context.renderContext().painter()
      p.setPen(color)
      p.drawEllipse(point, self.radius, self.radius)

  def clone(self):
      return FooSymbolLayer(self.radius)

The layerType() method determines the name of the symbol layer, it has to be unique among all symbol layers. Properties are used for persistence of attributes. 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 first feature, stopRender() when rendering is done. And renderPoint() method which does 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, resp. renderPolygon() which receives list of points on outer ring as a first parameter and a list of inner rings (or None) as a second parameter.

일반적으로 사용자가 표현을 변경할 수 있도록 심볼 레이어 유형의 속성을 설정하기 위한 GUI를 추가하는 것이 편리합니다. 앞의 예시에서 사용자가 원의 반경을 설정하도록 할 수 있습니다. 다음은 그런 위젯을 구현하는 예시 코드입니다.

class FooSymbolLayerWidget(QgsSymbolLayerV2Widget):
    def __init__(self, parent=None):
        QgsSymbolLayerV2Widget.__init__(self, parent)

        self.layer = None

        # setup a simple UI
        self.label = QLabel("Radius:")
        self.spinRadius = QDoubleSpinBox()
        self.hbox = QHBoxLayout()
        self.hbox.addWidget(self.label)
        self.hbox.addWidget(self.spinRadius)
        self.setLayout(self.hbox)
        self.connect(self.spinRadius, SIGNAL("valueChanged(double)"), \
            self.radiusChanged)

    def setSymbolLayer(self, layer):
        if layer.layerType() != "FooMarker":
            return
        self.layer = layer
        self.spinRadius.setValue(layer.radius)

    def symbolLayer(self):
        return self.layer

    def radiusChanged(self, value):
        self.layer.radius = value
        self.emit(SIGNAL("changed()"))

심볼 속성 대화 창 안에 이 위젯을 내장시킬 수 있습니다. 심볼 속성 대화창에서 심볼 레이어 유형을 선택하면, 심볼 레이어 및 심볼 레이어 위젯의 인스턴스를 생성합니다. 그 다음 심볼 레이어를 위젯에 할당하는 setSymbolLayer() 메소드를 호출합니다. 이런 방법을 쓸 경우 UI가 심볼 레이어의 속성을 반영하도록 위젯을 업데이트해줘야 합니다. 속성 대화 창은 심볼 레이어를 다시 불러와 심볼을 위해 사용할 수 있도록 symbolLayer() 함수를 호출합니다.

속성을 변경할 때마다, 위젯은 속성 대화 창이 심볼 미리보기를 업데이트할 수 있도록 changed() 신호를 보내야 합니다.

이제 마지막 단계만 남았습니다. QGIS가 이 새 클래스들을 인식하도록 만드는 일입니다. 심볼 레이어를 레지스트리에 추가하면 됩니다. 레지스트리에 추가하지 않고 심볼 레이어를 사용할 수도 있지만, 예를 들어 사용자 지정 심볼 레이어를 담고 있는 프로젝트 파일을 불러온다든가 GUI에서 레이어 속성을 편집할 수 없는 등 몇몇 기능들을 사용할 수 없게 됩니다.

심볼 레이어에 대한 메타데이터도 생성해야 합니다.

class FooSymbolLayerMetadata(QgsSymbolLayerV2AbstractMetadata):

  def __init__(self):
    QgsSymbolLayerV2AbstractMetadata.__init__(self, "FooMarker", QgsSymbolV2.Marker)

  def createSymbolLayer(self, props):
    radius = float(props[QString("radius")]) if QString("radius") in props else 4.0
    return FooSymbolLayer(radius)

  def createSymbolLayerWidget(self):
    return FooSymbolLayerWidget()

QgsSymbolLayerV2Registry.instance().addSymbolLayerType(FooSymbolLayerMetadata())

You should pass layer type (the same as returned by the layer) and symbol type (marker/line/fill) to the constructor of parent class. createSymbolLayer() takes care of creating an instance of symbol layer with attributes specified in the props dictionary. (Beware, the keys are QString instances, not “str” objects). And there is createSymbolLayerWidget() method which returns settings widget for this symbol layer type.

마지막 단계는 이 심볼 레이어를 레지스트리에 추가하는 일입니다. 이제 모두 끝났습니다.

사용자 정의 렌더러 생성

피처를 렌더링하는 데 어떻게 심볼을 선택할지에 대한 규칙을 마음대로 지정하고 싶을 경우 새로운 렌더러를 만드는 것이 유용할 수도 있습니다. 여러 항목을 조합해서 심볼을 결정해야 하거나, 현재 축척에 따라 심볼 크기를 변경해야 하는 등의 경우에 새로운 렌더러를 만들면 좋습니다.

다음 예시 코드는 마커 심볼 2개를 생성해서 각 피처마다 임의로 2개 중 1개를 선택하는 간단한 사용자 지정 렌더러입니다.

import random

class RandomRenderer(QgsFeatureRendererV2):
  def __init__(self, syms=None):
    QgsFeatureRendererV2.__init__(self, "RandomRenderer")
    self.syms = syms if syms else [QgsSymbolV2.defaultSymbol(QGis.Point), QgsSymbolV2.defaultSymbol(QGis.Point)]

  def symbolForFeature(self, feature):
    return random.choice(self.syms)

  def startRender(self, context, vlayer):
    for s in self.syms:
      s.startRender(context)

  def stopRender(self, context):
    for s in self.syms:
      s.stopRender(context)

  def usedAttributes(self):
    return []

  def clone(self):
    return RandomRenderer(self.syms)

The constructor of parent QgsFeatureRendererV2 class needs renderer name (has to be unique among renderers). 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. usedAttributes() method can return a list of field names that renderer expects to be present. Finally clone() function 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 QgsRendererV2Widget. The following sample code creates a button that allows user to set symbol of the first symbol

class RandomRendererWidget(QgsRendererV2Widget):
  def __init__(self, layer, style, renderer):
    QgsRendererV2Widget.__init__(self, layer, style)
    if renderer is None or renderer.type() != "RandomRenderer":
      self.r = RandomRenderer()
    else:
      self.r = renderer
    # setup UI
    self.btn1 = QgsColorButtonV2()
    self.btn1.setColor(self.r.syms[0].color())
    self.vbox = QVBoxLayout()
    self.vbox.addWidget(self.btn1)
    self.setLayout(self.vbox)
    self.connect(self.btn1, SIGNAL("clicked()"), self.setColor1)

  def setColor1(self):
    color = QColorDialog.getColor(self.r.syms[0].color(), self)
    if not color.isValid(): return
    self.r.syms[0].setColor(color);
    self.btn1.setColor(self.r.syms[0].color())

  def renderer(self):
    return self.r

The constructor receives instances of the active layer (QgsVectorLayer), the global style (QgsStyleV2) and 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, widget’s renderer() method is called to get the current renderer — it will be assigned to the layer.

마지막으로 렌더러의 메타데이터를 생성하고 레지스트리에 렌더러를 등록해야 합니다. 이렇게 하지 않으면 렌더러와 함께 레이어를 불러올 수 없고, 사용자가 렌더러 목록에서 렌더러를 선택할 수 없습니다. 이제 RandomRenderer 예시 코드를 완성해봅시다.

class RandomRendererMetadata(QgsRendererV2AbstractMetadata):
  def __init__(self):
    QgsRendererV2AbstractMetadata.__init__(self, "RandomRenderer", "Random renderer")

  def createRenderer(self, element):
    return RandomRenderer()
  def createRendererWidget(self, layer, style, renderer):
    return RandomRendererWidget(layer, style, renderer)

QgsRendererV2Registry.instance().addRenderer(RandomRendererMetadata())

심볼 레이어와 비슷하게, 추상 메타데이터 생성자도 렌더러 명칭, 사용자에게 보이는 명칭, 그리고 선택적인 렌더러 아이콘 명칭을 받습니다. createRenderer() 메소드가 DOM(Document Object Model) 트리로부터 렌더러의 상태를 복구하는 데 이용할 수 있는 QDomElement 클래스 인스턴스를 넘겨줍니다. createRendererWidget() 메소드는 설정 위젯을 생성합니다. 렌더러에 GUI가 없는 경우 이 함수가 없거나, 이 함수가 None 을 반환할 수 있습니다.

To associate an icon with the renderer you can assign it in QgsRendererV2AbstractMetadata constructor as a third (optional) argument — the base class constructor in the RandomRendererMetadata __init__() function becomes

QgsRendererV2AbstractMetadata.__init__(self,
       "RandomRenderer",
       "Random renderer",
       QIcon(QPixmap("RandomRendererIcon.png", "png")))

The icon can be associated also at any later time using 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 (PyQt4 includes .qrc compiler for Python).

남은 이야기들

TODO:

  • creating/modifying symbols
  • working with style (QgsStyleV2)
  • working with color ramps (QgsVectorColorRampV2)
  • rule-based renderer (see this blogpost)
  • exploring symbol layer and renderer registries