"Cannot use multiple queryOrderedBy calls!" in Firebase? - firebase

I have an iOS Swift project is running with PHP back-end. The app gets user location (lng & lat), posts to API (back-end), and receives data back. The data is a list of locations nearby user s' location.
Now, I move to Firebase. The data structure like as image below:
And, the code I tried to get data from Firebase
let radius: Double = 0.7
let lng_min = lng - radius/abs(cos(deg2rad(lat))*69)
let lng_max = lng + radius/abs(cos(deg2rad(lat))*69)
let lat_min = lat - (radius/69)
let lat_max = lat + (radius/69)
ref = FIRDatabase.database().reference()
ref.child("v1")
.queryOrderedByChild("lng")
.queryStartingAtValue(lng_min)
.queryEndingAtValue(lng_max)
.queryOrderedByChild("lat")
.queryStartingAtValue(lat_min)
.queryEndingAtValue(lat_max)
.observeEventType(.Value, withBlock: { snapshot in
print(snapshot.childrenCount)
})
App crashed while run with error: Cannot use multiple queryOrderedBy calls!
How can I query on Firebase like SQL query below:
SELECT lat, lng FROM `table`
WHERE (lng BETWEEN lng_min AND lng_max) //~> lng_min <= lng <= lng_max
AND (lat BETWEEN lat_min AND lat_max)
Thanks in advanced !

Related

firebase search and create list

Kindly help me write this function
I have this list where each driver is listed with
1 current latitude longitude
2 onDuty or not
3 vehicle type
4 driver details
whenever user initiates search
function searches for
1 vehicel type user has chosen
2 AND if driver is on duty
3 AND if driver is within 10 km radius (using latitude and longitude)
and copy all eligible drivers to -> availableDriverList
distance calculating code
function getDistanceFromLatLonInKm(lat1,lon1,lat2,lon2) {
var R = 6371; // Radius of the earth in km
var dLat = deg2rad(lat2-lat1); // deg2rad below
var dLon = deg2rad(lon2-lon1);
var a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
Math.sin(dLon/2) * Math.sin(dLon/2)
;
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c; // Distance in km
return d;
}
function deg2rad(deg) {
return deg * (Math.PI/180)
}
Searching for a radius around a point requires that you perform a range comparison on the longitude and the latitude of each document. In a Firestore query you can only perform a range comparison on a single field, so with your current data structure you cannot perform the query you want.
There is a way to encode longitude and latitude into a single value that can be used for such comparisons, known as a Geohash. If you add a Geohash to each document, you can then perform the query. To learn how to do this, have a look at the Firestore documentation on performing Geoqueries.
I also recommend reading: Query for nearby locations. While for a different database, the limits and thus solution are the same. If you got the time, I also recommend watching this talk I gave a few years ago about how to add geoqueries to Firestore.

Filtering imageCollection from an inicial date to the latest one

I'm working on a google earth engine app. In order to let it run without code changing, i need to specify a date range to filter an ImageCollection. The inicial date should be an arbitrary one, but the last date should be the "latest" available.
It would be nice also if anybody knows how to open a dialogue to let the user specify the inicial date.
var app = function (image)
{
//NDVI
var ndvi = image.normalizedDifference(['B8', 'B4']);
image = image.addBands(ndvi.rename('NDVI'));
return image;
}
var OLI = ee.ImageCollection("COPERNICUS/S2")
.filterDate('2016-10-01','2019-01-29')
.filterMetadata('CLOUDY_PIXEL_PERCENTAGE','less_than',10)
//.map aplica la funcion "app" definida previamente a la ImageCollection
.filterBounds(table)
.map(app);

Unable to convert BLOB to String with Cordova sqlite plugin for offline maps

