벡터 레이어 사용

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

Retrieving informations about attributes

You can retrieve informations 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()

피처 선택

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 fatures are normally higlighted 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.

선택 색상을 변경하려면 다음 예시에서처럼 QgsMapCanvas 클래스의 setSelectionColor() 메소드를 사용할 수 있습니다.

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

어떤 레이어의 선택된 피처 리스트에 다른 피처를 추가하려면, setSelectedFeatures() 함수를 호출해서 피처 ID 리스트에 새 피처를 건네줄 수 있습니다.

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

선택을 해제하려면, 빈 리스트를 건네기만 하면 됩니다.

layer.setSelectedFeatures([])

벡터 레이어 상의 반복 작업

벡터 레이어에 있는 피처에 대해 반복적으로 작업하는 일은 가장 흔한 작업 가운데 하나입니다. 다음은 각 피처에 관련된 정보를 표출하는 반복 작업을 수행하는 간단한 기본 코드의 예시입니다. layer 변수가 QgsVectorLayer 클래스 오브젝트를 가지고 있다고 가정합니다.

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

속성에 접근하기

속성을 속성명으로 조회할 수 있습니다.

print feature['name']

다른 방법으로, 속성을 인덱스로 조회할 수도 있습니다. 속성명보다 이 방법이 조금 더 빠를 것입니다. 예를 들어 첫 번째 속성을 얻어오는 방법은 다음과 같습니다.

print feature[0]

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

선택된 피처만 필요할 경우, 벡터 레이어에서 selectedFeatures() 메소드를 사용할 수 있습니다.

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

다른 방법은 processing 라이브러리의 features() 메소드를 사용하는 것 입니다.

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

기본적으로, 선택된 피처가 없을 경우 레이어에 있는 모든 피처에 대해 반복 작업을 하고, 선택된 피처가 있을 경우 선택된 피처에 대해 반복 작업을 할 것입니다. 이 동작들은 processing 라이브러리의 ignore selections 옵션에 따라 달라질 수 있음에 주의하십시요.

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

레이어 안의 어떤 피처 부분 집합에 대해, 예를 들면 주어진 범위 안에 있는 피처들에 대해 반복 작업을 하길 원할 경우, getFeatures() 함수를 호출할 때 QgsFeatureRequest 오브젝트를 인자로 주어야 합니다. 다음은 그 예시입니다.

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

앞의 예시에서 볼 수 있는 공간 필터 대신 (또는 함께) 속성 기반 필터가 필요할 경우, QgsExpression 오브젝트를 만들어서 QgsFeatureRequest 오브젝트 생성자에 넘겨줄 수 있습니다. 다음은 그 예시입니다.

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

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)

참고

If you only need a subset of the attributes or you don’t need the geometry informations, 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.

벡터 레이어 수정

벡터 데이터 제공자 대부분은 레이어 데이터의 편집을 지원하지만, 때때로 사용 가능한 편집 작업의 일부만 지원하기도 합니다. capabilities() 함수를 통해 어떤 기능들이 지원되는지 알아보십시오.

caps = layer.dataProvider().capabilities()

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

주석

QGIS 안에서 (콘솔이든 플러그인이든) 작업할 경우, 사용자가 도형, 스타일 또는 속성을 수정했을 때 변경 사항을 바로 볼 수 있도록 맵 캔버스를 강제로 다시 그리기 해야 할 수도 있습니다.

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

if caps & QgsVectorDataProvider.AddFeatures:
    feat = QgsFeature()
    feat.addAttribute(0, 'hello')
    feat.setGeometry(QgsGeometry.fromPoint(QgsPoint(123, 456)))
    (res, outFeats) = layer.dataProvider().addFeatures([feat])

피처 삭제

피처를 삭제하고자 할 경우, 해당 피처 ID의 리스트을 넘기기만 하면 됩니다.

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

피처 수정

피처의 도형을 변경하거나 속성을 변경할 수도 있습니다. 다음은 먼저 인덱스 0과 1의 속성값을 변경한 다음, 피처의 도형을 변경하는 예시입니다.

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

참고

도형만 변경하려 할 경우, 도형을 편집(변형, 삽입, 버텍스 이동 등)하는 데 유용한 몇몇 메소드를 제공하는 QgsVectorLayerEditUtils 클래스를 사용하는 것을 고려해볼 수도 있습니다.

항목 추가 및 제거

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

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

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

