Flutter 지도 - 구글지도 키 발급 및 프로젝트 설정, 그리기 API 구현하기

kkensu
조현철의 개발로그
22 min readMay 3, 2022

Flutter 에서 가장 많이 사용되는 지도는 단연 구글지도 입니다.(따로 조사를 해보진 않았지만… 맞겠죠?😅) 지도 관련 포스팅 중 가장 먼저 구글지도에 대해서 써보려고 합니다.

1. 무료같은 유료같은 무료 구글지도

구글 지도는 처음에 200달러를 지급해 줍니다. 이 200달러 내에서 원하는 서비스를 이용할 수 있습니다. 하지만 200달러 초과를 할 경우 비용을 지불해야 합니다. 2018년 경에 유료화로 전환 된 것으로 기억합니다.

구글지도 요금안내

유료화는 진행 됐지만 “Get mobile Dynamic Maps without Cloud-based maps styling at no cost” 라고 되어 있는 것 처럼 모바일 기기에서 Dynamic Map을 요청하는 것은 별도의 비용이 발생하지는 않습니다. 그렇지만 Web에서 구글지도를 사용하거나 다른 API들을 사용하기 위해서는 비용을 지불해야 합니다.

2. 프로젝트 생성 및 라이브러리 설치

저는 Android Studio를 사용합니다. 그래서 위와 같이 새 프로젝트를 생성하였습니다. 패키지 명은 “kr.co.kkensu.flutter_google_map_sample” 입니다.

pub.dev에 가서 flutter google map 으로 검색하면 google_maps_flutter 라는 패키지가 보입니다. 이 패키지를 설치합니다.

아래의 명령어를 통해 라이브러리를 설치합니다.

flutter pub add google_maps_flutter

3. API KEY 발급하기

API KEY를 발급 받기 위해서는 구글 콘솔에 가입되어 있어야 합니다. GCP(Google Cloud Platform)에 가입되어 있고, 저는 이미 생성된 다른 프로젝트가 있어서 저와는 처음 시작이 조금 다를 수 있지만 제 구글 콘솔 기준으로 진행하도록 하겠습니다.

3–1. 새 프로젝트 생성하기

가장 먼저 위의 링크를 눌러서 사이트에 접속합니다. 제 기준으로는 아래와 같이 나옵니다.

제 화면에는 상단에 “My Project”라고 되어 있는 부분을 누르면 프로젝트 선택 화면이 나옵니다.

프로젝트 선택 화면의 우측 상단에 “새 프로젝트” 가 있고 저는 flutter google map sample 이라는 프로젝트 이름을 적고 “만들기”를 눌러 프로젝트를 생성했습니다. 그러면 프로젝트가 생성되었다는 알림이 보이게 됩니다.

위처럼 생성은 되었지만 아직 프로젝트는 My Project 이므로 다시 한 번 눌러서 새로 생성된 flutter google map sample 이라는 프로젝트로 전화해 줍니다.

3-2. API 사용 설정

새로운 프로젝트 생성이 완료되면 구글 지도를 사용하겠다는 설정을 해주어야 합니다. 좌측상단 메뉴 버튼을 눌러 API 및 서비스 → 라이브러리 메뉴로 이동합니다.

Maps SDK for Android/iOS 를 선택해서 사용 설정을 해주면 됩니다. 이 설정을 하지 않으면 API KEY 발급 받아 실행해도 지도가 나오지 않으니 꼭 확인해야 합니다.

3–3. API KEY 발급 — Android API KEY

좌측 상단의 메뉴버튼을 눌러 API 및 서비스 → 사용자 인증 정보 화면 이로 이동합니다.

상단의 사용자 인증 정보 만들기 → API 키를 눌러서 API 키를 생성합니다.

API 키 1개 라고 써있는곳을 누르면 수정할 수 있는 화면이 나옵니다.

  • 이름 : Android Google Map API KEY
  • 애플리케이션 제한사항 : Android 앱 선택
  • API 제한 사항 : 키 제한 안함 선택
  • Android 앱의 사용량 제한 : 항목 추가 선택

