Google Maps API 를 활용한 C++ REST 예제

개요

Google Maps APIStatic Map을 이용, 지도이미지를 온라인으로 다운 받아 출력하는 예제입니다.

사용법은 구글지도를 웹브라우저에서 사용하는 방식과 유사합니다.

이전에 작성된 게시물 중 Python으로 작성된 예제와 동일하며, C++로 만들어 보았습니다.

동작 방식은 아래의 동영상과 캡쳐이미지를 참조 바랍니다.


완성본 앱 이미지


Google Static Map

Google Static Map API는 구글지도 서비스를 이용할 수 있는 API 함수를 제공하는 것을 말합니다.

웹브라우저, 구글앱으로 구글지도를 사용하는 것은 무료지만, 지도 API를 내 앱에서 활용하는 본 예제같은 서비스는 유료이며 사전에 GCP(Google Cloud Platform)에 해당 서비스를 신청하고 결제 정보를 연동해 두어야 합니다.

GCP (링크)에서 제공되는 방대한 서비스(A.I, DB, 머신대여, 스토리지, 지도 등)  중 내게 필요한 서비스를 신청 후, API Key를 받을 수 있으며, 해당 키가 있어야만 API 호출이 가능합니다.

아래는 구글의 소개자료 입니다. 

보다 자세한 내용은 구글 소개자료 링크를 참조 바랍니다.

Google Developer Guide

비용은 대략 1000번의 이미지 호출당 US 2$ 정도의 비용이 발생합니다.

더 많이 사용하는 경우는 약간 저렴하고 매우 많이 사용하는 경우는 별도의 비용으로 책정한다고 합니다.

Pricing 정책

처음 가입하면 무료 크래딧이 있으므로 공부목적의 사용빈도는 비용이 발생되지 않습니다.

(아마도 USD 300 불로 기억됩니다.)

아래는 제게 청구된 GCP 월간비용 보고서 입니다. 아직 무료 크래딧이 많이 남아있습니다.

GCP 비용보고서


개발 환경

  • MS Windows 11 pro 

  • Qt 6.7.1 MSVC Compiler, Qt Creator 13.0.1, C++17, CMake

  • MS cpprestsdk with vcpkg


소스코드

vcpkg에서 Microsoft cpprestsdk 를 미리 설치 필요. (링크 참조)

아래와 같이 Qt Project 생성.

Qt Creator >> New Project >> Qt Widgets App >> Build system(CMake) >> Base class (QWidget) >> with form.

CMakeLists.txt, main.cpp, widget.h, widget.cpp, widget.ui 파일 총5개로 구성.

(파일들은 같은 경로에 구성)


1. CMakeLists.txt

CMake Build 파일이며, 맨 아래 2줄에 MS의 Rest API 오픈소스 프로젝트인 cpprestsdk 를 연결하는 코드가 존재합니다.

이 파일은 따로 복사하지 말고 Qt 프로젝트 생성시 CMake 빌드 시스템을 선택하면 자동 생성됩니다.

프로젝트속성에서 "CMAKE_TOOLCHAIN_FILE" 설정 후, 가장 아래 2줄(72,73)만 복사해서 붙여넣기 바랍니다.

cmake_minimum_required(VERSION 3.5)

project(GoogleMap VERSION 0.1 LANGUAGES CXX)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)

set(PROJECT_SOURCES
        main.cpp
        widget.cpp
        widget.h
        widget.ui
)

if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    qt_add_executable(GoogleMap
        MANUAL_FINALIZATION
        ${PROJECT_SOURCES}
    )
# Define target properties for Android with Qt 6 as:
#    set_property(TARGET GoogleMap APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
#                 ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
    if(ANDROID)
        add_library(GoogleMap SHARED
            ${PROJECT_SOURCES}
        )
# Define properties for Android with Qt 5 after find_package() calls as:
#    set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
    else()
        add_executable(GoogleMap
            ${PROJECT_SOURCES}
        )
    endif()
endif()

target_link_libraries(GoogleMap PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
  set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.GoogleMap)