QGIS 응용 프로그램에서 벡터를 편집할 때, 먼저 특정 레이어의 편집 모드를 활성화한 다음 수정 작업을 하고 마지막으로 변경 사항을 반영(또는 되돌리기)해야 합니다. 모든 변경 사항은 반영하기 전까지는 데이터로 저장되지 않고 레이어의 내부 메모리 편집 버퍼에 머물러 있을 뿐입니다. 이 기능을 프로그램적으로 활용할 수도 있는데, 이는 데이터 제공자를 직접 사용하는 작업을 보완하는 벡터 레이어 편집의 다른 방법일 뿐입니다. 벡터 레이어 편집을 위한 GUI를 제공할 때 이 옵션을 사용하십시오. 사용자가 반영/되돌리기를 결정하고 언두/리두(undo/redo) 기능을 사용할 수 있게 해주기 때문입니다. 변경 사항을 반영하면, 편집 버퍼에 있는 모든 변경 사항이 데이터 제공자에 저장됩니다.

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

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

언두/리두 작업이 잘 동작하려면, 앞에서 언급했던 호출들을 언두 관련 명령어들로 싸줘야 합니다. (언두/리두에 관심이 없고 변경 사항을 즉시 저장하고 싶다면, 데이터 제공자로 편집 해 쉽게 작업할 수 있습니다.) 언두 기능을 사용하는 방법은 다음과 같습니다.

layer.beginEditCommand("Feature triangulation")

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

if problem_occurred:
    layer.destroyEditCommand()
   return

# ... more editing ...

layer.endEditCommand()

beginEditCommand() 메소드는 내부적인 “활성” 명령을 생성하고 그 이후 벡터 레이어의 변경 사항을 기록할 것입니다. endEditCommand() 메소드를 호출하면 명령이 언두 스택으로 넘어가 사용자가 GUI로 언두/리두할 수 있게 됩니다. 수정 작업 동안 어떤 문제가 발생할 경우 destroyEditCommand() 함수가 명령을 제거하고 해당 명령이 활성화되어 있을 동안의 모든 변경 사항을 되돌릴 것입니다.

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.

공간 인덱스 사용

벡터 레이어에 대해 자주 쿼리를 해야 할 경우 공간 인덱스를 사용하면 코드 실행 속도를 획기적으로 향상시킬 수 있습니다. 여러분이 보간 알고리듬을 작성하는데, 보간값을 계산하기 위해 주어진 위치에서 가장 가까운 포인트 레이어의 포인트 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.

  1. 공간 인덱스 생성 — 다음은 비어 있는 인덱스를 생성하는 코드입니다.

    index = QgsSpatialIndex()
    
  2. 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)
    
  3. 공간 인덱스가 채워지면 쿼리를 해보십시오.

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

벡터 레이어 작성

QgsVectorFileWriter 클래스를 이용해서 벡터 레이어 파일을 작성할 수 있습니다. OGR이 지원하는 모든 유형의 벡터 파일(shapefile, GeoJSON, KML 등)을 지원합니다.

벡터 레이어를 내보내는 데 사용할 수 있는 방법이 두 가지 있습니다.

  • 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.

  • 피처로부터 직접

    # define fields for feature attributes. A list of QgsField objects is needed
    fields = [QgsField("first", QVariant.Int),
              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: ", writer.hasError()
    
    # 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 (optional)
    del writer
    

메모리기반 제공자

메모리기반 제공자는 주로 플러그인이나 써드파티 응용 프로그램 개발자들이 사용하도록 되어 있습니다. 개발자가 일시적인 레이어를 위한 빠른 속도의 백엔드로 사용할 수 있도록 디스크에 데이터를 저장하지 않습니다.

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

메모리기반 제공자는 공간 인덱스 작업도 지원하는데, 제공자의 createSpatialIndex() 함수를 호출해서 사용할 수 있습니다. 공간 인덱스를 생성하고 나면 상대적으로 좁은 범위 안에 있는 피처에 대해 더 빨리 반복 작업을 할 수 있게 됩니다. (모든 피처들을 전부 처리할 필요없이 지정된 사각형 안의 피처만 처리하면 되기 때문입니다.)

QgsVectorLayer 생성자에 제공자 문자열에 "memory" 를 넘겨주면 메모리기반 제공자를 생성합니다.

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

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

crs=definition

좌표계를 설정합니다. QgsCoordinateReferenceSystem.createFromString() 함수가 지원하는 형식은 모두 definition 에 들어갈 수 있습니다.

index=yes

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

field=name:type(length,precision)

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

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

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

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

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

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

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

다음과 같은 방법으로 주어진 레이어에 대한 렌더러를 얻을 수 있습니다.

renderer = layer.rendererV2()

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

print "Type:", rendererV2.type()

QGIS 코어 라이브러리에서 사용할 수 있는 잘 알려진 렌더러가 몇 개 있습니다.

유형

클래스

설명

singleSymbol QgsSingleSymbolRendererV2

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

categorizedSymbol QgsCategorizedSymbolRendererV2

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

graduatedSymbol QgsGraduatedSymbolRendererV2

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

사용자 정의 렌더러 유형이 있을 수도 있으므로, 절대 이 유형밖에 없을 거라고 가정하지 마십시오. 현재 사용할 수 있는 렌더러를 알아보려면 QgsRendererV2Registry 클래스 개체를 조회하면 됩니다.

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

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

print rendererV2.dump()

단일 심볼 렌더러

symbol() 함수를 호출해서 렌더링에 사용된 심볼을 얻을수 있고, setSymbol() 함수를 통해 변경할 수 있습니다. (C++ 개발자에게 : 렌더러가 심볼의 소유권을 가집니다.)

적절한 심볼 인스턴스를 넘기며 setSymbol() 메소드를 호출해서, 특정 벡터 레이어가 사용하는 심볼을 변경할 수 있습니다. QgsMarkerSymbolV2, QgsLineSymbolV2, QgsFillSymbolV2 클래스의 createSimple() 함수를 호출하면 각각 포인트, 라인, 폴리곤 레이어를 위한 심볼을 생성할 수 있습니다.

createSimple() 함수에 넘기는 딕셔너리가 심볼의 스타일 속성을 설정합니다.

For example you can change 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
  • rectangle
  • diamond
  • pentagon
  • triangle
  • equilateral_triangle
  • star
  • regular_star
  • arrow
  • filled_arrowhead

카테고리 심볼 렌더러

classAttribute() 함수와 setClassAttribute() 함수를 사용하면 범주화에 사용된 속성명을 조회하고 설정할 수 있습니다.

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

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

value() 는 카테고리를 구분하는 데 쓰이는 값이며, label() 은 카테고리 설명에 쓰이는 텍스트이고, symbol() 함수는 할당된 심볼을 반환합니다.

일반적으로 렌더러는 범주화 작업에 사용된 원래 심볼과 색상표를 저장합니다. 이 값들은 sourceSymbol()sourceColorRamp() 메소드에서 사용 되었습니다.

등급 심볼 렌더러

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

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

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)