I'm following this tutorial : https://kuamoto.wordpress.com/2016/02/26/myth-1-cant-make-offline-apps/ to get offline maps in an ionic 2 app based on a sqlite database (.mbtiles). I use Cordova Sqlite plugin to query the database as shown in this repo : https://github.com/wilblack/offline-map-example
The database contains Blob corresponding to a combinaison of x, y and z which come for the map location I want to display.
I've succeeded to open the database and to query it but I got stuck with the following error :
unknown error (Sqlite code 0): Unable to convert BLOB to string, (OS error - 2:No such file or directory)"
It seems to be a common issue, but I only found solutions for Android directly. Here is my Typescript code:
getTileUrl: function(tilePoint, zoom, tile) {
var z = this._getZoomForUrl();
z = Math.round(z);
var x = tilePoint.x;
var y = tilePoint.y;
y = Math.pow(2, z) - y - 1;
var base64Prefix = 'data:image/gif;base64,';
this.mbTilesDB.transaction((tx) => {
tx.executeSql("SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?;", [z, x, y], (tx, res) => {
//Never get here
tile.src = base64Prefix + res.rows.item(0).tile_data;
}, (err, msg) => {
console.log('[MapPage.getTileUrl] error with executeSql', err);
});
}, (err, msg) => {
console.log("[MapPage.getTileUrl] Transaction err:", err);
});
},
_loadTile: function(tile, tilePoint, zoom) {
tile._layer = this;
tile.onload = this._tileOnLoad;
tile.onerror = this._tileOnError;
this.getTileUrl(tilePoint, zoom, tile);
}
The code breaks just after launching the query. If I launch the query inside a Database browser, I obtain a result as a Blob.
The issue is closed to this one: How to set tile_data from .mbtiles file as tiles for leaflet? except that I don't even get a result from the query
Thank you for your help
As far as I know, most JavaScript implementations do not support blobs.
That code assumes that the data in the database is stored as Base64-encoded text (or that the database driver does this conversion automatically).
You have to tell the database to convert the blob into some text format (SELECT hex(tile_data) ...), and then convert that hex string into some useful format in your code.
Cordova-sqlite-ext supports reading BLOBs from pre-populated sqlite databases like .mbtiles-files.
In order to build data-URLs containing tile data you can adapt the following code taken from the README:
```
db.readTransaction(function(tx) {
tx.executeSql("SELECT BASE64(data) AS base64_data FROM MyTable", [], function(tx, resultSet) {
console.log('BLOB data (base64): ' + resultSet.rows.item(0).base64_data);
});
});
```

Meteor calculate distance between users. Server side vs Client Side

I have a situation where I need to calculate the distance between users. In this particular scenario I have:
Employer geolocation - One user one location.
Candidate geolocation - One location for each user but when the employer generates the list of candidates there are multiple candidates.
I'm currently successfully pushing geolocations down the wire and running a rudimentary distance formula on the client side to figure out the distance between the employer and each candidate on the fly and then showing/hiding the candidates, as per the request.
I've been told that I should be running the calculation on the server side and just pushing down a single number, ie. 10 representing 10km, for each candidate. Then running a filter on that number.
So far, I have only pushed collections and fields down the wire. Is it possible to run the formula below on the server side and just pass down one number and 'attach' it to a user?
The second question is, what would be best practice for Meteor?
I'm still learning to code, so apologies if this is a really obvious question.
Client Side
Path: List.js
specialisations = specialisations.filter(function(element){
let distance = Template.instance().distanceFromEmployerFilter.get();
let user = Meteor.users.findOne({_id: element.candidateUserId});
let candidateLat = user && user.profile && user.profile.address && user.profile.address.latitude;
let candidateLong = user && user.profile && user.profile.address && user.profile.address.longitude;
let company = CompanyDetails.findOne({employerUserId: Meteor.userId()});
let companyLat = company && company.latitude;
let companyLong = company && company.longitude;
var R = 6371; // Radius of the earth in km
var dLat = (companyLat-candidateLat) * (Math.PI/180);
var dLon = (companyLong-candidateLong) * (Math.PI/180);
var a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos((candidateLat) * (Math.PI/180)) * Math.cos((companyLat) * (Math.PI/180)) *
Math.sin(dLon/2) * Math.sin(dLon/2)
;
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var distanceInKM = R * c; // Distance in km
if (distanceInKM <= distance) {
return true;
} else {
return false;
}
});
I'd do the filtering on the fetching of the candidates to display. Either as you publish/subscribe on your template or as you fetch in your helper:
Meteor.users.find({
"profile.address" : {
$near: {
$geometry: {
type: "Point" ,
coordinates: [ <Employerlongitude> , <Employerlatitude> ]
},
$maxDistance: <distance in meters>,
$minDistance: <distance in meters>
}}}).fetch();
If address is a 2d index. Specify coordinates in this order: “longitude, latitude.”
from Mongodb docs :
$near Specifies a point for which a geospatial query returns the
documents from nearest to farthest. The $near operator can specify
either a GeoJSON point or legacy coordinate point.
$minDistance & $maxDistance are optional

