중요

번역은 여러분이 참여할 수 있는 커뮤니티 활동입니다. 이 페이지는 현재 100.00% 번역되었습니다.

6. 벡터 레이어 사용하기

힌트

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)

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

이 장에서 소개하는 대부분의 작업은 QgsVectorLayer 클래스의 메소드들에 바탕을 두고 있습니다.

6.1. 속성에 대한 정보 검색하기

QgsVectorLayer 클래스 객체에 대해 fields() 메소드를 호출하면 벡터 레이어와 연관된 필드들에 대한 정보를 검색할 수 있습니다:

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

displayField()mapTipTemplate() 메소드는 표시 속성 탭에서 쓰이는 필드와 템플릿에 대한 정보를 제공합니다.

벡터 레이어를 불러왔을 때 QGIS는 언제나 필드 하나를 Display Name 으로 선택하는 반면, HTML Map Tip 은 기본적으로 비어 있습니다. 이 메소드들을 사용하면 둘 다 손쉽게 가져올 수 있습니다:

vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
print(vlayer.displayField())
NAME

참고

Display Name 을 필드에서 표현식으로 변경할 경우, displayField() 대신 displayExpression() 메소드를 사용해야 합니다.

6.2. 벡터 레이어 작업 반복하기

벡터 레이어에 있는 피처들에 대한 작업을 반복하는 일은 가장 흔한 작업 가운데 하나입니다. 다음은 이런 작업을 수행하는 단순한 기본 코드의 예시로, 각 피처에 대한 몇몇 정보를 출력합니다. layer 변수가 QgsVectorLayer 클래스를 가지고 있다고 가정합니다.

 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. 피처 선택하기

QGIS 데스크탑에서 피처를 여러 가지 방법으로 선택할 수 있습니다: 여러분은 피처 하나를 클릭하거나, 맵 캔버스 위에 직사각형을 그리거나, 또는 표현식 필터를 사용할 수 있습니다. 선택 집합에 여러분의 주의를 끌기 위해, 선택된 피처는 보통 다른 (기본값은 노란색) 색상으로 강조됩니다.

때로는 프로그래밍 방식으로 피처를 선택하거나 또는 기본 색상을 변경하는 편이 유용할 수도 있습니다.

피처를 모두 선택하려면, selectAll() 메소드를 사용하면 됩니다:

# Get the active layer (must be a vector layer)
layer = iface.activeLayer()
layer.selectAll()

표현식을 사용해서 선택하려면, selectByExpression() 메소드를 사용하십시오:

# 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)

선택 집합의 색상을 변경하려면 다음 예시에서 볼 수 있는 바와 같이 QgsMapCanvas 클래스의 setSelectionColor() 메소드를 사용하면 됩니다:

iface.mapCanvas().setSelectionColor( QColor("red") )

지정한 레이어의 선택 피처 목록에 피처를 추가하려면, 피처 ID 목록에 피처 ID를 전달하는 select() 메소드를 호출하면 됩니다:

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)

선택 집합을 지우려면:

layer.removeSelection()

6.3.1. 속성에 접근하기

속성은 속성 이름으로 참조할 수 있습니다:

print(feature['name'])
First feature

아니면, 인덱스로 속성을 참조할 수 있습니다. 이름을 사용하는 방법보다 좀 더 빠릅니다. 예를 들어 두 번째 속성을 가져오려면:

print(feature[1])
First feature

6.3.2. 선택한 피처에 대한 작업 반복하기

선택한 피처들만 필요한 경우, 벡터 레이어에서 selectedFeatures() 메소드를 사용하면 됩니다:

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

6.3.3. 피처 부분 집합에 대한 작업 반복하기

레이어에 있는 피처들 가운데 지정한 부분 집합에만, 예를 들면 지정한 영역 안에 있는 피처에만 작업을 반복하고 싶은 경우, getFeatures() 메소드 호출에 QgsFeatureRequest 클래스 객체를 추가해야 합니다. 다음은 그 예시입니다:

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

속도를 향상시키기 위해, 교차 여부를 확인하는 데 피처의 경계 상자만 사용하는 경우가 많습니다. 하지만 교차하는 피처들만 반환되도록 보장하는 ExactIntersect 플래그가 존재합니다:

