-
Notifications
You must be signed in to change notification settings - Fork 0
Basic usage
This guide will attend you through the basic functionality of gof.
Before you can start query locations you need to write them into the database. Gof provides you the three basic CRUD write functions to make this happen. Make sure you always use them, whenever you work with location data.
important: Never use the Firebase reference directly, since this will break the gof Lifecycle! You can learn more about the architecture principle here (coming soon).
So I'm pretty sure in the beginning you want to create new entries. To make this happen you can use the createEntry
method. So let's say you want to create a meetup platform. A possible way to use it would be the following:
// minimal usage
gof.createEntry({ location: { lat: 33.8324, lng: -112.5584 }, title: 'Soccer Meetup' })
.then(data => {
// do something after the entry was created
});
As you can see the data you pass through the entry itself is pretty much up to you. Just make sure to pass a valid location
object, with valid lat
and lng
properties. No worries, finally just the location object will run through the geo queries. Anyways make sure to avoid deep data nesting, since it has negative side effects on the performance.
Let's say you want to gain more control over your data and prioritize it. The createEntry
method takes a priority parameter as a second argument. (1 = highest prio, 5 = lowest prio - default)
Basically, this number is just the character length of the geohash, where the entry will be saved at. You can learn more about geohashing here or even better read the under the hood documentation (coming soon).
// advanced usage
gof.createEntry({ location: { lat: 33.8324, lng: -112.5584 }, title: 'Soccer Meetup' }, 3)
.then(data => {
// do something after the entry was created
});
Now, I can imagine this can be pretty confusing. Why is this useful?
Short answer: This implementation guarantees you a huge performance gain and great UX for the user!
Longs answer: To get an idea how extremely useful this approach is, we need to have a look at the event map demo. As you can see the data gets filtered by the current viewport of the map. (filter by boundaries) So let's imagine we have millions of events around the whole world in our database. What happens if you zoom out the map?
Exactly! You receive more events from your query since the boundaries range gets bigger each time you zoom out.
Technical viewpoint - From a technical viewpoint, we can say this is total nonsense, because it can extremely slow down the query.
UX viewpoint - But also from a UX viewpoint this is total nonsense. To be honest, not a lot of users will use the application on a low zoom level. But the users which do this, surely don't want to paginate through thousands of events. What they expect is to get the best events of this specific area.
That's where the priority
comes in. The priority can be used to show your data based on your own prioritization algorithms.
A simple example would be: All events with a rating of 5 stars will be created in a priority of 1 and all with a rating of 1 will be created in a priority of 5. (I'm sure you have some more complex rating algorithms to make use of)
By the way you can see this behavior also on airbnb. The page says the max amount of entries are 1800, but as you can imagine there much more entries which belong to this area.
The createEntry
method takes also a third argument called maxPrecision
. This gives you the ability to specify the maximum character length of the geohash. (range 7 - 9)
Warning: Just make use of this argument if you exactly know what you!
Once your locations are in the database you probably want to update your data. Gof provides you the updateEntryLocation
method to achieve this. Never update the data without this method and don't be confused about the name, you should also use the method if you want to update other properties then the location. (e.g. title) Updating an event could look similar to this:
gof.updateEntryLocation(
'-Kv06PT7BdhC6cQbFR3v',
{
// new value
location: {
lat: 35.8324,
lng: 104.5584
},
title: 'Volleyball Meetup',
// min and max precison are already set once you recieve the entry from the database - just make sure to leave them
minPrecision: 5,
maxPrecision: 9
}).then(() => {
// executed once the entry was successfully updated
});
As a first argument
, you need to pass the firebase push-key
of the entry you want to update. Make sure the entry exists in the database.
The second argument
takes the updated entry
object. The location needs to be valid and the minPrecision
(same as priority) and the maxPrecision
should not be modified.
The third argument
is optional and holds the priority. Just set it if you want to update the priority of the entry.
The time will come, when you wanna get rid of an entry. For this case Gof provides you the deleteEntry
method. You can use it like this:
gof.deleteEntry('-Kv06PT7BdhC6cQbFR3v')
.then(data => {
// executed once the entry was successfully deleted
});
This method only takes one argument, in fact the firebase push-key of the entry to delete. Make sure the entry you want to delete exists.
Now the time has come to read some entries from the database. Gof provides two query methods to achieve this. You can query the entries by a given location and an according radius or you can query them based on boundaries, often used from the map viewport.
To query data by a given radius you basically need two things. The radius itself in kilometers and a center location to measure from. (circular query) The image below gives you an idea how it works.
As you can see the query would return the locations 1, 2, 3 and 6. Location 4 and 5 are outside the radius and will not be returned. That simple is this. Now let's make it happen with some code.
Gof offers you a method called getLocationsByRadius
to achieve the scenario on the image.
api reference
The method gets called like this:
gof.getLocationsByRadius({ lat: 33.8324, lng: -112.5584 }, 8.5);
The first argument
contains a location object which represents the center to measure from.
The second argument
contains the radius in kilometers. Other units are not supported so far, but you can simply convert your unit to kilometers and pass it.
Now the function above will return a reference and not the expected values of your entries. To make it work you need to chain the function with an event.
So let's do it!
// to receive the value once
gof.getLocationsByRadius({ lat: 33.8324, lng: -112.5584 }, 8.5).once()
.then(data => {
// here you can access the data from the query
});
// to receive the value each time the 'value' event fires
gof.getLocationsByRadius({ lat: 33.8324, lng: -112.5584 }, 8.5).on('value')
.then(data => {
// here you can access the data from the query - will be executed each time the event fires
// don't be scared about how this works - this is a modified promise which is able to stream data
});
You can learn more about the different types of events here or check out how to access and filter the received data here.
There's also a third optional argument startAt
and fourth also optional argument endAt
which gives you the ability to limit the entries to a specified number. So for example startAt = 5
and endAt = 10
would return you the entries 5 - 10.
Warning: Be aware if you use the entry limitation parameters you will not be able to process (filter/sort...) your data in the frontend, since you just receive a certain amount of entries and not all.
The second way to query data is the boundaries approach. In this case you will recieve all the data in given boundaries. (rectangular query) The image below gives you an idea how it works.
As you can see the query would return the locations 1, 2, 3 and 6. Location 4 and 5 are outside the boundaries and will not be returned. You can also see the south west and north east locations are used to create the boundaries.
But now let's dive into the code!
Gof offers you a method called getLocationsByBoundaries
to achieve the scenario on the image.
api reference
The method can be called like this:
gof.getLocationsByBoundaries(
{
sw: {
lat: 33.8224,
lng: -112.5684 ¨
},
ne: {
lat: 33.84924,
lng: -112.5484
}
});
The first and only mandatory argument holds the boundaries object. This needs to contain a sw
property, which holds a valid location for the bottom left border and there should also be a ne
property with a valid location for the top right border.
Now the function above will return a reference and not the expected values of your entries. To make it work you need to chain the function with an event.
So let's do it!
// to receive the value once
gof.getLocationsByBoundaries(
{
sw: {
lat: 33.8224,
lng: -112.5684 ¨
},
ne: {
lat: 33.84924,
lng: -112.5484
}
})
.once()
.then(data => {
// here you can access the data from the query
});
// to receive the value each time the 'value' event fires
gof.getLocationsByBoundaries(
{
sw: {
lat: 33.8224,
lng: -112.5684 ¨
},
ne: {
lat: 33.84924,
lng: -112.5484
}
})
.on('value')
.then(data => {
// here you can access the data from the query - will be executed each time the event fires
// don't be scared about how this works - this is a modified promise which is able to stream data
});
You can learn more about the different types of events here or check out how to access and filter the received data here.
There's also a third optional argument startAt
and fourth also optional argument endAt
which gives you the ability to limit the entries to a specified number. So for example startAt = 5
and endAt = 10
would return you the entries 5 - 10.
Warning: Be aware if you use the entry limitation parameters you will not be able to process (filter/sort...) your data in the frontend, since you just receive a certain amount of entries and not all.
Gof provides you different types of events you can make use of. In order to use them, you need to simply chain them with one of the two query methods. Which looks like this:
gof.getLocationsByRadius({ lat: 33.8324, lng: -112.5584 }, 8.5).on('value').then();
The following events are supported by gof.
event | description |
---|---|
value | Fires on every value change in the query. (add, change, move, remove) |
child_added | Fires each time a child gets added to the query. |
child_changed | Fires each time when a child of the query changes. |
child_removed | Fires each time when a child of the query gets removed |
Once you've received the data you can start working with them. Remember the entries are already queried, this means you can do frontend processing without running into performance issues.
If this is not the case and you run into performance issues make sure to check out the advanced usage guide. It will tell you how to optimize your data structure. I guarantee you, with the right structure it's impossible to run into performance issues.
Since the value is an array we need to loop
over it to work with the data. That's how it's done.
gof.getLocationsByRadius({ lat: 33.8324, lng: -112.5584 }, 8.5).on('value')
.then(events => {
events.forEach(event => {
console.log(event.val()) // use .val() to get the value
});
});
or you can map
the array, which is my prefered way to do it.
gof.getLocationsByRadius({ lat: 33.8324, lng: -112.5584 }, 8.5).on('value')
.then(events => {
events.map(event => {
console.log(event.val()) // use .val() to get the value
});
});
But that's not all. You can make use of literally any array.prototpye
function. Let's say you want to filter your data. With the following code you are good to go.
gof.getLocationsByRadius({ lat: 33.8324, lng: -112.5584 }, 8.5).on('value')
.then(events => {
const filteredEvents = data.filter(val => val.val().title == 'Soccer Meetup');
filteredEvents.map(event => {
console.log(event.val()) // use .val() to get the value
});
});
Notice: if you want to sort more than just a few entries, make sure to write you own sorting function. The built in function is much slower.