Relations are utilized in OSM to group multiple other elements into a single object. Here we will focus solely on the (multi-) polygons. These specific elements have a type tag with one of two values: boundary, and multipolygon.
This kind of object is the most complex to reconstruct the geometry for and here is the list of steps we have to do:
- Select relations with proper
typevalue. - Unnest all refs related to the relation and keep only way refs — we only need related way refs to reconstruct a polygon.
- Select required ways with linestring geometries — here we can utilize steps from constructing ways.
- Assign an ‘outer’ role to the way ref if it’s
nulland check if any ref from the relation has the role ‘outer’ — if a relation has no ‘outer’ refs then treat all of them as ‘outer’. - Group all linestrings per ‘outer’ and ‘inner’ role and merge them into a single multilinestring — many relations are defined with multiple single linestrings that only together create a closed polygon.
- Split multilinestrings into single closed-loop linestrings and save them as polygons.
- Split geometries into ‘outer’ and ‘inner’ polygons. These can be extracted from the
ref_rolecolumn of the relation object. - For each ‘outer’ polygon, select all ‘inner’ polygons that are fully within it and make holes in this polygon.
- Make a union of all ‘outer’ polygons with holes.
Let’s start with selecting relations with matching tag values.
CREATE TEMP TABLE matching_relations AS
SELECT id, tags
FROM ST_READOSM('monaco-latest.osm.pbf')
WHERE kind = 'relation' AND len(refs) > 0
AND tags IS NOT NULL AND cardinality(tags) > 0
AND list_contains(map_keys(tags), 'type')
AND list_has_any(map_extract(tags, 'type'), ['boundary', 'multipolygon']);SELECT * FROM matching_relations;
┌──────────┬───────────────────────────────────────────────────────────────────┐
│ id │ tags │
│ int64 │ map(varchar, varchar) │
├──────────┼───────────────────────────────────────────────────────────────────┤
│ 7385 │ {ISO3166-2=FR-06, admin_level=6, alt_name:de=Meeralpen, border_… │
│ 8654 │ {ISO3166-2=FR-PAC, admin_level=4, border_type=region, boundary=… │
│ 36990 │ {ISO3166-1=MC, ISO3166-1:alpha2=MC, ISO3166-1:alpha3=MCO, admin… │
│ 174558 │ {admin_level=8, boundary=administrative, name=Roquebrune-Cap-Ma… │
│ 174562 │ {admin_level=8, boundary=administrative, name=Beausoleil, name:… │
│ 174956 │ {admin_level=8, alt_name:it=Capo d`Aglio, boundary=administrati… │
│ 174958 │ {admin_level=8, boundary=administrative, name=La Turbie, name:i… │
│ 393226 │ {building=castle, castle_type=palace, charge=10€, email=visites… │
│ 393481 │ {building=school, name=Pensionnat des Dames de Saint-Maur, type… │
│ 1124039 │ {ISO3166-1=MC, ISO3166-1:alpha2=MC, ISO3166-1:alpha3=MCO, ISO31… │
│ · │ · │
│ · │ · │
│ · │ · │
│ 14399505 │ area=yes, floating=yes, man_made=pier, type=multipolygon │
│ 16248281 │ building=apartments, name=F, type=multipolygon │
│ 16248282 │ building=apartments, name=E, type=multipolygon │
│ 16248283 │ building=apartments, name=D, type=multipolygon │
│ 16248284 │ building=apartments, name=C, type=multipolygon │
│ 16248285 │ building=apartments, name=B, type=multipolygon │
│ 16248286 │ building=apartments, name=A, type=multipolygon │
│ 16250182 │ {addr:country=MC, alt_name=Parc de la Roseraie, leisure=park, n… │
│ 16261416 │ natural=water, type=multipolygon, water=pond │
│ 16467322 │ {admin_level=2, boundary=land_area, land_area=administrative, n… │
├──────────┴───────────────────────────────────────────────────────────────────┤
│ 78 rows (20 shown) 2 columns │
└──────────────────────────────────────────────────────────────────────────────┘
Now we will unnest refs lists and split them into individual rows. Like with ways, here we will also utilize DuckDB’s indexing functions to remember the order of elements in the original list. Additionally, we will only keep the way refs.
CREATE TEMP TABLE matching_relations_with_ways_refs AS
WITH unnested_relation_refs AS (
SELECT
r.id,
UNNEST(refs) as ref,
UNNEST(ref_types) as ref_type,
UNNEST(ref_roles) as ref_role,
UNNEST(range(length(refs))) as ref_idx
FROM ST_READOSM('monaco-latest.osm.pbf') r
SEMI JOIN matching_relations USING (id)
WHERE kind = 'relation'
)
SELECT id, ref, ref_role, ref_idx
FROM unnested_relation_refs
WHERE ref_type = 'way';SELECT * FROM matching_relations_with_ways_refs;
┌──────────┬───────────┬──────────┬─────────┐
│ id │ ref │ ref_role │ ref_idx │
│ int64 │ int64 │ varchar │ int64 │
├──────────┼───────────┼──────────┼─────────┤
│ 7385 │ 30836152 │ outer │ 2 │
│ 7385 │ 889278953 │ outer │ 3 │
│ 7385 │ 889278956 │ outer │ 4 │
│ 7385 │ 889278957 │ outer │ 5 │
│ 7385 │ 889278958 │ outer │ 6 │
│ 7385 │ 889278962 │ outer │ 7 │
│ 7385 │ 960087656 │ outer │ 8 │
│ 7385 │ 30836155 │ outer │ 9 │
│ 7385 │ 889278965 │ outer │ 10 │
│ 7385 │ 889278963 │ outer │ 11 │
│ · │ · │ · │ · │
│ · │ · │ · │ · │
│ · │ · │ · │ · │
│ 11278320 │ 35150936 │ outer │ 197 │
│ 11278320 │ 466647567 │ outer │ 198 │
│ 11278320 │ 214008684 │ outer │ 199 │
│ 11278320 │ 466647569 │ outer │ 200 │
│ 11278320 │ 466647568 │ outer │ 201 │
│ 11278320 │ 214008689 │ outer │ 202 │
│ 11278320 │ 263776194 │ outer │ 203 │
│ 11278320 │ 31124893 │ outer │ 204 │
│ 11278320 │ 31124895 │ outer │ 205 │
│ 11278320 │ 31124899 │ outer │ 206 │
├──────────┴───────────┴──────────┴─────────┤
│ ? rows (>9999 rows, 20 shown) 4 columns │
└───────────────────────────────────────────┘
In the next step, we will construct linestrings for the ways required by the relations. The query below compresses almost full logic of reading ways in one go (getting required nodes, constructing points and grouping them into linestrings):
CREATE TEMP TABLE required_ways_linestrings AS
WITH ways_required_by_relations_with_nodes_refs AS (
SELECT id, UNNEST(refs) as ref, UNNEST(range(length(refs))) as ref_idx
FROM ST_READOSM('monaco-latest.osm.pbf') ways
SEMI JOIN matching_relations_with_ways_refs
ON ways.id = matching_relations_with_ways_refs.ref
WHERE kind = 'way'
),
nodes_required_by_relations_with_geometries AS (
SELECT id, ST_POINT(lon, lat) geometry
FROM ST_READOSM('monaco-latest.osm.pbf') nodes
SEMI JOIN ways_required_by_relations_with_nodes_refs
ON nodes.id = ways_required_by_relations_with_nodes_refs.ref
WHERE kind = 'node'
)
SELECT
ways.id,
ST_MakeLine(list(nodes.geometry ORDER BY ref_idx ASC)) linestring
FROM ways_required_by_relations_with_nodes_refs ways
JOIN nodes_required_by_relations_with_geometries nodes
ON ways.ref = nodes.id
GROUP BY 1;SELECT * FROM required_ways_linestrings;
┌────────────┬─────────────────────────────────────────────────────────────────┐
│ id │ linestring │
│ int64 │ geometry │
├────────────┼─────────────────────────────────────────────────────────────────┤
│ 37794470 │ LINESTRING (7.438411400000001 43.749422300000006, 7.4384056 4… │
│ 87878917 │ LINESTRING (7.418041100000001 43.7256933, 7.4181547 43.725613… │
│ 94252430 │ LINESTRING (7.4141873 43.729494100000004, 7.414203400000001 4… │
│ 94399618 │ LINESTRING (7.4239717 43.740543100000004, 7.4238252 43.740372… │
│ 94399736 │ LINESTRING (7.423881400000001 43.7402971, 7.4239411 43.740265… │
│ 104863462 │ LINESTRING (7.424840000000001 43.731281800000005, 7.424917000… │
│ 120114110 │ LINESTRING (7.420872 43.7370979, 7.4207787 43.7370534, 7.4206… │
│ 156242249 │ LINESTRING (7.4294728 43.739895600000004, 7.429579500000001 4… │
│ 165636038 │ LINESTRING (7.419717100000001 43.732398700000005, 7.419772900… │
│ 169297863 │ LINESTRING (7.422815300000001 43.7321967, 7.4234109 43.732263… │
│ · │ · │
│ · │ · │
│ · │ · │
│ 24874398 │ LINESTRING (7.418523400000001 43.7247599, 7.419012800000001 4… │
│ 92627424 │ LINESTRING (7.4166521 43.7322122, 7.4162303 43.73173910000000… │
│ 94452829 │ LINESTRING (7.4317066 43.7469647, 7.4317998 43.7470371, 7.431… │
│ 335740502 │ LINESTRING (7.4185151 43.7353633, 7.418442000000001 43.735298… │
│ 398377193 │ LINESTRING (7.420872 43.7370979, 7.420939000000001 43.7371298… │
│ 405529436 │ LINESTRING (7.417534900000001 43.7258914, 7.4175347 43.725833… │
│ 423632259 │ LINESTRING (7.4212208 43.7320944, 7.421461300000001 43.732057… │
│ 572935479 │ LINESTRING (7.427508100000001 43.7394168, 7.427496700000001 4… │
│ 586494707 │ LINESTRING (7.4270357 43.739109000000006, 7.4269512 43.739155… │
│ 1202570715 │ LINESTRING (7.426448400000001 43.739491400000006, 7.4264682 4… │
├────────────┴─────────────────────────────────────────────────────────────────┤
│ 141 rows (20 shown) 2 columns │
└──────────────────────────────────────────────────────────────────────────────┘
After creating the required linestrings, now we can join them with relations data. We will also make sure that the required ref_role is properly parsed — fill in the empty values or replace them if the relation has incorrectly defined ref_roles in the OSM database.
CREATE TEMP TABLE matching_relations_with_ways_linestrings AS
WITH unnested_relations_with_way_linestrings AS (
SELECT
r.id,
COALESCE(r.ref_role, 'outer') as ref_role,
r.ref,
w.linestring::GEOMETRY as geometry
FROM matching_relations_with_ways_refs r
JOIN required_ways_linestrings w
ON w.id = r.ref
ORDER BY r.id, r.ref_idx
),
any_outer_refs AS (
-- check if any way attached to the relation has the `outer` role
SELECT id, bool_or(ref_role == 'outer') has_any_outer_refs
FROM unnested_relations_with_way_linestrings
GROUP BY id
)
SELECT
unnested_relations_with_way_linestrings.* EXCLUDE (ref_role),
-- if none of the way refs has `outer` role - treat each ref as `outer`
CASE WHEN any_outer_refs.has_any_outer_refs
THEN unnested_relations_with_way_linestrings.ref_role
ELSE 'outer'
END as ref_role
FROM unnested_relations_with_way_linestrings
JOIN any_outer_refs
ON any_outer_refs.id = unnested_relations_with_way_linestrings.id;SELECT * FROM matching_relations_with_ways_linestrings;
┌──────────┬───────────┬────────────────────────────────────────────┬──────────┐
│ id │ ref │ geometry │ ref_role │
│ int64 │ int64 │ geometry │ varchar │
├──────────┼───────────┼────────────────────────────────────────────┼──────────┤
│ 7385 │ 772081595 │ LINESTRING (7.415291600000001 43.7234393… │ outer │
│ 7385 │ 212810311 │ LINESTRING (7.4120416 43.7280955, 7.4123… │ outer │
│ 7385 │ 176533407 │ LINESTRING (7.412670800000001 43.7316348… │ outer │
│ 7385 │ 37811853 │ LINESTRING (7.4127007 43.7346954, 7.4126… │ outer │
│ 7385 │ 37794471 │ LINESTRING (7.428754700000001 43.7460174… │ outer │
│ 7385 │ 398372186 │ LINESTRING (7.436885 43.7519173, 7.43677… │ outer │
│ 7385 │ 37794470 │ LINESTRING (7.438411400000001 43.7494223… │ outer │
│ 7385 │ 770774507 │ LINESTRING (7.439171000000001 43.7490109… │ outer │
│ 8654 │ 772081595 │ LINESTRING (7.415291600000001 43.7234393… │ outer │
│ 8654 │ 212810311 │ LINESTRING (7.4120416 43.7280955, 7.4123… │ outer │
│ · │ · │ · │ · │
│ · │ · │ · │ · │
│ · │ · │ · │ · │
│ 11546878 │ 840890844 │ LINESTRING (7.420754 43.735872900000004,… │ outer │
│ 11546878 │ 840890848 │ LINESTRING (7.4207413 43.7356673, 7.4207… │ outer │
│ 16467322 │ 772081595 │ LINESTRING (7.415291600000001 43.7234393… │ outer │
│ 16467322 │ 212810311 │ LINESTRING (7.4120416 43.7280955, 7.4123… │ outer │
│ 16467322 │ 176533407 │ LINESTRING (7.412670800000001 43.7316348… │ outer │
│ 16467322 │ 37811853 │ LINESTRING (7.4127007 43.7346954, 7.4126… │ outer │
│ 16467322 │ 37794471 │ LINESTRING (7.428754700000001 43.7460174… │ outer │
│ 16467322 │ 398372186 │ LINESTRING (7.436885 43.7519173, 7.43677… │ outer │
│ 16467322 │ 37794470 │ LINESTRING (7.438411400000001 43.7494223… │ outer │
│ 16467322 │ 770774507 │ LINESTRING (7.439171000000001 43.7490109… │ outer │
├──────────┴───────────┴────────────────────────────────────────────┴──────────┤
│ 410 rows (20 shown) 4 columns │
└──────────────────────────────────────────────────────────────────────────────┘
As you can see, there are multiple ways with linestrings assigned to each relation. Let’s look at an example to see how they look on the map:
To create full polygons, we have to utilize a ST_LineMerge function, that will combine a list of linestrings (you can compare it to tying pieces of string together). You can read more about this operation here:
As an additional validation step, we will check if the produced linestrings have at least 4 points and if the first point equals the last point, before converting them into polygons:
CREATE TEMP TABLE matching_relations_with_merged_polygons AS
WITH merged_linestrings AS (
SELECT
id,
ref_role,
UNNEST(
ST_Dump(ST_LineMerge(ST_Collect(list(geometry)))),
recursive := true
),
FROM matching_relations_with_ways_linestrings
GROUP BY id, ref_role
),
relations_with_linestrings AS (
SELECT
id,
ref_role,
-- ST_Dump returns column named `geom`
geom AS geometry,
row_number() OVER (PARTITION BY id) as geometry_id
FROM
merged_linestrings
-- discard linestrings with less than 4 points
WHERE ST_NPoints(geom) >= 4
),
valid_relations AS (
SELECT id, is_valid
FROM (
SELECT
id,
bool_and(
-- Check if start point equals the end point
ST_Equals(ST_StartPoint(geometry), ST_EndPoint(geometry))
) is_valid
FROM relations_with_linestrings
GROUP BY id
)
WHERE is_valid = true
)
SELECT
id,
ref_role,
ST_MakePolygon(geometry) geometry,
geometry_id
FROM relations_with_linestrings
SEMI JOIN valid_relations
ON relations_with_linestrings.id = valid_relations.id;SELECT * FROM matching_relations_with_merged_polygons;
┌──────────┬──────────┬──────────────────────────────────────────┬─────────────┐
│ id │ ref_role │ geometry │ geometry_id │
│ int64 │ varchar │ geometry │ int64 │
├──────────┼──────────┼──────────────────────────────────────────┼─────────────┤
│ 1369631 │ inner │ POLYGON ((7.438232200000001 43.7511057… │ 1 │
│ 1369631 │ outer │ POLYGON ((7.438008600000001 43.7502850… │ 2 │
│ 1484217 │ outer │ POLYGON ((7.423010700000001 43.7307028… │ 1 │
│ 1484217 │ inner │ POLYGON ((7.423114600000001 43.7305994… │ 2 │
│ 5986436 │ outer │ POLYGON ((7.428754700000001 43.7460174… │ 1 │
│ 8269572 │ outer │ POLYGON ((7.4287048 43.737701400000006… │ 1 │
│ 8269572 │ inner │ POLYGON ((7.4284492 43.7375604, 7.4286… │ 2 │
│ 16248286 │ outer │ POLYGON ((7.426306200000001 43.7391565… │ 1 │
│ 16248286 │ inner │ POLYGON ((7.4263241 43.7390763, 7.4263… │ 2 │
│ 16261416 │ inner │ POLYGON ((7.417829200000001 43.731382,… │ 1 │
│ · │ · │ · │ · │
│ · │ · │ · │ · │
│ · │ · │ · │ · │
│ 8280869 │ inner │ POLYGON ((7.426753300000001 43.7388868… │ 2 │
│ 8280869 │ inner │ POLYGON ((7.4270357 43.739109000000006… │ 3 │
│ 8280869 │ inner │ POLYGON ((7.4271374 43.739011500000004… │ 4 │
│ 8280869 │ inner │ POLYGON ((7.4272666 43.7389165, 7.4273… │ 5 │
│ 11384697 │ outer │ POLYGON ((7.427075 43.7315167, 7.42749… │ 1 │
│ 11384697 │ inner │ POLYGON ((7.426917700000001 43.7313266… │ 2 │
│ 11384697 │ inner │ POLYGON ((7.427001400000001 43.7313937… │ 3 │
│ 11546878 │ outer │ POLYGON ((7.4207413 43.7356673, 7.4207… │ 1 │
│ 11538023 │ inner │ POLYGON ((7.426528200000001 43.7329359… │ 1 │
│ 11538023 │ outer │ POLYGON ((7.426554 43.7328276, 7.42654… │ 2 │
├──────────┴──────────┴──────────────────────────────────────────┴─────────────┤
│ 88 rows (20 shown) 4 columns │
└──────────────────────────────────────────────────────────────────────────────┘
Let’s see the previous example after this operation. We should expect two separate polygons to be present:
I’ve mentioned previously the outer and inner ‘ref_types’ of ways that create a relation. Here you can see what it looks like:
The roles mean that the inner ways are the ‘holes’ within outer polygons and we have to reproduce this step to make proper geometries.
Let’s focus now on splitting the polygons into groups with and without holes, using the ST_Within predicate, which checks if a geometry is fully within another.
CREATE TEMP TABLE matching_relations_with_outer_polygons_with_holes AS
WITH outer_polygons AS (
SELECT id, geometry_id, geometry
FROM matching_relations_with_merged_polygons
WHERE ref_role = 'outer'
), inner_polygons AS (
SELECT id, geometry_id, geometry
FROM matching_relations_with_merged_polygons
WHERE ref_role = 'inner'
)
SELECT
op.id,
op.geometry_id,
ST_Difference(any_value(op.geometry), ST_Union_Agg(ip.geometry)) geometry
FROM outer_polygons op
JOIN inner_polygons ip
ON op.id = ip.id AND ST_WITHIN(ip.geometry, op.geometry)
GROUP BY op.id, op.geometry_id;CREATE TEMP TABLE matching_relations_with_outer_polygons_without_holes AS
WITH outer_polygons AS (
SELECT id, geometry_id, geometry
FROM matching_relations_with_merged_polygons
WHERE ref_role = 'outer'
)
SELECT
op.id,
op.geometry_id,
op.geometry
FROM outer_polygons op
ANTI JOIN matching_relations_with_outer_polygons_with_holes opwh
ON op.id = opwh.id AND op.geometry_id = opwh.geometry_id;
SELECT * FROM matching_relations_with_outer_polygons_with_holes
UNION ALL
SELECT * FROM matching_relations_with_outer_polygons_without_holes;
┌──────────┬─────────────┬─────────────────────────────────────────────────────┐
│ id │ geometry_id │ geometry │
│ int64 │ int64 │ geometry │
├──────────┼─────────────┼─────────────────────────────────────────────────────┤
│ 16248286 │ 1 │ POLYGON ((7.4263139 43.7391602, 7.426324900000001… │
│ 1369192 │ 1 │ POLYGON ((7.4226247 43.7402251, 7.4227848 43.7396… │
│ 8147763 │ 1 │ POLYGON ((7.438223000000001 43.7477296, 7.4382403… │
│ 393226 │ 1 │ POLYGON ((7.4204802 43.731016700000005, 7.4203979… │
│ 11484092 │ 4 │ POLYGON ((7.417896900000001 43.724827000000005, 7… │
│ 11384697 │ 1 │ POLYGON ((7.4274934 43.731250700000004, 7.4267196… │
│ 8269572 │ 1 │ POLYGON ((7.4287166 43.7376724, 7.4287948 43.7376… │
│ 16261416 │ 3 │ POLYGON ((7.4178309 43.731391, 7.417877000000001 … │
│ 16248284 │ 1 │ POLYGON ((7.426637500000001 43.7394678, 7.4266485… │
│ 16248285 │ 1 │ POLYGON ((7.426114600000001 43.739267600000005, 7… │
│ · │ · │ · │
│ · │ · │ · │
│ · │ · │ · │
│ 11546879 │ 1 │ POLYGON ((7.4209316 43.7356234, 7.421037 43.73562… │
│ 2220206 │ 1 │ POLYGON ((7.4120416 43.7280955, 7.412103900000001… │
│ 5986438 │ 1 │ POLYGON ((7.4191482 43.738887500000004, 7.4199833… │
│ 2221178 │ 1 │ POLYGON ((7.415860400000001 43.7313885, 7.416195 … │
│ 2221179 │ 1 │ POLYGON ((7.422280000000001 43.7367186, 7.4227584… │
│ 2220208 │ 1 │ POLYGON ((7.41849 43.730922400000004, 7.4185156 4… │
│ 2254506 │ 1 │ POLYGON ((7.432995900000001 43.7447102, 7.4329125… │
│ 5986437 │ 1 │ POLYGON ((7.4307593 43.745595, 7.4306621 43.74541… │
│ 5986437 │ 2 │ POLYGON ((7.4343358 43.7457085, 7.4341337 43.7455… │
│ 11546878 │ 1 │ POLYGON ((7.4207413 43.7356673, 7.4207413 43.7357… │
├──────────┴─────────────┴─────────────────────────────────────────────────────┤
│ 47 rows (20 shown) 3 columns │
└──────────────────────────────────────────────────────────────────────────────┘
In this query, there is utilized another special join — ANTI JOIN. This one filters out all the rows on the left-side table that are joined by the right-side table.
The last step that is needed is to merge all the polygons for a single relation using the ST_Union_Agg operation. It will combine all polygons into multipolygons (if there is more than one) and produce a single geometry.
WITH unioned_outer_geometries AS (
SELECT id, geometry
FROM matching_relations_with_outer_polygons_with_holes
UNION ALL
SELECT id, geometry
FROM matching_relations_with_outer_polygons_without_holes
),
final_geometries AS (
SELECT id, ST_Union_Agg(geometry) geometry
FROM unioned_outer_geometries
GROUP BY id
)
SELECT r_g.id, r.tags, r_g.geometry
FROM final_geometries r_g
JOIN matching_relations r
ON r.id = r_g.id;┌──────────┬──────────────────────┬────────────────────────────────────────────┐
│ id │ tags │ geometry │
│ int64 │ map(varchar, varch… │ geometry │
├──────────┼──────────────────────┼────────────────────────────────────────────┤
│ 393226 │ {building=castle, … │ POLYGON ((7.4204802 43.731016700000005, … │
│ 393481 │ {building=school, … │ POLYGON ((7.423992800000001 43.731841800… │
│ 1369191 │ {building=apartmen… │ POLYGON ((7.4231004 43.7412894, 7.423314… │
│ 1369192 │ {building=apartmen… │ POLYGON ((7.4226247 43.7402251, 7.422784… │
│ 1369193 │ {building=apartmen… │ POLYGON ((7.424 43.7405491, 7.4240401 43… │
│ 1369195 │ {building=apartmen… │ POLYGON ((7.418679900000001 43.738187100… │
│ 1369631 │ {addr:housenumber=… │ POLYGON ((7.4379729 43.7502505, 7.437937… │
│ 1369632 │ {addr:city=Monaco,… │ POLYGON ((7.4317121 43.74709, 7.4318277 … │
│ 1484190 │ {addr:city=Monaco,… │ POLYGON ((7.425469 43.731494000000005, 7… │
│ 1484217 │ {building=retail, … │ POLYGON ((7.423202300000001 43.7307117, … │
│ · │ · │ · │
│ · │ · │ · │
│ · │ · │ · │
│ 14399505 │ {area=yes, floatin… │ POLYGON ((7.4195986 43.7265293, 7.419595… │
│ 16248281 │ {building=apartmen… │ POLYGON ((7.4260438 43.739789800000004, … │
│ 16248282 │ {building=apartmen… │ POLYGON ((7.426243100000001 43.7396824, … │
│ 16248283 │ {building=apartmen… │ POLYGON ((7.426438200000001 43.739575200… │
│ 16248284 │ {building=apartmen… │ POLYGON ((7.426637500000001 43.7394678, … │
│ 16248285 │ {building=apartmen… │ POLYGON ((7.426114600000001 43.739267600… │
│ 16248286 │ {building=apartmen… │ POLYGON ((7.4263139 43.7391602, 7.426324… │
│ 16250182 │ {addr:country=MC, … │ POLYGON ((7.418348300000001 43.7256979, … │
│ 16261416 │ {natural=water, ty… │ POLYGON ((7.4178309 43.731391, 7.4178770… │
│ 11546878 │ {addr:city=Monaco,… │ POLYGON ((7.4207413 43.7356673, 7.420741… │
├──────────┴──────────────────────┴────────────────────────────────────────────┤
│ 46 rows (20 shown) 3 columns │
└──────────────────────────────────────────────────────────────────────────────┘
Here is the previous relation example, now with holes: