Автоматическое вычисление расстояния по данным географических координат, хранящимся в Firebase Firestore Flutter

#firebase #flutter

#firebase #flutter

Вопрос:

Я сохранил широту и долготу для элементов в базе данных firestore (поля: item_latitude и item_longitude). Таким образом, все элементы имеют широту и долготу. Я могу использовать поток для получения элементов, например:

   Stream<QuerySnapshot> getItems() async* {
   yield* FirebaseFirestore.instance.collection("items").snapshots();
  }
  

Используя StreamBuilder или FutureBuilder, я могу получить отдельные свойства элементов, такие как широта и долгота. У Geolocator есть метод вычисления расстояния, который также является будущим:

 double distance = await geolocator.distanceBetween(lat, long, lat1, long1);
  

Я могу получить текущее местоположение пользователей, и в данном случае это lat1, long1 (что является отдельной записью). Проблема в том, что Strem GetItems получает поток широт и долгот, и для каждого элемента мне нужно рассчитать его текущее расстояние по отношению к текущему местоположению. Это означает, что при переборе элементов, например, в GridView, мне нужно рассчитать и отобразить расстояние.
Я написал этот вопрос абстрактно, чтобы в ответе было указано, как выполнить асинхронное вычисление на основе синхронного потока данных, чтобы, хотя данные отображаются в разделе сборки страницы, вычисления выполняются снаружи, потому что, поскольку это не так, сборка не будет принимать асинхронное вычисление с синхронным.
Мои попытки привели к следующему: Первая попытка:

 child: StreamBuilder(
       stream: FetchItems().getItems(),
       builder: (context, snapshot) {
         if (!snapshot.hasData) {
            return Text("KE");
         }
         if (snapshot.hasData) {
         DocumentSnapshot data = snapshot.data.docs[index];
         double lat = data.data()[Str.ITEM_LATITUDE];
         double long = data.data()[Str.ITEM_LATITUDE];
         return 
         Text(getDistance(usersCurrentLocationLat,usersCurrentLocationLong,lat,long).toString());
       //This fails and returns on the Text place holder the following: Instance of 'Future<dynamic>'
     }
    }),
  

Моя вторая попытка заключается в следующем:

 child: StreamBuilder(
       stream: FetchItems().getItems(),
       builder: (context, snapshot) {
         if (!snapshot.hasData) {
            return Text("KE");
         }
         if (snapshot.hasData) {
         DocumentSnapshot data = snapshot.data.docs[index];
         double lat = data.data()[Str.ITEM_LATITUDE];
         double long = data.data()[Str.ITEM_LATITUDE];
         double x = getDistance(usersCurrentLocationLat,usersCurrentLocationLong,lat,long);
         return Text(x.toString());
       //This fails and gives erro: type 'Future<dynamic>' is not a subtype of type 'double'
     }
    }),
  

Дальнейшее исследование показывает, что приведенный ниже метод, который используется для получения текущего местоположения и также упоминается в iniState, действительно получает значения (при условии, что Gps включен вне курса):

   _getUserCurrentLocation() {
final Geolocator geolocator = Geolocator()..forceAndroidLocationManager;

geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.best).then(
  (Position position) {
    setState(
      () {
        _currentPosition = position;
        usersCurrentLocationLat = _currentPosition.latitude;
        usersCurrentLocationLong = _currentPosition.longitude;
     //a system print here returns current location as 0.3714267 32.6134379 (same for the 
     //StreamBuilder)
      },
    );
  },
).catchError((e) {
  print(e);
 });
}
  

Ниже приведен метод вычисления расстояния — с использованием предоставленного метода Geolocator distanceBetween().

   getDistance(double lat, double long, double lat1, double long1) async {
    return distance = await geolocator.distanceBetween(lat, long, lat1, long1);
  }

  @override
  void initState() {
  super.initState();
  _getUserCurrentLocation();
  }
  

Как я смогу перебирать элементы, получая их широту и долготу, вычислять расстояние и отображать его в тексте? Это общий вопрос, возможные комбинации решений будут очень приветствоваться. Обратите внимание, что в StreamBuilder я действительно могу печатать для консоли координаты для каждого, используя приведенное ниже:

 print("FROM CURRENT LOCATION HERE ----"   usersCurrentLocationLat.toString()  "::::::::"  
      usersCurrentLocationLong.toString());
print("FROM STREAM FROM DB SURE ----"   lat.toString()  "::::::::"   long.toString());
  

Который печатает в консоли для всех элементов в базе данных как (один пример):

 I/flutter (30351): FROM CURRENT LOCATION HERE ----0.3732317::::::::32.6128083
I/flutter (30351): FROM STREAM FROM DB SURE ----2.12323::::::::2.12323
  

Доказательство того, что координаты действительно получены.
Основная ошибка: тип ‘Future’ не является подтипом типа ‘double’, сохраняется, а текст для отображения расстояния вытянут красным. Руководство, если можно, по наилучшему подходу — это может помочь кому-то и в будущем.

Комментарии:

1. что index используется в snapshot.data.docs[index] ? какой диапазон значений можно использовать для этого?

2. для потока, FetchItems().GetItems(), для каждого снимка мы выполняем итерацию с индексом, подобным: DocumentSnapshot data = snapshot.data.docs[index]; откуда берется индекс: StaggeredGridView.countBuilder( itemBuilder: (BuildContext context, int index) Элементы отображаются в виде сетки. Проблема в том, что тип ‘Future’ не является подтипом типа ‘double’ — как в том, как получить future в getDistance (поскольку он имеет await) и передать то же самое, что и double в текстовом отображении.

3. чему равно значение index ? это 0, 100, 10000? какой диапазон значений можно использовать для этого?

4. в сетке мы сначала получаем количество, поэтому, если элементов 3 или 4 или n, мы выполняем итерацию и показываем то, что мы хотим. ItemCount: элементы. передается длина: StaggeredGridView.countBuilder( scrollDirection: Axis.vertical, shrinkWrap: true, контроллер: ScrollController, crossAxisCount: 4, ItemCount: элементы. длина, itemBuilder: (контекст BuildContext, индекс int)

5. итерация с индексом работает отлично, поэтому вывод на консоль работает идеально, так что, если в моем приложении я делаю выбор из разных элементов, геокоординаты будут отображаться на консоли, а не в тексте (вычисление расстояния не работает в основном потому, что оно ожидает будущего.

Ответ №1:

Мой подход заключается в следующем:

Запрашивайте все документы следующим образом:

 //This a synchronus operation    
final data = await Firestore.instance
            .collection('collection_name')
            .getDocuments();
  

Затем передайте все документы в список:
Поскольку DocumentSnapshot является List<dynamic>

 List doc = data.documents;
  

теперь вычислите расстояние для каждого LatLng в вашем firestore текущем местоположении LatLng и продолжайте добавлять его к пустому, list которое на итерации вы можете использовать в gridView :

 List distanceList=[]; //define and empty list
doc.forEach((e){
double lat=e.data[ITEM_LATITUDE]; //asuming ITEM_LATITUDE is the field name in firestore doc.
double lng=e.data[ITEM_LONGITUDE];//asuming ITEM_LONGITUDE is the field name in firestore doc.
distanceList.add(your distance calculating function); //call this inside an async function if you are using await;
});
  

Вместо использования асинхронности gelocator.distance я написал функцию в dart , используя формулу ‘haversine’, чтобы найти кратчайшую функцию:

 double calculateDistance (double lat1,double lng1,double lat2,double lng2){
    double radEarth =6.3781*( pow(10.0,6.0));
    double phi1= lat1*(pi/180);
    double phi2 = lat2*(pi/180);
    
    double delta1=(lat2-lat1)*(pi/180);
    double delta2=(lng2-lng1)*(pi/180);
    
    double cal1 = sin(delta1/2)*sin(delta1/2) (cos(phi1)*cos(phi2)*sin(delta2/2)*sin(delta2/2));
    
   double cal2= 2 * atan2((sqrt(cal1)), (sqrt(1-cal1)));
    double distance =radEarth*cal2;
    
    return (distance);
    
}
  

Это synchronous function и код внутри firestore doc.forEach(); функции списка будет выглядеть следующим образом:

 List distanceList; //define and empty list
doc.forEach((e){
double lat=e.data[ITEM_LATITUDE]; //asuming ITEM_LATITUDE is the field name in firestore doc.
double lng=e.data[ITEM_LONGITUDE];//asuming ITEM_LONGITUDE is the field name in firestore doc.
double distance = calculateDistance(currentLat,currentLng, lat,lng);
distanceList.add(distance);
});
//Now the distanceList would contain all the shortest distance between 
// current LatLng and all the other LatLng in your firestore documents:
  

Не забудьте очистить distanceList перед использованием, чтобы снова сохранить расстояния.


Весь код будет выглядеть следующим образом;

 //Shortest Distance Function definition:
double calculateDistance (double lat1,double lng1,double lat2,double lng2){
double radEarth =6.3781*( pow(10.0,6.0));
double phi1= lat1*(pi/180);
double phi2 = lat2*(pi/180);
    
double delta1=(lat2-lat1)*(pi/180);
double delta2=(lng2-lng1)*(pi/180);
    
double cal1 = sin(delta1/2)*sin(delta1/2) (cos(phi1)*cos(phi2)*sin(delta2/2)*sin(delta2/2));
    
double cal2= 2 * atan2((sqrt(cal1)), (sqrt(1-cal1)));
double distance =radEarth*cal2;
    
return (distance);
    
}

List distanceList; //list defination

// Call this function every time you want to calculate distance between currentLocation and all location in firestore.
void calculateDistanceAndStore(double currentLat, double currentLng) async{
distanceList=[];p;
final data = await Firestore.instance
            .collection('collection_name')
            .getDocuments();
doc.forEach((e){
double lat=e.data[ITEM_LATITUDE]; //asuming ITEM_LATITUDE is the field name in firestore doc.
double lng=e.data[ITEM_LONGITUDE];//asuming ITEM_LONGITUDE is the field name in firestore doc.
    double distance = calculateDistance(currentLat,currentLng, lat,lng);
    distanceList.add(distance);
    });
}
  

Комментарии:

1. позвольте мне попробовать это — я смотрю на это. минута, и я проверю, пожалуйста.

2. @ombiro попробуйте новый код, я исправил ошибки, и расстояние указано в метрах

3. не могли бы вы указать, как расстояние будет передано текстовому виджету?

4. таким образом Text('${documentList[0]}',) , где 0 будет вызывать первое расстояние, а 1 будет вторым элементом списка.

5. Используете ли вы ListView для отображения расстояний?