2017-10-08 9 views
9

Czy nowa baza danych firewalli z Firebase natywnie obsługuje zapytania geograficzne oparte na lokalizacji? np. Znajdź posty w promieniu 10 mil lub znajdź 50 najbliższych postów?Jak uruchomić geo "w pobliżu" zapytanie z firewallem?

Widzę, że istnieje kilka projektów dla bazy danych bazy danych w czasie rzeczywistym, projekty takie jak geofire - czy można je również dostosować do sklepu firewall?

+4

Możliwy duplikat zapytania [Jak wyszukiwać najbliżej GeoPoints w kolekcji w Firebase Cloud Firestore?] (Https://stackoverflow.com/questions/46607760/how-to-query-closest-geopoints-in-a-collection -in-fire-fire-cloud-firestore) –

Odpowiedz

3

Na dzień dzisiejszy nie ma sposobu na wykonanie takiego zapytania. Istnieją inne pytania w SO związane z nim:

Is there a way to use GeoFire with Firestore?

How to query closest GeoPoints in a collection in Firebase Cloud Firestore?

Is there a way to use GeoFire with Firestore?

W moim bieżącego projektu Android mogą korzystać https://github.com/drfonfon/android-geohash dodać pole geohash podczas Firebase zespół jest rozwijanie natywne wsparcie .

Korzystanie z bazy danych Firebase Realtime, jak sugerowano w innych pytaniach, oznacza, że ​​nie można filtrować wyników z uwzględnieniem lokalizacji i innych pól jednocześnie, co jest głównym powodem, dla którego chcę przełączyć się na Firestore.

14

Można to zrobić, tworząc obwiednię o wielkości mniejszej niż zapytanie. Jeśli chodzi o efektywność, nie mogę z nią rozmawiać.

Uwaga, dokładność lat/long offset ~ 1 mile powinny zostać poddane przeglądowi, ale tutaj jest to szybki sposób, aby to zrobić:

SWIFT 3.0 Wersja

func getDocumentNearBy(latitude: Double, longitude: Double, distance: Double) { 

    // ~1 mile of lat and lon in degrees 
    let lat = 0.0144927536231884 
    let lon = 0.0181818181818182 

    let lowerLat = latitude - (lat * distance) 
    let lowerLon = longitude - (lon * distance) 

    let greaterLat = latitude + (lat * distance) 
    let greaterLon = longitude + (lon * distance) 

    let lesserGeopoint = GeoPoint(latitude: lowerLat, longitude: lowerLon) 
    let greaterGeopoint = GeoPoint(latitude: greaterLat, longitude: greaterLon) 

    let docRef = Firestore.firestore().collection("locations") 
    let query = docRef.whereField("location", isGreaterThan: lesserGeopoint).whereField("location", isLessThan: greaterGeopoint) 

    query.getDocuments { snapshot, error in 
     if let error = error { 
      print("Error getting documents: \(error)") 
     } else { 
      for document in snapshot!.documents { 
       print("\(document.documentID) => \(document.data())") 
      } 
     } 
    } 

} 

func run() { 
    // Get all locations within 10 miles of Google Headquarters 
    getDocumentNearBy(latitude: 37.422000, longitude: -122.084057, distance: 10) 
} 
6

(pierwsze pozwól mi przeprosić za cały kod w tym poście, chciałem tylko, aby każdy czytający tę odpowiedź miał łatwy czas na odtworzenie funkcjonalności.)

Aby zająć się tym samym problemem, OP miał, na początku, dostosowałem GeoFire library do pracy z Firestore (możesz się wiele nauczyć na temat geolokalizacji, patrząc na tę bibliotekę). Wtedy zdałem sobie sprawę, że nie przeszkadzało mi to, że lokalizacje zostały zwrócone w dokładnym kole. Po prostu chciałem znaleźć sposób na "pobliskie" lokalizacje.

Nie mogę uwierzyć, ile czasu zajęło mi zrealizowanie tego, ale można po prostu wykonać zapytanie o podwójną nierówność na polu GeoPoint za pomocą narożnika SW i rogu NE, aby uzyskać lokalizacje w obwiedni wokół punktu środkowego.

Zrobiłem więc funkcję JavaScript podobną do poniższej (jest to w zasadzie wersja JS odpowiedzi Ryana Lee).

/** 
* Get locations within a bounding box defined by a center point and distance from from the center point to the side of the box; 
* 
* @param {Object} area an object that represents the bounding box 
* around a point in which locations should be retrieved 
* @param {Object} area.center an object containing the latitude and 
* longitude of the center point of the bounding box 
* @param {number} area.center.latitude the latitude of the center point 
* @param {number} area.center.longitude the longitude of the center point 
* @param {number} area.radius (in kilometers) the radius of a circle 
* that is inscribed in the bounding box; 
* This could also be described as half of the bounding box's side length. 
* @return {Promise} a Promise that fulfills with an array of all the 
* retrieved locations 
*/ 
function getLocations(area) { 
    // calculate the SW and NE corners of the bounding box to query for 
    const box = utils.boundingBoxCoordinates(area.center, area.radius); 

    // construct the GeoPoints 
    const lesserGeopoint = new GeoPoint(box.swCorner.latitude, box.swCorner.longitude); 
    const greaterGeopoint = new GeoPoint(box.neCorner.latitude, box.neCorner.longitude); 

    // construct the Firestore query 
    let query = firebase.firestore().collection('myCollection').where('location', '>', lesserGeopoint).where('location', '<', greaterGeopoint); 

    // return a Promise that fulfills with the locations 
    return query.get() 
    .then((snapshot) => { 
     const allLocs = []; // used to hold all the loc data 
     snapshot.forEach((loc) => { 
     // get the data 
     const data = loc.data(); 
     // calculate a distance from the center 
     data.distanceFromCenter = utils.distance(area.center, data.location); 
     // add to the array 
     allLocs.push(data); 
     }); 
     return allLocs; 
    }) 
    .catch((err) => { 
     return new Error('Error while retrieving events'); 
    }); 
} 

Funkcja powyżej dodaje również właściwość .distanceFromCenter do każdego elementu danych lokalizacji, która wróciła tak, że można uzyskać zachowanie Circle-jak tylko o sprawdzenie, czy że odległość jest w przedziale chcesz.

Używam dwóch funkcji util w powyższej funkcji, więc tutaj jest również kod dla tych funkcji. (Wszystkie poniższe funkcje są w rzeczywistości dostosowane z biblioteki GeoFire.)

odległość():

/** 
* Calculates the distance, in kilometers, between two locations, via the 
* Haversine formula. Note that this is approximate due to the fact that 
* the Earth's radius varies between 6356.752 km and 6378.137 km. 
* 
* @param {Object} location1 The first location given as .latitude and .longitude 
* @param {Object} location2 The second location given as .latitude and .longitude 
* @return {number} The distance, in kilometers, between the inputted locations. 
*/ 
distance(location1, location2) { 
    const radius = 6371; // Earth's radius in kilometers 
    const latDelta = degreesToRadians(location2.latitude - location1.latitude); 
    const lonDelta = degreesToRadians(location2.longitude - location1.longitude); 

    const a = (Math.sin(latDelta/2) * Math.sin(latDelta/2)) + 
      (Math.cos(degreesToRadians(location1.latitude)) * Math.cos(degreesToRadians(location2.latitude)) * 
      Math.sin(lonDelta/2) * Math.sin(lonDelta/2)); 

    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 

    return radius * c; 
} 

boundingBoxCoordinates(): (Istnieje więcej utils stosowane tu jak dobrze, że mam wklejone poniżej)

/** 
* Calculates the SW and NE corners of a bounding box around a center point for a given radius; 
* 
* @param {Object} center The center given as .latitude and .longitude 
* @param {number} radius The radius of the box (in kilometers) 
* @return {Object} The SW and NE corners given as .swCorner and .neCorner 
*/ 
boundingBoxCoordinates(center, radius) { 
    const KM_PER_DEGREE_LATITUDE = 110.574; 
    const latDegrees = radius/KM_PER_DEGREE_LATITUDE; 
    const latitudeNorth = Math.min(90, center.latitude + latDegrees); 
    const latitudeSouth = Math.max(-90, center.latitude - latDegrees); 
    // calculate longitude based on current latitude 
    const longDegsNorth = metersToLongitudeDegrees(radius, latitudeNorth); 
    const longDegsSouth = metersToLongitudeDegrees(radius, latitudeSouth); 
    const longDegs = Math.max(longDegsNorth, longDegsSouth); 
    return { 
    swCorner: { // bottom-left (SW corner) 
     latitude: latitudeSouth, 
     longitude: wrapLongitude(center.longitude - longDegs), 
    }, 
    neCorner: { // top-right (NE corner) 
     latitude: latitudeNorth, 
     longitude: wrapLongitude(center.longitude + longDegs), 
    }, 
    }; 
} 

metersToLongitudeDegrees().

/** 
* Calculates the number of degrees a given distance is at a given latitude. 
* 
* @param {number} distance The distance to convert. 
* @param {number} latitude The latitude at which to calculate. 
* @return {number} The number of degrees the distance corresponds to. 
*/ 
function metersToLongitudeDegrees(distance, latitude) { 
    const EARTH_EQ_RADIUS = 6378137.0; 
    // this is a super, fancy magic number that the GeoFire lib can explain (maybe) 
    const E2 = 0.00669447819799; 
    const EPSILON = 1e-12; 
    const radians = degreesToRadians(latitude); 
    const num = Math.cos(radians) * EARTH_EQ_RADIUS * Math.PI/180; 
    const denom = 1/Math.sqrt(1 - E2 * Math.sin(radians) * Math.sin(radians)); 
    const deltaDeg = num * denom; 
    if (deltaDeg < EPSILON) { 
    return distance > 0 ? 360 : 0; 
    } 
    // else 
    return Math.min(360, distance/deltaDeg); 
} 

wrapLongitude():

/** 
* Wraps the longitude to [-180,180]. 
* 
* @param {number} longitude The longitude to wrap. 
* @return {number} longitude The resulting longitude. 
*/ 
function wrapLongitude(longitude) { 
    if (longitude <= 180 && longitude >= -180) { 
    return longitude; 
    } 
    const adjusted = longitude + 180; 
    if (adjusted > 0) { 
    return (adjusted % 360) - 180; 
    } 
    // else 
    return 180 - (-adjusted % 360); 
} 
Powiązane problemy