Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Vector data

Points, lines, polygons, and the operations behind most maps

Eindhoven University of Technology

Vector data describes the world as shapes with coordinates: a café is a point, a street is a line, a municipality is a polygon. Each shape is a row in a table, so everything you know about filtering and grouping tables still applies. On top of that you get a small set of spatial operations, and a surprising share of real maps are built from just six of them.

Everything on this page is in EPSG:28992, so all distances and areas are in metres. If you skip the .to_crs(28992), every number below is wrong without any error. See Projections.

The three geometry types

TypeIsIn EindhovenChallenge days
Pointone location, no sizea café, a tree, a lamp post1 Points, 4 Clusters
LineStringa path through several pointsa road, a river, a bus route2 Lines, 24 Network
Polygona closed ring, with an areaa building, a park, a neighbourhood3 Polygons, 20 Hexagons

Each has a “multi” version (MultiPoint, MultiLineString, MultiPolygon) for one feature made of several pieces, such as a municipality with islands. You can move between them: the boundary of a polygon is a line, the centre of a polygon is a point, and a buffer around a point is a polygon.

<Figure size 1000x360 with 3 Axes>

Attributes are just columns

Filtering by attribute is ordinary table work. In Python that is pandas; in R it is dplyr or base R, and the geometry comes along for the ride.

16 tagged as coffee shops, 9 tagged wheelchair=yes
R
library(sf); library(dplyr)

coffee_shops <- cafes |> filter(grepl("coffee", cuisine))
step_free    <- cafes |> filter(wheelchair == "yes")

Six operations behind most maps

1. Measure: distance, length, area

Loading...

The station point was typed in by hand from a map, in lon/lat, and then reprojected like everything else. from_xy takes x first, so longitude first.

2. Buffer: everything within a distance

A buffer turns a geometry into the polygon of all places within a given distance of it.

<Figure size 600x600 with 1 Axes>

A straight-line buffer is a rough stand-in for walking distance, because people walk along streets. For real travel distance you need a street network (Day 24, Network): see Finding data for osmnx.

3. Spatial join: attach attributes by location

A spatial join is the spatial version of a table join: instead of matching on an ID, it matches on where things are. “Which neighbourhood is each café in?” is a spatial join of points to polygons. Here we make our own polygons, four quadrants around the city centre, to show the idea without downloading anything.

quadrant south-west 39 north-west 29 south-east 16 north-east 7 Name: count, dtype: int64

With real neighbourhood polygons (CBS wijken en buurten, from PDOK), exactly the same line gives you cafés per neighbourhood, which you can then divide by population or area.

4. Dissolve: merge shapes by a column

dissolve is groupby for geometry: it merges all shapes that share a value into one.

half north 54.263575 south 35.099284 dtype: float64

overlay with how="intersection" cut the quadrants to the municipal boundary first, so the areas are real land area.

5. Convex hull and clusters

A convex hull is the smallest convex polygon around a set of points: a quick way to draw “the area this group covers”. Combined with a simple clustering of the points, it gives a Day 4 Clusters map. Here, cafés closer than 250 m to each other are chained into the same cluster.

<Figure size 600x600 with 1 Axes>

This is a hand-made version of single-linkage clustering. For anything you will report, use a proper method such as DBSCAN (sklearn.cluster.DBSCAN in Python, dbscan in R) and say which distance you chose: the clusters depend entirely on it.

6. Nearest neighbour: lines from points

Lines do not have to come from a dataset. Connecting each café to its nearest neighbour makes a Day 2 Lines map out of point data, and the line lengths say something real about how spread out the city’s cafés are.

<Figure size 600x600 with 2 Axes>

exclusive=True stops each café being matched with itself. Colouring the lines by length makes the isolated cafés on the edges stand out. Setting vmax a little above the longest line keeps the palette away from its palest yellow, which would vanish against the grey background.

R equivalents
st_distance(cafes, station)                         # 1. measure
st_buffer(station, 800)                             # 2. buffer
st_join(cafes, quadrants, join = st_within)         # 3. spatial join
quadrants |> group_by(half) |> summarise()          # 4. dissolve
st_convex_hull(st_union(cluster_points))            # 5. convex hull
st_nearest_feature(cafes)                           # 6. nearest neighbour (index)
st_nearest_points(cafes, cafes[nn, ], pairwise = TRUE)   # ... and the lines

In sf, summarise() on a grouped data frame dissolves the geometry, so the dplyr habits carry straight over.

Valid geometry

Real data contains broken shapes: polygons that cross themselves, rings that are not closed. Operations on them fail with errors such as TopologyException, or quietly give the wrong answer.

gdf = gdf[~gdf.is_empty]
gdf["geometry"] = gdf.make_valid()          # R: st_make_valid(gdf)

Run this once after reading data from anywhere you do not control, before overlays and joins.