GDAL 항공 정사영상 타일의 ReactJS 및 Google Maps 기반 웹 시각화

GDAL 항공 정사영상 타일의 ReactJS 및 Google Maps 기반 웹 시각화

1. GDAL 타일과 React 기반 웹 매핑의 통합

1.1 연구 목적 및 범위

본 안내서는 GDAL(Geospatial Data Abstraction Library)을 사용하여 생성된 항공 정사영상 타일을 ReactJS 프레임워크와 Google Maps Platform을 통해 웹에 표출하는 전 과정을 기술적 깊이를 갖추어 상세히 설명하는 것을 목적으로 한다. 현대 지리정보 시스템(GIS) 및 웹 개발의 융합은 고해상도 공간 데이터를 사용자 친화적인 인터페이스로 제공하는 능력을 요구한다. 이러한 요구에 부응하기 위해, 본 안내서는 GDAL 타일 생성의 근본 원리 이해부터 시작하여, 최신 React 개발 환경 설정, Google Maps JavaScript API와의 정교한 연동, 그리고 최종적으로 구축된 웹 애플리케이션의 성능 최적화 및 고급 기능 구현에 이르기까지 포괄적인 기술 지침을 제공한다. 이 연구는 단순한 구현 가이드를 넘어, 각 기술 선택의 배경과 그에 따른 기술적 상충 관계(trade-off)를 분석함으로써, 개발자가 보다 견고하고 확장 가능한 시스템을 설계할 수 있는 이론적 토대를 마련하는 데 중점을 둔다.

1.2 기술 스택 개요

본 안내서에서 다루는 기술 스택은 데이터 처리, 프론트엔드 개발, 지도 시각화라는 세 가지 핵심 영역으로 구성되며, 각 영역에서 산업 표준으로 인정받는 기술들을 채택한다.

  • 데이터 처리 (Data Processing): 대용량 래스터 데이터를 처리하고 웹 환경에 최적화된 타일셋으로 변환하기 위해 GDAL, 특히 gdal2tiles.py 스크립트를 활용한다.1 GDAL은 지리 공간 데이터 변환 및 처리를 위한 가장 강력하고 널리 사용되는 오픈소스 라이브러리로, 본 안내서에서는 이 도구의 핵심 옵션들을 심층적으로 분석하여 웹 매핑 표준에 부합하는 결과물을 생성하는 방법을 탐구한다.
  • 프론트엔드 (Frontend): 사용자 인터페이스(UI) 구축을 위해 ReactJS를 채택한다.3 React는 컴포넌트 기반 아키텍처와 선언적 UI 패러다임을 통해 복잡한 웹 애플리케이션을 효율적으로 개발하고 유지보수할 수 있게 하는 현대적인 JavaScript 라이브러리다. 상태 관리와 생명주기(lifecycle) 제어를 통해 동적인 지도 상호작용을 구현하는 데 최적의 환경을 제공한다.
  • 지도 시각화 (Map Visualization): 기본 지도 레이어와 커스텀 오버레이 기능을 위해 Google Maps Platform JavaScript API를 사용한다. React 환경에서 이 API를 효과적으로 활용하기 위해 @vis.gl/react-google-maps 라이브러리를 채택한다.5 이 라이브러리는 Google이 공식적으로 후원하는 최신 솔루션으로, 과거에 널리 사용되었으나 현재는 유지보수가 중단된 라이브러리들의 단점을 극복하고, 현대적인 Hooks 기반 API를 제공하여 안정성과 최신 기능 지원 측면에서 가장 신뢰할 수 있는 선택지이다.7

1.3 최종 결과물 아키텍처

본 안내서에서 구축할 시스템의 전체 아키텍처는 세 가지 주요 구성 요소 간의 상호작용을 기반으로 설계된다.

  1. 정적 타일 서버 (Static Tile Server): gdal2tiles.py를 통해 생성된 항공 정사영상 타일셋({z}/{x}/{y}.png 구조)이 호스팅되는 서버이다. 이는 Amazon S3, Google Cloud Storage와 같은 클라우드 객체 스토리지 또는 간단한 웹 서버가 될 수 있다. 이 서버의 역할은 HTTP 요청에 따라 특정 타일 이미지를 제공하는 것이다.
  2. React 애플리케이션 (Client-Side Application): 사용자의 웹 브라우저에서 실행되는 단일 페이지 애플리케이션(SPA)이다. @vis.gl/react-google-maps 라이브러리를 사용하여 Google 지도를 렌더링하고, 사용자의 지도 조작(패닝, 줌)을 감지한다.
  3. Google Maps Platform: 전 세계의 도로, 지명 등이 포함된 고품질의 기본 지도(basemap)를 제공한다.

이 아키텍처의 데이터 흐름은 다음과 같다. 먼저, React 애플리케이션이 로드되면 Google Maps Platform으로부터 기본 지도 레이어를 받아온다. 그 후, 애플리케이션은 Google Maps API의 ImageMapType 클래스를 사용하여 커스텀 타일 레이어를 생성하고 이를 지도 위에 오버레이로 추가한다. 사용자가 지도를 패닝하거나 줌 레벨을 변경하면, Google 지도 API는 현재 뷰포트에 필요한 타일들의 좌표(z, x, y)를 계산하여 ImageMapType에 정의된 getTileUrl 함수를 호출한다. 이 함수는 계산된 좌표를 바탕으로 정적 타일 서버에 저장된 이미지의 URL을 동적으로 생성하여 반환한다. 브라우저는 이 URL을 통해 타일 서버에 이미지를 요청하고, 응답받은 이미지들을 기본 지도 위에 매끄럽게 렌더링하여 사용자에게 항공 정사영상이 중첩된 지도를 보여준다.

2. 항공 정사영상 타일링: GDAL 심층 분석

2.1 gdal2tiles.py 명령어 해부

gdal2tiles.py는 단일 래스터 파일을 웹 지도 타일 피라미드로 변환하는 강력한 유틸리티이다. 이 도구의 효과적인 사용은 다양한 옵션에 대한 깊은 이해를 바탕으로 한다.

  • 기본 구조: 명령어의 기본 형식은 gdal2tiles.py [options] <input_file> [<output_directory>]이다.1

<input_file>은 변환할 원본 GeoTIFF와 같은 래스터 파일이며, <output_directory>는 타일이 저장될 디렉토리이다.

  • 핵심 옵션 --profile: 이 옵션은 타일을 생성하는 방식을 결정하는 가장 중요한 설정 중 하나이다. 여러 프로파일이 존재하지만, Google Maps와의 호환성을 위해서는 mercator 프로파일을 사용하는 것이 필수적이다.8

  • mercator: 이 프로파일은 원본 래스터 데이터를 EPSG:3857, 즉 웹 메르카토르 투영법으로 변환하여 타일을 생성한다. 이는 단순히 좌표계를 바꾸는 것을 넘어, Google Maps, OpenStreetMap 등 대부분의 전 세계 웹 지도 서비스가 채택한 타일 그리드 체계에 정확히 정렬되도록 보장한다. 이 프로파일을 선택하는 것은 기술적 세부사항을 넘어, 웹 매핑의 역사적 표준에 시스템을 정렬하는 전략적 결정이다. Google Maps가 2005년 웹 메르카토르 투영법을 채택하면서 거대한 생태계가 형성되었고 10, 이 프로파일을 사용하는 것은 해당 생태계와의 완벽한 호환성을 확보하는 가장 확실한 방법이다.

  • geodetic: 이 프로파일은 데이터를 EPSG:4326(WGS84) 좌표계 그대로 유지하며 타일을 생성한다. 이는 Google Earth와 같은 특정 GIS 애플리케이션에서는 유용할 수 있으나, 웹 메르카토르 기반의 일반적인 웹 지도 위에 중첩할 경우 좌표 불일치로 인해 정확한 위치에 표시되지 않는다.

  • raster: 이 프로파일은 원본 래스터의 좌표계와 픽셀 그리드를 그대로 유지한 채 타일을 생성한다. 이는 원본 데이터와의 픽셀 단위 정합성이 매우 중요하고, 다른 지도와의 중첩이 필요 없는 독립적인 뷰어에서 사용할 때 유용하다. 그러나 전 지구적 웹 지도와의 상호운용성 측면에서는 심각한 한계를 가진다.12

  • 타일 체계 옵션 --xyz: 이 옵션은 생성되는 타일의 디렉토리 구조 및 명명 규칙을 Google Maps 표준에 맞추는 데 결정적인 역할을 한다. GDAL 버전 3.1부터 추가된 이 기능은 2,