Android keystore 파일의 SHA-1 서명을 추가해야 합니다. 저는 맥을 사용하기 때문에 위 오른쪽 이미지에 보면 “디버그 인증서 디지털 지문”이라고 되어 있는 부분의 Linux 또는 maxOS 부분을 복사해서 서명을 했습니다.

이렇게 설정을 완료 한 후에 “저장”버튼을 눌러 주세요. 이렇게 하면 Android API KEY 는 발급 했습니다.

3–4. API KEY 발급 — iOS API KEY

“사용자 인증 정보 만들기” → “API 키” 를 선택하고 새로 생성된 API KEY를 눌러 수정 페이지로 이동하세요.

  • 이름 : iOS Google Map API KEY → 임의대로 변경하셔도 됩니다.
  • 애플리케이션 제한사항 : iOS 앱 선택
  • API 제한 사항 : 키 제한 안함 선택
  • 다음 번들 식별자 중 하나가 포함된 iOS 애플리케이션의 요청 수락 : 항목 추가 선택 → iOS Bundle ID : kr.co.kkensu.flutter_google_map_sample 을 입력해주시면 됩니다.

“저장” 버튼을 누르면 iOS API KEY 발급도 끝났습니다.

4. Android API KEY 입력하기

4–1. 최소 SDK 버전 변경

android/app/build.gradle 파일을 열어 최소 SDK 버전을 입력해 주면 됩니다. 처음에 프로젝트를 생성하면 minSdkVersion이 flutter.minSdkVersion 이라고 되어 있을 텐데 이 부분을 20 이상으로 변경하시면 됩니다.

4–2. API KEY 입력

위에서 발급받은 Android Google Map API KEY를 android/app/src/main/AndroidManifest.xml 에 추가해주어야 합니다. 저는 flutterEmbedding meta-data 아래에 추가 했습니다.

5. iOS API KEY 입력하기

위에서 발급받은 iOS Google Map API KEY를 ios/Runner/AppDelegate.swift 에 추가해줍니다.

이렇게 해서 Android, iOS API 키를 발급 받고 프로젝트에 어떻게 추가 하는지를 알아보았습니다. 여러 번 해보면 그리 어렵지 않지만 처음에는 다소 복잡한 부분이라 상세 하게 적어보았습니다.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

그리기 API 라고 하면 마커 그리기, 선, 원, 다격형 그리기가 있습니다. 예제를 만들어 보면서 API를 살펴보도록 하겠습니다.

6. Flutter 구글 지도 생성

Flutter 에서 구글 지도를 보여주려면 GoogleMap 위젯을 이용하면 됩니다.

GoogleMap(
mapType: MapType.normal,
polylines: polylines, // 선 그리기 변수
circles: circles, // 원 그리기 변수
polygons: polygons, // 다각형 그리기 변수
markers: markers, // 마커 그리기 변수
initialCameraPosition: initPosition, // 처음 로딩 시 보여줄 화면 정의
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller); //
}, // 맵 로드가 완료되었을 때
),

7. 직선 그리기(Polyline) API

지도에 직선을 그리는 것은 언제 필요 할까요? 카카오택시, 타다 같은 서비스를 보면 출발지 부터 도착지 까지 어느 경로로 가면 어느 정도의 요금이 나오는지 보여주곤 합니다.

타다 경로 및 요금, 시간 정보 안내 화면

이렇게 예상 이동 경로를 표시해 줄 때 직선 그리기 API를 사용합니다. 경로를 받아오는 것은 티맵 API, NCloud API 같이 출/도착지를 전달하면 경로를 받아올 수 있는 API를 요청하여 경로를 그릴 데이터를 받아와야 합니다.

polylines 변수는 Set으로 선언해줍니다. 왜냐하면 중복된 데이터는 하나로 표기하기 위함입니다.

Set<Polyline> polylines = {};

이렇게 선언 해 준 후에 버튼이 선택되면 직선을 하나 그리도록 아래와 같이 구현 했습니다.

