Android 지도 데이터 관리 완벽 가이드 — GeoJSON, GPX, KML 변환과 QGIS 연동
Android 지도 앱을 개발하다 보면 단순히 지도에 마커를 표시하는 것보다 더 복잡한 작업이 필요해집니다.
예를 들어 사용자가 GPS로 산책로를 기록한 뒤 PC에서 경로를 수정하고, 다시 Android 앱으로 가져오고 싶을 수 있습니다.
또는 드론 사진으로 제작한 정사영상(Orthophoto) 위에 시설 위치와 산책로를 표시하고, 해당 데이터를 모바일 안내도 앱에서 활용해야 할 수도 있습니다.
이때 중요한 것이 지도 데이터 형식과 좌표계입니다.
지도 데이터는 GeoJSON, GPX, KML 등 다양한 형식으로 저장할 수 있지만, 각 형식의 구조와 목적은 다릅니다.
또한 QGIS에서 편집한 데이터를 Android 앱으로 가져올 때는 좌표 순서, 좌표계, 지오메트리 유형, 파일 인코딩 등을 확인해야 합니다.
이번 글에서는 다음 내용을 다룹니다.
GeoJSON, GPX, KML의 차이
위도·경도와 좌표계 이해
Kotlin으로 GeoJSON 내보내기
GPX 경로 가져오기
KML 데이터 처리
QGIS에서 지도 데이터 편집
QGIS에서 GeoJSON 내보내기
Android SQLite에 지도 데이터 저장
Google Maps SDK에 장소와 경로 표시
드론 정사영상과 지도 데이터 연결
데이터 검증 및 성능 최적화
개발 환경: Kotlin, XML, MVVM, SQLiteOpenHelper, Coroutine, Storage Access Framework, Google Maps SDK for Android, QGIS. minSdk 24 이상을 기준으로 설명합니다.
1. 지도 데이터 관리가 필요한 이유
지도 앱에서 사용하는 데이터는 크게 세 종류로 나눌 수 있습니다.
| 데이터 | 예시 | 지도 표현 |
|---|---|---|
| 점(Point) | 입구, 화장실, 관찰 지점 | Marker |
| 선(LineString) | 산책로, GPS 이동 경로 | Polyline |
| 면(Polygon) | 정원 구역, 호수, 건물 영역 | Polygon |
처음에는 앱 내부 SQLite에 위도와 경도만 저장해도 충분할 수 있습니다.
하지만 다른 프로그램과 데이터를 주고받거나 지도 데이터를 편집하려면 표준 파일 형식이 필요합니다.
대표적인 형식이 GeoJSON, GPX, KML입니다.
GeoJSON, GPX, KML 비교
2. 세 가지 형식의 차이
| 구분 | GeoJSON | GPX | KML |
|---|---|---|---|
| 기본 구조 | JSON | XML | XML |
| 주요 목적 | 지리 공간 데이터 교환 | GPS 경로 및 위치 기록 | 지도 시각화·표현 |
| 점 데이터 | Point | Waypoint | Placemark |
| 선 데이터 | LineString | Track·Route | LineString |
| 면 데이터 | Polygon | 기본 구조에 없음 | Polygon |
| 스타일 정보 | 표준 핵심 범위 밖 | 제한적 | 다양한 스타일 지원 |
| Android 처리 | JSON 파싱 | XML 파싱 | XML 파싱 |
| QGIS 지원 | 지원 | 지원 | 지원 |
GPS 트래킹 기록은 GPX, 앱의 장소·경로·구역 데이터는 GeoJSON, Google Earth와의 데이터 교환은 KML을 우선 검토할 수 있습니다.
다만 실제로 어떤 형식을 사용할지는 데이터의 목적과 외부 프로그램의 지원 범위에 따라 결정해야 합니다.
좌표계 이해하기
3. 위도와 경도
Android의 Google Maps SDK에서는 일반적으로 위도와 경도를 사용합니다.
val location = LatLng(
37.5665,
126.9780
)LatLng의 인자 순서는 다음과 같습니다.
LatLng(latitude, longitude)반면 GeoJSON에서는 순서가 다릅니다.
{
"type": "Point",
"coordinates": [126.9780, 37.5665]
}GeoJSON의 좌표 순서는 경도, 위도입니다.
이 차이를 혼동하면 지도에 엉뚱한 위치가 표시될 수 있습니다.
4. WGS84와 EPSG:4326
GPS와 모바일 지도에서 자주 사용하는 좌표 기준이 WGS84입니다.
QGIS에서는 일반적으로 EPSG:4326으로 표시합니다.
예를 들어 서울의 한 위치는 다음과 같이 표현할 수 있습니다.
위도: 37.5665
경도: 126.9780하지만 QGIS에서 모든 데이터가 EPSG:4326을 사용하는 것은 아닙니다.
국내 지도 데이터에는 투영 좌표계가 적용된 경우도 있습니다.
따라서 QGIS에서 데이터를 가져올 때는 원본 데이터의 좌표계를 반드시 확인해야 합니다.
5. 좌표계가 다른 데이터를 합치면?
다음과 같은 상황을 가정하겠습니다.
데이터 A: EPSG:4326
데이터 B: EPSG:5179두 데이터는 좌표 표현 방식이 다릅니다.
QGIS에서는 좌표계 정보를 올바르게 지정하면 서로 다른 좌표계의 데이터를 같은 지도 위에 표시할 수 있습니다.
하지만 원본 좌표계를 잘못 지정하면 위치가 어긋날 수 있습니다.
여기서 주의할 점은 좌표계 지정과 좌표계 변환은 서로 다르다는 것입니다.
원본 데이터의 좌표계가 무엇인지 확인한 뒤 필요한 좌표계로 변환해야 합니다.
GeoJSON 구조 이해하기
6. FeatureCollection
여러 장소를 하나의 GeoJSON 파일에 저장하려면 FeatureCollection을 사용할 수 있습니다.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"id": 1,
"name": "정원 입구",
"category": "entrance"
},
"geometry": {
"type": "Point",
"coordinates": [127.1000, 37.1000]
}
},
{
"type": "Feature",
"properties": {
"id": 2,
"name": "전망대",
"category": "viewpoint"
},
"geometry": {
"type": "Point",
"coordinates": [127.1010, 37.1010]
}
}
]
}이 파일에는 두 개의 장소가 포함되어 있습니다.
각 Feature는 properties와 geometry로 구성됩니다.
properties에는 장소 이름이나 카테고리 같은 속성을 저장하고, geometry에는 좌표 정보를 저장합니다.
Android 지도 데이터 모델
7. Kotlin 데이터 클래스
SQLite에서 사용하는 장소 모델을 다음과 같이 정의하겠습니다.
data class MapPlace(
val id: Long,
val name: String,
val category: String?,
val latitude: Double,
val longitude: Double
)지도 경로는 여러 좌표로 구성됩니다.
data class TrackPoint(
val latitude: Double,
val longitude: Double,
val altitude: Double? = null,
val timestamp: Long? = null
)점 데이터와 경로 데이터는 구조가 다르므로 별도 모델로 관리하는 것이 좋습니다.
GeoJSON 내보내기
8. Kotlin으로 Feature 생성
Android의 JSONObject와 JSONArray를 이용하면 GeoJSON을 생성할 수 있습니다.
fun placeToFeature(
place: MapPlace
): JSONObject {
val properties = JSONObject().apply {
put("id", place.id)
put("name", place.name)
put("category", place.category)
}
val geometry = JSONObject().apply {
put("type", "Point")
put(
"coordinates",
JSONArray().apply {
put(place.longitude)
put(place.latitude)
}
)
}
return JSONObject().apply {
put("type", "Feature")
put("properties", properties)
put("geometry", geometry)
}
}이 함수는 하나의 장소를 GeoJSON Feature로 변환합니다.
좌표를 추가할 때는 경도와 위도의 순서를 확인해야 합니다.
9. FeatureCollection 생성
fun createPlacesGeoJson(
places: List<MapPlace>
): String {
val features = JSONArray()
places.forEach { place ->
features.put(
placeToFeature(place)
)
}
val collection = JSONObject().apply {
put("type", "FeatureCollection")
put("features", features)
}
return collection.toString(2)
}이제 SQLite에서 조회한 장소 목록을 GeoJSON 문자열로 변환할 수 있습니다.
val geoJson = createPlacesGeoJson(places)이 데이터를 파일로 저장하면 QGIS나 다른 GIS 프로그램에서 열 수 있습니다.
SAF로 GeoJSON 저장하기
10. CreateDocument 사용
사용자가 파일 저장 위치를 직접 선택하도록 하겠습니다.
private val createGeoJson =
registerForActivityResult(
ActivityResultContracts.CreateDocument(
"application/geo+json"
)
) { uri ->
if (uri != null) {
exportGeoJson(uri)
}
}저장 버튼에서 실행합니다.
binding.buttonExport.setOnClickListener {
createGeoJson.launch(
"garden_places.geojson"
)
}이 방식에서는 일반적인 저장소 전체 접근 권한이 필요하지 않습니다.
11. GeoJSON 파일 기록
private fun exportGeoJson(uri: Uri) {
viewLifecycleOwner.lifecycleScope.launch {
try {
val places = withContext(Dispatchers.IO) {
repository.getAllPlaces()
}
val geoJson = withContext(Dispatchers.Default) {
createPlacesGeoJson(places)
}
withContext(Dispatchers.IO) {
requireContext().contentResolver
.openOutputStream(uri)
?.bufferedWriter(Charsets.UTF_8)
?.use { writer ->
writer.write(geoJson)
}
?: error("파일을 열 수 없습니다.")
}
Toast.makeText(
requireContext(),
"GeoJSON 저장 완료",
Toast.LENGTH_SHORT
).show()
} catch (e: Exception) {
Log.e("GeoJSON", "내보내기 실패", e)
}
}
}위 예제의 repository는 SQLite 조회 기능을 제공하는 객체라고 가정합니다.
파일 기록에 실패한 경우 사용자에게 오류 메시지를 표시하는 처리도 추가해야 합니다.
GeoJSON 가져오기
12. OpenDocument 사용
이번에는 사용자가 GeoJSON 파일을 선택하도록 구현하겠습니다.
private val openGeoJson =
registerForActivityResult(
ActivityResultContracts.OpenDocument()
) { uri ->
if (uri != null) {
importGeoJson(uri)
}
}실행 코드는 다음과 같습니다.
binding.buttonImport.setOnClickListener {
openGeoJson.launch(
arrayOf(
"application/geo+json",
"application/json"
)
)
}파일 제공자에 따라 MIME 유형이 다르게 표시될 수 있으므로 실제 기기에서 확인해야 합니다.
13. GeoJSON 파싱
다음은 Point Feature를 읽는 간단한 예제입니다.
fun parsePlacesGeoJson(
json: String
): List<MapPlace> {
val result = mutableListOf<MapPlace>()
val root = JSONObject(json)
require(
root.getString("type") == "FeatureCollection"
)
val features = root.getJSONArray("features")
for (i in 0 until features.length()) {
val feature = features.getJSONObject(i)
val geometry = feature.getJSONObject("geometry")
if (geometry.getString("type") != "Point") {
continue
}
val coordinates =
geometry.getJSONArray("coordinates")
val longitude = coordinates.getDouble(0)
val latitude = coordinates.getDouble(1)
require(latitude in -90.0..90.0)
require(longitude in -180.0..180.0)
val properties =
feature.optJSONObject("properties")
?: JSONObject()
result.add(
MapPlace(
id = properties.optLong("id", 0L),
name = properties.optString("name", ""),
category = properties.optString(
"category",
""
),
latitude = latitude,
longitude = longitude
)
)
}
return result
}이 코드는 일반적인 Point Feature만 처리하는 예제입니다.
실제 앱에서는 파일 크기, 필수 속성, 중복 ID, 좌표 유효성, GeometryCollection 등 지원하지 않는 형식을 별도로 검증해야 합니다.
SQLite에 가져온 장소 저장
14. 트랜잭션 활용
여러 장소를 한 번에 저장할 때는 SQLite 트랜잭션을 사용하는 것이 좋습니다.
fun insertPlaces(
db: SQLiteDatabase,
places: List<MapPlace>
) {
db.beginTransaction()
try {
places.forEach { place ->
val values = ContentValues().apply {
put("name", place.name)
put("category", place.category)
put("latitude", place.latitude)
put("longitude", place.longitude)
}
db.insertOrThrow(
"map_places",
null,
values
)
}
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
}이 예제에서는 가져온 파일의 ID를 사용하지 않고 SQLite가 새 ID를 생성하도록 구성했습니다.
외부 파일의 ID를 그대로 사용해야 한다면 기존 데이터와의 충돌 정책을 별도로 설계해야 합니다.
GPX 데이터 가져오기
15. GPX 구조 이해하기
GPX는 GPS 경로 데이터를 저장하는 데 널리 사용되는 XML 형식입니다.
<?xml version="1.0" encoding="UTF-8"?>
<gpx
version="1.1"
creator="MyGPSApp"
xmlns="http://www.topografix.com/GPX/1/1">
<trk>
<name>Garden Track</name>
<trkseg>
<trkpt lat="37.1000" lon="127.1000">
<ele>45.0</ele>
<time>2026-10-31T01:00:00Z</time>
</trkpt>
<trkpt lat="37.1002" lon="127.1003">
<ele>46.2</ele>
<time>2026-10-31T01:00:05Z</time>
</trkpt>
</trkseg>
</trk>
</gpx>trk는 트랙, trkseg는 트랙 세그먼트, trkpt는 하나의 위치 좌표를 의미합니다.
GPS 신호가 끊긴 구간은 별도 세그먼트로 구분할 수 있습니다.
16. GPX 파싱 시 주의사항
GPX 파일을 읽을 때는 다음 항목을 확인해야 합니다.
위도와 경도
고도
측정 시각
트랙 세그먼트 구분
파일 크기
XML 네임스페이스
잘못된 좌표
누락된 데이터
특히 여러 세그먼트를 하나의 Polyline으로 무조건 연결하면 실제로 이동하지 않은 구간이 직선으로 표시될 수 있습니다.
따라서 트랙 세그먼트는 별도로 관리하는 것이 좋습니다.
Kotlin GPX 파서
17. XmlPullParser 사용
Android에서는 XmlPullParser를 이용해 XML 파일을 읽을 수 있습니다.
다음 예제는 GPX의 trkpt 좌표를 읽습니다.
fun parseGpx(
input: InputStream
): List<TrackPoint> {
val points = mutableListOf<TrackPoint>()
val parser = Xml.newPullParser()
parser.setInput(input, "UTF-8")
var event = parser.eventType
while (event != XmlPullParser.END_DOCUMENT) {
if (
event == XmlPullParser.START_TAG &&
parser.name == "trkpt"
) {
val lat = parser.getAttributeValue(
null,
"lat"
)?.toDoubleOrNull()
val lon = parser.getAttributeValue(
null,
"lon"
)?.toDoubleOrNull()
if (
lat != null &&
lon != null &&
lat in -90.0..90.0 &&
lon in -180.0..180.0
) {
points.add(
TrackPoint(
latitude = lat,
longitude = lon
)
)
}
}
event = parser.next()
}
return points
}이 예제는 좌표 추출의 기본 구조를 설명하기 위한 것입니다.
고도, 시간, 트랙 세그먼트, 여러 트랙을 처리하려면 파서를 확장해야 합니다.
또한 XML 파일을 읽을 때 외부 엔티티나 과도한 입력으로 인한 보안 문제를 고려해야 합니다.
KML 이해하기
18. KML이란?
KML은 Google Earth 등에서 사용하는 지리 공간 데이터 형식입니다.
장소, 경로, 구역, 스타일 등의 정보를 표현할 수 있습니다.
예를 들어 다음과 같습니다.
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<Placemark>
<name>정원 입구</name>
<Point>
<coordinates>
127.1000,37.1000,0
</coordinates>
</Point>
</Placemark>
</Document>
</kml>KML 좌표 순서는 일반적으로 경도, 위도, 고도입니다.
19. KML과 GeoJSON 변환
KML 데이터를 Android 앱에서 직접 파싱할 수도 있습니다.
하지만 다양한 KML 스타일과 구조를 모두 처리해야 한다면 구현이 복잡해질 수 있습니다.
이 경우 QGIS에서 KML을 GeoJSON으로 변환한 뒤 앱에서 사용하는 방식이 더 단순할 수 있습니다.
특히 앱에서 Point, LineString, Polygon 정도만 사용한다면 GeoJSON으로 내부 형식을 통일하는 것이 관리하기 쉽습니다.
QGIS 활용
20. QGIS란?
QGIS는 오픈소스 GIS 프로그램입니다.
지도 데이터 확인, 편집, 좌표계 변환, 공간 분석, 파일 형식 변환 등에 활용할 수 있습니다.
Android 앱 개발자가 QGIS를 사용하면 다음 작업을 수행할 수 있습니다.
GPS 경로 확인
산책로 편집
장소 좌표 수정
구역 Polygon 생성
좌표계 변환
GeoJSON 내보내기
드론 정사영상과 지도 데이터 비교
공식 사이트:
QGIS에서 GPX 열기
21. GPX 파일 불러오기
QGIS에서 GPX 파일을 열려면 일반적으로 다음 순서로 진행합니다.
QGIS 실행
데이터 소스 관리자 열기
벡터 데이터 추가
GPX 파일 선택
트랙 또는 웨이포인트 레이어 선택
지도에 데이터 표시
GPX 파일에는 트랙, 경로, 웨이포인트가 별도 레이어로 표시될 수 있습니다.
불러온 뒤 실제 이동 경로와 기록된 GPS 좌표를 비교할 수 있습니다.
22. QGIS에서 경로 편집
GPS로 수집한 경로에 오류가 있다면 QGIS에서 편집할 수 있습니다.
예를 들어 다음과 같은 작업입니다.
잘못된 위치 좌표 제거
경로 일부 수정
산책로 구간 분리
경로 이름 변경
장소 위치 보정
불필요한 지점 정리
다만 GPS 기록을 수정할 때는 원본 데이터를 별도로 보관하는 것이 좋습니다.
편집된 경로와 원본 GPS 경로를 구분해야 나중에 데이터의 정확성을 검증할 수 있습니다.
QGIS에서 GeoJSON 내보내기
23. GeoJSON 저장 방법
QGIS에서 편집한 레이어를 GeoJSON으로 저장할 수 있습니다.
일반적인 과정은 다음과 같습니다.
내보낼 레이어 선택
레이어 우클릭
내보내기 메뉴 선택
객체를 다른 이름으로 저장
파일 형식을 GeoJSON으로 선택
좌표계를 EPSG:4326으로 지정
파일 저장
QGIS 버전에 따라 메뉴 이름은 조금 다를 수 있습니다.
GeoJSON으로 내보낸 뒤에는 좌표 순서와 Geometry 유형을 확인하는 것이 좋습니다.
드론 정사영상과 지도 데이터
24. 정사영상이란?
정사영상(Orthophoto)은 항공사진이나 드론 사진의 기하학적 왜곡을 보정해 지도처럼 사용할 수 있도록 만든 영상입니다.
일반적인 드론 사진은 촬영 각도와 지형에 따라 위치 왜곡이 발생할 수 있습니다.
반면 정사영상은 적절한 처리 과정을 거쳐 지리 좌표와 연결할 수 있습니다.
다만 정사영상의 위치 정확도는 촬영 이미지의 GPS 정확도, 지상기준점, 처리 방식 등에 영향을 받습니다.
25. WebODM 결과 활용
WebODM과 같은 사진측량 소프트웨어에서는 촬영 사진을 이용해 정사영상 등의 결과물을 생성할 수 있습니다.
이 결과를 QGIS에서 열면 다음 작업을 수행할 수 있습니다.
정사영상 확인
산책로 위치 확인
건물·시설 위치 확인
Point 데이터 생성
LineString 경로 작성
Polygon 구역 작성
GeoJSON으로 내보내기
이렇게 만든 GeoJSON 데이터를 Android 앱으로 가져오면 실제 시설 위치를 지도에 표시할 수 있습니다.
정사영상과 Android 앱 연결
26. 권장 데이터 흐름
드론 사진 촬영
│
▼
WebODM 처리
│
▼
정사영상 생성
│
▼
QGIS에서 열기
│
▼
장소·산책로·구역 편집
│
▼
GeoJSON 내보내기
│
▼
Android 앱 가져오기
│
▼
SQLite 저장
│
▼
지도에 표시이 구조를 사용하면 드론으로 수집한 공간 정보를 Android 안내도 앱에 활용할 수 있습니다.
단, 정사영상 자체를 Google Maps SDK에 중첩 표시하려면 이미지 오버레이의 좌표 범위와 투영을 정확히 맞춰야 합니다.
Google Maps SDK 연동
27. GeoJSON 레이어 표시
Google Maps SDK Utility Library의 GeoJsonLayer를 활용할 수 있습니다.
val layer = GeoJsonLayer(
googleMap,
R.raw.garden_map,
requireContext()
)
layer.addLayerToMap()이 예제에서는 res/raw/garden_map.geojson 파일을 사용합니다.
동적으로 가져온 GeoJSON 파일은 입력 스트림을 통해 파싱하거나 앱 내부 데이터 모델로 변환한 뒤 지도에 표시할 수 있습니다.
SQLite 지도 데이터 구조
28. 장소와 경로 분리
지도 데이터는 지오메트리 유형에 따라 분리해서 저장할 수 있습니다.
CREATE TABLE map_places (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
category TEXT,
latitude REAL NOT NULL,
longitude REAL NOT NULL
);경로 테이블:
CREATE TABLE map_routes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
created_at INTEGER NOT NULL
);경로 좌표 테이블:
CREATE TABLE map_route_points (
id INTEGER PRIMARY KEY AUTOINCREMENT,
route_id INTEGER NOT NULL,
point_order INTEGER NOT NULL,
latitude REAL NOT NULL,
longitude REAL NOT NULL,
FOREIGN KEY(route_id)
REFERENCES map_routes(id)
);경로 좌표에는 point_order를 저장해 순서를 유지하도록 합니다.
데이터 정확도 검증
29. 지도 데이터 가져오기 전 확인할 사항
외부 지도 파일을 가져올 때는 다음 항목을 확인해야 합니다.
| 항목 | 확인 내용 |
|---|---|
| 파일 형식 | GeoJSON, GPX, KML |
| 인코딩 | UTF-8 |
| 좌표계 | WGS84 등 |
| 좌표 순서 | 경도·위도 또는 위도·경도 |
| Geometry | Point, LineString, Polygon |
| 좌표 범위 | 위도 -90~90, 경도 -180~180 |
| 파일 크기 | 허용 가능한 용량 |
| 데이터 개수 | 앱에서 처리 가능한 수준 |
| 속성 정보 | 필수 필드 존재 여부 |
| 중복 데이터 | 기존 SQLite 데이터와 충돌 여부 |
외부 파일은 신뢰할 수 없는 입력으로 취급하는 것이 좋습니다.
따라서 파싱 전에 파일 크기를 제한하고, 파싱 후에는 좌표와 속성 정보를 검증해야 합니다.
지도 데이터 성능 최적화
30. 대량 GeoJSON 처리
GeoJSON은 사람이 읽기 쉬운 형식이지만 데이터가 많아지면 파일 크기가 커질 수 있습니다.
예를 들어 수십만 개의 GPS 좌표를 하나의 GeoJSON 파일로 저장하면 메모리 사용량이 증가할 수 있습니다.
이러한 경우 다음 방법을 검토할 수 있습니다.
데이터를 구역별로 분리
필요한 영역만 로드
좌표 수 단순화
SQLite에 분할 저장
백그라운드 파싱
대용량 파일 스트리밍 처리
렌더링용 좌표와 원본 좌표 분리
특히 지도 표시용 경로는 원본 GPS 좌표를 모두 표시할 필요가 없는 경우도 있습니다.
실제 앱 적용 사례
31. 정원 안내도 앱
정원 안내도 앱에서는 다음과 같은 데이터를 관리할 수 있습니다.
입구 Point
주차장 Point
화장실 Point
산책로 LineString
연못 Polygon
정원 구역 Polygon
안내도 이미지
시설 상세 정보
QGIS에서 편집한 GeoJSON을 Android 앱으로 가져오면 장소와 경로를 체계적으로 관리할 수 있습니다.
32. 조류 관찰 앱
조류 관찰 앱에서는 다음과 같은 기능을 구현할 수 있습니다.
관찰 위치 Point 저장
탐조 경로 GPX 내보내기
관찰 지역 Polygon 관리
GeoJSON으로 관찰 위치 공유
QGIS에서 관찰 위치 분석
관찰 기록과 지도 데이터 연결
다만 희귀 조류나 보호종의 정확한 위치 정보는 외부 공개 범위를 신중하게 결정해야 합니다.
지도 데이터 관리 체크리스트
33. 출시 전 점검
GeoJSON 구조 이해
GPX 구조 이해
KML 구조 이해
좌표계 확인
위도·경도 순서 확인
GeoJSON 내보내기
GeoJSON 가져오기
GPX 파싱
KML 변환
SAF 파일 선택
SAF 파일 저장
SQLite 트랜잭션 처리
중복 장소 처리
좌표 유효성 검사
파일 크기 제한
대용량 데이터 처리
QGIS 데이터 편집
EPSG:4326 내보내기
Google Maps SDK 표시
원본 GPS 데이터 보관
정사영상 위치 정확도 확인
실제 기기 테스트
34. 마무리
Android 지도 앱을 개발할 때 GeoJSON, GPX, KML은 서로 다른 목적을 가진 중요한 데이터 형식입니다.
GPS 이동 경로를 기록하고 공유할 때는 GPX가 편리하며, 장소와 경로, 구역을 함께 관리하려면 GeoJSON이 유용합니다.
KML은 Google Earth와의 데이터 교환이나 스타일 정보를 포함한 지도 표현에 활용할 수 있습니다.
이번 글의 핵심을 정리하면 다음과 같습니다.
GeoJSON은 Point, LineString, Polygon 데이터를 표현할 수 있습니다.
GPX는 GPS 이동 경로를 기록하고 교환하는 데 적합합니다.
KML은 Google Earth와의 데이터 교환에 활용할 수 있습니다.
GeoJSON의 좌표 순서는 경도, 위도입니다.
QGIS에서는 좌표계 변환과 지도 데이터 편집을 수행할 수 있습니다.
Android에서는 SAF를 이용해 지도 파일을 가져오고 저장할 수 있습니다.
SQLite에 장소와 경로 데이터를 분리해 저장할 수 있습니다.
Google Maps SDK에 GeoJSON 데이터를 표시할 수 있습니다.
WebODM 정사영상과 QGIS를 연결하면 드론 기반 안내도 제작에 활용할 수 있습니다.
외부 지도 파일은 좌표계와 데이터 유효성을 반드시 검증해야 합니다.
특히 정원 안내도나 관광지 안내도 앱을 개발한다면 QGIS에서 장소와 경로를 편집하고, GeoJSON으로 내보낸 뒤 Android 앱에서 사용하는 구조를 추천합니다.
이렇게 하면 앱 코드를 수정하지 않고도 지도 데이터를 갱신할 수 있는 기반을 만들 수 있습니다.
지도 데이터 관리의 핵심은 특정 지도 SDK에 종속되지 않고, 장소와 경로 데이터를 표준 형식으로 보관하고 재사용할 수 있도록 설계하는 것입니다.
참고 자료
GeoJSON 표준 RFC 7946
https://www.rfc-editor.org/rfc/rfc7946GPX 1.1 스키마
https://www.topografix.com/GPX/1/1/QGIS 공식 사이트
https://qgis.org/QGIS 문서
https://docs.qgis.org/Google Maps SDK for Android
https://developers.google.com/maps/documentation/android-sdk/overviewGoogle Maps SDK Utility Library
https://github.com/googlemaps/android-maps-utilsAndroid Storage Access Framework
https://developer.android.com/training/data-storage/shared/documents-files
태그: Android, Kotlin, GeoJSON, GPX, KML, QGIS, Google Maps SDK, SQLite, 지도 데이터, GPS 트래킹, 좌표계, EPSG4326, WebODM, 드론 정사영상, 관광지 안내도, Android 앱 개발