request = QgsFeatureRequest().setFilterRect(areaOfInterest) \
                             .setFlags(QgsFeatureRequest.ExactIntersect)

setLimit() 메소드를 사용하면 요청한 피처들의 개수를 제한할 수 있습니다. 다음은 그 예시입니다:

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>

앞의 예시에서 볼 수 있는 것과 같은 공간 필터 대신 (또는 추가로) 속성 기반 필터가 필요한 경우, QgsExpression 클래스 객체를 작성해서 QgsFeatureRequest 작성자(constructor) 클래스로 전달하면 됩니다. 다음은 그 예시입니다:

# 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)

QgsExpression 클래스가 지원하는 문법(syntax)에 대해 자세히 알고 싶다면 값을 필터링하고 계산하는 표현식 을 참조하세요.

요청을 각 피처에 대해 검색된 데이터를 정의하기 위해 사용할 수 있기 때문에, 반복 작업자(iterator)가 모든 피처를 반환하긴 하지만 각 피처의 부분적인 데이터를 반환합니다.

 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. 벡터 레이어 수정하기

벡터 데이터 제공자 대부분은 레이어 데이터의 편집을 지원합니다. 가능한 편집 작업의 일부만 지원하는 경우도 있습니다. 어떤 기능들을 지원하는지 알고 싶다면 capabilities() 함수를 사용해보세요.

caps = layer.dataProvider().capabilities()
# Check if a particular capability is supported:
if caps & QgsVectorDataProvider.DeleteFeatures:
    print('The layer supports DeleteFeatures')
The layer supports DeleteFeatures

사용할 수 있는 케이퍼빌리티의 전체 목록을 보고 싶다면 QgsVectorDataProvider의 API 문서 클래스를 참조해주십시오.

레이어의 케이퍼빌리티를 쉼표로 구분된 설명 텍스트로 출력하려면 다음 예시처럼 capabilitiesString() 메소드를 사용하면 됩니다:

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'

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

참고

QGIS 안에서 (콘솔에서든 플러그인에서든) 작업하는 경우, 여러분이 도형의 스타일이나 속성을 변경한 결과를 보기 위해 맵 캔버스를 강제로 다시 그리게 해야 할 필요가 있을 수도 있습니다:

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. 피처 추가하기

QgsFeature 클래스 인스턴스를 몇 개 생성한 다음 제공자의 addFeatures() 메소드에 그 목록을 전달하십시오. 이 메소드가 결과(True 또는 False)와 추가된 피처 목록(데이터 저장소가 설정한, 추가된 피처의 피처 ID)이라는 값 2개를 반환할 것입니다.

피처 속성을 설정하려면, (벡터 레이어의 fields() 메소드에서 얻을 수 있는) QgsFields 클래스 객체를 전달하는 피처를 초기화하거나, 또는 여러분이 추가하고 싶은 필드들의 개수를 전달하는 initAttributes() 메소드를 호출하면 됩니다.

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. 피처 삭제하기

피처를 몇 개 삭제하려면, 삭제하려는 피처의 ID 목록만 지정하면 됩니다.

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

6.4.3. 피처 수정하기

피처의 도형을 변경하거나 아니면 속성을 몇 개 변경할 수 있습니다. 다음 예시에서는 먼저 인덱스가 0과 1인 속성들의 값을 변경한 다음, 피처의 도형을 변경합니다.

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 })

도형만 편집하는 경우 QgsVectorLayerEditUtils 클래스 권장

도형만 변경해야 하는 경우, 도형을 편집하는 몇 가지 유용한 (꼭짓점 변위, 삽입, 이동 등등의) 메소드들을 제공하는 QgsVectorLayerEditUtils 클래스를 사용하는 편이 좋을 수도 있습니다.

6.4.4. 편집 버퍼로 벡터 레이어 수정하기