endif()
set_target_properties(GoogleMap PROPERTIES
    ${BUNDLE_ID_OPTION}
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

include(GNUInstallDirs)
install(TARGETS GoogleMap
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

if(QT_VERSION_MAJOR EQUAL 6)
    qt_finalize_executable(GoogleMap)
endif()

find_package(cpprestsdk REQUIRED)
target_link_libraries(GoogleMap PRIVATE cpprestsdk::cpprest cpprestsdk::cpprestsdk_zlib_internal cpprestsdk::cpprestsdk_brotli_internal)


2. main.cpp

앱 진입점이며, Qt의 QApplication, QWidget class를 이용해 GUI를 구성.

8번 라인 Widget w 가 실행되는 순간 widget.h,cpp에 구현된 Wiget class의 생성자함수가 호출되며 앱이 시작.

#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}


3. widget.h

QWidget에서 상속받은 Widget Class.

앱의 디자인 파일인 ui 포인터, 버튼을 누르면 실행할 Callback onClickGo() 함수로 간단히 구현된 모습.

Qt는 Callback 구조를 Signal, Slot이라 표현합니다.

  • Signal : 행위 (버튼 클릭 같은것)

  • Slot : Signal이 발생될 때 호출되는 함수

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;

private slots:
    void onClickGo();

};
#endif // WIDGET_H


4. widget.cpp

Widget 클래스의 구현부, 대부분 동작이 이곳에 구현.

[12~28] 라인

생성자 함수, 지도 줌 설정, 지도 종료 설정 초기화.

connect() 함수를 통해 QPushButton의 Click시 onClickGo() Callback 연결.

 

[35~79]  라인

지도를 검색 버튼을 누르면 호출되는 슬롯 함수.

Google Static Map Server로 보낼 Request 문자열 작성.

(위치, 지도 종류, 크기, API KEY...)

Rest Client 객체 및 Query Builder 생성.

45번 라인에 Your API Key를 입력.

비동기 방식으로 query를 전송하고 Response를 수신.

수신된 지도 이미지를 vector<unsigned char> 버퍼에 저장하고 QPixmap으로 이미지 생성.

해당 이미지는 1280x1280 해상도이므로 화면의 QLabel에 맞춰 Resizing 후 출력.

#include "widget.h"
#include "./ui_widget.h"
#include <cpprest/http_client.h>
#include <vector>
#include <QPixmap>

using namespace utility;
using namespace web;
using namespace web::http;
using namespace web::http::client;

Widget::Widget(QWidget *parent)
    : QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("Ocean Coding School");
    //zoom
    for(int i=1; i<=20; i++)
        ui->cb1->addItem(QString::number(i));
    ui->cb1->setCurrentIndex(14);

    //map type
    QString s("roadmap,terrain,satellite,hybrid");
    ui->cb2->addItems(QStringList(s.split(',')));

    //signal
    connect(ui->pb, &QPushButton::clicked, this, &Widget::onClickGo);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::onClickGo()
{
    QString place = ui->le->text();
    QString zoom = ui->cb1->currentText();
    QString type = ui->cb2->currentText();

    QString PLACE   = QString("center=%1").arg(place);
    QString ZOOM    = QString("&zoom=%1").arg(zoom);
    QString SIZE    = "&size=640x640&scale=2";
    QString TYPE    = QString("&maptype=%1").arg(type);
    QString KEY     = QString("&key=%1").arg("YOUR API KEY HERE");

    QString MSG     = PLACE + ZOOM + SIZE + TYPE + KEY;

    http_client client(U("https://maps.googleapis.com"));    
    uri_builder builder(U("/maps/api/staticmap?"));
    string_t s(MSG.toStdWString().c_str());
    builder.append_query(s, true);

    //sync request
    // auto response = client.request(methods::GET, builder.to_string()).get();
    // qDebug() << "Sync Code: " << response.status_code();
    // qDebug() << "Sync Type: " << response.headers().content_type();

    // async request
    client.request(methods::GET, builder.to_string()).then([=](http_response response)
    {
        auto code = response.status_code();
        auto type = response.headers().content_type();

        qDebug() << "Async Code: " << code;
        qDebug() << "Async Type: " << type;

        response.extract_vector().then([=](std::vector<unsigned char> v) {
            QPixmap img;
            img.loadFromData( v.data(), v.size()  );
            // resizing to fit the QLaabel
            int w = ui->lb->width();
            int h = ui->lb->height();
            img = img.scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation);
            // draw pixmap
            ui->lb->setPixmap(img);
        }).wait();
    }).wait();
}


5. widget.ui

XML 형태의 위젯 디자인 파일이며 링크를 첨부합니다.

widget.ui 링크

다운로드해서 다른 파일들과 같은 경로에 두면 됩니다.

 

마무리

지도서비스는 Naver, Kakao 등도 제공하지만 국내에 한정되어 불편합니다.

Google Maps API는 국내외 모두 가능하며, 위경도 좌표를 입력해도 사용가능합니다.

마지막으로 요즘의 C++은 프로그래머가 직접 std::thread를 생성, 관리, 사용하기 보다는 안전, 편의를 위해 이를 지원하는 비동기객체 사용을 보다 선호하는것 같습니다.

백악관에서 C++의 사용을 중단을 권고한 것도 같은 맥락인데, 성능적인 측면이나 임베디드 분야를 고려하지 않은 발언이라 생각합니다. 


참고자료

동기와 비동기 참조

MS cpprestsdk Github Link


이상으로 모든 설명을 마칩니다. 감사합니다.

댓글

이 블로그의 인기 게시물

Qt Designer 설치하기

PyQt5 기반 동영상 플레이어앱 만들기