gdal2tiles의 기본 동작인 TMS(Tile Map Service) 체계 대신 XYZ(Slippy Map) 체계를 사용하도록 강제한다.2 Google Maps를 포함한 대부분의 현대 웹 매핑 라이브러리는 XYZ 체계를 기대하므로, 이 옵션을 명시적으로 사용하는 것이 강력히 권장된다.

2.2 타일 체계의 이해: TMS 대 XYZ

TMS와 XYZ는 웹 지도 타일의 좌표를 정의하는 두 가지 주요 표준이다. 두 체계는 거의 동일하지만, Y축 좌표를 계산하는 방식에서 결정적인 차이가 존재한다.

  • 근본적 차이: 두 체계의 유일한 차이점은 Y축 좌표의 원점(y=0) 위치이다. OSGeo에서 정의한 TMS 표준은 지도의 남서쪽(bottom-left) 모서리를 좌표 원점 (0,0)으로 간주한다. 따라서 Y 값은 남쪽에서 북쪽으로 갈수록 증가한다.2 반면, Google Maps가 대중화시킨 XYZ 체계는 지도의 북서쪽(top-left) 모서리를 원점

(0,0)으로 정의하며, Y 값은 북쪽에서 남쪽으로 갈수록 증가한다.13

  • 문제점 및 해결책: gdal2tiles는 역사적인 이유로 TMS 체계를 기본값으로 사용한다. 만약 --xyz 옵션 없이 타일을 생성하고 이를 Google Maps에 직접 로드하면, Y축이 완전히 뒤집혀서 지도가 상하 반전되어 나타나는 심각한 오류가 발생한다. 이 문제를 해결하는 유일하고 올바른 방법은 타일 생성 시점에 --xyz 플래그를 추가하여 처음부터 Google Maps가 기대하는 좌표 체계로 타일을 생성하는 것이다.

  • 수학적 변환: 두 체계 간의 Y좌표 변환은 주어진 줌 레벨(z)에서 간단한 수학 공식을 통해 이루어진다. 줌 레벨 z에서 한 축의 타일 개수는 2^z개이다. 이를 바탕으로 한 변환 공식은 다음과 같다.16
    y_{\text{xyz}} = (2^z - 1) - y_{\text{tms}}
    이 공식은 디버깅이나 기존 TMS 타일셋을 XYZ로 변환해야 할 때 유용하게 사용될 수 있다.

2.3 좌표계의 중요성: WGS84와 웹 메르카토르

좌표 참조 시스템(Coordinate Reference System, CRS)은 지구상의 위치를 숫자로 표현하는 방법을 정의한다. 웹 매핑에서는 주로 두 가지 CRS가 핵심적인 역할을 한다.

  • 지리 좌표계 (Geographic CRS): EPSG:4326 (WGS 84)

WGS 84는 지구를 완벽한 구가 아닌, 회전 타원체(ellipsoid)로 모델링하고, 이 3차원 표면 위의 위치를 위도(latitude)와 경도(longitude)라는 각도 단위(degree)로 표현하는 체계이다.19 이는 GPS 시스템의 기본 표준이며, 전 세계적인 위치 데이터를 저장, 교환, 분석하는 데 가장 널리 사용되는 기준 좌표계이다.21

  • 투영 좌표계 (Projected CRS): EPSG:3857 (WGS 84 / Pseudo-Mercator)

웹 지도는 평면적인 2차원 화면에 표시되어야 하므로, 3차원 타원체 상의 위경도 좌표를 2차원 평면으로 변환하는 과정, 즉 ’투영(projection)’이 필요하다. 웹 메르카토르는 이러한 투영 방법 중 하나로, 웹 지도 시각화에 특화되어 있다. 이 좌표계의 단위는 미터(meter)이며, 적도 부근에서는 왜곡이 적지만 극지방으로 갈수록 면적이 기하급수적으로 팽창하여 심각하게 왜곡되는 특징을 가진다.22 예를 들어, 메르카토르 지도에서 그린란드는 아프리카 대륙만큼 커 보이지만 실제 면적은 약 14분의 1에 불과하다.

  • “가짜(Pseudo)” 메르카토르의 기술적 타협

웹 메르카토르의 공식 명칭에 “Pseudo(가짜)“라는 단어가 붙는 이유는 그것이 수학적으로 엄밀한 메르카토르 투영이 아니기 때문이다. 웹 메르카토르는 WGS 84 타원체에 정의된 위경도 좌표를 입력값으로 사용하지만, 실제 투영 계산은 계산상의 편의성과 웹 브라우저의 렌더링 성능을 극대화하기 위해 지름이 WGS 84 타원체의 장반경과 같은 완벽한 구(sphere)를 가정하고 수행한다.10 이 기술적 타협으로 인해, 각도를 완벽하게 보존하는 정각성(conformality)이 미세하게 깨지게 된다.25 그러나 이러한 왜곡은 일반적인 웹 지도 스케일에서는 시각적으로 거의 인지할 수 없는 수준이며, 대신 복잡한 타원체 계산을 피함으로써 얻는 막대한 계산 속도 이점은 웹 환경에서 매우 중요하다. 사용자는 gdal2tilesmercator 프로파일이 원본 데이터를 변환할 때, 이러한 웹 환경에 최적화된 “의도된 부정확성“을 포함한 표준을 따르게 된다는 점을 인지해야 한다.

다음 표는 TMS와 XYZ 타일 좌표계의 핵심적인 차이점을 요약한다.

속성 (Attribute)TMS (Tile Map Service)XYZ (Slippy Map / Google)
Y축 원점 (Y-axis Origin)남서쪽 (Bottom-Left)북서쪽 (Top-Left)
Y 좌표 방향 (Y-coordinate Direction)남쪽 → 북쪽으로 증가북쪽 → 남쪽으로 증가
주요 사용처 (Primary Use)OSGeo 표준Google Maps, OpenStreetMap, Leaflet 등
gdal2tiles 옵션기본값 (Default)--xyz
변환 공식 (Conversion Formula)y_{\text{tms}} = (2^z - 1) - y_{\text{xyz}}y_{\text{xyz}} = (2^z - 1) - y_{\text{tms}}

3. ReactJS 및 Google Maps 환경 설정

