GeoFire retrieve all queries within a range when moving. - firebase

I'm trying to retrieve all elements from my GeoFire database when moving. For example: when walking around I want to retrieve in realtime the locations of elements around me (moving or not moving).
If I use the following code:
var geoQuery = geoFire.query({
center: [52.35500, 4.931000],
radius: 0.1 //kilometers
});
var onKeyEnteredRegistration = geoQuery.on("key_entered", function(key, location, distance) {
console.log(key + " entered query at " + location + " (" + distance + " km from center)");
});
I only receive updates when the keys are changing position (and entering my query). Is there a possibility to retrieve all elements within a certain range as a sort of a snapshot? And from there monitor realtime?
I could of course query the whole database and then use
GeoFire.distance(location1, location2)
But this looks like a very expensive option.

You can call GeoQuery.updateCriteria(newQueryCriteria) to update the center of the query as you move.
Note that you probably want to register a key_exited callback as well.
Check out the GeoFire API Reference

You don't need to query whole database. You can store your places in a variable and calculate new distances when your current position (query center) changes.
private places: any;
var onKeyEnteredRegistration = geoQuery.on("key_entered", function(key, location, distance) {
this.places.push({ key, location, distance });
});
var onKeyExitedRegistration = geoQuery.on("key_exited", function(key, location, distance) {
this.places = this.places.filter(place => place.key !== key);
});
updatePlacesDistance(currentLocation) {
this.places.map(place => {
place.distance = GeoFire.distance(currentLocation, place.location);
});
}

Related

Update collection with an array in firebase

I need to update a collection in values like this :
{
"email" : "x#gmail.com",
"fullName" : "Mehr",
"locations" : ["sss","dsds","adsdsd"]
}
Locations needs to be an array. in firebase how can I do that ... and also it should check duplicated.
I did like this :
const locations=[]
locations.push(id)
firebase.database().ref(`/users/ + ${userId}`).push({ locations })
Since you need to check for duplicates, you'll need to first read the value of the array, and then update it. In the Firebase Realtime Database that combination can is done through a transaction. You can run the transaction on the locations node itself here:
var locationsRef = firebase.database().ref(`/users/${userId}/locations`);
var newLocation = "xyz";
locationsRef.transaction(function(locations) {
if (locations) {
if (locations.indexOf(newLocation) === -1) {
locations.push(newLocation);
}
}
return locations;
});
As you can see, this loads the locations, ensures the new location is present once, and then writes it back to the database.
Note that Firebase recommends using arrays for set-like data structures such as this. Consider using the more direct mapping of a mathematical set to JavaScript:
"locations" : {
"sss": true,
"dsds": true,
"adsdsd": true
}
One advantage of this structure is that adding a new value is an idempotent operation. Say that we have a location "sss". We add that to the location with:
locations["sss"] = true;
Now there are two options:
"sss" was not yet in the node, in which case this operation adds it.
"sss" was already in the node, in which case this operation does nothing.
For more on this, see best practices for arrays in Firebase.
you can simply push the items in a loop:
if(locations.length > 0) {
var ref = firebase.database().ref(`/users/ + ${userId}`).child('locations');
for(i=0; i < locations.length; i++) {
ref.push(locations[i]);
}
}
this also creates unique keys for the items, instead of a numerical index (which tends to change).
You can use update rather than push method. It would much easier for you. Try it like below
var locationsObj={};
if(locations.length > 0) {
for(i=0; i < locations.length; i++) {
var key= firebase.database().ref(`/users/ + ${userId}`).child('locations').push().key;
locationsObj[`/users/ + ${userId}` +'/locations/' + key] =locations[i];
}
firebase.database().ref().update(locationsObj).then(function(){// which return the promise.
console.log("successfully updated");
})
}
Note : update method is used to update multiple paths at a same time. which will be helpful in this case, but if you use push in the loop then you have to wait for the all the push to return the promises. In the update method it will take care of the all promises and returns at once. Either you get success or error.

Save location with geofire on an existing document