Firebase query nearby object

In mysql, I was using haversine formula to query nearby object.
Using this formula
Formula
SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin( radians( lat ) ) ) ) AS distance
FROM markers HAVING distance < 25 ORDER BY distance LIMIT 0 , 20;
Which
3959: radius of earth in miles
37,-122 : given lat lng
25: within 25 miles
In Firebase,
Can I store the users lat lng like what I did in mysql?
Create a marker table. id, lat, lng columns and then use the formula to query
Updated
I should ask, what is the way to query nearby using this formula in firebase.
Short answer, not like you want it to.
Firebase essentially has two ways to query for data: by path and by
priority. This is more limited than SQL, and there's a very good
reason for that — our API is carefully designed to only allow
operations we can guarantee to be fast. Firebase is a real-time and
scalable backend, and we want to enable you to build great apps that
can serve millions of users without compromising on responsiveness.
See, what is firebase and deNormalizing data
Also, this SO question is similar.
Response to comment:
Firebase will not calculate the sin( radians(X) ) for you. That's a 'slow' operation. So, you would need to store that information into the data when you save it.
I'm not 100% certain, but you could store the markers and the also store the longitude/latitude in a separate parent.
Root
-> Markers
-> longitude (Use the value as priority) -> MarkerId
-> latitude (Use the value as priority) -> MarkerId
Then you should be able to use bounding to find the Max and Min longitude and latitude.
Use that to query the longitude and latitude paths by priority. If a MarkerId exists in both, you use it.
A quick bit of research found this article on Latitude Longitude Bounding Coordinates
Hey I just finished building a real time google map using firebase and GeoFire. GeoFire is really cool and easy to use. It allows you to query using lon lat and radius. It returns a key that you can use to query your firebase db. You set the key, while you create the geoFire object, to be whatever you want. It is usually a ref that you can use to get the object that is associated with that distance.
Here is a link to geoFire:
https://github.com/firebase/geofire-js
Here is an example use case:
You have a lon lat, that you got using navigator:
var lon = '123.1232';
var lat = '-123.756';
var user = {
name: 'test user',
longitude: lon,
latitude: lat
}
usersRef.push(user).then(function(response) {
var key = response.key;
var coords = [lon, lat];
geoFire.set(key, coords, function(success){
console.log('User and geofire object has been created');
});
})
Now you can query for the user using:
// Set your current lon lat and radius
var geoQuery = geoFire.query({
center: [latitude, longitude],
radius: radiusKm
});
geoQuery.on('key_entered', function(key, location, distance) {
// You can now get the user using the key
var user = firebaseRefUrl + '/' + key;
// Here you can create an array of users that you can bind to a scope in the controller
});
If you are using google maps. I reccomend you use angular-google-maps.
Its a really cool google maps directive that takes in an array of markers and circles. So when ever $scope.markers or $scope.circles change in the controller it will automatically be applied to the map without any messy code. They have very good documentation.
Here is a link:
http://angular-ui.github.io/angular-google-maps/

Resources