GIS

QGIS 실전 플러그인 개발 - (5) 플러그인의 핵심 로직 구현

유병혁 2024. 12. 8. 11:36

안녕하세요? 이번 글은 "QGIS 실전 플러그인 개발" 시리즈의 다섯 번째로, "플러그인의 핵심 로직 구현"에 대해 다룹니다. 이번에는 geoquery.py 파일을 변경하여 플러그인의 핵심 로직을 구현합니다.

핵심 로직 구현

두 번째 글에서 작성한 Python 코드를 적용하되, 일부 코드만 변경해주시면 되겠습니다.

 

QGIS 실전 플러그인 개발 - (2) Vworld API를 활용한 Python 코드 작성

안녕하세요? 이번 글은 "QGIS 실전 플러그인 개발" 시리즈의 두 번째로, "Vworld API를 활용한 Python 코드 작성"에 대해 다룹니다.라이브러리 호출먼저 HTTP 요청, 데이터 파싱, QGIS 작업, 그리고 Py

foss4g.tistory.com

import requests
import json
from qgis.core import *
from qgis.utils import iface
from PyQt5.QtCore import QVariant

def get_bbox_epsg5179():
    """
    현재 QGIS 맵 캔버스의 BBox를 EPSG:5179 좌표계로 변환하여 반환
    """
    canvas = iface.mapCanvas()
    extent = canvas.extent()
    canvas_crs = canvas.mapSettings().destinationCrs()
    target_crs = QgsCoordinateReferenceSystem("EPSG:5179")
    transform = QgsCoordinateTransform(canvas_crs, target_crs, QgsProject.instance())

    min_point = transform.transform(QgsPointXY(extent.xMinimum(), extent.yMinimum()))
    max_point = transform.transform(QgsPointXY(extent.xMaximum(), extent.yMaximum()))
    return f"{min_point.x()},{min_point.y()},{max_point.x()},{max_point.y()}"

def fetch_vworld_data(api_key, bbox, query, page, page_size):
    """
    Vworld API 요청을 보내고 응답 데이터를 반환

    Args:
        api_key (str): Vworld API 키
        bbox (str): EPSG:5179 기준 BBox
        query (str): 검색 키워드
        page (int): 페이지 번호
        page_size (int): 페이지 크기

    Returns:
        dict: API 응답 데이터
    """
    url = (
        f"https://api.vworld.kr/req/search?service=search&request=search"
        f"&version=2.0&crs=EPSG:5179&bbox={bbox}&type=place&format=json"
        f"&errorformat=json&key={api_key}&query={query}&size={page_size}&page={page}"
    )
    response = requests.get(url)
    if response.status_code == 200:
        return response.json()
    else:
        print(f"Error: {response.status_code}")
        return None

def add_data_to_qgis_layer(all_items):
    """
    Vworld API에서 가져온 데이터를 QGIS 레이어에 추가

    Args:
        all_items (list): 검색된 데이터 리스트
    """
    try:
        features = []
        for item in all_items:
            x = float(item["point"]["x"])
            y = float(item["point"]["y"])
            road = item.get("address", {}).get("road", "")
            parcel = item.get("address", {}).get("parcel", "")

            point = QgsPointXY(x, y)
            feature = QgsFeature()
            feature.setGeometry(QgsGeometry.fromPointXY(point))
            feature.setAttributes(
                [
                    item.get("id", ""),
                    item.get("title", ""),
                    item.get("category", ""),
                    road,
                    parcel,
                ]
            )
            features.append(feature)
        layer = QgsVectorLayer("Point?crs=EPSG:5179", "Vworld Search Results", "memory")
        provider = layer.dataProvider()
        provider.addAttributes(
            [
                QgsField("ID", QVariant.String),
                QgsField("Title", QVariant.String),
                QgsField("Category", QVariant.String),
                QgsField("Road", QVariant.String),
                QgsField("Parcel", QVariant.String),
            ]
        )
        layer.updateFields()
        provider.addFeatures(features)

        QgsProject.instance().addMapLayer(layer)
        iface.messageBar().pushMessage(
            "Vworld Search", f"Added {len(all_items)} items to QGIS.", level=1
        )
    except KeyError as e:
        print(f"KeyError: {e} - Check the response data structure.")

api_key = "3A99F800-2E60-3397-9F0E-2746BC4169F6"
query = "국립공원"

bbox = get_bbox_epsg5179()
page = 1
page_size = 1000
all_items = []

while True:
    data = fetch_vworld_data(api_key, bbox, query, page, page_size)
    if data and "response" in data and "result" in data["response"]:
        items = data["response"]["result"]["items"]
        all_items.extend(items)

        total_results = int(data["response"]["record"]["total"])
        print(
            f"Total Results: {total_results}, Current Page: {page}, Items Retrieved: {len(items)}"
        )
        if len(items) < page_size:
            break
        page += 1
    else:
        print("Failed to fetch or parse data.")
        break

# 수집한 데이터를 QGIS에 추가
add_data_to_qgis_layer(all_items)

 

먼저 다음 코드는 geoquery.py에 그대로 붙여넣기 합니다.

import requests
import json
from qgis.core import *
from qgis.utils import iface
from PyQt5.QtCore import QVariant

def get_bbox_epsg5179():
    """
    현재 QGIS 맵 캔버스의 BBox를 EPSG:5179 좌표계로 변환하여 반환
    """
    canvas = iface.mapCanvas()
    extent = canvas.extent()
    canvas_crs = canvas.mapSettings().destinationCrs()
    target_crs = QgsCoordinateReferenceSystem("EPSG:5179")
    transform = QgsCoordinateTransform(canvas_crs, target_crs, QgsProject.instance())

    min_point = transform.transform(QgsPointXY(extent.xMinimum(), extent.yMinimum()))
    max_point = transform.transform(QgsPointXY(extent.xMaximum(), extent.yMaximum()))
    return f"{min_point.x()},{min_point.y()},{max_point.x()},{max_point.y()}"

