Firebase query nearby object - firebase

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/

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.

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

"Cannot use multiple queryOrderedBy calls!" in 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 !

how to generate a map based on a given scale [Bing Map]?

I recently started working with [Bing Api] in my webService [wcf] in c #.
I would like to recover a satellite image of a given scale with Bing!
for example
Scale 1:200 (1 centimeter on the map equal 200 centimeters on the world)
Of course I found this function that explains how to calculate the image resolution satellite bing but this is not what I'm looking for ..
Map resolution = 156543.04 meters/pixel * cos(latitude) / (2 ^ zoomlevel)
Here is my function used to generate my bing map, but I do not know what to send parameter to retrieve an image scale of 1:200.
I need :
Scale = 1:200
I search :
int mapSizeHeight = ?
int mapSizeWidth = ?
int zoomLevel = ?
public string GetImageMap(double latitude,double longitude,int mapSizeHeight, int mapSizeWidth, int zoomLevel)
{
string key = "ddsaAaasm5vwsdfsfd2ySYBxfEFsdfsdfcFh6iUO5GI4v";
MapUriRequest mapUriRequest = new MapUriRequest();
// Set credentials using a valid Bing Maps key
mapUriRequest.Credentials = new ImageryService.Credentials();
mapUriRequest.Credentials.ApplicationId = key;
// Set the location of the requested image
mapUriRequest.Center = new ImageryService.Location();
mapUriRequest.Center.Latitude = latitude;
mapUriRequest.Center.Longitude = longitude;
// Set the map style and zoom level
MapUriOptions mapUriOptions = new MapUriOptions();
mapUriOptions.Style = MapStyle.Aerial;
mapUriOptions.ZoomLevel = zoomLevel;
mapUriOptions.PreventIconCollision = true;
// Set the size of the requested image in pixels
mapUriOptions.ImageSize = new ImageryService.SizeOfint();
mapUriOptions.ImageSize.Height = mapSizeHeight;
mapUriOptions.ImageSize.Width = mapSizeWidth;
mapUriRequest.Options = mapUriOptions;
//Make the request and return the URI
ImageryServiceClient imageryService = new ImageryServiceClient();
MapUriResponse mapUriResponse = imageryService.GetMapUri(mapUriRequest);
return mapUriResponse.Uri;
}
If you haven't already, you might want to check out this article on the Bing Maps tile system calculations, within you will find a section discussing ground resolution and map scale. From that article:
map scale = 1 : ground resolution * screen dpi / 0.0254 meters/inch
Depending on which implementation of Bing Maps you use, specifying the view via a precise map scale might not be possible. I think this is due to the fact that you don't have precise control over the zoom level. For example, in the javascript ajax version, you can only specify zoom levels in integer values, so the ground resolution part of the above equation will jump in discreet steps. At the equator, using a zoom level of 21 will give you a scale of 1: 282, and a zoom level of 22 will give you 1:141. Since you can't specify a decimal value for zoom level, it is not possible to get an exact 1:200 scale using the ajax control. I don't have extensive experience with the .net Bing Maps control, so you might want to investigate that API to see if you can specify an arbitrary zoom level.
If you can precisely control the zoom level and know the dpi value, then the 1:200 scale is achievable using the equation described in the above linked article.

Find which tiles are currently visible in the viewport of a Google Maps v3 map

I am trying to build support for tiled vector data into some of our Google Maps v3 web maps, and I'm having a hard time figuring out how to find out which 256 x 256 tiles are visible in the current map viewport. I know that the information needed to figure this out is available if you create a google.maps.ImageMapType like here: Replacing GTileLayer in Google Maps v3, with ImageMapType, Tile bounding box?, but I'm obviously not doing this to bring in traditional pre-rendered map tiles.
So, a two part question:
What is the best way to find out which tiles are visible in the current viewport?
Once I have this information, what is the best way to go about converting it into lat/lng bounding boxes that can be used to request the necessary data? I know I could store this information on the server, but if there is an easy way to convert on the client it would be nice.
Here's what I came up with, with help from the documentation (http://code.google.com/apis/maps/documentation/javascript/maptypes.html, especially the "Map Coordinates" section) and a number of different sources:
function loadData() {
var bounds = map.getBounds(),
boundingBoxes = [],
boundsNeLatLng = bounds.getNorthEast(),
boundsSwLatLng = bounds.getSouthWest(),
boundsNwLatLng = new google.maps.LatLng(boundsNeLatLng.lat(), boundsSwLatLng.lng()),
boundsSeLatLng = new google.maps.LatLng(boundsSwLatLng.lat(), boundsNeLatLng.lng()),
zoom = map.getZoom(),
tiles = [],
tileCoordinateNw = pointToTile(boundsNwLatLng, zoom),
tileCoordinateSe = pointToTile(boundsSeLatLng, zoom),
tileColumns = tileCoordinateSe.x - tileCoordinateNw.x + 1;
tileRows = tileCoordinateSe.y - tileCoordinateNw.y + 1;
zfactor = Math.pow(2, zoom),
minX = tileCoordinateNw.x,
minY = tileCoordinateNw.y;
while (tileRows--) {
while (tileColumns--) {
tiles.push({
x: minX + tileColumns,
y: minY
});
}
minY++;
tileColumns = tileCoordinateSe.x - tileCoordinateNw.x + 1;
}
$.each(tiles, function(i, v) {
boundingBoxes.push({
ne: projection.fromPointToLatLng(new google.maps.Point(v.x * 256 / zfactor, v.y * 256 / zfactor)),
sw: projection.fromPointToLatLng(new google.maps.Point((v.x + 1) * 256 / zfactor, (v.y + 1) * 256 / zfactor))
});
});
$.each(boundingBoxes, function(i, v) {
var poly = new google.maps.Polygon({
map: map,
paths: [
v.ne,
new google.maps.LatLng(v.sw.lat(), v.ne.lng()),
v.sw,
new google.maps.LatLng(v.ne.lat(), v.sw.lng())
]
});
polygons.push(poly);
});
}
function pointToTile(latLng, z) {
var projection = new MercatorProjection();
var worldCoordinate = projection.fromLatLngToPoint(latLng);
var pixelCoordinate = new google.maps.Point(worldCoordinate.x * Math.pow(2, z), worldCoordinate.y * Math.pow(2, z));
var tileCoordinate = new google.maps.Point(Math.floor(pixelCoordinate.x / MERCATOR_RANGE), Math.floor(pixelCoordinate.y / MERCATOR_RANGE));
return tileCoordinate;
};
An explanation: Basically, everytime the map is panned or zoomed, I call the loadData function. This function calculates which tiles are in the map view, then iterates through the tiles that are already loaded and deletes the ones that are no longer in the view (I took this portion of code out, so you won't see it above). I then use the LatLngBounds stored in the boundingBoxes array to request data from the server.
Hope this helps someone else...
For more recent users, it's possible to get tile images from the sample code in the documentation on this page of the Google Maps Javascript API documentation.
Showing Pixel and Tile Coordinates

Resources