---
title: 파스와 플러터로 지리 쿼리 수행하기: GeoPoint 활용
slug: docs-kr/flutter/parse-sdk/data-objects/flutter-geolocation
description: 플러터와 파스에서 GeoPoint 데이터 타입을 사용해 정확한 지리 쿼리를 실행하는 방법을 살펴봅니다.
image: https://www.back4app.com/images/logo-social.png
createdAt: 2024-03-18T12:33:18.194Z
---

# 플러터 지리 위치를 사용하여 파스에서 지리 쿼리 수행하기

## 소개

이 가이드에서는 파스의 GeoPoint 데이터 유형과 플러터 지리 위치를 사용하여 파스에서 지리 쿼리를 수행하는 방법에 대해 배웁니다. 지리 쿼리를 수행하고 <font color="#2166ae">`파스 지리 포인트`</font>을 사용하여 레코드를 검색하는 애플리케이션을 만들 것입니다.

이 가이드의 주요 초점이 플러터와 파스에 있기 때문에 플러터 애플리케이션 코드는 설명하지 않겠습니다.

## 전제 조건

**이 튜토리얼을 완료하려면 다음이 필요합니다:**

:::hint{type="info"}
- Back4App에서 생성된 앱입니다.
  - **참고:&#x20;**&#xB2E4;음의 [새 Parse 앱 튜토리얼](https://www.back4app.com/docs/get-started/new-parse-app)을 따라 Back4App에서 Parse 앱을 만드는 방법을 배우세요.
- Back4app에 연결된 Flutter 앱입니다.
  - **참고:&#x20;**&#xB2E4;음의 [Flutter 프로젝트에 Parse SDK 설치하기](https://www.back4app.com/docs/flutter/parse-sdk/parse-flutter-sdk)를 따라 Back4App에 연결된 Flutter 프로젝트를 만드세요.