QGIS 응용 프로그램 안에서 벡터를 편집할 때, 먼저 특정 레이어에 대한 편집 모드를 시작한 다음 어떤 수정 작업을 하고 마지막으로 변경 사항을 커밋(또는 롤백)해야 합니다. 여러분이 무엇을 수정했든, 모든 변경 사항은 커밋하기 전까지는 작성되지 않습니다 — 레이어의 인메모리(in-memory) 편집 버퍼에 머물 뿐입니다. 이 기능을 프로그래밍 방식으로도 사용할 수 있습니다 — 그저 데이터 제공자를 직접 사용하는 작업을 보완하는 벡터 레이어 편집의 또다른 방법일 뿐입니다. 벡터 레이어 편집을 위한 몇몇 GUI 도구들을 제공할 때 이 옵션을 사용하십시오. 여러분이 커밋/롤백할지 여부를 결정할 수 있게 해주고, 실행 취소(undo)/다시 실행(redo) 기능을 사용할 수 있게 해주기 때문입니다. 변경 사항을 커밋하면, 데이터 제공자에 편집 버퍼에 있는 변경 사항을 전부 저장합니다.

이 메소드들은 제공자에서 볼 수 있는 메소드들과 비슷하지만, 제공자에서와는 달리 QgsVectorLayer 클래스 객체에 대해 호출됩니다.

이 메소드들이 작동하기 위해서는 레이어가 반드시 편집 모드 상태여야만 합니다. 편집 모드를 시작하려면, startEditing() 메소드를 사용하십시오. 편집을 끝내려면, commitChanges() 또는 rollBack() 메소드를 사용하십시오. 전자는 데이터 소스에 여러분의 변경 사항을 전부 저장할 것이고, 후자는 변경 사항을 버리고 데이터 소스를 하나도 수정하지 않을 것입니다.

레이어가 편집 모드 상태인지 알아내려면, isEditable() 메소드를 사용하십시오.

다음은 이 편집 메소드들을 사용하는 방법을 보여주는 몇몇 예시입니다.

 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)

실행 취소/다시 실행이 제대로 작동하게 하려면, 앞에서 설명한 호출을 실행 취소 명령어 안에 래핑(wrapping)시켜야 합니다. (실행 취소/다시 실행에는 관심이 없고 변경하는 즉시 저장되기를 바라는 경우, 데이터 제공자로 편집하기 를 통해 더 쉽게 작업하게 될 것입니다.)

다음은 실행 취소 기능을 어떻게 사용할 수 있는지 보여주는 예시입니다:

 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()

beginEditCommand() 메소드가 내부 “활성” 명령어를 생성한 다음 벡터 레이어의 순차적인 변경 사항들을 기록할 것입니다. endEditCommand() 메소드를 호출하면 명령어를 실행 취소 스택으로 넘겨서 여러분이 GUI에서 실행 취소/다시 실행할 수 있게 될 것입니다. 벡터를 변경하는 도중 무언가 문제가 발생한 경우, destroyEditCommand() 메소드가 명령어를 제거하고 해당 명령어가 활성 상태인 동안 발생한 모든 변경 사항들을 롤백시킬 것입니다.

또한 다음 예시에서 볼 수 있는 바와 같이 with edit(layer) 선언문을 사용해서 커밋과 롤백을 좀 더 의미를 알 수 있는(sementic) 코드 블록으로 래핑시킬 수 있습니다:

with edit(layer):
  feat = next(layer.getFeatures())
  feat[0] = 5
  layer.updateFeature(feat)

이 코드는 마지막에 commitChanges() 메소드를 자동 호출할 것입니다. 어떤 예외가 발생하는 경우, 이 코드는 모든 변경 사항을 롤백시키는 rollBack() 메소드를 호출할 것입니다. commitChanges() 메소드가 실행되는 동안 문제가 발생하는 경우 (이 메소드가 거짓을 반환하는 경우) QgsEditError 예외가 발생할 것입니다.

6.4.5. 필드 추가하기 및 제거하기

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

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)

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

layer.updateFields()

with 기반 명령어를 사용해서 변경 사항을 직접 저장하기

with edit(layer): 선언문을 사용하면 마지막에 commitChanges() 메소드를 자동으로 호출해서 변경 사항을 커밋시킬 것입니다. 어떤 예외가 발생하는 경우, rollBack() 메소드를 호출해서 모든 변경 사항을 롤백시킬 것입니다. 편집 버퍼로 벡터 레이어 수정하기 를 참조하세요.

6.5. 공간 인덱스 사용하기

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

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