def fetch_vworld_data(api_key, bbox, query, page, page_size):
    """
    Vworld API 요청을 보내고 응답 데이터를 반환

    Args:
        api_key (str): Vworld API 키
        bbox (str): EPSG:5179 기준 BBox
        query (str): 검색 키워드
        page (int): 페이지 번호
        page_size (int): 페이지 크기

    Returns:
        dict: API 응답 데이터
    """
    url = (
        f"https://api.vworld.kr/req/search?service=search&request=search"
        f"&version=2.0&crs=EPSG:5179&bbox={bbox}&type=place&format=json"
        f"&errorformat=json&key={api_key}&query={query}&size={page_size}&page={page}"
    )
    response = requests.get(url)
    if response.status_code == 200:
        return response.json()
    else:
        print(f"Error: {response.status_code}")
        return None

def add_data_to_qgis_layer(all_items):
    """
    Vworld API에서 가져온 데이터를 QGIS 레이어에 추가

    Args:
        all_items (list): 검색된 데이터 리스트
    """
    try:
        features = []
        for item in all_items:
            x = float(item["point"]["x"])
            y = float(item["point"]["y"])
            road = item.get("address", {}).get("road", "")
            parcel = item.get("address", {}).get("parcel", "")

            point = QgsPointXY(x, y)
            feature = QgsFeature()
            feature.setGeometry(QgsGeometry.fromPointXY(point))
            feature.setAttributes(
                [
                    item.get("id", ""),
                    item.get("title", ""),
                    item.get("category", ""),
                    road,
                    parcel,
                ]
            )
            features.append(feature)
        layer = QgsVectorLayer("Point?crs=EPSG:5179", "Vworld Search Results", "memory")
        provider = layer.dataProvider()
        provider.addAttributes(
            [
                QgsField("ID", QVariant.String),
                QgsField("Title", QVariant.String),
                QgsField("Category", QVariant.String),
                QgsField("Road", QVariant.String),
                QgsField("Parcel", QVariant.String),
            ]
        )
        layer.updateFields()
        provider.addFeatures(features)

        QgsProject.instance().addMapLayer(layer)
        iface.messageBar().pushMessage(
            "Vworld Search", f"Added {len(all_items)} items to QGIS.", level=1
        )
    except KeyError as e:
        print(f"KeyError: {e} - Check the response data structure.")

 

이제 남은 코드는 아래와 같습니다.

api_key = "3A99F800-2E60-3397-9F0E-2746BC4169F6"
query = "국립공원"

bbox = get_bbox_epsg5179()
page = 1
page_size = 1000
all_items = []

while True:
    data = fetch_vworld_data(api_key, bbox, query, page, page_size)
    if data and "response" in data and "result" in data["response"]:
        items = data["response"]["result"]["items"]
        all_items.extend(items)

        total_results = int(data["response"]["record"]["total"])
        print(
            f"Total Results: {total_results}, Current Page: {page}, Items Retrieved: {len(items)}"
        )
        if len(items) < page_size:
            break
        page += 1
    else:
        print("Failed to fetch or parse data.")
        break

# 수집한 데이터를 QGIS에 추가
add_data_to_qgis_layer(all_items)

 

query 변수는 대화 상자의 텍스트 입력으로 변경해 줍니다.

query = "국립공원"
query = self.dlg.lineEdit.text()

 

geoquery.py의 run() 메서드는 "확인" 버튼을 클릭했을 때 코드를 추가할 수 있도록 빈 조건문( if result: ... )을 제공하고 있습니다.

 def run(self):
        """Run method that performs all the real work"""

        # Create the dialog with elements (after translation) and keep reference
        # Only create GUI ONCE in callback, so that it will only load when the plugin is started
        if self.first_start == True:
            self.first_start = False
            self.dlg = GeoQueryPluginDialog()

        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # See if OK was pressed
        if result:
            # Do something useful here - delete the line containing pass and
            # substitute with your code.
            pass

 

여기에 query 변수를 변경한 남은 코드를 추가해 줍니다.

 if result:
            api_key = "3A99F800-2E60-3397-9F0E-2746BC4169F6"
            query = self.dlg.lineEdit.text()

            bbox = get_bbox_epsg5179()
            page = 1
            page_size = 1000
            all_items = []

            while True:
                data = fetch_vworld_data(api_key, bbox, query, page, page_size)
                if data and "response" in data and "result" in data["response"]:
                    items = data["response"]["result"]["items"]
                    all_items.extend(items)

                    total_results = int(data["response"]["record"]["total"])
                    print(
                        f"Total Results: {total_results}, Current Page: {page}, Items Retrieved: {len(items)}"
                    )
                    if len(items) < page_size:
                        break
                    page += 1
                else:
                    print("Failed to fetch or parse data.")
                    break

            # 수집한 데이터를 QGIS에 추가
            add_data_to_qgis_layer(all_items)

핵심 로직 구현 확인

이제 OpenStreetMap에서 북한산국립공원 영역을 선택한 후, "GeoQuery for QGIS" 플러그인을 실행하겠습니다. "국립공원"을 검색한 뒤 확인을 클릭합니다!

 

아래와 같이 124개의 지점이 QGIS 레이어에 자동으로 추가되었습니다! 다음 글에서는 마지막으로 아이콘 변경을 위한 리소스 파일 컴파일 방법을 소개하겠습니다.