I want an using firebase real time database to store events of some sort.
An event can have a changing status in its life cycle.
I want to be able to fetch all active events near a user current location.
Because it doesn't seems like geofire enables me to save the location on the event document, I would have to get all events near the location, and then filter by status and given event ids (is that event possible??).
Is there a better way?
Thanks!
Geofire works by returning what's close to coordinates you give it. For an idea or starter template, check out https://getaa.org. (click the map icon top center and enter 'san francisco' or 'baton rouge' if you are in an area with no meetings.
The code is all on github.
Here's a code snippet:
function loadQuery(lat,lng,today){
//console.log('loadQuery function fired with ' + lat,lng,today);
var geoKeys = [];
var records=[];
var geoQuery = geofire.query({
center: [lat,lng],
radius: radius
});
var onKeyEnteredRegistration = geoQuery.on("key_entered", function(key, location, distance) {
geoKeys.push(key);
//console.log('geoQuery event KEY ENTERED with key: ' + key);
});
// GeoKeys now in array
var onReadyRegistration = geoQuery.on("ready", function() {
//console.log('geoQuery event READY');
if(geoKeys.length > 0){
toastUp('Fetching Meetings...');
// Get recordset for each key into sites array
readFirebaseNodes(geoKeys).then(function(value) {
//filter for today
var todaysMeetings = dayFilter(today);
drop(todaysMeetings);
}, function(err) {
//console.log(err); // Error!
});
} else {
toastUp('No area meetings found. You are encouraged to volunteer to add them. Click Meetings Manager to become a site administrator.');
toastDown(2000);
}
});
}
The Firebase Realtime Database can only query on a single property. The fact that GeoFire can filter on two axes (longitude and latitude) is already quite magical (thanks to the use of geohashes). Adding one more value to the mix is beyond what GeoFire is made for.
But it's actually quite simple to only show nearby active events: keep a location/reference in your database with just the locations of those active events. Then you can simply create a GeoFire object on that location, and it will only find nearby active events:
// Create a Firebase reference where GeoFire will store its information
var firebaseRef = firebase.database().ref("activeEventLocations");
// Create a GeoFire index
var geoFire = new GeoFire(firebaseRef);

Appmaker Query on Calculated Records