QGIS 벡터 레이어에 공간 인덱스를 기본으로 생성하지는 않지만, 쉽게 생성할 수 있습니다. 여러분이 해야 할 일은 다음과 같습니다:

  • QgsSpatialIndex 클래스를 사용해서 공간 인덱스를 생성합니다:

    index = QgsSpatialIndex()
    
  • 인덱스에 피처를 추가합니다 — 인덱스는 QgsFeature 클래스 객체를 받아 내부 데이터 구조에 추가합니다. 여러분이 객체를 직접 생성할 수도 있고, 또는 제공자의 getFeatures() 메소드를 호출했을 때 받은 객체를 사용할 수도 있습니다.

    index.addFeature(feat)
    
  • 아니면 벌크 불러오기를 사용해서 레이어의 피처들을 한번에 전부 불러올 수 있습니다.

    index = QgsSpatialIndex(layer.getFeatures())
    
  • 공간 인덱스가 값들로 채워지고 나면 쿼리를 해볼 수 있습니다.

    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))
    

QgsSpatialIndexKDBush 클래스 공간 인덱스도 사용할 수 있습니다. 이 인덱스는 표준 QgsSpatialIndex 클래스 인덱스와 비슷하지만:

  • 단일 포인트 피처 지원합니다.

  • 정적 입니다. (인덱스 작성 후에 인덱스에 어떤 피처도 추가할 수 없습니다.)

  • 훨씬 빠릅니다!

  • 추가적인 피처 요청 없이도 원본 피처의 포인트들을 직접 검색할 수 있습니다.

  • 예를 들어 검색 포인트로부터 어떤 반경 안에 들어오는 모든 포인트를 반환받는다거나 하는, 진정한 거리 기반 검색을 지원합니다.

6.6. QgsVectorLayerUtils 클래스

QgsVectorLayerUtils 클래스는 벡터 레이어에 사용할 수 있는 몇몇 매우 유용한 메소드들을 담고 있습니다.

예를 들면 createFeature() 메소드는 벡터 레이어에 추가될, 레이어의 각 필드의 모든 최종 제약조건과 기본값을 유지하는 QgsFeature 클래스를 준비합니다:

vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
feat = QgsVectorLayerUtils.createFeature(vlayer)

getValues() 메소드는 필드 또는 표현식의 값을 빠르게 얻을 수 있게 해줍니다:

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. 벡터 레이어 생성하기

몇 가지 방법으로 벡터 레이어 데이터셋을 생성할 수 있습니다:

  • QgsVectorFileWriter 클래스: 디스크에 벡터 파일을 작성할 수 있는 편리한 클래스입니다. 전체 벡터 레이어를 저장하는 writeAsVectorFormatV3() 메소드를 정적 호출하거나, 클래스의 인스턴스를 생성한 다음 addFeature() 메소드를 호출합니다. 이 클래스는 GDAL이 지원하는 벡터 포맷들을 모두 (GeoPackage, 셰이프파일, GeoJSON, KML 및 기타 등등) 지원합니다.

  • QgsVectorLayer 클래스: 데이터에 연결하고 접근하기 위한 데이터 소스의 지정 경로(URL)을 해석하는 데이터 제공자를 인스턴스화합니다. 이 클래스를 사용하면 메모리 기반 임시 레이어(memory)를 생성하고 GDAL 벡터 데이터셋(ogr), 데이터베이스(postgres, spatialite, mysql, mssql), 그리고 기타 등등(wfs, gpx, delimitedtext, …)에 연결할 수 있습니다.

6.7.1. 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)

또한 FieldValueConverter 클래스를 사용하면 필드들을 서로 다른 포맷들과 호환되도록 변환할 수 있습니다. 예를 들어 (PostgreSQL에서) 배열 변수 유형을 텍스트 유형으로 변환하려면 다음과 같이 하면 됩니다:

 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

대상 좌표를 지정할 수도 있습니다 — QgsCoordinateReferenceSystem 클래스의 무결한 인스턴스를 네 번째 파라미터로 전달하면, 레이어가 해당 좌표계로 변환됩니다.

무결한 드라이버 이름을 알고 싶다면 supportedFiltersAndFormats() 메소드를 호출하거나 supported formats by OGR (OGR가 지원하는 포맷들) 문서를 살펴보십시오 — “Code” 열에 이 값을 드라이어 이름으로 전달해야 할 것입니다.