심볼 다루기

심볼을 표현하려면, 다음 3개의 파생 클래스를 가진 QgsSymbolV2 기반 클래스를 사용하면 됩니다.

  • QgsMarkerSymbolV2 — 포인트 피처 용

  • QgsLineSymbolV2 — 라인 피처 용

  • QgsFillSymbolV2 — 폴리곤 피처 용

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

심볼의 인스턴스(예를 들면 렌더러로부터 받은)를 가지고 있다면 탐색해 볼 수 있습니다. type() 메소드는 인스턴스가 마커인지, 라인인지 또는 폴리곤 심볼인지 알려줍니다. 심볼의 간단한 설명을 반환하는 dump() 메소드도 있습니다. 심볼 레이어의 목록을 얻으려면 다음과 같이 할 수 있습니다.

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

심볼의 색상을 알아보려면 color() 함수 메소드를 사용하고 색상을 변경하려면 setColor() 함수를 사용하십시오. 마커 심볼의 경우 size()angle() 함수 메소드를 통해 심볼의 크기와 회전각을 추가로 조회할 수 있고, 라인 심볼의 경우 width() 함수가 라인 두께를 반환합니다.

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

심볼 레이어 다루기

이전에 설명했듯이, 심볼 레이어(QgsSymbolLayerV2 의 하위 클래스)가 피처의 외양을 결정합니다. 일반적으로 사용되는 몇 가지 기초 심볼 레이어 클래스들이 있는데, 이를 이용해 새로운 심볼 레이어를 만들 수 있으므로, 피처를 어떻게 렌더링할지 사용자 마음대로 지정할 수 있습니다. 심볼 레이어 클래스를 식별할 수 있는 메소드는 layerType() 함수가 유일합니다. "SimpleMarker", "SimpleLine""SimpleFill" 심볼 레이어가 기초적이며 기본적인 심볼 레이어 유형입니다.

주어진 심볼 레이어 클래스에 대해 사용자가 생성할 수 있는 심볼 레이어 유형의 전체 목록을 다음과 같이 얻을 수 있습니다.

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

결과는 다음과 같습니다.

EllipseMarker
FontMarker
SimpleMarker
SvgMarker
VectorField

QgsSymbolLayerV2Registry 클래스는 사용 가능한 모든 심볼 레이어 유형의 데이터베이스를 관리합니다.

심볼 레이어 데이터에 접근하려면, 외양을 결정하는 속성들의 키-값 사전(key-value dictionary)을 반환하는 properties() 메소드를 이용하십시오. 각 심볼 레이어 유형은 자체적으로 사용하는 특정 속성 집합을 가지고 있습니다. 또, 각각의 설정자(setter)에 대응하는 일반적인 메소드인 color(), size(), angle(), width() 함수들도 있습니다. 물론 크기와 각도는 마커 심볼 레이어에서만 사용할 수 있고, 두께는 라인 심볼 레이어에서만 쓸 수 있습니다.

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

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

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)