3.1 프로젝트 초기화 및 라이브러리 설치

현대적인 웹 매핑 애플리케이션을 구축하기 위한 첫 단계는 견고한 개발 환경을 설정하는 것이다.

  • 프로젝트 생성: 최신 JavaScript 개발 동향에 맞춰, 빠른 빌드 속도와 간결한 설정을 제공하는 Vite를 사용하여 React 프로젝트를 생성한다. 터미널에서 다음 명령어를 실행하여 프로젝트를 초기화할 수 있다.4
npm create vite@latest my-map-app -- --template react
  • 라이브러리 설치: 생성된 프로젝트 디렉토리로 이동한 후, Google Maps와 React를 연결해 줄 핵심 라이브러리를 설치한다.
cd my-map-app
npm install @vis.gl/react-google-maps

이 명령어는 @vis.gl/react-google-maps 패키지를 프로젝트의 의존성으로 추가한다.5

  • 올바른 라이브러리 선택의 중요성: React와 Google Maps를 연동하는 라이브러리는 여러 가지가 존재한다. 과거에는 react-google-maps@react-google-maps/api와 같은 라이브러리가 널리 사용되었으나, 일부는 현재 공식적인 유지보수가 중단된 상태이다.3 반면,

@vis.gl/react-google-maps는 Google의 공식적인 후원을 받아 vis.gl 커뮤니티에서 활발하게 개발되고 있는 최신 라이브러리이다.7 이 라이브러리는 React의 최신 기능인 Hooks를 기반으로 설계되어 있어, 함수형 컴포넌트 내에서 직관적이고 효율적으로 지도 상태를 관리할 수 있게 해준다. 따라서 신규 프로젝트에서는 잠재적인 기술 부채를 피하고 안정적인 유지보수를 보장받기 위해 @vis.gl/react-google-maps를 선택하는 것이 기술적으로 가장 타당하다.

3.2 Google Maps Platform API 키 발급 및 보안

Google Maps Platform의 서비스를 이용하기 위해서는 인증을 위한 API 키가 필수적이다.

  • API 키 생성:
  1. Google Cloud Platform (GCP) 콘솔에 접속하여 새 프로젝트를 생성하거나 기존 프로젝트를 선택한다.
  2. ‘API 및 서비스’ 대시보드로 이동하여 ’API 및 서비스 사용 설정’을 클릭한다.
  3. ’Maps JavaScript API’를 검색하여 활성화한다.4
  4. ’API 및 서비스’의 ‘사용자 인증 정보’ 페이지로 이동하여 ‘사용자 인증 정보 만들기’ > ’API 키’를 선택하여 새로운 키를 생성한다.29
  • API 키 보안: 생성된 API 키는 과금을 유발할 수 있는 중요한 정보이므로, 소스 코드에 직접 노출하는 것은 심각한 보안 위협이 될 수 있다. 따라서 환경 변수를 사용하여 안전하게 관리해야 한다.
  1. React 프로젝트의 루트 디렉토리에 .env 파일을 생성한다.

  2. 파일 내에 다음과 같이 환경 변수를 정의한다. Vite는 VITE_ 접두사를 가진 환경 변수만 클라이언트 측 코드에 노출하므로, 반드시 이 규칙을 따라야 한다.5

VITE_GOOGLE_MAPS_API_KEY="YOUR_API_KEY_HERE"

