PostGIS adds spatial database features to PostgreSQL. With PostGIS, PostgreSQL can store geographic data such as coordinates, points, routes, and boundaries. It can also answer location-based questions directly inside SQL.
For example, in a bus application, PostGIS can help answer questions like:
Which bus stops are near the user?
How far is this bus stop from Taipei Main Station?
Which stops are within 3 km?
Which stop is closest to the current GPS location?In this post, we will install PostgreSQL 18 and PostGIS on Ubuntu 24.04, then test PostGIS with a simple bus stop example.
The example is based on a tested Ubuntu 24.04 server where PostgreSQL 18.3, PostGIS 3.6, and the sample spatial queries were verified successfully.
Environment
OS: Ubuntu 24.04
PostgreSQL: 18.3
PostGIS: 3.6
Database name: taiwan_bus
Server hostname: taiwan-bus- Add the PostgreSQL PGDG Repository
Ubuntu 24.04 includes PostgreSQL packages in the default repository, but if you want PostgreSQL 18, use the PostgreSQL Global Development Group repository, usually called PGDG.
Update the package list first:
sudo apt updateInstall postgresql-common:
sudo apt install -y postgresql-commonRun the PGDG repository setup script:
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.shUpdate the package list again:
sudo apt update- Install PostgreSQL 18
Install PostgreSQL 18 and the PostgreSQL 18 client:
sudo apt install -y postgresql-18 postgresql-client-18Check the PostgreSQL cluster status:
pg_lsclustersIn this example, PostgreSQL 18 is running on port 5432:
Ver Cluster Port Status Owner Data directory Log file
18 main 5432 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.logIf you already had another PostgreSQL version installed, such as PostgreSQL 16, PostgreSQL 18 may use another port. Always check with:
pg_lsclusters- Install PostGIS for PostgreSQL 18
Install the PostGIS package for PostgreSQL 18:
sudo apt install -y postgresql-18-postgis-3 postgresql-18-postgis-3-scriptsThe package installation will also install many GIS-related dependencies, such as GEOS, PROJ, GDAL, and SFCGAL libraries.
In this tested environment, the packages included:
postgresql-18-postgis-3
postgresql-18-postgis-3-scriptsThe installed PostGIS package version was:
3.6.3+dfsg-1.pgdg24.04+1After installation, there was no need to restart services:
No services need to be restarted.
No containers need to be restarted.- Create a PostgreSQL Database
Create a database named taiwan_bus:
sudo -u postgres createdb taiwan_busConnect to the database:
sudo -u postgres psql -d taiwan_busYou should enter the PostgreSQL shell:
psql (18.3 (Ubuntu 18.3-1.pgdg24.04+1))
Type "help" for help.
taiwan_bus=#- Enable PostGIS in the Database
PostGIS is enabled per database. Installing the PostGIS package does not automatically enable it in every database.
Inside psql, run:
CREATE EXTENSION IF NOT EXISTS postgis;Expected output:
CREATE EXTENSIONVerify the PostGIS version:
SELECT PostGIS_Version();In this tested environment, the output was:
postgis_version
---------------------------------------
3.6 USE_GEOS=1 USE_PROJ=1 USE_STATS=1
(1 row)This means PostGIS is working.
Exit psql:
\q- Create a Simple Bus Stop Table
Now we will create a simple table to store bus stops.
Connect to the database again:
sudo -u postgres psql -d taiwan_busCreate the table:
CREATE TABLE bus_stops (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
city TEXT NOT NULL,
location GEOGRAPHY(Point, 4326)
);Expected output:
CREATE TABLEThe important column is:
location GEOGRAPHY(Point, 4326)This means the column stores a real-world geographic point.
The meaning is:
GEOGRAPHY: A PostGIS type for real-world geographic coordinates
Point: A single point location
4326: The SRID for WGS 84 longitude/latitude coordinatesFor GPS data, 4326 is commonly used.
- Insert Sample Bus Stops
PostGIS point coordinates use this order:
longitude, latitudeThis is important. Do not insert latitude first.
Insert a few sample Taipei locations:
INSERT INTO bus_stops (name, city, location)
VALUES
(
'Taipei Main Station',
'Taipei',
ST_SetSRID(ST_MakePoint(121.5170, 25.0478), 4326)::geography
),
(
'Taipei 101',
'Taipei',
ST_SetSRID(ST_MakePoint(121.5645, 25.0330), 4326)::geography
),
(
'Ximen Station',
'Taipei',
ST_SetSRID(ST_MakePoint(121.5080, 25.0421), 4326)::geography
),
(
'Songshan Station',
'Taipei',
ST_SetSRID(ST_MakePoint(121.5786, 25.0493), 4326)::geography
);Expected output:
INSERT 0 4The function used here is:
ST_MakePoint(121.5170, 25.0478)This creates a point using:
longitude = 121.5170
latitude = 25.0478Then this part sets the coordinate system:
ST_SetSRID(..., 4326)Finally, this part converts the value to the geography type:
::geography- Query the Bus Stop Locations
Run:
SELECT
id,
name,
city,
ST_AsText(location::geometry) AS location_text
FROM bus_stops;Expected output:
id | name | city | location_text
----+---------------------+--------+-------------------------
1 | Taipei Main Station | Taipei | POINT(121.517 25.0478)
2 | Taipei 101 | Taipei | POINT(121.5645 25.033)
3 | Ximen Station | Taipei | POINT(121.508 25.0421)
4 | Songshan Station | Taipei | POINT(121.5786 25.0493)
(4 rows)ST_AsText() converts the PostGIS point into a readable text format.
For example:
POINT(121.517 25.0478)Again, the order is:
longitude latitude- Calculate Distance from Taipei Main Station
Now calculate the distance from Taipei Main Station to every stop.
Run:
SELECT
name,
ROUND(
ST_Distance(
location,
ST_SetSRID(ST_MakePoint(121.5170, 25.0478), 4326)::geography
)
) AS distance_meters
FROM bus_stops
ORDER BY distance_meters;Expected output:
name | distance_meters
---------------------+-----------------
Taipei Main Station | 0
Ximen Station | 1106
Taipei 101 | 5066
Songshan Station | 6218
(4 rows)This is the first useful PostGIS feature.
Instead of manually calculating distance in application code, PostgreSQL can calculate it directly.
The key function is:
ST_Distance(...)Because the column uses GEOGRAPHY(Point, 4326), the result is returned in meters.
- Find Bus Stops Within 3 km
A common feature in a bus app is:
Show nearby bus stops.For example, find all bus stops within 3 km of Taipei Main Station:
SELECT
name,
city,
ROUND(
ST_Distance(
location,
ST_SetSRID(ST_MakePoint(121.5170, 25.0478), 4326)::geography
)
) AS distance_meters
FROM bus_stops
WHERE ST_DWithin(
location,
ST_SetSRID(ST_MakePoint(121.5170, 25.0478), 4326)::geography,
3000
)
ORDER BY distance_meters;Expected output:
name | city | distance_meters
---------------------+--------+-----------------
Taipei Main Station | Taipei | 0
Ximen Station | Taipei | 1106
(2 rows)This result makes sense.
Only these two stops are within 3 km:
Taipei Main Station
Ximen StationThese two are not returned because they are farther than 3 km:
Taipei 101
Songshan StationThe key function here is:
ST_DWithin(location, target_location, distance_in_meters)For example:
ST_DWithin(
location,
ST_SetSRID(ST_MakePoint(121.5170, 25.0478), 4326)::geography,
3000
)This means:
Find rows where location is within 3000 meters of the target point.- Create a Spatial Index
For a small table with only four rows, an index does not matter.
But for a real bus application, you may have thousands or tens of thousands of stops. You should create a spatial index.
Run:
CREATE INDEX bus_stops_location_idx
ON bus_stops
USING GIST (location);Expected output:
CREATE INDEXThis creates a GiST index on the location column.
A normal B-tree index is not enough for spatial search. For PostGIS spatial columns, GiST is commonly used.
Did this guide save you time?
Support this site