layerType() 메소드가 심볼 레이어의 명칭을 결정하는데, 기존의 모든 심볼 레이어 명칭과 달라야 합니다. properties() 메소드는 속성의 지속성을 결정합니다. clone() 메소드는 모든 속성이 정확히 동일한 심볼 레이어의 복사본을 반환합니다. 마지막으로 렌더링 메소드도 있습니다. 첫 번째 피처를 렌더링하기 전에 startRender() 메소드를 호출하고, 렌더링을 완료하면 stopRender() 메소드를 호출합니다. 포인트(들)의 좌표는 이미 출력 좌표로 변환돼 있습니다.

폴리라인과 폴리곤의 유일한 차이점은 렌더링 방법일 것입니다. 사용자가 renderPolyline() 함수에 라인 목록 넘겨 그릴 수도 있고, renderPolygon() 함수에 첫 번째 파라미터로 외곽(outer ring)에 있는 포인트 목록을, 두 번째 파라미터로 내부(inner ring)에 있는 포인트 목록을 (또는 빈 목록을) 넘겨 그릴 수도 있습니다.

일반적으로 사용자가 표현을 변경할 수 있도록 심볼 레이어 유형의 속성을 설정하기 위한 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())

(레이어가 반환하는 것과 동일한) 레이어 유형 및 심볼 유형(마커/라인/채움)을 부모 클래스의 생성자에 넘겨야 합니다. createSymbolLayer() 함수가 props 딕셔너리 안에 속성을 지정한 심볼 레이어 인스턴스를 생성하는 일을 맡습니다. (키 값이 “str” 오브젝트가 아니라 QString 인스턴스라는 점을 주의하십시오.) 또 이 심볼 레이어 유형을 위한 설정 위젯을 반환하는 createSymbolLayerWidget() 메소드도 있습니다.

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

사용자 정의 렌더러 생성

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

다음 예시 코드는 마커 심볼 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)

QgsFeatureRendererV2 부모 클래스의 생성자는 (다른 렌더러 명칭들과는 다르게 유일한) 렌더러 명칭을 필요로 합니다. symbolForFeature() 메소드는 특정 피처에 어떤 심볼을 사용할지 결정합니다. startRender()stopRender() 메소드는 심볼 렌더링 작업의 시작과 완료를 처리합니다. usedAttributes() 메소드는 렌더러가 표출해야 할 항목명의 목록을 반환할 수 있습니다. 마지막으로 clone() 함수는 렌더러의 복사본을 반환할 것입니다.

심볼 레이어와 마찬가지로, 렌더러를 설정값을 설정하는 GUI를 붙일 수 있습니다. QgsRendererV2Widget 클래스로부터 이 GUI를 상속받아야 합니다. 다음은 사용자가 첫 번째 심볼을 설정할 수 있게 해주는 버튼을 생성하는 예시 코드입니다.

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

생성자는 활성화된 레이어(QgsVectorLayer), 전역 스타일(QgsStyleV2)과 현재 렌더러의 인스턴스를 받습니다. 렌더러가 존재하지 않거나 다른 유형을 가지고 있을 경우, 사용자가 만든 새로운 렌더러로 대체될 것입니다. 그렇지 않다면 (필요한 유형을 이미 가지고 있는) 기존 렌더러를 사용하게 됩니다. 위젯 컨텐츠는 렌더러의 현재 상태를 보여주도록 업데이트 되어야 합니다. 렌더러 대화창에서 사용자가 입력을 완료하면, 현재 렌더러를 얻기 위한 위젯의 renderer() 메소드가 호출되어, 렌더러가 레이어에 할당될 것입니다.

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

렌더러에 아이콘을 부여하려면 QgsRendererV2AbstractMetadata 클래스 생성자에서 3번째 (선택적인) 인자로 아이콘을 할당할 수 있습니다. RandomRendererMetadata 예시 코드의 기존 클래스 생성자 __init__() 함수를 다음과 같이 변경하면 됩니다.

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

메타데이터 클래스의 setIcon() 메소드를 이용하면 다른 언제라도 아이콘을 부여할 수 있습니다. (앞의 코드처럼) 파일에서 아이콘을 불러올 수도 있고, Qt 리소스 에서 불러올 수도 있습니다. (PyQt4는 파이썬을 위한 .qrc 컴파일러를 포함하고 있습니다.)

남은 이야기들

TODO:

스타일을 적용할 수 있는 심볼을 생성/수정하기 (QgsStyleV2), 색상표 다루기 (QgsVectorColorRampV2), 규칙 기반 렌더러 (이 블로그 포스트 를 참조), 심볼 레이어와 렌더러 레지스트리 탐색하기