선택한 피처들만 내보낼지, 생성 용 드라이버 전용 심화 옵션들을 전달할지, 작성기에 속성을 생성하지 말라고 할지 등등의 여부를 선택적으로 설정할 수 있습니다. 기타 (선택적) 파라미터들이 여럿 있습니다. 자세한 내용은 QgsVectorFileWriter 클래스 문서를 참조하세요.

6.7.2. 피처로부터 직접

 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. QgsVectorLayer 클래스 인스턴스로부터

QgsVectorLayer 클래스가 지원하는 모든 데이터 제공자들 가운데, 메모리 기반 레이어를 주목해봅시다. 메모리 제공자는 주로 플러그인이나 제3자 응용 프로그램 개발자들이 사용할 목적으로 설계되었습니다. 메모리 제공자는 디스크 상에 데이터를 저장하지 않기 때문에, 개발자들이 몇몇 임시 레이어에 대한 빠른 백엔드로 사용할 수 있기 때문입니다.

이 제공자는 문자열(string), 정수(int), 더블형 실수(double) 유형의 필드를 지원합니다.

메모리 제공자는 공간 인덱스 작업도 지원하는데, 제공자의 createSpatialIndex() 함수를 호출하면 됩니다. 공간 인덱스를 생성하고 나면 좁은 지역 안에 있는 피처들을 더 빠르게 반복 작업할 수 있게 될 것입니다. (피처들을 전부 처리할 필요 없이 지정한 직사각형 안에 있는 피처들만 처리하면 되기 때문입니다.)

QgsVectorLayer 클래스 작성자(constructor)에 "memory" 를 제공자 문자열로 전달하면 메모리 제공자가 생성됩니다.

이 작성자는 레이어의 도형 유형을 정의하는 URI도 받습니다. 도형 유형은 다음 가운데 하나일 수 있습니다: "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon" or "None"

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

crs=definition

좌표계를 지정합니다. 이때 좌표계 정의는 QgsCoordinateReferenceSystem.createFromString() 메소드가 받아들이는 어떤 양식도 될 수 있습니다.

index=yes

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

field=name:type(length,precision)

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

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

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

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

 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()

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

 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. 벡터 레이어의 표현(심볼)

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

지정한 레이어의 렌더링 작업자는 다음과 같이 얻을 수 있습니다:

renderer = layer.renderer()

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

print("Type:", renderer.type())
Type: singleSymbol

QGIS 핵심 라이브러리에서 알려진 몇몇 렌더링 작업자 유형을 사용할 수 있습니다:

유형

클래스

설명

singleSymbol

QgsSingleSymbolRenderer

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

categorizedSymbol

QgsCategorizedSymbolRenderer

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

graduatedSymbol

QgsGraduatedSymbolRenderer

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

몇몇 사용자 정의 렌더링 작업자 유형이 있을 수도 있기 때문에, 이 유형들만 있을 거라고 절대로 가정하지 마십시오. 현재 사용할 수 있는 렌더링 작업자들을 알아내려면 응용 프로그램의 QgsRendererRegistry 클래스를 쿼리하면 됩니다:

print(QgsApplication.rendererRegistry().renderersList())
['nullSymbol', 'singleSymbol', 'categorizedSymbol', 'graduatedSymbol', 'RuleRenderer', 'pointDisplacement', 'pointCluster', 'mergedFeatureRenderer', 'invertedPolygonRenderer', 'heatmapRenderer', '25dRenderer', 'embeddedSymbol']

렌더링 작업자의 콘텐츠를 텍스트 형식으로 덤프받을 수도 있습니다 — 디버깅 작업에 유용할 수 있습니다.

renderer.dump()
SINGLE: MARKER SYMBOL (1 layers) color 190,207,80,255

6.8.1. 단일 심볼 렌더링 작업자

symbol() 메소드를 호출하면 렌더링에 쓰인 심볼을 가져올 수 있고, setSymbol() 메소드로 심볼을 변경할 수 있습니다. (C++ 개발자는 기억하세요: 렌더링 작업자가 심볼을 소유합니다.)

setSymbol() 메소드를 호출해서 적당한 심볼의 인스턴스를 전달하면 특정 벡터 레이어가 사용하는 심볼을 변경할 수 있습니다. QgsMarkerSymbol, QgsLineSymbol, 그리고 QgsFillSymbol 클래스의 각각 대응하는 createSimple() 함수를 호출하면 각각 포인트, 라인, 폴리곤 레이어 용 심볼을 생성할 수 있습니다.

createSimple() 메소드에 전달되는 목록(dictionary)이 심볼의 스타일 속성을 설정합니다.

예를 들면 다음 코드 예시와 같이 setSymbol() 메소드를 호출해서 QgsMarkerSymbol 클래스의 인스턴스를 전달하면, 특정 포인트 레이어가 사용하는 심볼을 대체시킬 수 있습니다.

symbol = QgsMarkerSymbol.createSimple({'name': 'square', 'color': 'red'})
layer.renderer().setSymbol(symbol)
# show the change
layer.triggerRepaint()

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

  • circle

  • square

  • cross

  • rectangle

  • diamond

  • pentagon

  • triangle

  • equilateral_triangle

  • star

  • regular_star

  • arrow

  • filled_arrowhead

  • x

심볼 인스턴스의 첫 번째 심볼 레이어에 대한 전체 속성 목록을 알고 싶다면 다음 코드 예시를 따라해보십시오:

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'}

여러분이 속성을 일부 변경하고 싶다면 다음이 유용할 것입니다:

 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. 카테고리 심볼 렌더링 작업자

카테고리 렌더링 작업자를 사용할 때, 범주화에 사용되는 속성을 쿼리하고 설정할 수 있습니다: classAttribute()setClassAttribute() 메소드를 사용하십시오.

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

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>

이때 value() 메소드가 카테고리들을 구분하는 데 쓰이는 값이고, label() 메소드는 카테고리 설명에 쓰이는 텍스트이며, symbol() 메소드는 할당된 심볼을 반환합니다.

이 렌더링 작업자는 일반적으로 범주화에 사용된 원본 심볼과 색상표도 저장합니다: sourceColorRamp()sourceSymbol() 메소드를 사용하십시오.

6.8.3. 등급 심볼 렌더링 작업자

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

이 렌더링 작업자가 사용하는 범위에 대해 상세히 알아내려면 다음과 같은 방법을 사용합니다.

 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>

(범주화 속성 이름을 알아내기 위한) classAttribute() 메소드와 sourceSymbol()sourceColorRamp() 메소드를 다시 사용하면 됩니다. 범위 생성 방법을 결정하는 추가적인 mode() 메소드도 있습니다: 등간격을 사용할지, 사분위를 사용할지, 또는 기타 다른 방법을 사용할지 결정할 수 있습니다.

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

 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. 심볼 다루기

심볼을 표현하기 위한 QgsSymbol 기저(base) 클래스는 파생 클래스 3개를 가지고 있습니다:

모든 심볼은 하나 이상의 (QgsSymbolLayer 클래스에서 파생된 클래스인) 심볼 레이어로 이루어집니다. 이 심볼 레이어들이 실제 렌더링 작업을 하며, 심볼 클래스 자체는 심볼 레이어 용 컨테이너 역할만 합니다.

(예를 들어 렌더링 작업자로부터 나온) 심볼 인스턴스를 가지고 있다면, 심볼을 탐색해볼 수 있습니다: type() 메소드는 심볼이 마커, 라인, 또는 채우기 심볼 가운데 어떤 유형인지 알려줍니다. 심볼의 짧은 설명을 반환하는 dump() 메소드도 있습니다. 심볼 레이어 목록을 보고 싶다면:

marker_symbol = QgsMarkerSymbol()
for i in range(marker_symbol.symbolLayerCount()):
    lyr = marker_symbol.symbolLayer(i)
    print("{}: {}".format(i, lyr.layerType()))
0: SimpleMarker

심볼의 색상을 알아내려면 color() 메소드를 사용하고 그 색상을 변경하려면 setColor() 메소드를 사용하십시오. 마커 심볼의 경우 추가적으로 size()angle() 메소드를 사용해서 각각 심볼 크기와 회전 각도를 쿼리할 수 있습니다. 라인 심볼의 경우 width() 메소드가 라인 너비를 반환합니다.

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

