Chapter 6: Introduction to Spatial Data¶
Part II — Geographic Data Science
Learning Objectives¶
By the end of this chapter you will be able to:
- Distinguish vector and raster data models
- Understand coordinate reference systems (CRS)
- Load and inspect GeoJSON, Shapefile, and GeoTIFF files
# Standard imports — add chapter-specific imports below
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import geopandas as gpd
Part I never asked where anything was — world_data.csv had a country column, not a shape. Part II adds that missing dimension. Its running example, from here through Chapter 16, is the state of Maranhão, on Brazil's northern Atlantic coast — home to this book's own research group, and, not coincidentally, a state whose coastline of mangroves and shifting dunes is exactly the kind of place discrete spatial models (Part III) get built for.
Vector vs Raster: Two Ways to Model Space¶
Every representation of geographic space reduces, in the end, to one of two models.
Vector data represents the world as discrete objects with exact geometry: a city is a point, a road is a line, a municipality's boundary is a polygon. Each object carries its own attributes — a municipality polygon might carry a name, a population, an area — in a structure that should already feel familiar from Part I: one row per object, one column per attribute, plus a special geometry column holding the shape itself. That structure is exactly what a GeoDataFrame is, as you'll see in a moment.
Raster data instead divides space into a regular grid of cells — pixels — and stores one or more values per cell: elevation, temperature, a land-cover class. There is no list of "objects"; there is only the grid itself and the value sitting in each cell. A satellite image is a raster. So, for that matter, is every digital photograph — a geographic raster just adds the information needed to place each pixel at a real-world coordinate.
Neither model is strictly better — each fits some phenomena more naturally than the other:
| Vector | Raster | |
|---|---|---|
| Natural fit for | Discrete objects — parcels, roads, administrative boundaries | Continuous fields — elevation, temperature, satellite imagery |
| Storage | A list of geometries + attribute table | A grid of values, uniform cell size |
| Precision | Exact, at the resolution the geometry was drawn | Limited by cell (pixel) size |
| Getting bigger | More geometries, each still exact | More cells, or coarser cells to keep the file size down |
This distinction is not a historical accident of GIS software — it runs all the way through this book. Chapter 7 works entirely in vector; Chapter 8 entirely in raster; Chapter 16 tackles the patterns for converting between them. And when Part III turns to building spatial models, you'll find the same fork again: DisSModel's SpatialModel operates on a vector GeoDataFrame, its RasterModel on a NumPy array grid, and Chapter 21 dedicates a full section to exactly this choice.
Geometry Types and WKT: The OGC Standard¶
"Vector data represents the world as discrete objects" is true, but it hides a real question: which objects, exactly? The Open Geospatial Consortium (OGC) answered this decades ago with Simple Features, a small, fixed hierarchy of geometry types that essentially every vector format and library in this book — Shapefile, GeoPackage, PostGIS, and shapely underneath geopandas itself — implements identically:
| Type | Built from | Example |
|---|---|---|
Point |
a single coordinate | a monitoring station |
LineString |
an ordered sequence of points | a road, a river |
Polygon |
one or more closed rings (an outer boundary, optional holes) | a municipality, a lake |
MultiPoint, MultiLineString, MultiPolygon |
several of the above, treated as one feature | an archipelago as one MultiPolygon |
GeometryCollection |
a mix of different types together | rarely needed, but always legal |
A Polygon's "one or more rings" detail is worth sitting with: the first ring is the outer boundary, and every ring after it is a hole cut out of the interior — Chapter 7's São Luís example, with its islands, is a MultiPolygon; a municipality with a hole for an enclave it doesn't administer would be a Polygon with two rings, not two separate shapes.
from shapely.geometry import Point, LineString, Polygon
from shapely import wkt
station = Point(-44.30, -2.53)
print(repr(station))
print(station.wkt)
<POINT (-44.3 -2.53)> POINT (-44.3 -2.53)
That POINT (-44.3 -2.53) text — printed automatically whenever a shapely geometry is shown — is not a Python-specific format. It's Well-Known Text (WKT), the exact OGC-standard serialization every geometry type above has: coordinates for a Point, a coordinate sequence for a LineString, ring(s) of coordinates for a Polygon. A GeoDataFrame's geometry column, under the hood, is nothing more than a column of shapely objects that all know how to read and write this format:
polygon_wkt = "POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))"
recovered = wkt.loads(polygon_wkt)
print(type(recovered))
print(recovered.area)
<class 'shapely.geometry.polygon.Polygon'> 16.0
wkt.loads() going one direction, .wkt going the other — this round trip is exactly what happens invisibly every time geopandas.read_file() (Chapter 7) parses a Shapefile or GeoPackage, and it's why a WKT string is a perfectly valid, if verbose, way to hand a geometry to any spatial database or web API that speaks the OGC standard, PostGIS included.
Coordinate Reference Systems¶
A polygon's coordinates are meaningless without knowing what they're measured against. (-44.3, -2.5) could be degrees of longitude and latitude on a sphere, or it could be meters on a flat projected grid — and a Coordinate Reference System (CRS) is the piece of metadata that says which.
Two broad families matter here:
- Geographic CRS — coordinates are angles (degrees) measured on a model of the Earth's curved surface: longitude east-west, latitude north-south. WGS84 (EPSG:4326) is the global default, used by GPS; SIRGAS 2000 (EPSG:4674) is the geographic CRS Brazil's own agencies, including IBGE, publish data in. Distances and areas computed directly in degrees are not physically meaningful — a degree of longitude spans a very different ground distance at the Equator than near a pole.
- Projected CRS — the curved Earth has been mathematically flattened onto a plane, and coordinates are linear units — usually meters. UTM (Universal Transverse Mercator) divides the world into 60 numbered zones, each narrow enough that distortion inside it stays small; Maranhão sits mostly in UTM zone 23S (EPSG:31983 on the SIRGAS 2000 datum). Distances and areas computed in a projected CRS are physically meaningful, which is exactly why Chapter 16's distance-transform techniques need one.
Every EPSG code names one specific, unambiguous CRS. GeoPandas exposes the current one on any GeoDataFrame as .crs, and reprojects with .to_crs():
# gdf.crs # inspect the current CRS
# gdf_projected = gdf.to_crs(epsg=31983) # reproject to UTM 23S (meters)
The rule of thumb this book follows from here on: stay in a geographic CRS (WGS84 or SIRGAS 2000) for display — most basemaps and web tools expect it — and reproject to a UTM zone the moment you need to measure a real distance or area, exactly as Chapter 16 does when it builds a proximity driver from roads and airports.
Common Spatial Formats¶
Vector and raster data each have their own common file formats, and it's worth being able to recognize them on sight:
Vector
| Format | Extension(s) | Notes |
|---|---|---|
| Shapefile | .shp + .shx, .dbf, and usually .prj |
The oldest common format, still everywhere (IBGE publishes in it); a "single" shapefile is really a bundle of files that must stay together |
| GeoJSON | .geojson (or plain .json) |
Human-readable text, geometry and attributes together in one file; ubiquitous on the web |
| GeoPackage | .gpkg |
A single SQLite database file, can hold several layers at once; the modern, tidier alternative to a shapefile |
Raster
| Format | Extension(s) | Notes |
|---|---|---|
| GeoTIFF | .tif, .tiff |
The standard for a single raster grid — an ordinary TIFF image plus georeferencing metadata; Chapter 8 is built around it |
| NetCDF | .nc |
Built for stacks of grids sharing coordinates — typically a time series, or several variables on the same grid; Chapter 9's xarray chapter is built around it |
All of them, vector or raster, ultimately decompress to the same two ingredients Vector vs Raster already introduced: geometry (or a grid) plus attributes (or cell values).
Loading Spatial Data in Python¶
geopandas.read_file() is the single entry point for essentially every vector format above — Shapefile, GeoJSON, GeoPackage — and it works directly on a URL, exactly as pandas.read_csv() did for world_data.csv back in Chapter 3. IBGE, Brazil's official statistics agency, publishes municipal boundaries for every state at a stable URL; here is Maranhão's, the running example for the rest of Part II:
url = (
"https://geoftp.ibge.gov.br/organizacao_do_territorio/"
"malhas_territoriais/malhas_municipais/municipio_2022/"
"UFs/MA/MA_Municipios_2022.zip"
)
maranhao = gpd.read_file(url)
maranhao.head()
| CD_MUN | NM_MUN | SIGLA_UF | AREA_KM2 | geometry | |
|---|---|---|---|---|---|
| 0 | 2100055 | Açailândia | MA | 5805.159 | POLYGON ((-47.40208 -5.03469, -47.47673 -5.046... |
| 1 | 2100105 | Afonso Cunha | MA | 371.338 | POLYGON ((-43.28145 -4.31924, -43.42924 -4.223... |
| 2 | 2100154 | Água Doce do Maranhão | MA | 442.292 | POLYGON ((-42.17657 -2.84415, -42.17639 -2.844... |
| 3 | 2100204 | Alcântara | MA | 1167.964 | MULTIPOLYGON (((-44.57175 -2.21957, -44.56274 ... |
| 4 | 2100303 | Aldeias Altas | MA | 1942.121 | POLYGON ((-43.3651 -4.64627, -43.36676 -4.6442... |
Notice read_file() needed no format flag — it inspected the URL and the file inside the zip itself to work out that this was a shapefile. The result is a GeoDataFrame: everything from Chapter 3's DataFrame (.head(), .info(), boolean filtering, .groupby()) still applies, plus the geometry column and .crs:
print("CRS:", maranhao.crs)
print("Municipalities:", len(maranhao))
maranhao.plot(edgecolor="black", facecolor="none", figsize=(6, 6))
plt.title("Maranhão — 217 municipalities")
plt.show()
CRS: EPSG:4674 Municipalities: 217
That single .plot() call is doing more than it looks — it read every polygon's geometry, worked out a bounding box, and drew each municipality's boundary, all from three lines of code. Chapter 7 picks up exactly here and starts asking real questions of this same GeoDataFrame: which municipality is which, how to select just the state capital, and how to combine this boundary data with other layers.
Exercises¶
- Inspect before you trust. Load the Maranhão municipalities
GeoDataFrameabove and run.info()on it. How many columns does IBGE's shapefile carry beyondgeometry? What does theCD_MUNcolumn look like — and given Chapter 4's cleaning checklist, would you trust its dtype at a glance, or check it? - Reproject and compare. Reproject
maranhaoto UTM zone 23S (maranhao.to_crs(epsg=31983)) and plot it next to the original. The shape on screen barely changes — so what actually changed? (Hint: check.total_boundsbefore and after.) - Vector or raster? For each of the following, decide which data model fits more naturally, and why: (a) the boundary of a national park, (b) daily rainfall across a state, (c) the location of every fire station in a city, (d) satellite-derived forest cover for the Amazon.
- Find a format. Using the Common Spatial Formats table, name a format you'd choose to publish a single elevation grid for public download, and a different one you'd choose to share a table of 5,000 sensor locations with a colleague who doesn't use GIS software. Justify each choice in one sentence.
# Your code here
Summary¶
Key concepts introduced¶
- The vector/raster split: discrete objects with exact geometry versus a continuous grid of cell values — a distinction that runs through the rest of this book, including DisSModel's own
SpatialModel/RasterModelsplit in Part III - Coordinate Reference Systems: geographic (degrees, e.g. WGS84 / SIRGAS 2000) versus projected (meters, e.g. UTM), and the rule of thumb — display in geographic, measure in projected
- The common file formats for each model: Shapefile, GeoJSON, and GeoPackage for vector; GeoTIFF and NetCDF for raster
geopandas.read_file()as the universal vector entry point, reading directly from a URL exactly aspandas.read_csv()did in Chapter 3
Maranhão's municipal boundaries, loaded here, carry through as the running example for the rest of Part II — Chapter 7 starts working with them directly.
Further Reading¶
- geopandas documentation, Introduction to GeoPandas: https://geopandas.org/en/stable/getting_started/introduction.html
- IBGE, Malhas Territoriais (the source of the Maranhão boundaries used above): https://www.ibge.gov.br/geociencias/organizacao-do-territorio/malhas-territoriais.html
- epsg.io — a searchable reference for any EPSG code, including 4326 (WGS84), 4674 (SIRGAS 2000), and 31983 (SIRGAS 2000 / UTM zone 23S): https://epsg.io/