I'm sure I'm doing something wrong... but every time I query on a calculated datasource, I get the error "cannot handle returning cyclic object."
Here's the gist:
I have a calculated model that fetches a user's google contacts and places the full name field into a table on the UI. The goal is to have a separate text box that can be used to search the full name field and then repopulate the table on the same page with the results of the search, similar to how google contacts search behavior works. The on value change event of the text box sends the textbox value to this server script:
function searchContacts (sq) {
var ds = app.models.Contacts.newQuery();
ds.filters.FullName._contains = sq;
var results = ds.run();
return results;
}
But every time I get the cyclic object error when the values are returned from that function. The error actually fires when the query run command (ds.run) is executed.
I've tried querying the datasource as well, but I've read somewhere that you can't query the datasource of a calculated model because it doesn't exist, so you have to query the model.
Any help would be much appreciated.
From your question it is not 100% clear, what you are trying to do. In case you are actually using Calculated Model, then your Server Script Query should look like this:
var sq = query.parameters.SearchQuery;
var contactsQuery = app.models.Contacts.newQuery();
contactsQuery.filters.FullName._contains = sq;
var contacts = ds.run();
var results = contacts.map(function(contact) {
var calcRecord = app.MyCalcModel.newRecord();
calcRecord.Name = contact.FullName;
return calcRecord;
});
return results;
Note, that you cannot return objects of arbitrary type from Server Script Query, only of type of this particular Calculated Model.
But from some parts of your description and error text if feels like you are trying to load records with async serever call using google.scritp.run. In this case you cannot return App Maker records(App Script doesn't allow this) and you need to map them to simple JSON objects.
I don't think I was super-clear on my original post.
I have a calculated model that is all of the user's contacts from Google Contacts (full name, email, mobile, etc...) On the UI I have a list widget that's populated with all of the Full Name fields and above the list widget a text input that's used to search the list widget. So the search text box's on input change event sends a request to query the Full Names, similar to how Google Contact's search feature works.
Screen Shot
It appears that App Maker doesn't let you query calculated models, so I have this workaround - unless someone comes up with something better:
This is the onInputChange handler for the search text box:
sq = app.pages.SelectClient.descendants.TextBox1.value;
app.datasources.SearchContacts.query.parameters.Name = sq;
app.datasources.SearchContacts.load();
This is the Server Script Code (thanks to #Pavel Shkleinik for the heads up):
var sq = query.parameters.Name;
if (sq !== null) {
return getContactsbyName(sq);
} else {
return getContacts();
}
And the server code with no query:
function getContacts() {
var results = [];
var contacts = ContactsApp.getContacts();
contacts.forEach(function(item) {
var contact = app.models.Contacts.newRecord();
contact.FullName = item.getFullName();
var emails = item.getEmails(ContactsApp.Field.WORK_EMAIL);
if (emails.length > 0) {
contact.PrimaryEmail = emails[0].getAddress();
}
contact.LastName = item.getFamilyName();
contact.FirstName = item.getGivenName();
var phones = item.getPhones(ContactsApp.Field.MOBILE_PHONE);
if (phones.length > 0) {
contact.Mobile = phones[0].getPhoneNumber();
}
var addresses = item.getAddresses(ContactsApp.Field.WORK_ADDRESS);
if (addresses.length > 0) {
contact.Address = addresses[0].getAddress();
}
results.push(contact);
results.sort();
});
return results;
}
And with the query:
function getContactsbyName(sq) {
var results = [];
var contacts = ContactsApp.getContactsByName(sq);
contacts.forEach(function(item) {
var contact = app.models.Contacts.newRecord();
contact.FullName = item.getFullName();
var emails = item.getEmails(ContactsApp.Field.WORK_EMAIL);
if (emails.length > 0) {
contact.PrimaryEmail = emails[0].getAddress();
}
contact.LastName = item.getFamilyName();
contact.FirstName = item.getGivenName();
var phones = item.getPhones(ContactsApp.Field.MOBILE_PHONE);
if (phones.length > 0) {
contact.Mobile = phones[0].getPhoneNumber();
}
var addresses = item.getAddresses(ContactsApp.Field.WORK_ADDRESS);
if (addresses.length > 0) {
contact.Address = addresses[0].getAddress();
}
results.push(contact);
results.sort();
});
return results;
}
This way, the list populates with all of the names when there's no search query present, and then re-populates with the search query results as needed.
The only issue is that the call to the Google Contacts App to populate the Calculated Model is sometimes very slow.

Google maps Autocomplete: output only address without country and city

I use Places library to autocomplete address input. Search is limited to only one city, and I get output like this:
"Rossiya, Moskva, Leninskiy prospekt 28"
How to hide "Rossiya, Moskva"? ...
My query:
function() {
// Search bounds
var p1 = new google.maps.LatLng(54.686534, 35.463867);
var p2 = new google.maps.LatLng(56.926993, 39.506836);
self.options = {
bounds : new google.maps.LatLngBounds(p1, p2),
componentRestrictions: {country: 'ru'},
};
var elements = document.querySelectorAll('.address');
for ( var i = 0; i < elements.length; i++) {
var autocomplete = new google.maps.places.Autocomplete(elements[i],
self.options);
}
You can but you have to replace the value of the input field in two places.
Example:
var autocomplete = new google.maps.places.Autocomplete(input, placesOptions);
var input = document.getElementById('searchTextField');
inside the 'place_changed' event you need to do the following:
placeResult = autocomplete.getPlace();
//This will get only the address
input.value = placeResult.name;
This will change the value in the searchtextfield to the street address.
The second place is a bit tricky:
input.addEventListener('blur', function(){
// timeoutfunction allows to force the autocomplete field to only display the street name.
if(placeResult){ setTimeout(function(){ input.value = placeResult.name; }, 1); } });
The reason why we have to do this is because if you only add the event listener for blur, google places will populate the input field with the full address, so you have to 'wait' for google to update and then force your change by waiting some miliseconds.
Try it without the setTimeout function and you will see what I mean.
EDIT
You can't. I had it the other way around, that you were just looking for a city. There is no way to only print out the street name (I'm assuming that's a street name) from the address component.
OPPOSITE OF WHAT WAS ASKED
From the docs:
the (cities) type collection instructs the Place service to return results that match either locality or administrative_area3.
var input = document.getElementById('searchTextField');
var options = {
bounds: defaultBounds,
types: ['(cities)']
};
autocomplete = new google.maps.places.Autocomplete(input, options);
in result u have hash and from it u can get part what u want:
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
now from "place" u can get it
place.geometry.location.lat()
and for address
place.address_components[0] or place.address_components[1] ...
depends on what u want to get
I had a very similar problem which indeed was solvable. This in an Angular 2 project but it should be applicable elsewhere as well. I filter my results for establishments, and wanted to show only the name and hide the address part of the result. This did the trick for me, a function executing once you select a suggestion:
getAddress(place: Object) {
this.zone.run(() => {
this.establishment = place['name'];
});
where zone is an NgZone component injected in the constructor and this.establishment is the variable tied to [(NgModel)] in the input field.
Inside place_changed set a timeout function:
var streetString = place.address_components[0] or place.address_components[1];
window.setTimeout(function() {
$('input').val(streetString);
}, 200);
This solution worked for me.

Removing an individual marker from Google Map - API v3

I want to remove an individual marker from Google map. I am using version 3 API. I know how I can remove all the markers by maintaining a markerArray and setting map null for all.
For removing one by one, I am thinking to make a key value pair combination. So that I give a key and remove the particular marker. I need help over this.
Following is the code, that I use to dram marker:
function geoCodeAddresses(data) {
var markerInfo = {addressKey: '', marker:''};
for (var i = 0; i < data.length; i++) {
myLocation = data[i];
geocoder.geocode({"address":myLocation}, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({map:map, position:results[0].geometry.location});
// checkpoint A
alert(myLocation);
/*
markerInfo.addressKey = myLocation;
markerInfo.marker = marker;*/
//mArray.push(markerInfo);
}
});
}
}
I will search for addresskey and remove the marker from mArray. But I get last value every time in geocode callback method. And one object got pushed every time. the var myLocation always give me the address of the last index of my array. If I alert it at check point A.
My approach is right?
Your problem is this line:
mArray.push(markerInfo);
That doesn't push the values of markerInfo into your array. It pushes a reference to markerInfo into your array. Now, on your next iteration of the loop, when you change the value of markerInfo, it changes the value pointed at by the references in the array too. So your array ends up having elements that all have the same value.
Try this instead:
mArray.push({addressKey:myLocation,marker:marker});
If that doesn't work, then this:
mArray.push({addressKey:data[i],marker:marker});

Resources