ElevatedButton(
child: const Text('직선'),
onPressed: () {
List<LatLng> list = const [
LatLng(37.3625806, 126.9248464),
LatLng(37.3626138, 126.9264801),
LatLng(37.3632727, 126.9280313),
];

setState(() {
polylines.add(Polyline(
polylineId: const PolylineId("1"),
points: list,
color: Colors.red,
width: 4,
));
});
},
),

polylines 변수에 Polyline을 하나 추가하였습니다. 만약 여러개의 선을 그려야 한다고 하면 polylines.add를 계속 해주면 됩니다.

직선 그리기 실행 결과

8. 원 그리기(Circle) API

저는 제가 만들었던 프로젝트 들에서 원 그리기를 해 본 적은 없었습니다. 그러면 지도에 원을 그리는 것은 언제 필요 할까요? 중심점과 반경으로 원을 그리게 되는데 그 범위 내에 무엇이 있는지 표시 할 때 사용하지 않을까 생각합니다.

혹시 지도에 원을 그려서 무언가를 알려주는 앱이나 웹이 있는지 살펴 보았는데 위 링크를 보니 반경 1km 내의 생활 시설에 대한 것을 알려줄 때 원을 그려서 사용하는 것을 볼 수 있었습니다.

원 그리기 실행 결과

9. 다각형 그리기(Polygon) API

다각형 이라고 하면 최소 삼각형 이상의 각이 있는 도형을 말합니다. (사전적 의미) 그렇다면 이 다각형을 어디서 사용할까요? 주차대행 서비스 마지막삼십분의 잇차 앱에서는 서비스가 가능한 지역을 표시하기 위해서 사용했습니다.

잇차의 서비스 지역을 표시하는 화면

이렇게 그려줄 때 다각형 그리기 API를 사용합니다. 최소 3개 이상의 좌표를 이용해서 Polygon 을 구현 하면됩니다. 일반적으로 Polygon을 구현하는 방법과 위의 이미지처럼 서비스지역을 표시하기 위한 지역만 구멍(?)이 뚫려있는 형태로 Polygon을 구현하는 방법은 조금 다르긴 합니다.

9–1. Polygon(Multi Polygon)

Set<Polygon> polygons = {};

이렇게 선언 해 준 후에 버튼이 선택되면 다각형을 두 개 그리도록 아래와 같이 구현 했습니다.

ElevatedButton(
child: const Text('다각형'),
onPressed: () {
List<LatLng> polygon1 = const [
LatLng(37.3625806, 126.9248464),
LatLng(37.3626138, 126.9264801),
LatLng(37.3632727, 126.9280313),
];

List<LatLng> polygon2 = const [
LatLng(37.36119, 126.9193982),
LatLng(37.3534215, 126.9295909),
LatLng(37.3549206, 126.9327015),
];

setState(() {
polygons.add(Polygon(polygonId: const PolygonId("1"), points: polygon1, fillColor: Colors.transparent, strokeColor: Colors.red, strokeWidth: 4));
polygons.add(Polygon(polygonId: const PolygonId("2"), points: polygon2, fillColor: Colors.transparent, strokeColor: Colors.red, strokeWidth: 4));
});
},
),

polygons 변수에 Polygon을 두 개 추가하였습니다. 이렇게 하면 2개의 다각형이 그려집니다.

다각형 그리기 실행 결과

9–2. Polygon(Multi Polygon) Hole 구현

잇차 앱에서와 같이 서비스지역을 표현하기 위해 다른 지역음 음영처리 하고 서비스 지역만 활성화하는 형태로 구현 하려면 아래와 같습니다.

ElevatedButton(
child: const Text('다각형-반전'),
onPressed: () {
List<LatLng> polygon1 = const [
LatLng(37.3625806, 126.9248464),
LatLng(37.3626138, 126.9264801),
LatLng(37.3632727, 126.9280313),
];

List<LatLng> polygon2 = const [
LatLng(37.36119, 126.9193982),
LatLng(37.3534215, 126.9295909),
LatLng(37.3549206, 126.9327015),
];

setState(() {
polygons.add(
Polygon(
polygonId: const PolygonId("1"),
points: createOuterBounds(),
holes: [polygon1, polygon2],
fillColor: Colors.black38,
strokeColor: Colors.red,
strokeWidth: 4,
),
);
});
},
),

