
Does your app need to handle geo data like latitude, longitude, or distance between geographic locations?
Then Supabase got you covered again as you can unlock all of this with the PostGIS extension!
In this tutorial you will learn to:
- Enable and use PostGIS extension with Supabase
- Store and retrieve geolocation data
- Use database functions for geo-queries with PostGIS
- Display a Capacitor Google Map with a marker
- Upload files to Supabase Storage and use image transformations
Since there are quite some code snippets we need I've put together the full source code on Github so you can easily run the project yourself!
Ready for some action?
Let's start within Supabase.
Creating the Supabase Project
To get started we need a new Supabase project. If you don't have a Supabase account yet, you can get started for free!
In your dashboard, click "New Project" and leave it to the default settings, but make sure you keep a copy o your Database password!
After a minute your project should be ready, and we can configure our tables and extensions with SQL.
Why PostGIS Extension?
Why do we actually need the PostGIS extension for our Postgres database?
Turns out storing lat/long coordinates and querying them isn't very effective and doesn't scale well.
By enabling this extension, we get access to additional data types like Point
or Polygon
, and we can easily add an index to our data that makes retrieving locations within certain bounds super simpler.
It's super easy to use PostGIS with Supabase as we just need to enable the extension - which is just one of many other Postgres extensions that you can toggle on with just a click!
Defining your Tables with SQL
Adding the PostGIS Extensions
We could enable PostGIS from the Supabase project UI but we can actually do it with SQL
as well, so let's navigate to the SQL Editor from the menu and run the following:
You can now find this and many other extensions under Database -> Extensions:
It's as easy as that, and we can now create the rest of our table structure.
Creating the SQL Tables
For our example, we need one Stores
table so we can add stores with some text and their location.
Additionally, we create a spartial index on the location of our store to make our queries more performant.
Finally, we can also create a new storage bucket for file upload, so go ahead and run the following in the SQL Editor:
For our tests, I also added some dummy data. Feel free to use mine or use coordinates closer to you:
To wrap this up we define 2 database functions:
nearby_stores
will return a list of all stores and their distance to a lat/long placestores_in_view
uses more functions likeST_MakeBox2D
to find all locations in a specific box of coordinates
Those are some powerful calculations, and we can easily use them through the PostGIS extension and by defining database functions like this:
With all of that in place we are ready to build a powerful app with geo-queries based on our Supabase geolocation data!
Working with Geo Queries in Ionic Angular
Setting up the Project
We are not bound to any framework, but in this article, we are using Ionic Angular to build a cross-platform application.
Additionally we use Capacitor to include a native Google Maps component and to retrieve the user location.
Get started by bringing up a new Ionic project, then add two pages and a service and run the first build so we can generate the native platforms with Capacitor.
Finally we can install the Supabase JS package, so go ahead and run:
Within the new project we need to add our Supabase credentials and a key for the Google Maps API to the src/environments/environment.ts like this:
You can find those values in your Supabase project by clicking on the Settings icon and then navigating to API where it shows your Project API keys.
The Google Maps API key can be obtained from the Google Cloud Platform where you can add a new project and then create credentials for the Maps Javascript API.
Native Project Configuration
To use the Capacitor plugin we also need to update the permissions of our native projects, so within the ios/App/App/Info.plist we need to include these:
Additionally, we need to add our Maps Key to the android/app/src/main/AndroidManifest.xml:
Finally also add the required permissions for Android in the android/app/src/main/AndroidManifest.xml at the bottom:
You can also find more information about using Capacitor maps with Ionic in my Ionic Academy!
Finding Nearby Places with Database Functions
Now the fun begins, and we can start by adding a function to our src/app/services/stores.service.ts that calls the database function (Remote Procedure Call) that we defined in the beginning:
This should return a nice list of StoreResult
items that we can render in a list.
For that, let's display a modal from our src/app/home/home.page.ts:
We also need a button to present that modal, so change the src/app/home/home.page.html to include one:
Now we are able to use the getNearbyStores
from our service on that modal page, and we also load the current user location using Capacitor.
Once we got the user coordinates, we pass them to our function and PostGIS will do its magic to calculate the distance between us and the stores of our database!
Go ahead and change the src/app/nearby/nearby.page.ts to this now:
At this point, you can already log the values, but we can also quickly display them in a nice list by updating the src/app/nearby/nearby.page.html to:
If you open the modal, you should now see a list like this after your position was loaded:
It looks so easy - but so many things are already coming together at this point:
- Capacitor geolocation inside the browser
- Supabase RPC to a stored database function
- PostGIS geolocation calculation
We will see more of this powerful extension soon, but let's quickly add another modal to add our own data.
Add Stores with Coordinates to Supabase
To add data to Supabase we create a new function in our src/app/services/stores.service.ts:
Notice how we convert the lat/long information of an entry to a string.
This is how PostGIS expects those values!
We use our Supabase storage bucket to upload an image file if it's included in the new StoreEntry
. It's almost too easy and feels like cheating to upload a file to cloud storage in just three lines...
Now we need a simple modal, so just like before we add a new function to the src/app/home/home.page.ts:
That function get's called from another button in our src/app/home/home.page.html:
Back in this new modal, we will define an empty StoreEntry
object and then connect it to the input fields in our view.
Because we defined the rest of the functionality in our service, we can simply update the src/app/store/store.page.ts to:
The view is not really special and simply holds a bunch of input fields that are connected to the new store
entry, so bring up the src/app/store/store.page.html and change it to:
As a result, you should have a clean input modal:
Give your storage inserter a try and add some places around you - they should be available in your nearby list immediately!
Working with Google Maps and Marker
Adding a Map
Now we have some challenges ahead: adding a map, loading data, and creating markers.
But if you've come this far, I'm sure you can do it!
Get started by adding the CUSTOM_ELEMENTS_SCHEMA
to the src/app/home/home.module.ts which is required to use Capacitor native maps:
In our src/app/home/home.page.ts we can now create the map by passing in a reference to a DOM element and some initial settings for the map and of course your key.
Update the page with our first step that adds some new variables:
The map needs a place to render, so we can now add it to our src/app/home/home.page.html and wrap it in a div to add some additional styling later:
Because the Capacitor map essentially renders behind your webview inside a native app, we need to make the background of our current page invisible.
For this, simply add the following to the src/app/home/home.page.scss:
Now the map should fill the whole screen.
This brings us to the last missing piece…
Loading Places in a Box of Coordinates
Getting all stores is usually too much - you want to show what's nearby to a user, and you can do this by sending basically a box of coordinates to our previously stored database function.
For this, we first add another call in our src/app/services/stores.service.ts:
Nothing fancy, just passing those values to the database function.
The challenging part is now listening to map boundary updates, which happen whenever you slightly touch the list.
Because we don't want to call our function 100 times in one second, we use a bit of RxJS to delay the update of our coordinates so the updateStoresInView
function is called after the user finished swiping the list.
At that point, we grab the map bounds and call our function, so go ahead and update the src/app/home/home.page.ts with the following:
We can also fill one of our functions with some code as we already used the Geolocation
plugin to load users' coordinates before, so update the function to:
Now we are loading the user location and zooming in to the current place, which will then cause our updateStoresInView
function to be triggered and we receive a list of places that we just need to render!
Displaying Marker on our Google Map
You can already play around with the app and log the stores after moving the map - it really feels magical how PostGIS returns only the elements that are within the box of coordinates.
To actually display them we can add the following function to our src/app/home/home.page.ts now:
This function got a bit longer because we need to manage our marker information. If we just remove and repaint all markers, it looks and feels horrible so we always keep track of existing markers and only render new markers.
Additionally, these Marker
have limited information, and if we click a marker we want to present a modal with information about the store from Supabase.
That means we also need the real ID of that object, and so we create an array activeMarkers
that basically connects the information of a store ID with the marker ID!
At this point, you should be able to see markers on your map. If you can't see them, zoom out and you might find them.
To wrap this up, let's take a look at one more cool Supabase feature.
Presenting Marker with Image Transform
We have the marker and store ID, so we can simply load the information from our Supabase database.
Now a store might have an image, and while we download the image from our storage bucket we can use image transformations to get an image exactly in the right dimensions to save time and bandwidth!
For this, add two new functions to our src/app/services/stores.service.ts:
To use image transformations we only need to add an object to the getPublicUrl()
function and define the different properties we want to have.
Again, it's that easy.
Now we just need to load this information when we click on a marker, so add the following function to our src/app/home/home.page.ts which handles the click on a map marker:
We simply load the information and image and set this to our selectedStore
variable.
This will now be used to trigger an inline modal, so we don't need to come up with another component and can simply define our Ionic modal right inside the src/app/home/home.page.html like this:
Because we also used breakpoints
and the initialBreakpoint
properties of the modal we get this nice bottom sheet modal UI whenever we click on a marker:
And with that, we have finished our Ionic app with Supabase geo-queries using PostGIS!
Conclusion
I was fascinated by the power of this simple PostGIS extension that we enabled with just one command (or click).
Building apps based on geolocation data is a very common scenario, and with PostGIS we can build these applications easily on the back of a Supabase database (and auth ), and storage, and so much more..)
You can find the full code of this tutorial on Github where you just need to insert your own Supabase instance. your Google Maps key and then create the tables with the included SQL file.
If you enjoyed the tutorial, you can find many more tutorials and courses on Galaxies.dev where I help modern web and mobile developers build epic apps 🚀
Until next time and happy coding with Supabase!