- Android 또는 iOS를 실행하는 장치(또는 가상 장치)입니다.
- 이 가이드 예제를 실행하려면 [Geolocator](https://pub.dev/packages/geolocator) 플러그인을 올바르게 설정해야 합니다. 장치 위치에 접근하기 위해 Android 및 iOS에 대한 권한을 추가하는 것을 잊지 마세요 \{: .btn target=”\_blank” rel=”nofollow noreferer noopener”} 프로젝트에서. Android 및 iOS 프로젝트 설정에 대한 지침을 주의 깊게 읽으세요.
:::

## 목표

Back4App 및 Flutter 지리 위치를 사용하여 지오쿼리를 수행합니다.

## GeoPoint 데이터 유형

Parse는 실제 세계의 위도 및 경도 좌표를 객체와 연관시킬 수 있도록 합니다. <font color="#2166ae">`ParseGeoPoint`</font>을 <font color="#2166ae">`ParseObject`</font>에 추가하면 객체와 기준점 간의 근접성을 고려하여 쿼리를 수행할 수 있습니다. 이 기능을 통해 다른 사용자와 가장 가까운 사용자를 찾거나 사용자에게 가장 가까운 장소를 쉽게 찾을 수 있습니다.

객체와 점을 연관시키려면 먼저 <font color="#2166ae">`ParseGeoPoint`</font>을 생성해야 합니다. 아래에서 위도가 40.0도이고 경도가 -30.0도인 지오포인트를 찾을 수 있습니다.

:::BlockQuote
**최종** 포인트 **=** ParseGeoPoint(latitude: 40.0, longitude: 30.0);
:::

이 포인트는 다른 데이터 유형(문자열, 숫자, 날짜 등)과 마찬가지로 객체에 일반 필드로 저장됩니다.

:::BlockQuote
placeObjec&#x74;**.**&#x73;et("location", point);
**await** placeObjec&#x74;**.**&#x73;ave();
:::

\*\* 참고: 현재 클래스에서 하나의 키만 ParseGeoPoint일 수 있습니다.

## 1 - QueryBuilder 클래스

모든 Parse 쿼리 작업은 <font color="#2166ae">`QueryBuilder`</font> 객체 유형을 사용하며, 이는 앱 전반에 걸쳐 데이터베이스에서 특정 데이터를 검색하는 데 도움이 됩니다. 새 <font color="#2166ae">`QueryBuilder`</font>를 생성하려면 원하는 <font color="#2166ae">`ParseObject`</font> 하위 클래스를 매개변수로 전달해야 하며, 이는 쿼리 결과를 포함할 것입니다.

알아야 할 중요한 점은 <font color="#2166ae">`QueryBuilder`</font>는 retrieve 메서드를 호출한 후에만 해결된다는 것입니다 <font color="#2166ae">`query`</font>, 따라서 쿼리를 설정하고 실제로 호출되기 전에 여러 수정자를 연결할 수 있습니다. <font color="#2166ae">`QueryBuilder`</font> 클래스에 대한 자세한 내용은 [여기에서 공식 문서](https://github.com/parse-community/Parse-SDK-Flutter/tree/master/packages/flutter#complex-queries)를 참조하세요.

## Back4App에서 JavaScript 콘솔 사용하기

Back4App 애플리케이션의 대시보드 내에서 JavaScript 코드를 직접 실행할 수 있는 매우 유용한 API 콘솔을 찾을 수 있습니다. 이 가이드에서는 Back4App에 데이터 객체를 저장하는 데 사용할 것입니다. 앱의 주요 대시보드에서 <font color="#2166ae">`Core->API Console->Javascript`</font>로 이동하세요.

![](https://api.archbee.com/api/optimize/yD3zCY-NNBBIfd0uqcfR5/a_0axHyd4EviC1lkQUYl6_image.png)

## 2 - Back4app에 데이터 저장하기

이 가이드에서 쿼리를 실행하려면 먼저 앱에 데이터를 채워야 합니다. <font color="#2166ae">`City`</font> 클래스를 생성할 것이며, 이는 이 가이드에서 우리의 쿼리의 대상이 될 것입니다. 다음은 <font color="#2166ae">`Parse.Object`</font> 클래스 생성 코드입니다. API 콘솔에서 실행해 보세요:

```javascript
1   // Add City objects and create table
2   // Note how GeoPoints are created, passing latitude and longitude as arguments
3   // Montevideo
4   City = new Parse.Object('City');
5   City.set('name', 'Montevideo - Uruguay');
6   City.set('location', new Parse.GeoPoint(-34.85553195363169, -56.207280375137955));
7   await City.save();
8
9   // Brasília
10  City = new Parse.Object('City');
11  City.set('name', 'Brasília - Brazil');
12  City.set('location', new Parse.GeoPoint(-15.79485821477289, -47.88391074690196));
13  await City.save();
14
15  // Bogotá
16  City = new Parse.Object('City');
17  City.set('name', 'Bogotá - Colombia');
18  City.set('location', new Parse.GeoPoint(4.69139880891712, -74.06936691331047));
19  await City.save();
20
21  // Mexico City
22  City = new Parse.Object('City');
23  City.set('name', 'Mexico City - Mexico');
24  City.set('location', new Parse.GeoPoint(19.400977162618933, -99.13311378164776));
25  await City.save();
26
27  // Washington, D.C.
28  City = new Parse.Object('City');
29  City.set('name', 'Washington, D.C. - USA');
30  City.set('location', new Parse.GeoPoint(38.930727220189944, -77.04626261880388));
31  await City.save();
32  
33  // Ottawa
34  City = new Parse.Object('City');
35  City.set('name', 'Ottawa - Canada');
36  City.set('location', new Parse.GeoPoint(45.41102167733425, -75.695414598736));
37  await City.save();
38
39  // Tokyo
40  City = new Parse.Object('City');
41  City.set('name', 'Tokyo - Japan');
42  City.set('location', new Parse.GeoPoint(35.6897, 139.6922));
43  await City.save();
44
45  // Mumbai
46  City = new Parse.Object('City');
47  City.set('name', 'Mumbai - India');
48  City.set('location', new Parse.GeoPoint(18.9667, 72.8333));
49  await City.save();
50
51  // Shanghai
52  City = new Parse.Object('City');
53  City.set('name', 'Shanghai - China');
54  City.set('location', new Parse.GeoPoint(31.1667, 121.4667));
55  await City.save();
56
57  // New York
58  City = new Parse.Object('City');
59  City.set('name', 'New York - USA');
60  City.set('location', new Parse.GeoPoint(40.6943, -73.9249));
61  await City.save();
62
63  // Moscow
64  City = new Parse.Object('City');
65  City.set('name', 'Moscow - Russia');
66  City.set('location', new Parse.GeoPoint(55.7558, 37.6178));
67  await City.save();
68 
69  // Paris
70  City = new Parse.Object('City');
71  City.set('name', 'Paris - France');
72  City.set('location', new Parse.GeoPoint(48.8566, 2.3522));
73  await City.save();
74
75  // Paris
76  City = new Parse.Object('City');
77  City.set('name', 'London - United Kingdom');
78  City.set('location', new Parse.GeoPoint(51.5072, -0.1275));
79  await City.save();
80  
81  // Luanda
82  City = new Parse.Object('City');
83  City.set('name', 'Luanda - Angola');
84  City.set('location', new Parse.GeoPoint(-8.8383, 13.2344));
85  await City.save();
86
87  // Johannesburg
88  City = new Parse.Object('City');
89  City.set('name', 'Johannesburg - South Africa');
90  City.set('location', new Parse.GeoPoint(-26.2044, 28.0416));
91  await City.save();
92
93  console.log('Success!');
```

이 코드를 실행한 후, 데이터베이스에 <font color="#2166ae">`City`</font> 클래스가 생성되어야 합니다. 새 클래스는 다음과 같아야 합니다:

![](https://api.archbee.com/api/optimize/yD3zCY-NNBBIfd0uqcfR5/sgzV_xHxAqb687O4oBItc_image.png)

이제 모든 <font color="#2166ae">`QueryBuilder`</font> 메서드의 예제를 살펴보고, 그들이 하는 일에 대한 간단한 설명을 추가하겠습니다.

## 3 - 데이터 쿼리하기

이제 클래스가 채워졌으므로, GeoPoint 쿼리를 수행할 수 있습니다.

시작하겠습니다 <font color="#2166ae">`도시`</font> 결과를 미국 달라스에서 가장 가까운 순으로 정렬하겠습니다 (위도 32.779167, 경도 -96.808891), <font color="#2166ae">`whereNear`</font> 메서드를 사용하여:

```javascript
1       // Create your query
2       final QueryBuilder<ParseObject> parseQuery =
3           QueryBuilder<ParseObject>(ParseObject('City'));
4
5       // Create our GeoPoint for the query
6       final dallasGeoPoint =
7           ParseGeoPoint(latitude: 32.779167, longitude: -96.808891);
8
9       // `whereNear` will order results based on distance between the GeoPoint
10      // type field from the class and the GeoPoint argument
11      parseQuery.whereNear('location', dallasGeoPoint);
12
13      // The query will resolve only after calling this method, retrieving
14      // an array of `ParseObjects`, if success
15      final ParseResponse apiResponse = await parseQuery.query();
16
17      if (apiResponse.success && apiResponse.results != null) {
18        // Let's show the results
19        for (var o in apiResponse.results! as List<ParseObject>) {
20          print(
21              'City: ${o.get<String>('name')} - Location: ${o.get<ParseGeoPoint>('location')!.latitude}, ${o.get<ParseGeoPoint>('location')!.longitude}');
22        }
23      }
```

이제 <font color="#2166ae">`whereWithinKilometers`</font>, 메서드를 사용하여 쿼리하겠습니다. 이 메서드는 GeoPoint 필드가 최대 거리 내에 있는 모든 결과를 검색합니다.
달라스를 다시 참조로 사용하고 거리 제한은 3000km가 될 것입니다.

```javascript
1       // Create your query
2       final QueryBuilder<ParseObject> parseQuery =
3           QueryBuilder<ParseObject>(ParseObject('City'));
4
5       // Create our GeoPoint for the query
6       final dallasGeoPoint =
7           ParseGeoPoint(latitude: 32.779167, longitude: -96.808891);
8
9       // You can also use `withinMiles` and `withinRadians` the same way,
10      // but with different measuring unities
11      parseQuery.whereWithinKilometers('location', dallasGeoPoint, 3000);
12     //parseQuery.whereWithinMiles('location', dallasGeoPoint, 3000);
13
14      // The query will resolve only after calling this method, retrieving
15      // an array of `ParseObjects`, if success
16      final ParseResponse apiResponse = await parseQuery.query();
17
18      if (apiResponse.success && apiResponse.results != null) {
19        // Let's show the results
20        for (var o in apiResponse.results! as List<ParseObject>) {
21          print(
22              'City: ${o.get<String>('name')} - Location: ${o.get<ParseGeoPoint>('location')!.latitude}, ${o.get<ParseGeoPoint>('location')!.longitude}');
23        }
24      }
```

이제 <font color="#2166ae">`whereWithinMiles`</font> 메서드를 사용하여 쿼리를 수행해 보겠습니다. 이 메서드는 GeoPoint 필드가 최대 거리 내에 있는 모든 결과를 검색합니다.
다시 한 번 댈러스를 참조로 사용하고 거리 제한은 3000마일이 될 것입니다.

```javascript
1       // Create your query
2       final QueryBuilder<ParseObject> parseQuery =
3           QueryBuilder<ParseObject>(ParseObject('City'));
4
5       // Create our GeoPoint for the query
6       final dallasGeoPoint =
7           ParseGeoPoint(latitude: 32.779167, longitude: -96.808891);
8
9       // You can also use `whereWithinKilometers` and `whereWithinRadians` the same way,
10      parseQuery.whereWithinMiles('location', dallasGeoPoint, 3000);
11
12      // The query will resolve only after calling this method, retrieving
13      // an array of `ParseObjects`, if success
14      final ParseResponse apiResponse = await parseQuery.query();
15
16      if (apiResponse.success && apiResponse.results != null) {
17        // Let's show the results
18        for (var o in apiResponse.results! as List<ParseObject>) {
19          print(
20              'City: ${o.get<String>('name')} - Location: ${o.get<ParseGeoPoint>('location')!.latitude}, ${o.get<ParseGeoPoint>('location')!.longitude}');
21        }
22      }
```

## 4 - Flutter에서 쿼리

이제 Flutter 앱 내에서 예제 쿼리를 사용해 보겠습니다. 결과를 보여주는 목록과 쿼리를 호출하는 3개의 버튼이 있는 간단한 인터페이스를 갖추고 있습니다.

앱은 또한 [Geolocator](https://pub.dev/packages/geolocator) 플러그인을 사용하여 장치의 현재 위치를 검색합니다(지침을 따르세요). 따라서 쿼리는 실제 데이터를 사용할 것입니다.

Flutter 프로젝트를 열고, <font color="#2166ae">`main.dart`</font> 파일의 모든 코드를 정리하고 다음으로 교체하세요:

```dart
1   import 'package:flutter/cupertino.dart';
2   import 'package:flutter/material.dart';
3   import 'package:geolocator/geolocator.dart';
4   import 'package:parse_server_sdk_flutter/parse_server_sdk.dart';
5
6   void main() async {
7     WidgetsFlutterBinding.ensureInitialized();
8
9     final keyApplicationId = 'YOUR_APP_ID_HERE';
10    final keyClientKey = 'YOUR_CLIENT_KEY_HERE';
11
12    final keyParseServerUrl = 'https://parseapi.back4app.com';
13
14    await Parse().initialize(keyApplicationId, keyParseServerUrl,
15        clientKey: keyClientKey, debug: true);
16
17    runApp(MaterialApp(
18      title: 'Flutter - GeoPoint',
19      debugShowCheckedModeBanner: false,
20      home: HomePage(),
21    ));
22  }
23  
24  class HomePage extends StatefulWidget {
25    @override
26    _HomePageState createState() => _HomePageState();
27  }
28
29  class _HomePageState extends State<HomePage> {
30    List<ParseObject> results = <ParseObject>[];
31    double selectedDistance = 3000;
32
33    Future<Position> getCurrentPosition() async {
34      bool serviceEnabled;
35      LocationPermission permission;
36  
37      // Test if location services are enabled.
38      serviceEnabled = await Geolocator.isLocationServiceEnabled();
39      if (!serviceEnabled) {
40        return Future.error('Location services are disabled.');
41      }
42
43      permission = await Geolocator.checkPermission();
44      if (permission == LocationPermission.denied) {
45        permission = await Geolocator.requestPermission();
46        if (permission == LocationPermission.denied) {
47          return Future.error('Location permissions are denied');
48        }
49      }
50
51      if (permission == LocationPermission.deniedForever) {
52        return Future.error(
53            'Location permissions are permanently denied, we cannot request permissions.');
54      }
55
56      // When we reach here, permissions are granted and we can
57      // continue accessing the position of the device.
58      return await Geolocator.getCurrentPosition();
59    }
60  
61    void doQueryNear() async {
62      // Create your query
63      final QueryBuilder<ParseObject> parseQuery =
64          QueryBuilder<ParseObject>(ParseObject('City'));
65  
66      // Get current position from device
67      final position = await getCurrentPosition();
68
69      final currentGeoPoint = ParseGeoPoint(
70          latitude: position.latitude, longitude: position.longitude);
71
72      // `whereNear` will order results based on distance between the GeoPoint
73      // type field from the class and the GeoPoint argument
74      parseQuery.whereNear('location', currentGeoPoint);
75  
76      // The query will resolve only after calling this method, retrieving
77      // an array of `ParseObjects`, if success
78      final ParseResponse apiResponse = await parseQuery.query();
79  
80      if (apiResponse.success && apiResponse.results != null) {
81        // Let's show the results
82        for (var o in apiResponse.results! as List<ParseObject>) {
83          print(
84              'City: ${o.get<String>('name')} - Location: ${o.get<ParseGeoPoint>('location')!.latitude}, ${o.get<ParseGeoPoint>('location')!.longitude}');
85        }
86  
87        setState(() {
88          results = apiResponse.results as List<ParseObject>;
89        });
90      } else {
91        setState(() {
92          results.clear();
93        });
94      }
95   }
96  
97    void doQueryInKilometers() async {
98      // Create your query
99      final QueryBuilder<ParseObject> parseQuery =
100         QueryBuilder<ParseObject>(ParseObject('City'));
101 
102     // Get current position from device
103     final position = await getCurrentPosition();
104 
105     final currentGeoPoint = ParseGeoPoint(
106         latitude: position.latitude, longitude: position.longitude);
107 
108     // You can also use `whereWithinMiles` and `whereWithinRadians` the same way,
109     // but with different measuring unities
110     parseQuery.whereWithinKilometers(
111         'location', currentGeoPoint, selectedDistance);
112
113     // The query will resolve only after calling this method, retrieving
114     // an array of `ParseObjects`, if success
115     final ParseResponse apiResponse = await parseQuery.query();
116 
117     if (apiResponse.success && apiResponse.results != null) {
118       // Let's show the results
119       for (var o in apiResponse.results! as List<ParseObject>) {
120         print(
121             'City: ${o.get<String>('name')} - Location: ${o.get<ParseGeoPoint>('location')!.latitude}, ${o.get<ParseGeoPoint>('location')!.longitude}');
122       }
123
124       setState(() {
125         results = apiResponse.results as List<ParseObject>;
126       });
127     } else {
128       setState(() {
129         results.clear();
130       });
131     }
132   }
133
134   void doQueryInMiles() async {
135     // Create your query
136     final QueryBuilder<ParseObject> parseQuery =
137         QueryBuilder<ParseObject>(ParseObject('City'));
138 
139     // Get current position from device
140     final position = await getCurrentPosition();
141 
142     final currentGeoPoint = ParseGeoPoint(
143         latitude: position.latitude, longitude: position.longitude);
144 
145     // You can also use `whereWithinKilometers` and `whereWithinRadians` the same way,
146     parseQuery.whereWithinMiles('location', currentGeoPoint, selectedDistance);
147 
148     // The query will resolve only after calling this method, retrieving
149     // an array of `ParseObjects`, if success
150     final ParseResponse apiResponse = await parseQuery.query();
151 
152     if (apiResponse.success && apiResponse.results != null) {
153     // Let's show the results
154       for (var o in apiResponse.results! as List<ParseObject>) {
155         print(
156             'City: ${o.get<String>('name')} - Location: ${o.get<ParseGeoPoint>('location')!.latitude}, ${o.get<ParseGeoPoint>('location')!.longitude}');
157       }
158
159       setState(() {
160         results = apiResponse.results as List<ParseObject>;
161       });
162     } else {
163       setState(() {
164         results.clear();
165       });
166     }
167   }
168 
169   void doClearResults() async {
170     setState(() {
171       results = [];
172     });
173   }
174 
175   @override
176   Widget build(BuildContext context) {
177     return Scaffold(
178         body: Padding(
179       padding: const EdgeInsets.all(16.0),
180       child: Column(
181         crossAxisAlignment: CrossAxisAlignment.stretch,
182         children: [
183           Container(
184             height: 200,
185             child: Image.network(
186                 'https://blog.back4app.com/wp-content/uploads/2017/11/logo-b4a-1-768x175-1.png'),
187           ),
188           Center(
189             child: const Text('Flutter on Back4app - GeoPoint',
190                 style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
191           ),
192           SizedBox(
193             height: 8,
194           ),
195           Container(
196             height: 50,
197             child: ElevatedButton(
198                 onPressed: doQueryNear,
199                 child: Text('Query Near'),
200                 style: ElevatedButton.styleFrom(primary: Colors.blue)),
201           ),
202           SizedBox(
203             height: 16,
204           ),
205           Center(child: Text('Distance')),
206           Slider(
207             value: selectedDistance,
208             min: 0,
209             max: 10000,
210             divisions: 10,
211             onChanged: (value) {
212               setState(() {
213                 selectedDistance = value;
214               });
215             },
216             label: selectedDistance.toStringAsFixed(0),
217           ),
218           SizedBox(
219             height: 8,
220           ),
221           Container(
222             height: 50,
223             child: ElevatedButton(
224                 onPressed: doQueryInKilometers,
225                 child: Text('Query in Kilometers'),
226                 style: ElevatedButton.styleFrom(primary: Colors.blue)),
227           ),
228           SizedBox(
229             height: 8,
230           ),
231           Container(
232             height: 50,
233             child: ElevatedButton(
234                 onPressed: doQueryInMiles,
235                 child: Text('Query Miles'),
236                 style: ElevatedButton.styleFrom(primary: Colors.blue)),
237           ),
238           SizedBox(
239             height: 8,
240           ),
241           Container(
242             height: 50,
243             child: ElevatedButton(
244                 onPressed: doClearResults,
245                 child: Text('Clear results'),
246                 style: ElevatedButton.styleFrom(primary: Colors.blue)),
247           ),
248           SizedBox(
249             height: 8,
250           ),
251           Text(
252             'Result List: ${results.length}',
253           ),
254           Expanded(
255             child: ListView.builder(
256                 itemCount: results.length,
257                 itemBuilder: (context, index) {
258                   final o = results[index];
259                   return Container(
260                     padding: const EdgeInsets.all(4),
261                     decoration:
262                         BoxDecoration(border: Border.all(color: Colors.black)),
263                     child: Text(
264                         '${o.get<String>('name')} \nLocation: ${o.get<ParseGeoPoint>('location')!.latitude}, ${o.get<ParseGeoPoint>('location')!.longitude}'),
265                   );
266                 }),
267           )
268         ],
269       ),
270     ));
271   }
272 }
```

애플리케이션 ID와 클라이언트 키 자격 증명을 찾으려면 [Back4App 웹사이트](https://www.back4app.com/)로 이동하세요.

코드를 다음과 같이 업데이트하세요 <font color="#2166ae">`main.dart`</font> Back4app의 프로젝트의 ApplicationId와 ClientKey 값으로.

- **keyApplicationId = 앱 ID**
- **keyClientKey = 클라이언트 키**

프로젝트를 실행하면 앱이 이미지와 같이 로드됩니다.

::Image[]{src="https://api.archbee.com/api/optimize/yD3zCY-NNBBIfd0uqcfR5/m8MRUzwQA1gerzIsCY8oA_image.png" size="30" width="318" height="629" position="center" showCaption="false"}

## 결론

이 가이드의 끝에서, 당신은 GeoPoint 데이터 쿼리가 Parse에서 어떻게 작동하는지와 Flutter 앱에서 Back4App에서 이를 수행하는 방법을 배웠습니다.