points 에는 createOuterBounds()라는 함수가 들어가는데 이것은 이렇게 구현했습니다.

List<LatLng> createOuterBounds() {
double delta = 0.01;

List<LatLng> list = [];

list.add(LatLng(90 - delta, -180 + delta));
list.add(LatLng(0, -180 + delta));
list.add(LatLng(-90 + delta, -180 + delta));
list.add(LatLng(-90 + delta, 0));
list.add(LatLng(-90 + delta, 180 - delta));
list.add(LatLng(0, 180 - delta));
list.add(LatLng(90 - delta, 180 - delta));
list.add(LatLng(90 - delta, 0));
list.add(LatLng(90 - delta, -180 + delta));

return list;
}

이렇게 하면 일단 지도 전체를 Colors.black38 색상으로 덮게 됩니다. 그 후 다각형을 holes 에 추가하면 해당 하는 지역만 구멍(?)을 낼 수 있습니다.

다각형-반전 그리기 실행 결과

10. 마커 그리기(Marker) API

지도를 사용하면서 가장 많이 사용하는 API 인 것 같습니다. 카카오택시 또는 타다와 같은 서비스에는 출/도착지를 마커로 보여줍니다. 또 직방 또는 다방 같은 경우에는 매물이 있는 위치를 나타내기 위해 마커를 사용하기도 합니다.

타다 경로 및 요금, 시간 정보 안내 화면 / 직방 매물 위치 마커 화면

markers 변수는 Set으로 선언해줍니다. 여기도 중복으로 찍히는 것은 방지 하기 위함입니다. 음식점을 지도 상에 표시해주는 경우 같은 좌표에 여러 음식점이 있을 수는 있습니다. 이럴 경우는 Set이 아니라 List로 선언해 주어야 합니다. 저는 Set으로 해주겠습니다.

Set<Marker> markers = {};

그리고 지도를 선택한 부분에 마커를 그려주었습니다. 지도를 선택하면 해당 좌표를 tap 한 것을 인지 하도록 GoogleMap 위젯의 onTap 에 코드를 작성했습니다.

GoogleMap(
mapType: MapType.normal,
polylines: polylines,
circles: circles,
polygons: polygons,
markers: markers,
initialCameraPosition: initPosition,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
onTap: (latLng) async {
final GoogleMapController controller = await _controller.future;

setState(() {
markers.add(Marker(markerId: MarkerId(latLng.toString()), position: latLng));
controller.animateCamera(CameraUpdate.newLatLng(latLng));
});
},

),

markers 변수에 Marker를 하나 추가하였습니다.

11. 마커 정보창(InfoWindow) API

InfoWindow의 경우는 마커를 터치하면 해당 마커의 정보를 상세하게 보여줄 때 사용하게 됩니다.

직방 매물 위치 마커 화면

직방을 보면 건물마커 위에 “3.3억~3.9억”이라고 되어 있습니다. 언뜻 보면 이 부분이 InfoWindow라고 볼 수 있긴 한데 제가 보기에는 하단의 “동막마을군포제일” 까지 합쳐서 하나의 마커로 만든 것으로 보입니다. InfoWindow의 경우 주로 마커를 터치 했을 때 표시해주는 역할을 합니다. 그리고 다른 마커를 선택하면 기존에 보여졌던 InfoWindow는 종료되고 새롭게 눌려진 마커에 InfoWindow가 보여지게 됩니다. InfoWindow는 하나만 띄우는 형태라고 보시면 됩니다.

markers.add(Marker(
markerId: MarkerId(latLng.toString()),
position: latLng,
infoWindow: InfoWindow(title: "${latLng.latitude}/${latLng.longitude}"),
));