YOUR_API_KEY_HERE 부분을 발급받은 실제 API 키로 대체한다.

  1. .gitignore 파일에 .env가 포함되어 있는지 확인하여, 민감한 정보가 Git 저장소에 커밋되지 않도록 한다.
  • API 키 제한 설정: 보안을 더욱 강화하기 위해, GCP 콘솔에서 API 키에 제한을 설정하는 것이 좋다. ‘HTTP 리퍼러(웹사이트)’ 제한을 선택하고, 애플리케이션이 배포될 도메인(예: your-domain.com/*)과 개발 환경의 주소(예: localhost:5173/*)를 추가한다.30 이렇게 하면 지정된 웹사이트에서 발생하는 요청에 대해서만 API 키가 유효하게 되어, 키가 유출되더라도 무단 사용을 방지할 수 있다.

3.3 기본 지도 렌더링

API 키 설정이 완료되면, React 컴포넌트 내에서 기본 지도를 렌더링할 수 있다.

  • APIProvider 설정: @vis.gl/react-google-maps 라이브러리는 APIProvider라는 컨텍스트 제공 컴포넌트를 통해 Google Maps JavaScript API 스크립트를 로드하고 관리한다. 애플리케이션의 최상위 컴포넌트(일반적으로 src/App.jsx 또는 src/main.jsx)를 APIProvider로 감싸고, apiKey prop에 환경 변수를 통해 불러온 API 키를 전달한다.5
// src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { APIProvider } from '@vis.gl/react-google-maps';

ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<APIProvider apiKey={import.meta.env.VITE_GOOGLE_MAPS_API_KEY}>
<App />
</APIProvider>
</React.StrictMode>
);
  • Map 컴포넌트 렌더링: 이제 App 컴포넌트나 그 하위 컴포넌트 어디에서든 Map 컴포넌트를 사용하여 지도를 렌더링할 수 있다. 지도의 초기 상태를 정의하기 위해 defaultCenterdefaultZoom prop을 사용한다.5
// src/App.jsx
import { Map } from '@vis.gl/react-google-maps';

function App() {
const position = { lat: 37.5665, lng: 126.9780 }; // 서울 중심 좌표

return (
<div style={{ height: "100vh", width: "100%" }}>
<Map
defaultZoom={10}
defaultCenter={position}
mapId="YOUR_MAP_ID" // 선택 사항: Cloud-based Maps Styling 사용 시
/>
</div>
);
}

export default App;

여기서 중요한 점은, Google 지도는 내용물의 크기에 따라 자동으로 크기가 조절되지 않는다는 것이다. 따라서 지도를 담고 있는 부모 <div> 요소에 heightwidth를 명시적으로 지정해야만 지도가 화면에 정상적으로 나타난다.31 위 예시에서는 100vh(뷰포트 높이의 100%)와 100%를 사용하여 전체 화면을 채우는 지도를 만들었다.

4. 커스텀 타일 오버레이 구현

4.1 google.maps.ImageMapType의 역할과 구조

google.maps.ImageMapType 클래스는 Google Maps JavaScript API에서 사용자 정의 래스터 타일셋을 지도 위에 중첩(overlay)시키기 위해 제공되는 핵심 인터페이스이다.32 이 클래스를 사용하면 GDAL로 생성한 정사영상 타일과 같이 외부에서 제공되는 이미지들을 Google의 기본 지도 위에 하나의 레이어처럼 표시할 수 있다.

ImageMapType의 인스턴스를 생성할 때는 ImageMapTypeOptions라는 설정 객체를 전달해야 한다. 이 객체는 커스텀 타일 레이어의 동작 방식을 정의하는 여러 속성을 포함한다.32

  • getTileUrl: 가장 핵심적인 속성으로, 함수를 값으로 가진다. 지도 API가 특정 위치의 타일을 필요로 할 때마다 이 함수를 호출한다. 함수는 타일의 좌표(coord)와 줌 레벨(zoom)을 인자로 받아, 해당 타일 이미지의 전체 URL을 문자열로 반환해야 한다.
  • tileSize: 타일 하나의 크기를 픽셀 단위로 지정하는 google.maps.Size 객체이다. 웹 매핑의 표준 타일 크기는 256x256 픽셀이므로, 일반적으로 new google.maps.Size(256, 256)으로 설정한다.
  • minZoommaxZoom: 이 오버레이 레이어가 표시될 최소 및 최대 줌 레벨을 지정한다. GDAL로 타일을 생성할 때 지정한 줌 레벨 범위와 일치시키는 것이 일반적이다.
  • name: 지도 유형 컨트롤러에 표시될 레이어의 이름을 지정한다.
  • opacity: 타일 레이어의 불투명도를 0.0(완전 투명)에서 1.0(완전 불투명) 사이의 값으로 설정한다.

4.2 핵심 함수 getTileUrl 설계 및 구현

getTileUrl 함수는 지도 API와 우리가 호스팅하는 타일 서버를 연결하는 다리 역할을 한다. 이 함수의 구현은 GDAL이 생성한 타일의 디렉토리 구조 및 URL 패턴과 정확히 일치해야 한다.

gdal2tiles--xyz 옵션과 함께 실행하면, 출력 디렉토리 내에 {z}/{x}/{y}.png 형식으로 타일이 저장된다. getTileUrl 함수는 지도 API로부터 받은 zoom, coord.x, coord.y 값을 이 패턴에 맞춰 조합하여 완전한 URL을 생성해야 한다.30

다음은 로컬 개발 서버(http://localhost:8080)에 tiles라는 디렉토리 아래에 타일이 호스팅되어 있다고 가정한 getTileUrl 함수의 구체적인 구현 예시이다.30

function getTileUrl(coord, zoom) {
// 지도 경계를 벗어나는 타일에 대한 요청일 경우 coord가 null일 수 있다.
// 이 경우 유효하지 않은 요청을 보내지 않도록 null을 반환한다.
if (!coord) {
return null;
}

// 타일 서버의 기본 URL과 타일 좌표를 조합한다.
// 이 URL은 로컬 정적 서버 또는 프로덕션 CDN의 경로가 될 수 있다.
const tileUrl = `http://localhost:8080/tiles/${zoom}/${coord.x}/${coord.y}.png`;

return tileUrl;
}

이 함수는 매우 효율적으로 동작해야 한다. 사용자가 지도를 패닝하거나 줌할 때마다 수십 번씩 호출될 수 있기 때문이다. 따라서 함수 내에서는 복잡한 계산이나 동기적인 네트워크 요청을 피하고, 문자열 조합과 같은 가벼운 작업만 수행하는 것이 성능에 유리하다.

4.3 React 생명주기와 Hooks를 활용한 오버레이 적용

Google Maps API의 ImageMapType과 같은 객체는 map.overlayMapTypes.push()와 같이 지도 인스턴스를 직접 조작하는 명령형(imperative) API 스타일을 따른다. 이를 컴포넌트의 상태에 따라 UI가 결정되는 선언형(declarative) 패러다임의 React에 올바르게 통합하는 것은 이 프로젝트의 핵심적인 기술 과제이다. 이 둘 사이의 간극을 메우기 위해 React Hooks를 활용하는 것이 최적의 아키텍처 패턴이다.

이러한 통합 로직을 재사용 가능한 커스텀 컴포넌트(예: <GdalTileLayer>)로 캡슐화하면, 복잡한 내부 구현을 숨기고 다른 개발자들이 선언적으로 쉽게 사용할 수 있게 된다.

  1. useMap() 훅으로 지도 인스턴스 접근: @vis.gl/react-google-maps 라이브러리가 제공하는 useMap() 훅을 사용하면, 현재 렌더링 컨텍스트에 있는 google.maps.Map 인스턴스에 안전하게 접근할 수 있다.6 이 훅은 맵이 초기화되기 전에는

null을 반환하고, 초기화가 완료되면 맵 객체를 반환한다.

  1. useEffect 훅으로 부수 효과 관리: ImageMapType을 생성하고 지도에 추가하는 작업은 React의 렌더링 흐름에 직접적인 영향을 주지 않는 부수 효과(side effect)이다. 이러한 작업은 useEffect 훅 내부에서 처리하는 것이 React의 원칙에 부합한다.
  • useEffect의 의존성 배열(dependency array)에 map 객체를 전달한다. 이렇게 하면, map 객체가 null에서 유효한 인스턴스로 변경될 때(즉, 지도가 처음 로드될 때) useEffect 내부의 코드가 단 한 번 실행된다.
  • useEffect 내부에서 map 객체가 유효한지 확인한 후, new google.maps.ImageMapType(...)을 사용하여 오버레이 인스턴스를 생성하고 map.overlayMapTypes.push(imageMapType)을 호출하여 지도에 추가한다.
  1. 클린업(Cleanup) 함수 구현: React 컴포넌트가 화면에서 사라질 때(unmount), 지도에 추가했던 오버레이를 제거하여 메모리 누수(memory leak)를 방지하고 예기치 않은 동작을 막아야 한다. useEffect는 함수를 반환할 수 있는데, 이 함수는 컴포넌트가 언마운트되거나 의존성이 변경되기 직전에 실행된다. 이 클린업 함수 내에서 map.overlayMapTypes.clear() 또는 특정 인덱스의 오버레이를 제거하는 로직을 구현한다.

이러한 패턴을 적용하면, 명령형 API 조작을 React의 생명주기에 안전하게 동기화하여 안정적이고 예측 가능한 코드를 작성할 수 있다.

5. 전체 통합 코드 및 실행

5.1 로컬 개발 환경을 위한 타일 서버 구축

React 애플리케이션에서 gdal2tiles로 생성된 타일들을 불러오려면, 이 타일들이 웹을 통해 접근 가능해야 한다. 로컬 개발 환경에서는 간단한 정적 파일 서버를 구동하여 이를 해결할 수 있다.

Node.js 환경에서는 servehttp-server와 같은 패키지를 사용하면 손쉽게 서버를 실행할 수 있다. npx를 사용하면 별도의 설치 과정 없이 즉시 실행이 가능하다. gdal2tiles의 결과물이 my-tiles라는 디렉토리에 저장되었다고 가정하고, 이 디렉토리에서 다음 명령어를 실행한다.

npx serve -p 8080 --cors
  • -p 8080: 서버가 8080번 포트에서 실행되도록 지정한다.
  • --cors: 이 옵션은 교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS) 헤더를 응답에 추가한다. React 개발 서버(예: localhost:5173)와 타일 서버(localhost:8080)는 서로 다른 출처(origin)를 가지므로, 이 옵션이 없으면 브라우저의 동일 출처 정책(Same-Origin Policy)에 의해 React 애플리케이션이 타일 이미지를 불러오는 요청이 차단된다. --cors 옵션은 이러한 문제를 해결하여 원활한 로컬 개발을 가능하게 하는 필수적인 설정이다.

이제 브라우저에서 http://localhost:8080/{z}/{x}/{y}.png와 같은 URL로 타일 이미지에 접근할 수 있게 된다.

5.2 최종 통합 React 컴포넌트 코드

이제 모든 조각을 하나로 합쳐 전체 애플리케이션을 구성한다. 프로젝트 구조를 논리적으로 분리하기 위해, 오버레이 로직은 GdalTileLayer.jsx 컴포넌트로, 지도 표출 로직은 MapComponent.jsx로, 그리고 이들을 조합하는 최상위 로직은 App.jsx에 배치한다.

5.2.1 GdalTileLayer.jsx (커스텀 오버레이 컴포넌트)

import { useEffect, useMemo } from 'react';
import { useMap } from '@vis.gl/react-google-maps';

// GDAL 타일 오버레이를 관리하는 재사용 가능한 컴포넌트
function GdalTileLayer({ tileUrl, minZoom, maxZoom, opacity = 1.0 }) {
const map = useMap();

// ImageMapType 인스턴스를 useMemo로 메모이제이션하여 불필요한 재생성을 방지한다.
// tileUrl, minZoom, maxZoom, opacity가 변경될 때만 재생성된다.
const imageMapType = useMemo(() => {
if (!window.google) return null;

return new window.google.maps.ImageMapType({
getTileUrl: (coord, zoom) => {
// 유효한 좌표와 줌 레벨 범위 내에서만 URL을 생성한다.
if (zoom < minZoom |

| zoom > maxZoom ||!coord) {
return null; // 범위를 벗어나면 빈 타일을 반환
}
const url = tileUrl
.replace('{z}', zoom)
.replace('{x}', coord.x)
.replace('{y}', coord.y);
return url;
},
tileSize: new window.google.maps.Size(256, 256),
minZoom: minZoom,
maxZoom: maxZoom,
opacity: opacity,
name: 'Orthophoto',
});
}, [tileUrl, minZoom, maxZoom, opacity]);

useEffect(() => {
// map 인스턴스와 imageMapType이 모두 준비되었을 때 오버레이를 추가한다.
if (!map ||!imageMapType) return;

map.overlayMapTypes.push(imageMapType);

// 컴포넌트가 언마운트될 때 실행될 클린업 함수
return () => {
// 추가했던 오버레이를 찾아서 제거한다.
const index = map.overlayMapTypes.getArray().indexOf(imageMapType);
if (index > -1) {
map.overlayMapTypes.removeAt(index);
}
};
},);

// 이 컴포넌트는 UI를 렌더링하지 않으므로 null을 반환한다.
return null;
}

export default GdalTileLayer;

5.2.2 MapComponent.jsx (지도 래퍼 컴포넌트)

import { Map } from '@vis.gl/react-google-maps';
import GdalTileLayer from './GdalTileLayer';

function MapComponent() {
const position = { lat: 37.5665, lng: 126.9780 };

// 환경 변수나 설정 파일에서 타일 정보를 가져오는 것이 좋다.
const TILE_BASE_URL = 'http://localhost:8080/{z}/{x}/{y}.png';
const MIN_ZOOM = 10;
const MAX_ZOOM = 18;

return (
<div style={{ height: '100%', width: '100%' }}>
<Map
defaultZoom={12}
defaultCenter={position}
mapId={import.meta.env.VITE_GOOGLE_MAP_ID} // 선택 사항
gestureHandling={'greedy'} // 모든 스크롤/터치 제스처 허용
>
<GdalTileLayer
tileUrl={TILE_BASE_URL}
minZoom={MIN_ZOOM}
maxZoom={MAX_ZOOM}
/>
</Map>
</div>
);
}

export default MapComponent;

5.2.3 App.jsx (애플리케이션 진입점)

import MapComponent from './MapComponent';

function App() {
return (
<div style={{ height: "100vh", width: "100%" }}>
<h1>GDAL Orthophoto on Google Maps</h1>
<div style={{ height: "90vh", width: "100%" }}>
<MapComponent />
</div>
</div>
);
}

export default App;

5.3 실행 및 검증

  1. 애플리케이션 실행: 프로젝트 루트 디렉토리에서 다음 명령어를 실행하여 React 개발 서버를 시작한다.
npm run dev
  1. 브라우저 확인: 터미널에 표시된 주소(보통 http://localhost:5173)를 웹 브라우저에서 연다. 서울을 중심으로 한 Google 지도 위에 항공 정사영상 타일이 오버레이된 것을 확인할 수 있다.

  2. 네트워크 검증: 브라우저의 개발자 도구(F12 또는 Ctrl+Shift+I)를 열고 ‘네트워크(Network)’ 탭으로 이동한다. 지도를 패닝하거나 줌 레벨을 변경할 때, http://localhost:8080/12/3221/1267.png와 같은 형식의 이미지 요청이 지속적으로 발생하는지 확인한다. 각 요청의 상태 코드가 200 OK이고, ‘미리보기(Preview)’ 탭에서 해당 타일 이미지가 정상적으로 보이는지 검증한다. 이를 통해 getTileUrl 함수가 올바르게 동작하고 타일 서버가 요청에 성공적으로 응답하고 있음을 확인할 수 있다.

6. 고급 주제 및 최적화

6.1 성능 최적화 전략

웹 매핑 애플리케이션의 성능은 사용자의 경험에 직접적인 영향을 미치며, 이는 타일 생성 단계에서부터 고려되어야 한다.

  • 타일 생성 단계 최적화: gdal2tiles.py는 결과물의 품질과 파일 크기를 제어할 수 있는 여러 옵션을 제공한다.
  • 리샘플링(--resampling): 이 옵션은 원본 래스터를 다른 줌 레벨의 타일로 축소하거나 확대할 때 사용할 보간(interpolation) 알고리즘을 지정한다.1
  • near (최근접 이웃): 가장 빠르지만 결과물이 계단 현상처럼 보일 수 있다. 분류된 래스터 데이터에 적합하다.
  • bilinear: near보다 부드러운 결과물을 생성하며 속도와 품질 사이의 적절한 균형을 제공한다.
  • cubic, lanczos: 계산 비용이 가장 높지만, 항공사진과 같이 세밀한 디테일이 중요한 연속적인 톤의 이미지에서 가장 선명하고 시각적으로 우수한 결과물을 생성한다.37 프로젝트의 요구사항에 따라 품질과 처리 시간 사이의 상충 관계를 고려하여 적절한 알고리즘을 선택해야 한다.
  • 파일 포맷(--tiledriver): 생성될 타일의 이미지 파일 포맷을 지정하여 파일 크기를 최적화할 수 있다.2
  • PNG (기본값): 알파 채널(투명도)을 지원하는 무손실 압축 포맷이다. 품질 저하가 없지만 파일 크기가 가장 크다.
  • JPEG: 손실 압축 포맷으로, 파일 크기를 크게 줄일 수 있다. 투명도가 필요 없는 항공 정사영상에 매우 적합하다. --jpeg-quality 옵션(예: 85)으로 압축률을 조절할 수 있다.
  • WEBP: Google이 개발한 최신 이미지 포맷으로, 동일한 시각적 품질에서 JPEG보다 더 작은 파일 크기를 제공하는 경우가 많다. 손실 및 무손실 압축을 모두 지원하며, 최신 브라우저에서 폭넓게 지원된다. 투명도가 필요 없다면 WEBP를 사용하는 것이 네트워크 대역폭을 절약하고 사용자에게 더 빠른 로딩 경험을 제공하는 가장 효과적인 방법이다.38
  • 병렬 처리(--processes): 멀티코어 CPU 환경에서 이 옵션을 사용하면 타일 생성 작업을 여러 프로세스에 분산하여 처리 속도를 극적으로 향상시킬 수 있다. 가용한 CPU 코어 수를 지정하는 것이 강력히 권장된다.
  • Raster vs. Vector Tiles - 전략적 관점: 본 안내서는 항공 정사영상이라는 특성상 래스터 타일을 다루고 있다. 래스터 타일은 픽셀 기반의 이미지로, 위성사진이나 항공사진과 같은 실사 이미지를 표현하는 데 필수적이다.39 그러나 현대 웹 매핑에서는 벡터 타일이라는 또 다른 중요한 패러다임이 존재한다. 벡터 타일은 점, 선, 면과 같은 지리적 피처의 기하학적 정보와 속성을 저장한다. 렌더링은 서버가 아닌 클라이언트 측에서 이루어지므로, 사용자의 상호작용에 따라 동적으로 스타일(색상, 두께, 라벨 등)을 변경할 수 있으며, 파일 크기가 매우 작아 로딩 속도가 빠르다는 장점이 있다.41 도로망, 건물, 행정구역과 같은 데이터를 시각화할 때는 벡터 타일이 훨씬 더 유연하고 효율적이다. 프로젝트의 데이터 특성을 이해하고 래스터와 벡터 타일 중 적절한 기술을 선택하는 것은 성공적인 웹 매핑 시스템 설계의 핵심 요소이다.

다음 표는 gdal2tiles.py의 주요 성능 최적화 옵션을 비교한다.

옵션 (Option)값 (Value)설명 (Description)권장 사항 (Recommendation)
--resamplingnear, bilinear, cubic, lanczos타일 축소/확대 시 사용할 리샘플링 알고리즘. 품질과 속도 간에 트레이드오프가 존재함.항공사진과 같은 연속톤 이미지의 경우, 고품질 결과물을 위해 lanczos 또는 cubic 사용을 권장. 처리 속도가 더 중요하다면 bilinear 선택.
--tiledriverPNG, JPEG, WEBP생성될 타일의 이미지 파일 포맷을 지정.투명도가 필요 없다면 WEBP (품질 75) 또는 JPEG (품질 85)를 사용하여 파일 크기를 크게 줄일 수 있음. WEBP는 최신 브라우저에서 JPEG보다 우수한 압축률을 보임.
--processes(CPU 코어 수)타일 생성을 병렬 처리할 프로세스(CPU 코어) 수를 지정.멀티코어 CPU 환경에서 처리 속도를 극적으로 향상시키므로, 가용한 코어 수를 지정하는 것을 강력히 권장.

6.2 사용자 경험(UX) 향상

  • 지도 경계 제한: 사용자가 정사영상 데이터가 존재하지 않는 영역으로 무한정 지도를 이동시키는 것은 혼란을 유발할 수 있다. Map 컴포넌트의 restriction prop을 사용하면 사용자의 패닝 및 줌 범위를 특정 지리적 경계 상자(Bounding Box) 내로 제한할 수 있다.44 원본 래스터의 경계 좌표는

gdalinfo <input_file> 명령어를 통해 확인할 수 있다. 이 좌표를 사용하여 restriction prop을 설정하면, 사용자는 지정된 영역 내에서만 지도를 조작할 수 있게 되어 컨텍스트를 잃지 않는다.

const TILE_BOUNDS = {
north: 38.0,
south: 37.0,
west: 126.5,
east: 127.5,
};

<Map restriction={{ latLngBounds: TILE_BOUNDS, strictBounds: false }} />
  • 초기 뷰 최적화: 애플리케이션이 처음 로드될 때, 지도가 정사영상 영역 전체를 최적의 뷰로 보여주도록 설정하는 것이 중요하다. @vis.gl/react-google-maps 라이브러리는 defaultBounds prop을 제공하여 이 기능을 간단하게 구현할 수 있다. 또는, useMapuseEffect 훅을 조합하여 지도가 로드된 직후 map.fitBounds() 메소드를 동적으로 호출하는 방법도 가능하다.3

6.3 안정성 확보: 로딩 상태 관리 및 오류 처리

  • 로딩 상태 관리: 대용량 타일셋을 로드하는 데는 시간이 걸릴 수 있다. 이 시간 동안 사용자에게 빈 화면이나 불완전한 지도를 보여주는 대신, 로딩 중임을 명확히 알려주는 것이 좋다. Map 컴포넌트는 onTilesLoaded라는 이벤트 핸들러 prop을 제공한다.31 이 이벤트는 기본 지도와 모든 오버레이 타일의 로딩이 완료되었을 때 트리거된다. React의

useState 훅과 함께 이 이벤트를 사용하여 로딩 상태를 관리하고, 로딩 중에는 스피너(spinner)와 같은 인디케이터를 표시할 수 있다.

  • 오류 처리 전략: getTileUrl 함수 자체는 네트워크 요청의 성공 여부를 직접 알 수 있는 메커니즘을 제공하지 않는다. 만약 서버에 해당 타일이 존재하지 않아 404 Not Found 오류가 발생하면, 브라우저 콘솔에만 오류가 기록되고 지도에는 깨진 이미지 아이콘이 표시될 수 있다.46 이에 대한 실용적인 대응 방안은 다음과 같다.
  1. 클라이언트 측 방어: getTileUrl 함수 내에서, 지도 API로부터 전달받은 coordzoom 값이 유효한 범위 내에 있는지 먼저 검사한다. 예를 들어, 생성된 줌 레벨(minZoom, maxZoom)을 벗어나거나, 해당 줌 레벨에서 존재할 수 없는 x, y 좌표(0 미만 또는 2^z 이상)인 경우, 서버에 요청을 보내지 않고 즉시 null 또는 투명한 1x1 픽셀 이미지의 URL을 반환한다. 이는 불필요한 404 오류 요청을 원천적으로 차단한다.
  2. 서버 측 대체 이미지: 타일을 호스팅하는 웹 서버(Nginx, Apache 등) 또는 CDN 설정을 통해, 특정 경로의 파일 요청이 404 오류를 반환할 경우, 미리 준비된 “데이터 없음” 또는 투명한 이미지를 대신 반환하도록 구성한다. 이 방법은 클라이언트 측에서 예상치 못한 오류가 발생하더라도 사용자에게 깨진 이미지 아이콘이 노출되는 것을 방지하여 보다 안정적인 사용자 경험을 제공한다.

6.4 데이터 연동: 좌표 변환의 원리

  • 지도 클릭 이벤트 및 좌표 획득: Map 컴포넌트의 onClick prop을 사용하면 사용자의 클릭 이벤트를 감지할 수 있다. 이 이벤트 핸들러는 MapMouseEvent 객체를 인자로 받으며, event.detail.latLng 속성을 통해 클릭된 지점의 위경도(Latitude/Longitude) 좌표를 lat()lng() 메소드로 쉽게 얻을 수 있다.29
const handleMapClick = (event) => {
if (event.detail.latLng) {
const lat = event.detail.latLng.lat;
const lng = event.detail.latLng.lng;
console.log(`Clicked at: ${lat}, ${lng}`);
// 이 좌표를 사용하여 추가적인 데이터 요청 등의 작업을 수행할 수 있다.
}
};

<Map onClick={handleMapClick} />
  • 심화 과정: 위경도-픽셀 좌표 변환: 획득한 위경도 좌표를 사용하여 원본 정사영상의 특정 픽셀 값을 조회하는 것은 매우 강력한 기능이다(예: 특정 지점의 고도값, 온도, 식생 지수 등). 이 변환은 클라이언트 측 JavaScript에서 직접 수행하기에는 복잡한 투영 변환 계산이 필요하다. 따라서 보다 현실적이고 효율적인 접근 방식은 백엔드 API를 구축하는 것이다.
  1. 클라이언트(React): 지도 클릭으로 위경도 좌표를 획득한 후, 이 좌표를 백엔드 API의 특정 엔드포인트(예: /api/get-pixel-value)로 전송한다.
  2. 서버(Python/Node.js): API 서버에서는 GDAL 또는 rasterio와 같은 라이브러리를 사용한다. Python의 rasterio를 예로 들면, 먼저 원본 GeoTIFF 파일을 연다. 그 다음, src.index(longitude, latitude) 메소드를 호출하여 입력된 위경도 좌표에 해당하는 래스터의 픽셀 인덱스(row, col)를 계산한다.49
  3. 마지막으로, src.read(1)[row, col]과 같은 방식으로 해당 픽셀의 값을 읽어와 클라이언트에 JSON 형태로 응답한다.52 이 아키텍처는 복잡한 공간 데이터 처리를 서버에 위임함으로써 클라이언트 애플리케이션을 가볍고 반응성 있게 유지할 수 있게 해준다.

7. 결론: 성공적인 정사영상 웹 매핑을 위한 제언

7.1 핵심 내용 요약

본 안내서는 GDAL로 생성된 항공 정사영상 타일을 React와 Google Maps를 사용하여 웹에 시각화하는 전 과정을 체계적으로 분석하고 구현 방안을 제시했다. 성공적인 프로젝트 수행을 위해 다음의 핵심 사항들을 다시 한번 강조한다.

  • GDAL 타일 생성의 표준화: 웹 표준과의 호환성을 확보하기 위해 gdal2tiles.py 실행 시 --profile=mercator 옵션으로 웹 메르카토르 투영을 적용하고, --xyz 옵션으로 Google Maps가 기대하는 타일 좌표 체계를 따르는 것은 필수적인 선행 조건이다.
  • 현대적 React 통합 패턴: Google Maps의 명령형 API를 React의 선언적 패러다임에 통합하는 가장 현대적이고 안정적인 방법은 @vis.gl/react-google-maps 라이브러리가 제공하는 useMapuseEffect 훅을 활용하는 것이다. 이 패턴은 컴포넌트의 생명주기에 맞춰 오버레이를 안전하게 생성하고 제거함으로써 코드의 안정성과 예측 가능성을 높인다.
  • 전 과정에 걸친 최적화: 성공적인 웹 매핑 애플리케이션은 단편적인 기술의 조합이 아닌, 타일 생성 단계의 포맷 및 리샘플링 최적화부터 클라이언트 렌더링 단계의 사용자 경험 향상, 로딩 상태 관리 및 오류 처리에 이르기까지 전 과정에 걸친 종합적인 최적화를 통해 완성된다.

7.2 운영 및 배포를 위한 고려사항

개발이 완료된 애플리케이션을 실제 운영 환경(production)에 배포할 때는 대규모 트래픽과 안정적인 서비스를 고려한 인프라 설계가 필요하다.

  • 타일 호스팅 전략: gdal2tiles가 생성한 타일셋은 수십만 개에서 수백만 개의 작은 파일로 구성될 수 있다. 이러한 대량의 정적 파일을 효율적으로 제공하기 위한 최적의 전략은 클라우드 기반의 객체 스토리지와 CDN(Content Delivery Network)을 결합하는 것이다.53
  1. 객체 스토리지: Amazon S3, Google Cloud Storage, Azure Blob Storage와 같은 객체 스토리지 서비스에 타일 디렉토리 전체를 업로드한다. 이 서비스들은 높은 내구성과 확장성을 제공하며, 파일 수에 관계없이 안정적인 저장을 보장한다.
  2. CDN 연동: Amazon CloudFront, Google Cloud CDN, Akamai 등과 같은 CDN 서비스를 객체 스토리지 앞에 구성한다. CDN은 전 세계에 분산된 엣지 서버에 타일 이미지의 복사본을 캐싱한다. 사용자가 타일을 요청하면, 지리적으로 가장 가까운 엣지 서버에서 이미지를 전송받게 되므로 네트워크 지연 시간(latency)이 크게 감소하여 전 세계 어디서든 빠른 지도 로딩 속도를 경험할 수 있다. 이는 프로덕션 환경에서 사용자 경험을 극대화하는 표준적인 모범 사례이다.

7.3 향후 확장 방향

본 안내서에서 다룬 기술은 견고한 기반을 제공하며, 이를 바탕으로 더욱 풍부하고 동적인 웹 매핑 애플리케이션으로 확장할 수 있다.

  • 고급 데이터 시각화: deck.gl은 대규모 데이터셋을 위한 고성능 웹 기반 시각화 라이브러리이다. deck.glTileLayer 또는 MVTLayer를 Google Maps와 연동하면 55, 단순한 이미지 오버레이를 넘어 항공사진 위에 3D 건물 모델을 올리거나, 실시간 데이터 스트림을 시각화하는 등 훨씬 더 복잡하고 동적인 데이터 기반의 시각화를 구현할 수 있다.

@vis.gl/react-google-mapsdeck.gl과의 원활한 통합을 지원한다.

  • 서버 사이드 렌더링(SSR) 환경 적용: Next.js와 같은 서버 사이드 렌더링 프레임워크에서 Google Maps API를 사용할 경우, 서버 환경에는 windowdocument와 같은 브라우저 전역 객체가 존재하지 않아 초기 렌더링 시 오류가 발생할 수 있다. 이 문제는 next/dynamic과 같은 기능을 사용하여 지도 관련 컴포넌트들이 클라이언트 측에서만 렌더링되도록 동적으로 임포트함으로써 해결할 수 있다.58 이는 SSR의 장점(초기 로딩 속도, SEO)을 유지하면서 웹 매핑 기능을 통합하기 위한 표준적인 해결책이다.

8. 참고 자료

  1. gdal2tiles.py man | Linux Command Library, https://linuxcommandlibrary.com/man/gdal2tiles.py
  2. gdal2tiles — GDAL documentation, https://gdal.org/en/stable/programs/gdal2tiles.html
  3. @react-google-maps/api - npm, https://www.npmjs.com/package/@react-google-maps/api
  4. How to Set Up and Use the React Google Maps API Library: A Beginner’s Guide - Medium, https://medium.com/@byrda05/how-to-set-up-and-use-the-react-google-maps-api-library-a-beginners-guide-a1811a65763e
  5. Get Started | React Google Maps - GitHub Pages, https://visgl.github.io/react-google-maps/docs/get-started
  6. visgl/react-google-maps: React components and hooks for the Google Maps JavaScript API - GitHub, https://github.com/visgl/react-google-maps
  7. Blog: Google Maps Platform Graduates React Integration Library to 1.0, https://mapsplatform.google.com/resources/blog/google-maps-platform-graduates-react-integration-library-to-1-0/
  8. gdal2tiles(1) — gdal - openSUSE Manpages Server, https://manpages.opensuse.org/Leap-16.0/gdal/gdal2tiles.1.en.html
  9. gdal2tiles 0.1.9 documentation - Read the Docs, https://gdal2tiles.readthedocs.io/en/latest/readme.html
  10. Web Mercator projection - Wikipedia, https://en.wikipedia.org/wiki/Web_Mercator_projection
  11. Mercator—ArcMap | Documentation, https://desktop.arcgis.com/en/arcmap/latest/map/projections/mercator.htm
  12. gdal2tiles.py Differs from Original Raster - Geographic Information Systems Stack Exchange, https://gis.stackexchange.com/questions/437774/gdal2tiles-py-differs-from-original-raster
  13. gdal2tiles.py generates wrong tiles / MapTiler the right ones - GIS StackExchange, https://gis.stackexchange.com/questions/66986/gdal2tiles-py-generates-wrong-tiles-maptiler-the-right-ones
  14. gdal2tiles.py TLDR page - Cheat-Sheets.org, https://www.cheat-sheets.org/project/tldr/command/gdal2tiles.py/
  15. gdal2tiles package - Read the Docs, https://gdal2tiles.readthedocs.io/en/latest/gdal2tiles.html
  16. XYZ Tile API - MetroMap Knowledge Hub, https://docs.metromap.com.au/docs/xyztileapi
  17. The difference between XYZ and TMS tiles and how to convert between them - GitHub Gist, https://gist.github.com/tmcw/4954720
  18. The difference between XYZ and TMS tiles and how to convert between them - GitHub Gist, https://gist.github.com/tmcw/4954720?permalink_comment_id=1885105
  19. Geographic Coordinate Systems 101: A Primer for Software Generalists - 8th Light, https://8thlight.com/insights/geographic-coordinate-systems-101
  20. EPSG 4326 vs 3857 - TransparentGov, https://transparentgov.net/cleargov1/758/epsg-4326-vs-3857
  21. EPSG 4326 vs EPSG 3857 (projections, datums, coordinate systems, and more!) - Victory Formation, https://lyzidiamond.github.io/posts/4326-vs-3857
  22. FAQ: Why Are My Map, Distance and Area Measurements Wrong When Using WGS 1984 Web Mercator - Esri Support, https://support.esri.com/en-us/knowledge-base/why-are-my-map-distance-and-area-measurements-wrong-whe-000011356
  23. EPSG 3857 or 4326 for Web Mapping - GIS Stack Exchange, https://gis.stackexchange.com/questions/48949/epsg-3857-or-4326-for-web-mapping
  24. Postgis 2 - ST_area : huge difference between 3857 and 4326 - GIS StackExchange, https://gis.stackexchange.com/questions/294908/postgis-2-st-area-huge-difference-between-3857-and-4326
  25. gdal2tiles 0.1.9 documentation - Read the Docs, https://gdal2tiles.readthedocs.io/en/latest/_modules/gdal2tiles/gdal2tiles.html
  26. Integrating Google Maps with React - LogRocket Blog, https://blog.logrocket.com/integrating-google-maps-react/
  27. How to load Maps JavaScript API in React (2023) - YouTube, https://www.youtube.com/watch?v=PfZ4oLftItk
  28. Set up the Maps JavaScript API - Google for Developers, https://developers.google.com/maps/documentation/javascript/get-api-key
  29. Add a Google map to a React app | Maps JavaScript API - Google for Developers, https://developers.google.com/codelabs/maps-platform/maps-platform-101-react-js
  30. Custom tiles in Google Maps - GitHub Gist, https://gist.github.com/clhenrick/dcce31036d3e3940c55b31ddb86ca1ec
  31. Component | React Google Maps - GitHub Pages, https://visgl.github.io/react-google-maps/docs/api-reference/components/map
  32. Image overlays | Maps JavaScript API - Google for Developers, https://developers.google.com/maps/documentation/javascript/reference/image-overlay
  33. ImageMapType | PrimeFaces JavaScript API Docs, https://primefaces.github.io/primefaces/jsdocs/classes/node_modules__types_google_maps.google.maps.ImageMapType.html
  34. Image Map Types | Maps JavaScript API - Google for Developers, https://developers.google.com/maps/documentation/javascript/examples/maptype-image
  35. getTileUrl · Issue #362 · google-map-react/google-map-react - GitHub, https://github.com/google-map-react/google-map-react/issues/362
  36. Setting bounds · visgl react-google-maps · Discussion #93 - GitHub, https://github.com/visgl/react-google-maps/discussions/93
  37. gdal2tiles doesn’t use resampling method correct? - GIS Stack Exchange, https://gis.stackexchange.com/questions/75511/gdal2tiles-doesnt-use-resampling-method-correct
  38. gdal2tiles optimization or acceleration? · Issue #10803 · OSGeo/gdal - GitHub, https://github.com/OSGeo/gdal/issues/10803
  39. docs.maptiler.com, https://docs.maptiler.com/guides/general/raster-vs-vector-map-tiles-what-is-the-difference-between-the-two-data-types/#:~:text=Raster%20map%20tiles%20are%20larger,of%20both%20map%20data%20types.
  40. Raster vs Vector Map Tiles: What is the Difference Between the Two Data Types? | Guides, https://docs.maptiler.com/guides/general/raster-vs-vector-map-tiles-what-is-the-difference-between-the-two-data-types/
  41. Tile layers—Portal for ArcGIS, https://enterprise.arcgis.com/en/portal/10.9/use/tile-layers.htm
  42. Raster Tile vs Vector Tile: Pros and Cons of Both Map Tile Types | Geoapify, https://www.geoapify.com/raster-vs-vector-map-tiles/
  43. Raster Versus Vector: Which Map Style Is Right for You? | TomTom Developer Blog, https://developer.tomtom.com/blog/decoded/raster-versus-vector-which-map-style-right-you/
  44. Restricting Map Bounds | Maps JavaScript API - Google for Developers, https://developers.google.com/maps/documentation/javascript/examples/control-bounds-restriction
  45. Restrict Map Bounds - Educative.io, https://www.educative.io/courses/google-maps-api-beginners/restrict-map-bounds
  46. Handling errors - Map Tiles API - Google for Developers, https://developers.google.com/maps/documentation/tile/error_handling
  47. Getting Lat/Lng from a Click Event | Maps JavaScript API - Google for Developers, https://developers.google.com/maps/documentation/javascript/examples/event-click-latlng
  48. react-google-maps click the map to get coordinates - Stack Overflow, https://stackoverflow.com/questions/61776224/react-google-maps-click-the-map-to-get-coordinates
  49. rasterio.io module — rasterio 1.5.0.dev documentation - Read the Docs, https://rasterio.readthedocs.io/en/latest/api/rasterio.io.html
  50. Python Quickstart — rasterio 1.4.3 documentation - Read the Docs, https://rasterio.readthedocs.io/en/stable/quickstart.html
  51. print the pixel coordinates of a specific point in the raster - Stack Overflow, https://stackoverflow.com/questions/75499270/print-the-pixel-coordinates-of-a-specific-point-in-the-raster
  52. Rasters (rasterio) — Spatial Data Programming with Python - Michael Dorman, https://geobgu.xyz/py/10-rasterio1.html
  53. Strategies for creating and serving map tiles | GEOG 585: Web Mapping - Dutton Institute, https://www.e-education.psu.edu/geog585/node/708
  54. Best practices for using hosted layers in maps - ArcGIS Enterprise, https://enterprise.arcgis.com/en/portal/11.1/use/best-practices-layers.htm
  55. What’s New | deck.gl, https://deck.gl/docs/whats-new
  56. TileLayer | deck.gl, https://deck.gl/docs/api-reference/geo-layers/tile-layer
  57. MVTLayer - Deck.gl, https://deck.gl/docs/api-reference/geo-layers/mvt-layer
  58. Using deck.gl with React, https://deck.gl/docs/get-started/using-with-react