6.8.4.1. 심볼 레이어 다루기

앞에서 말했듯이, (QgsSymbolLayer 클래스의 하위 클래스인) 심볼 레이어가 피처의 모양을 결정합니다. 일반적인 사용례를 위한 기본 심볼 레이어 클래스가 몇 개 있습니다. 새로운 심볼 레이어 유형을 구현할 수 있기 때문에, 사용자가 피처를 어떻게 렌더링할지를 임의로 정의할 수 있습니다. layerType() 메소드가 심볼 레이어 클래스를 고유하게 식별합니다 — 기본 클래스는 SimpleMarker, SimpleLineSimpleFill 심볼 레이어 유형입니다.

지정한 심볼 레이어 클래스에 생성할 수 있는 심볼 레이어 유형들의 전체 리스트를 다음 코드로 볼 수 있습니다:

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

QgsSymbolLayerRegistry 클래스는 사용할 수 있는 모든 레이어 유형들의 데이터베이스를 관리합니다.

심볼 레이어 데이터에 접근하려면, 심볼 모양을 결정하는 속성들의 키-값 목록(dictionary)을 반환하는 properties() 메소드를 사용하십시오. 각각의 심볼 레이어 유형은 해당 유형이 사용하는 특정한 속성 집합을 가지고 있습니다. 또한 각각의 설정자(setter) 대응 항목을 가진 일반적인 color(), size(), angle()width() 메소드들도 있습니다. 물론 크기와 각도는 마커 심볼에만 사용할 수 있으며 너비는 라인 심볼에만 사용할 수 있습니다.

6.8.4.2. 사용자 지정 심볼 레이어 유형 생성하기

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

 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)

layerType() 메소드가 심볼 레이어의 이름을 결정합니다. 이 이름은 전체 심볼 레이어들 사이에서 유일해야 합니다. properties() 메소드는 속성들의 지속성을 위해 쓰입니다. clone() 메소드는 반드시 심볼 레이어와 모든 속성이 정확하게 동일한 복제물을 반환해야만 합니다. 마지막으로 렌더링 메소드들이 있습니다: 첫 번째 피처를 렌더링하기 전에 startRender() 메소드를 호출하고, 렌더링을 완료했을 때 stopRender() 메소드를 호출하며, 렌더링을 하기 위해서는 renderPoint() 메소드를 호출합니다. 포인트(들)의 좌표는 이미 산출 좌표로 변환되어 있습니다.

폴리라인 및 폴리곤의 경우 유일한 차이점은 렌더링 메소드에 있을 것입니다: 라인의 경우 라인 목록을 받는 renderPolyline() 메소드를 사용하는 반면, 폴리곤의 경우 외곽 고리 상에 있는 포인트 목록을 첫 번째 파라미터로 그리고 내곽 고리의 포인트 목록(또는 None)을 두 번째 파라미터로 받는 renderPolygon() 메소드를 사용할 것입니다.

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

 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()"))

심볼 속성 대화창 안에 이 위젯을 내장시킬 수 있습니다. 심볼 속성 대화창에서 심볼 레이어 유형을 선택했을 때, 심볼 레이어의 인스턴스 및 심볼 레이어 위젯의 인스턴스를 생성합니다. 그 다음 setSymbolLayer() 메소드를 호출해서 위젯에 심볼 레이어를 할당합니다. 이때 위젯이 심볼 레이어의 속성을 반영하도록 UI를 업데이트해야 합니다. 심볼 속성 대화창에서 심볼에 사용할 수 있도록 심볼 레이어를 다시 검색하는 데 symbolLayer() 메소드를 사용합니다.

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

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

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

 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)

상위 클래스의 작성자에 (레이어가 반환하는 유형과 동일한) 레이어 유형과 심볼 유형(마커/라인/채우기)을 전달해야 합니다. createSymbolLayer() 메소드가 props 목록(dictionary)에 지정된 속성들을 가진 심볼 레이어의 인스턴스 생성을 처리합니다. 그리고 해당 심볼 레이어 유형을 위한 설정 위젯을 반환하는 createSymbolLayerWidget() 메소드도 있습니다.

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