위 소스를 보면 알 수 있듯이 Marker에 infoWindow를 추가해 주는 형태 입니다. 이렇게 하면 InfoWindow를 볼 수 있습니다.

마커 선택시 InfoWindow 실행 결과

InfoWindow를 사용하면 상세 정보를 표시해줄 수 있기 때문에 음식점이나 매장 마커를 보여주고 마커가 선택되면 해당 상세 정보를 보여줄 수 있습니다. 하지만 위에서 봤던 직방과 같이 구현하기 위해서는 InfoWindow가 아닌 Custom 마커를 사용해야 합니다.

12. 추가 기능

구글 지도에 직선, 원, 다각형을 어떻게 그릴 수 있는지 살펴 보았습니다. 샘플 앱을 만들면서 제가 추가적으로 한 몇가지 기능이 있습니다. 직선 버튼을 눌렀다가 원을 누르면 직선이 그려진 것 위에 원이 그려져서 중첩이 됩니다. 이것을 초기화 시키기 위해서 지워주는 작업을 진행했습니다.

clearMap() {
polylines.clear();
circles.clear();
polygons.clear();
}

이 함수를 만들어서 직선, 원, 다각형을 그리지 직전에 한 번 호출해서 지우도록 했습니다. 또 모든 좌표들을 화면에 보여지도록 하기 위한 작업도 진행했습니다.

fitBounds(List<LatLng> bounds) async {
final GoogleMapController controller = await _controller.future;

controller.animateCamera(CameraUpdate.newLatLngBounds(boundsFromLatLngList(bounds), 0));
}

LatLngBounds boundsFromLatLngList(List<LatLng> list) {
assert(list.isNotEmpty);

double? x0, x1, y0, y1;
for (LatLng latLng in list) {
if (x0 == null) {
x0 = x1 = latLng.latitude;
y0 = y1 = latLng.longitude;
} else {
if (latLng.latitude > x1!) x1 = latLng.latitude;
if (latLng.latitude < x0) x0 = latLng.latitude;
if (latLng.longitude > y1!) y1 = latLng.longitude;
if (latLng.longitude < y0!) y0 = latLng.longitude;
}
}
return LatLngBounds(northeast: LatLng(x1!, y1!), southwest: LatLng(x0!, y0!));
}

직선, 원, 다각형 버튼을 누르면 그리기를 한 후에 fitBounds 함수를 호출하도록 구현하였습니다. 아래와 같이 구현되는 것을 볼 수 있습니다.

다각형-반전 눌렀을 때 그리기 후 fitBounds 실행 결과

13. 전체소스

위 샘플을 github에 올려두었습니다.

프로젝트 받으셔서 실행시켜 보시면 어떻게 실행되는지 볼 수 있습니다. 다만 여기서 한가지 추가로 말씀드리면 Google Map API KEY 를 개인이 발급받아서 추가해 주셔야 합니다.

13–1. Android

android/local.properties 파일 내부에 google.api.key 추가 필요

google.api.key=KEY를_적어주세요

13–2. iOS

ios/Runner/Constants.swift 파일 내부에 googleApiKey에 본인의 키를 넣어주세요

let googleApiKey = "KEY를 적어주세요"

이렇게 하면 Android, iOS에서 구글지도를 확인 할 수 있습니다.

14. 정리

소스파일을 보시면 아시겠지만… 간단한 소스입니다. 그런데 설명을 위해 이런저런 내용을 추가하다 보니 좀 길어졌습니다😭 예제로 만든 앱은 상태관리를 단순하게 setState로 하였습니다. 저는 간단하게 구현했기 때문에 setState를 사용했지만 BLoC, Provider, Riverpod, GetX 등 상태관리를 사용하여 구현 할 수도 있습니다. 이 부분을 추 후에 포스팅 할 수 있을지 없을지는 장담은 못하겠습니다…🤔

제가 잘 못 이해했거나 궁금한 점은 댓글로 알려주세요~ 잘못 된 부분을 지적받는 것 또한 저에게 큰 도움이 됩니다!

감사합니다! 👏🏻👏🏻👏🏻

--

--