Building a Realtime Geolocation App with Hasura GraphQL and PostGIS
Location-based apps are used across industries and have changed the way users interact with the app. For developers though, building a realtime geolocation app is complex on multiple fronts. Some of the complexities involved are:
- Managing a large dataset in the database
- Ease of API consumption
- Realtime frontend rendering
In this post, we will look at building a data-heavy real-time geo-location app with PostGIS and Hasura GraphQL to plot nearby restaurants on a map. At the end, you will realize that Postgres can handle required datasets, GraphQL gives a neat API layer to consume on the client and makes it easy for frontend rendering.
To set up the backend, we will be running a Postgres database, seeding some location data from OpenStreetMap and creating a Postgres function to query nearby
data. On the frontend, we will be running a React app with Apollo Client integration to query for location data using Hasura GraphQL API.
Set up Hasura & Postgres with PostGIS
Alright, let's start with the backend setup.
Step 1: Clone the sample app
git clone https://github.com/karthikvt26/postgis-hasura-map
cd postgis-hasura-map
Step 2: Run GraphQL Engine locally
cd hasura
docker-compose up -d
You can verify if Hasura is running by heading to http://localhost:8080/console
and you should be able to see the console UI.
Step 3: Install Hasura CLI
Head to Hasura CLI docs to setup the CLI on your machine which we will use in the next step for applying migrations.
Step 4: Configure Hasura Endpoint in config.yaml
endpoint: http://localhost:8080
Now we are done setting up Hasura, Postgres with PostGIS extension and configured Hasura CLI as well.
Seeding Location Data
We already have location data exported from OpenStreetMap for Bangalore and nearby localities for the same. We can quickly get started by applying the migrations for the same.
hasura migrate apply
This will create the necessary tables, seed data and metadata for Hasura. Additionally this also creates a Postgres function which we will talk about a little later.
In case you are interested to export a data set for a different city, then follow the steps below. Else skip to Fetching Nearby Data: Postgres Function section below :)
Head to overpass turbo and update the query to the following:
node
[amenity=restaurant]
({{bbox}});
out;
Now select the region of choice to plot the restaurant data and click on Run
at the top. Once you have the locations plotted on the map, click on Export
and choose raw OSM data.
osm2pgsql
We need a way to import this raw data into Postgres. We will make use of a module called osm2pgsql
which reads this raw data and imports into Postgres. Head over to its github repo for installation instructions.
Once you are done setting this up, execute the following command to do the migration:
osm2pgsql --create -d postgres -H <postgres_host> -P 5432 -U postgres restaurants_data.osm
Replace <postgres_host> with the right IP (Note that you are running Postgres in docker and hence the host value is different for different platforms).
Once the import is done, head over to the Hasura Console at http://localhost:8080/console
-> Data tab and Track the table.
Fetching Nearby Data: Postgres Function
Now that we have a dataset ready in Postgres, we need a way to fetch the list of restaurants nearby. The frontend that renders the location map gives the lat/long co-ordinates as input. We will make use of PostGIS, a spatial database extender for Postgres to query nearby data directly in Postgres.
Our dataset is available on planet_osm_point
table. We will make use of the following PostGIS's functions:
- ST_TRANSFORM - changes the coordinates of a geometry from one spatial reference system to another
- ST_DWITHIN - Returns true if the geometries are within the specified distance of one another.
- ST_X and ST_Y - Returns the x and y co-ordinates respectively.
We will create a Postgres function which makes use of these PostGIS functions:
CREATE OR REPLACE FUNCTION public.get_nearby_restaurants(lat double precision, long double precision, bound integer)
RETURNS SETOF nearby_restaurant
LANGUAGE sql
STABLE
AS $function$
SELECT name, ST_X(ST_Transform(way, 4326)) as long, ST_Y(ST_Transform(way, 4326)) as lat
FROM planet_osm_point
WHERE ST_DWITHIN(way, ST_TRANSFORM(ST_SetSRID(ST_Point(long, lat),4326), 3857), bound);
$function$
This function accepts lat, long and a bound range arguments to calculate the locations nearby that bound. The input for these comes from the GraphQL query made from the React app. We will look at this in the next section.
React App Integration
The react app can be started with the Hasura endpoint env.
REACT_APP_HASURA_ENDPOINT=localhost:8080 npm start
If you had successfully applied the data migration, your app should look something similar to this:
Alright, now let's see how this is done.
We need to setup a GraphQL client for React. We chose Apollo Client for this example.
import ApolloClient from 'apollo-boost';
const httpUrl = `http://${ process.env.REACT_APP_HASURA_ENDPOINT }`;
const client = new ApolloClient({
uri: `${httpUrl}/v1/graphql`,
});
export {
client
};
The GraphQL query to fetch the restaurants is pretty straightforward.
query fetchNearbyRestaurants (
$bound: Int!,
$lat: float8!,
$long: float8!
) {
get_nearby_restaurants (args: {
bound: $bound,
lat: $lat,
long: $long,
}) {
name
lat
long
}
}
You can notice that, get_nearby_restaurants
is the Postgres function and name, lat and long are the fields returned by the function. We are passing in the necessary variables for the function arguments.
To render the Map on React, we are using React Leaflet
- https://react-leaflet.js.org.
Summary
We looked at the capabilities of Postgres via functions and it's extensibility via PostGIS. Hasura being a layer over Postgres, leverages all the power of the database and gives a neat GraphQL API for the client to consume.
This app uses OpenStreetMap, although you can make use of Google Places API too. You can read more about integrating with Google Places API with Hasura GraphQL.