6.8.5. 사용자 정의 렌더링 작업자 생성하기

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

다음 코드는 마커 심볼 2개를 생성해서 각 피처마다 랜덤하게 2개 중 1개를 할당하는 간단한 사용자 정의 렌더링 작업자의 예시입니다:

 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)

상위 QgsFeatureRenderer 클래스의 작성자는 (렌더링 작업자들 가운데 유일해야 하는) 렌더링 작업자 이름을 필요로 합니다. symbolForFeature() 가 특정 피처에 어떤 심볼을 사용할지 결정하는 메소드입니다. startRender()stopRender() 메소드는 각각 심볼 렌더링 작업의 시작과 마무리를 처리합니다. usedAttributes() 메소드는 렌더링 작업자가 존재할 것으로 예상하는 필드 이름 목록을 반환할 수 있습니다. 마지막으로 clone() 메소드는 렌더링 작업자의 복제물을 반환할 것입니다.

심볼 레이어와 마찬가지로, 렌더링 작업자를 환경설정하기 위한 GUI를 추가할 수 있습니다. 이 GUI는 QgsRendererWidget 클래스로부터 파생시켜야 합니다. 다음 코드는 여러분이 첫 번째 심볼을 설정할 수 있게 해주는 버튼을 생성하는 예시입니다:

 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

이 작성자는 활성 레이어의 인스턴스(QgsVectorLayer), 전체 수준 스타일(QgsStyle), 그리고 현재 렌더링 작업자를 받습니다. 렌더링 작업자가 없거나 렌더링 작업자가 다른 유형인 경우, 새로운 사용자 정의 렌더링 작업자로 대체할 것입니다. 그렇지 않은 경우 (이미 필요한 유형인) 현재 렌더링 작업자를 사용할 것입니다. 렌더링 작업자의 현재 상태를 보이도록 위젯의 내용을 업데이트해야 합니다. 렌더링 작업자 대화창이 승인되면, 현재 렌더링 작업자를 가져오기 위해 위젯의 renderer() 메소드를 호출합니다 — 레이어에 가져온 렌더링 작업자를 할당할 것입니다.

마지막으로 렌더링 작업자의 메타데이터를 생성하고 레지스트리에 렌더링 작업자를 등록해야 합니다. 이렇게 하지 않으면 렌더링 작업자와 함께 레이어를 불러올 수 없고, 사용자가 렌더링 작업자 목록에서 렌더링 작업자를 선택할 수 없습니다. 이제 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)

심볼 레이어의 경우와 비슷하게, 추상(abstract) 메타데이터 작성자도 렌더링 작업자 이름, 여러분이 볼 수 있는 이름, 그리고 선택적인 렌더링 작업자의 아이콘 이름을 기다립니다. createRenderer() 메소드가 DOM 트리로부터 렌더링 작업자의 상태를 복구하는 데 사용할 수 있는 QDomElement 클래스 인스턴스를 전달합니다. createRendererWidget() 메소드는 환경설정 위젯을 생성합니다. 렌더링 작업자가 GUI를 가지고 있지 않은 경우, 이 메소드를 사용할 필요가 없고 또는 이 메소드가 None 을 반환할 수 있습니다.

렌더링 작업자에 아이콘을 연결하려면 QgsRendererAbstractMetadata 작성자에 아이콘을 세 번째 (선택적) 인자로 할당하면 됩니다 — RandomRendererMetadata __init__() 함수에 있는 기저 클래스 작성자는 다음과 같이 보일 것입니다:

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

메타데이터 클래스의 setIcon() 메소드를 사용하면 이후 어느 때에라도 아이콘을 연결시킬 수 있습니다. 이 아이콘은 (앞에서 볼 수 있듯이) 파일에서 불러올 수도 있고, 또는 Qt 리소스 에서 불러올 수도 있습니다. (PyQt5는 파이썬 용 .qrc 컴파일러를 포함하고 있습니다.)

6.9. 남은 이야기들

할 일:

  • 심볼 생성/수정하기

  • 스타일 작업하기 (QgsStyle 클래스)

  • 색상표 작업하기 (QgsColorRamp 클래스)

  • 심볼 레이어 및 렌더링 작업자 레지스트리 탐색하기