7 Exercise 6: Spatial relations
Contents – In this exercise we try out PostGIS functions for handling spatial relations.
Goal – The goal of the exercise is to understand PostGIS spatial relation functions and perform simple analyses with them.
7.0.1 Preparation
Open pgAdmin in the browser and log in. Open the Query Tool (Right click trainingdatabase -> Query Tool).
7.0.2 Functions
Most of the following functions are used in the following exercises:
| PostGIS function | Returns |
|---|---|
| ST_Contains(geometry A, geometry B) | TRUE if A contains B |
| ST_Crosses(geometry A, geometry B) | TRUE if A crosses B |
| ST_Disjoint(geometry A , geometry B) | TRUE if geometries do not cross each other |
| ST_Distance(geometry A, geometry B) | Minimum distance between geometries |
| ST_DWithin(geometry A, geometry B, radius) | TRUE if A is closer to B than the given distance |
| ST_Equals(geometry A, geometry B) | TRUE if A is equal to B |
| ST_Intersects(geometry A, geometry B) | TRUE if A intersects B |
| ST_Overlaps(geometry A, geometry B) | TRUE if A and B overlap but are not contained in one another |
| ST_Touches(geometry A, geometry B) | TRUE if the border of A touches B |
| ST_Within(geometry A, geometry B) | TRUE if A is inside B |
7.1 Exercise 6.1
How many airports are within a 50 km radius of the Dublin Airport:
WITH dublin_airport AS (
SELECT
wkb_geometry
FROM
ne.airports_eu
WHERE
name = 'Dublin Airport'
)
SELECT
name
FROM
dublin_airport a,
ne.airports_eu b
WHERE
ST_DWithin(a.wkb_geometry, b.wkb_geometry, 50*1000);Note how the letters a and b are used as aliases in the query. Aliases replace the names of different columns. Using them makes it easier to write and interpret queries, especially longer ones. You can read more about aliases here: postgresql-alias.
Which rivers are entirely contained inside one country?
SELECT
...
FROM
...
WHERE
...SELECT
b.name_en
FROM
ne.countries_eu a, ne.rivers_eu b
WHERE
ST_...(a.wkb_geometry, b.wkb_geometry);SELECT
b.name_en
FROM
ne.countries_eu a, ne.rivers_eu b
WHERE
ST_Contains(a.wkb_geometry, b.wkb_geometry);Note! Of course, more countries than seen in the result contain entire rivers. The result seen here is because the Natural Earth river data set does not include all rivers, only major ones.
If you add the DISTINCT parameter to the SELECT command, you can find out how many countries entirely contain rivers.
SELECT
FROM
(SELECT DISTINCT
FROM
WHERE
ST_Contains(..., ...));-- use a subquery and give it an alias
SELECT
count(country_distinct)
FROM
(SELECT DISTINCT
a.name_en as country_distinct
FROM
ne.countries_eu a, ne.rivers_eu b
WHERE
ST_...(... , ... )) AS country_distinct;SELECT
count(country_distinct)
FROM
(SELECT DISTINCT
a.name_en as country_distinct
FROM
ne.countries_eu a, ne.rivers_eu b
WHERE
ST_Contains(a.wkb_geometry, b.wkb_geometry)) as DistincKunta;7.2 Exercise 6.2
Which countries in the EU have more than 100 airports?
SELECT ...-- Option 1 - Use a CTE
WITH apm AS (
SELECT DISTINCT
a.name_en, COUNT(...) OVER(PARTITION BY ...) AS CountAirports
FROM
ne.countries_eu a, ne.airports_eu b
WHERE
contains(a.wkb_geometry, b.wkb_geometry)
ORDER BY
CountAirports DESC
)
SELECT
name_en as "Country", countairports AS "Count of Airports"
FROM
apm
WHERE
CountAirports > 100;-- Option 2
SELECT
a.name_en as "Country", count(...) as "Count"
FROM
ne.countries_eu a, ne.airports_Eu b
WHERE
contains(a.wkb_geometry, b.wkb_geometry)
GROUP BY
a.name_en
HAVING count(...) > ...;
-- fill in the blanks. Choose the correct functionThorough solution:
WITH apm AS (
SELECT DISTINCT
a.name_en, COUNT(a.name_en) OVER(PARTITION BY a.name_en) AS CountAirports
FROM
ne.countries_eu a, ne.airports_eu b
WHERE
ST_Contains(a.wkb_geometry, b.wkb_geometry)
ORDER BY
CountAirports DESC
)
SELECT
name_en as "Country", countairports AS "Count of Airports"
FROM
apm
WHERE
CountAirports > 100;Simpler solution:
SELECT
a.name_en as "Country", count(*) as "Count"
FROM
ne.countries_eu a, ne.airports_Eu b
WHERE
ST_Contains(a.wkb_geometry, b.wkb_geometry)
GROUP BY
a.name_en
HAVING count(*) > 100;7.3 Exercise 6.3
Let’s find the EU countries that have no railroads:
SELECT
b.name_en
FROM
(SELECT
ST_union(wkb_geometry) as wkb_geometry
FROM
ne.railroads_eu) as a, ne.countries_eu b
WHERE
ST_Disjoint(a.wkb_geometry, b.wkb_geometry);7.4 Exercise 6.4
Let’s find the three nearest airports.
Using the K Nearest Neighbours method (KNN) you can find the three closest airports to a city point.
WITH madrid AS
(SELECT
wkb_geometry
FROM
ne.capitals_eu
WHERE
name_en = 'Madrid')
SELECT
round(ST_Distance(madrid.wkb_geometry, a.wkb_geometry)/1000) as "km",*
FROM
ne.airports_eu a, madrid
ORDER BY
madrid.wkb_geometry <-> a.wkb_geometry
LIMIT 3;The same result can be achieved without the KNN algorithm:
SELECT
*, round(ST_Distance(wkb_geometry,(
SELECT ST_Centroid(wkb_geometry)
FROM
ne.capitals_eu
WHERE
name_en ='Madrid'))/1000) as distance
FROM
ne.airports_eu
ORDER by
distance
LIMIT 3;7.5 Exercise 6.5
What are the neighbouring countires of the Czech Republic?
SELECT
b.name_en
FROM
(SELECT
name_en, wkb_geometry
FROM
ne.countries_eu
WHERE
name_en = 'Czech Republic') a, ne.countries_eu b
WHERE
ST_Touches(a.wkb_geometry, b.wkb_geometry);7.6 Exercise 6.6
Find the rivers which cross over countries’ borders:
SELECT
a.name_en, a.wkb_geometry
FROM
ne.rivers_eu a, ne.countries_eu b
WHERE
ST_Crosses(a.wkb_geometry, b.wkb_geometry)For visualizing results you can create a new schema (tmp). You can create a new table based on query results. QGIS can be used to visualize the results. You can also create a view from the results, but remember to add a DISTINCT identifier to the rows.
CREATE SCHEMA IF NOT EXISTS tmp;DROP TABLE IF EXISTS tmp.crossrivers;
CREATE TABLE tmp.crossrivers AS
(
SELECT
a.name_en, a.wkb_geometry
FROM
ne.rivers_eu a, ne.countries_eu b
WHERE
ST_Crosses(a.wkb_geometry, b.wkb_geometry)
);DROP VIEW IF EXISTS tmp.view_crossrivers;
CREATE VIEW tmp.view_crossrivers AS
(
SELECT DISTINCT
a.name_en, a.wkb_geometry, a.fid
FROM
ne.rivers_eu a, ne.countries_eu b
WHERE
ST_Crosses(a.wkb_geometry, b.wkb_geometry)
);Which was faster, creating the table or the view?
How about viewing them in QGIS? Why?
7.7 Exercise 6.7
Let’s calculate the minimum distance from Ireland’s airports to the nearest railroad:
SELECT
a.name, MIN(ST_Distance(a.wkb_geometry, b.wkb_geometry)) as "dist"
FROM
ne.airports_eu a, ne.railroads_eu b
WHERE
a.country = 'Ireland'
GROUP BY
a.name
ORDER BY
dist;