mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 12:25:05 +03:00
Merge pull request #11441 from supabase/guide/postgis
guide: Adds PostGIS guide
This commit is contained in:
4 files changed
+312
No files matched your search
@@ -204,6 +204,11 @@ export const menuItems: NavMenu = {
|
||||
url: '/guides/database/extensions/plv8',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'PostGIS: Geo queries',
|
||||
url: '/guides/database/extensions/postgis',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'uuid-ossp: Unique Identifiers',
|
||||
url: '/guides/database/extensions/uuid-ossp',
|
||||
|
||||
@@ -389,6 +389,11 @@ export const database = {
|
||||
url: '/guides/database/extensions/pgnet',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'PostGIS: Geo queries',
|
||||
url: '/guides/database/extensions/postgis',
|
||||
items: [],
|
||||
},
|
||||
{ name: 'pgTAP: Unit Testing', url: '/guides/database/extensions/pgtap', items: [] },
|
||||
{
|
||||
name: 'uuid-ossp: Unique Identifiers',
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'postgis',
|
||||
title: 'PostGIS: Geo queries',
|
||||
description: 'Working with geo-spatial data in Postgres',
|
||||
}
|
||||
|
||||
[PostGIS](https://postgis.net/) is a Postgres extension that allows you to interact with Geo data within Postgres. You can sort your data by geographic location, get data within certain geographic boundaries, and do much more with it.
|
||||
|
||||
## Overview
|
||||
|
||||
While you may be able to store simple lat/long geographic coordinates as a set of decimals, it does not scale very well when you try to query through a large data set. PostGIS comes with special data types that are efficient, and indexable for high scalability.
|
||||
|
||||
The additional data types that PostGIS provides include [Point](https://postgis.net/docs/using_postgis_dbmanagement.html#Point), [Polygon](https://postgis.net/docs/using_postgis_dbmanagement.html#Polygon), [Linestring](https://postgis.net/docs/using_postgis_dbmanagement.html#LineString), and many more to represent different types of geographical data. In this guide, we will mainly focus on how to interact with `Point` type, which represents a single set of latitude and longitude. If you are interested in digging deeper, you can learn more about different data types on the [data management section of PostGIS docs](https://postgis.net/docs/using_postgis_dbmanagement.html).
|
||||
|
||||
## Usage
|
||||
|
||||
### Enable the extension
|
||||
|
||||
You can get started with PostGIS by enabling the PostGIS extension in your Supabase dashboard.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="dashboard"
|
||||
>
|
||||
<TabPanel id="dashboard" label="Dashboard">
|
||||
|
||||
1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
|
||||
2. Click on **Extensions** in the sidebar.
|
||||
3. Search for "postgis" and enable the extension.
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
-- Example: enable the "postgis" extension
|
||||
create extension postgis with schema extensions;
|
||||
|
||||
-- Example: disable the "postgis" extension
|
||||
drop extension if exists postgis;
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Examples
|
||||
|
||||
Now that we are ready to get started with PostGIS, let’s create a table and see how we can utilize PostGIS for some typical use cases. Let’s imagine we are creating a simple restaurant-searching app.
|
||||
|
||||
Let’s create our table. Each row represents a restaurant with its location stored in `location` column as a `Point` type.
|
||||
|
||||
```sql
|
||||
create table if not exists public.restaurants (
|
||||
id int generated by default as identity primary key,
|
||||
name text not null,
|
||||
location geography(POINT) not null
|
||||
);
|
||||
```
|
||||
|
||||
We can then set a [spatial index](https://postgis.net/docs/using_postgis_dbmanagement.html#build-indexes) on the `location` column of this table.
|
||||
|
||||
```sql
|
||||
create index restaurants_geo_index
|
||||
on public.restaurants
|
||||
using GIST (location);
|
||||
```
|
||||
|
||||
### Inserting data
|
||||
|
||||
You can insert geographical data through SQL or through our API.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="data"
|
||||
>
|
||||
<TabPanel id="data" label="Data">
|
||||
|
||||
<h4>Restaurants</h4>
|
||||
|
||||
| id | name | location |
|
||||
| --- | ----------- | -------------------------------- |
|
||||
| 1 | Supa Burger | lat: 40.807416, long: -73.946823 |
|
||||
| 2 | Supa Pizza | lat: 40.807475, long: -73.94581 |
|
||||
| 3 | Supa Taco | lat: 40.80629, long: -73.945826 |
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
insert into public.restaurants
|
||||
(name, location)
|
||||
values
|
||||
('Supa Burger', st_point(-73.946823 40.807416)),
|
||||
('Supa Pizza', st_point(-73.94581 40.807475)),
|
||||
('Supa Taco', st_point(-73.945826 40.80629));
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
const { error } = await supabase.from('restaurants').insert([
|
||||
{
|
||||
name: 'Supa Burger',
|
||||
location: 'POINT(-73.946823 40.807416)',
|
||||
},
|
||||
{
|
||||
name: 'Supa Pizza',
|
||||
location: 'POINT(-73.94581 40.807475)',
|
||||
},
|
||||
{
|
||||
name: 'Supa Taco',
|
||||
location: 'POINT(-73.945826 40.80629)',
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dart" label="Dart">
|
||||
|
||||
```dart
|
||||
await supabase.from('restaurants').insert([
|
||||
{
|
||||
'name': 'Supa Burger',
|
||||
'location': 'POINT(-73.946823 40.807416)',
|
||||
},
|
||||
{
|
||||
'name': 'Supa Pizza',
|
||||
'location': 'POINT(-73.94581 40.807475)',
|
||||
},
|
||||
{
|
||||
'name': 'Supa Taco',
|
||||
'location': 'POINT(-73.945826 40.80629)',
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
Notice the order in which you pass the latitude and longitude. Longitude comes first, and is because longitude represents the x-axis of the location. Another thing to watch for is that there is no comma between the two values, just a single space.
|
||||
|
||||
At this point, if you go into your Supabase dashboard and look at the data, you will notice that the value of the `location` column looks something like this.
|
||||
|
||||
```
|
||||
0101000020E6100000A4DFBE0E9C91614044FAEDEBC0494240
|
||||
```
|
||||
|
||||
We can query the `restaurants` table directly, but it will return the `location` column in the format you see above. We will create [database functions](https://supabase.com/docs/guides/database/functions) so that we can use the [st_astext()](https://postgis.net/docs/ST_AsText.html) function to convert it back to a human-readable format like `POINT(-73.946713 40.807313)`.
|
||||
|
||||
### Order by distance
|
||||
|
||||
Sorting datasets from closest to farthest, sometimes called nearest-neighbor sort, is a very common use case in Geo-queries. PostGIS can handle it very easily with the use of the [`<->`](https://postgis.net/docs/geometry_distance_knn.html) operator. `<->` operator returns the two-dimensional distance between two geometries and will utilize the spatial index when used within `order by` clause. You can create the following database function to sort the restaurants from closest to farthest by passing the current locations as parameters.
|
||||
|
||||
```sql
|
||||
create or replace function nearby_restaurants(lat float, long float)
|
||||
returns setof record
|
||||
language sql
|
||||
as $$
|
||||
select id, name, st_astext(location) as location, st_distance(location, st_point(long, lat)::geography) as dist_meters
|
||||
from public.restaurants
|
||||
order by location <-> st_point(long, lat)::geography;
|
||||
$$;
|
||||
```
|
||||
|
||||
You can call this function from your client using `rpc()` like this:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
const { data, error } = await supabase.rpc('nearby_restaurants', {
|
||||
lat: 40.807313,
|
||||
long: -73.946713,
|
||||
})
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dart" label="Dart">
|
||||
|
||||
```dart
|
||||
final data = await supabase.rpc('nearby_restaurants',params: {
|
||||
'lat': 40.807313,
|
||||
'long': -73.946713,
|
||||
});
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="result" label="Result">
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Supa Burger",
|
||||
"location": "POINT(-73.946823 40.807416)",
|
||||
"dist_meters": 14.73033739
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Supa Pizza",
|
||||
"location": "POINT(-73.94581 40.807475)",
|
||||
"dist_meters": 78.28980007
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Supa Taco",
|
||||
"location": "POINT(-73.945826 40.80629)",
|
||||
"dist_meters": 136.04329002
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Finding all data points within a bounding box
|
||||
|
||||

|
||||
|
||||
When you are working on a map-based application where the user scrolls through your map, you might want to load the that lies within the bounding box of the map every time your users scroll. PostGIS can return the rows that are within the bounding box just by supplying the bottom left and the top right coordinates. Let’s look at what the function would look like.
|
||||
|
||||
```sql
|
||||
create or replace function restaurants_in_view(min_lat float, min_long float, max_lat float, max_long float)
|
||||
returns setof record
|
||||
language sql
|
||||
as $$
|
||||
select id, name, st_astext(location) as location
|
||||
from public.restaurants
|
||||
where location && ST_SetSRID(ST_MakeBox2D(ST_Point(min_long, min_lat), ST_Point(max_long, max_lat)),4326)
|
||||
$$;
|
||||
```
|
||||
|
||||
[`&&`](https://postgis.net/docs/geometry_overlaps.html) operator used in the `where` statement here returns a boolean of whether the bounding box of the two geometries intersect or not. We basically are creating a bounding box from the two points and finding those points that fall under the bounding box. We are also utilizing a few different PostGIS functions here.
|
||||
|
||||
- [ST_MakeBox2D](https://postgis.net/docs/ST_MakeBox2D.html): Creates a 2-dimensional box from two points.
|
||||
- [ST_SetSRID](https://postgis.net/docs/ST_SetSRID.html): Sets the [SRID](https://postgis.net/docs/manual-dev/using_postgis_dbmanagement.html#spatial_ref_sys), which is an identifier of what coordinate system to use, for the geometry. 4326 the standard longitude and latitude coordinate systems.
|
||||
|
||||
You can call this function from your client using `rpc()` like this:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
const { data, error } = await supabase.rpc('restaurants_in_view', {
|
||||
min_lat: 40.807,
|
||||
min_long: -73.946,
|
||||
max_lat: 40.808,
|
||||
max_long: -73.945,
|
||||
})
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dart" label="Dart">
|
||||
|
||||
```dart
|
||||
final data = await supabase.rpc('restaurants_in_view', params: {
|
||||
'min_lat': 40.807,
|
||||
'min_long': -73.946,
|
||||
'max_lat': 40.808,
|
||||
'max_long': -73.945,
|
||||
});
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="result" label="Result">
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Supa Pizza",
|
||||
"location": "POINT(-73.94581 40.807475)"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Resources
|
||||
|
||||
- [Official PostGIS documentation](https://postgis.net/documentation/)
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.3 MiB |
Reference in new issue
Block a user