Python and GIS tutorials
86 Python tutorials, each run end-to-end before publishing: download the scripts and you should get the same figures and numbers you see on the page. Alongside them, 9 QGIS and ArcGIS Pro walkthroughs. Filter by what you want to learn, or follow a learning path or a series.
All tutorials and articles
-
Visualising SPT N-values from AGS4 data in Python
Read the Standard Penetration Tests (ISPT) from a real AGS4 file, separate full tests from refusals, find the stratum each test was made in, and show the N-values four ways: by unit against elevation, across the base of a landslide, as coloured discs on 3D borehole logs over LIDAR terrain, and as a map of the depth to the first refusal.
-
3D-print a geological ground model: one watertight STL per stratum
Turn the kriged stratum surfaces from Part 5 into a multi-material 3D print of the Cotswold escarpment at Crickley Hill. Cut a window, decide what is too thin to print, build one solid per unit with mesh booleans, prove the parts fit exactly and are watertight, and write five STLs and a colour 3MF for AMS and MMU printers.
-
A 3D ground model from AGS4 boreholes: kriged stratum surfaces in Python
Pick rockhead and the top and base of the key formations from real AGS4 logs, krige them into stratum surfaces under Environment Agency LIDAR ground, and build a 3D ground model of the A417 escarpment in PyVista. Then test it: leave-one-out cross-validation at every borehole, kriging standard deviation maps, a check against every hole that stops short of a surface, and proof that no surface crosses another.
-
A geological cross-section from AGS4 boreholes and LIDAR in Python and QGIS
Draw a 3.2 km geological cross-section up the Cotswold escarpment from real AGS4 borehole logs, with the ground profile from Environment Agency LIDAR. Project every hole in a corridor onto the line, pick and correlate the contacts, and measure how much of each correlation line rests on a log and how much is inferred. Then draw the same section with the QGIS elevation profile.
-
3D borehole logs from AGS4 data in PyVista and QGIS
Build every borehole log in a real AGS4 file as coloured 3D cylinders at their true elevation under Environment Agency LIDAR terrain with PyVista, then style the same intervals in the QGIS 3D map view. Along the way, test whether neighbouring logs agree, and find a block of ground at the top of Crickley Hill where they don't.
-
Boreholes on a map: AGS4 locations in British National Grid with QGIS
Turn the LOCA group of a real AGS4 file into points in British National Grid, see what happens when the CRS is wrong, summarise each hole's geology from GEOL, check every ground level against Environment Agency LIDAR, and build a QGIS project with labels and a map tip that shows the borehole log.
-
Read and validate AGS4 ground investigation data with Python
Find a real ground investigation in the British Geological Survey's AGS archive, download it as AGS4, learn how the file is put together, read it into pandas with python-ags4, fix every error the AGS checker reports, and check that the borehole logs make geological sense. The example is the A417 Missing Link at Crickley Hill.
-
Tiling a large 3D-printed terrain model: jigsaw joints and dowel pins in Python
Split a 45 × 30 km model of the central Lake District into six interlocking 150 mm tiles from Copernicus GLO-30, with jigsaw knobs and a 0.2 mm printing clearance or straight edges and filament dowels, and prove every tile is watertight and fits its neighbours.
-
3D print a city model with Python: OpenStreetMap buildings on Environment Agency LiDAR
Build a printable model of central Durham from OpenStreetMap footprints and LiDAR heights. Give every building a roof height, or shape the roofs from the surface model, remove detail too thin to print, and prove the city and its two-colour parts are watertight.
-
Multi-colour 3D-printed terrain with Python: one STL per colour for AMS and MMU printers
Split a 3D-printed model of Yr Wyddfa (Snowdon) into four watertight colour parts (water, woodland, open ground and high ground) from Copernicus GLO-30 and ESA WorldCover, remove specks too small to print, and write STLs and a 3MF for a Bambu Lab AMS or Prusa MMU.
-
Emboss a GPX route on 3D-printed terrain with Python: raised, engraved or two-colour inlay
Turn a GPX track into a printable terrain model: the Pennine Way from Edale to Crowden, draped on Environment Agency LiDAR, with the route raised, engraved, or printed as a separate colour inlay. Every STL is proved watertight before it is written.
-
3D terrain visualisation in Python with PyVista: Helvellyn from LIDAR, Sentinel-2 and a GPX route
Make a 3D terrain map in Python with PyVista. Turn an Environment Agency LIDAR DEM into a StructuredGrid, drape a Sentinel-2 image on it, compare vertical exaggeration and lighting, drape a GPX route over Striding Edge, and render off-screen to PNG and a rotating MP4 on Windows, macOS or Linux.
-
Voronoi (Thiessen) polygons in Python: catchment rainfall from EA rain gauges and fire station areas in Cumbria
Build Voronoi (Thiessen) polygons in Python with shapely and GeoPandas, clipped to a real boundary. Compute Thiessen-weighted catchment rainfall for the River Eden from Environment Agency rain gauge readings, check the gauges, compare with the gauge average and IDW, then compare Voronoi 'nearest fire station' areas in Cumbria with drive-time service areas from OpenStreetMap.
-
Build a STAC catalogue in Python with pystac, rio-stac and pystac-client
Turn 25 Cloud Optimised GeoTIFFs of Eryri into a static STAC catalogue with pystac and rio-stac, validate it against the JSON schemas, query it, serve it as static files, and compare it with the Earth Search and Planetary Computer STAC APIs through pystac-client.
-
Cloud Optimised GeoTIFFs in Python: reproject, mosaic and validate a COG
Mosaic four Copernicus DEM GLO-30 tiles straight from AWS with rasterio, reproject them to British National Grid, write a validated Cloud Optimised GeoTIFF with rio-cogeo, and measure exactly how many bytes HTTP range requests fetch.
-
Geocoding in Python: batch and reverse geocoding with geopy, Nominatim and postcodes.io
Batch geocode all 192 open schools in Sheffield in Python with geopy and OpenStreetMap Nominatim (1 request per second, a custom user agent and an on-disk cache) and with postcodes.io bulk postcode lookups, reverse geocode a sample, and measure every method honestly against the Department for Education's own coordinates. Covers free text against structured queries, failure modes, cross-checking two geocoders, and the licensing of geocoded results.
-
GPS data analysis in Python with MovingPandas: trajectories, speed, stops, generalisation and flow maps
A MovingPandas tutorial on one day of real AIS vessel tracks in Puget Sound: 277,574 GPS fixes from 703 vessels turned into trajectories, with speed and direction, outlier cleaning, stop detection at ferry terminals, splitting into 148 trips, Douglas-Peucker against time-aware generalisation, a flow map of 319 aggregated flows, and reading and writing GPX.
-
Height above nearest drainage (HAND) flood mapping in Python: the River Severn
Compute height above nearest drainage (HAND) from the Environment Agency LIDAR DTM at 5 m with pysheds, turn it into inundation extents for a range of stage heights on the River Severn between Shrewsbury and Buildwas, relate them to the Buildwas gauge, and score them honestly against the Environment Agency Flood Map for Planning with the hit rate, false-alarm ratio and critical success index.
-
Parallel raster processing in Python with dask and rioxarray: a whole Sentinel-2 tile without running out of memory
Process large satellite rasters in Python with dask and rioxarray. Open eight full Sentinel-2 scenes lazily, choose a chunk size, inspect the task graph, watch CPU and memory with dask diagnostics, compare peak memory with eager NumPy, time chunk sizes and thread counts, and write a median NDVI GeoTIFF to disk chunk by chunk.
-
Satellite timelapse in Python: a Sentinel-2 GIF, WebP and MP4 of Hinkley Point C
Create a satellite timelapse in Python from free Sentinel-2 imagery. Select clear scenes with STAC, mask cloud, build one median composite per summer, apply a consistent stretch, add date labels and a scale bar, and export an animated GIF, WebP and MP4 of eleven years of construction at Hinkley Point C.
-
Least-cost path analysis in Python: cost surfaces and walking routes up Yr Wyddfa
Build a walking cost surface from real slope (Copernicus GLO-30) and land cover (ESA WorldCover), compute the accumulated cost surface and the least-cost path with scikit-image's MCP_Geometric, add a direction-aware Dijkstra version with SciPy, and compare the routes with the straight line and the OpenStreetMap footpaths from Pen-y-Pass to the summit of Yr Wyddfa (Snowdon).
-
Farm check: has the rain started on your farm?
A free, phone-friendly check for any farm: whether the rains have started, when they usually start, rain and heat for the next 10 days and how green the crops are. Examples from Lilongwe, Malawi, and Gazipur, Bangladesh.
-
Pasture, water points, fires, floods and rainfall insurance: five more free tools
Five new free tools for herders, district offices and cooperatives: a pasture and water bulletin, a water point check, a weekly fire count, flood water near a village today and a rainfall insurance check. Real answers from Kenya and South Sudan.
-
Free community tools: season condition and flood impact for any district
Two free tools for any district: is this season's rain and crop growth better or worse than usual, and where did it flood and how many people live there? Worked examples from Marsabit, Kenya, in 2022 and Feni, Bangladesh, in 2024.
-
Climate time-series tools: charts and time-lapses for any place
An introduction to the TechnicalGIS climate time-series tools: five open datasets, how to draw an area on the map, worked examples from London, north Evia, Nairobi and Las Vegas, the processing limits and the terms for reusing every output.
-
DBSCAN and HDBSCAN spatial clustering in Python: finding Glasgow's dining districts
Cluster GPS and point-of-interest data in Python with DBSCAN and HDBSCAN: use the haversine metric on latitude and longitude or plain metres on a projected grid, choose eps with a k-distance plot, handle variable density with HDBSCAN, draw convex and concave hull polygons, treat noise properly, read silhouette and DBCV with care, and scale up with a BallTree. Uses 3,950 real Overture Maps restaurants, cafés and bars in Glasgow.
-
Geodemographic classification in Python: k-means on Census 2021 Output Areas, and SKATER and max-p regionalisation
Build a small geodemographic classification of Cardiff's 1,115 Census 2021 Output Areas in Python: choose and transform variables from Nomis bulk tables, standardise them, check correlations, choose k for k-means with the elbow, silhouette and stability across seeds, profile and cautiously name the clusters with index scores and radar charts, compare the result with the official 2021/2 OAC, test the MAUP at LSOA level, and contrast it with contiguous regions from spopt's SKATER and max-p.
-
Geographically weighted regression in Python: GWR and MGWR with mgwr
Fit geographically weighted regression (GWR) and multiscale GWR (MGWR) in Python with the mgwr package on real West Yorkshire house prices: choose the bandwidth by AICc, map local coefficients with a multiple-testing correction, read local R² and local collinearity, and compare against global OLS, spatial lag and spatial error models.
-
Kernel density estimation in Python for GIS: bandwidths, GeoTIFF heatmaps and relative risk
Make a kernel density heatmap from points in Python: compare Scott/Silverman, likelihood cross-validation and a fixed planning distance, Gaussian and Epanechnikov kernels and sample weights, export an edge-corrected GeoTIFF with rasterio, show it on a basemap, and map case-control relative risk on Camden's London plane trees.
-
Land cover accuracy assessment in Python: confusion matrix, Olofsson area estimates and sample size
Assess a real land cover map in Python: ESA WorldCover 2021 against 8,597 Eurostat LUCAS 2022 field survey points in Ireland. Build the confusion matrix, compute overall, user's and producer's accuracy, see why kappa is discouraged, and use the Olofsson et al. (2014) stratified estimators to get error-adjusted areas with 95% confidence intervals and to plan a new stratified random sample.
-
Location-allocation in Python: p-median, p-center and maximal covering (MCLP) with spopt
Solve location-allocation problems in Python with spopt and the CBC solver: the p-median, p-center and maximal covering location problem (MCLP). Choose sites for new urgent-care or mobile clinic bases among Cornwall's community buildings, on OSMnx drive times and Census 2021 population, compare the three answers on maps and charts, draw the trade-off curve as the number of sites grows, and see how problem size drives solve time.
-
Point pattern analysis in Python: quadrats, nearest neighbour and Ripley's K on Camden's street trees
Test whether points are clustered with point pattern analysis in Python: a quadrat chi-squared test, the Clark-Evans nearest-neighbour index and Ripley's G, F, K and L functions with Monte Carlo CSR envelopes, edge correction, a global MAD test and a random-labelling null, on 3,292 London plane trees in Camden.
-
Mann-Kendall trend test on rasters in Python: Theil-Sen NDVI trends with xarray
Run a pixel-wise Mann-Kendall trend test and Theil-Sen slope on 24 years of MODIS NDVI in Python. Read MOD13A1 from the Microsoft Planetary Computer, mask it with the QA layer, aggregate to growing-season means, vectorise the tests with xarray.apply_ufunc (checked against pymannkendall), compare the seasonal and modified Mann-Kendall tests, control the false discovery rate across 229,229 pixels, and map greening and browning on China's Loess Plateau.
-
Two-step floating catchment area (2SFCA) in Python: spatial access to GP practices in Cornwall
Measure spatial accessibility to GP practices in Python with the two-step floating catchment area (2SFCA) method and its distance-decay version (E2SFCA). Build OSMnx drive times for Cornwall from OpenStreetMap, use NHS GP workforce data and Census 2021 population, compare the PySAL access package with a vectorised NumPy version, and test the catchment threshold, the decay function and edge effects.
-
Spatial regression in Python: OLS diagnostics, spatial lag and spatial error models with spreg
Fit spatial regression models in Python with PySAL's spreg on real West Yorkshire house prices: test the OLS residuals for spatial autocorrelation, read the Lagrange Multiplier diagnostics, fit spatial lag and spatial error models, compare them by AIC and log-likelihood, interpret direct, indirect and total impacts, and check how much the spatial weights matter.
-
H3 hexagon grids in Python: indexing, aggregating and smoothing 541,000 London places with Uber's H3
A practical guide to Uber's H3 hexagonal grid in Python with the h3-py v4 API: index points, choose a resolution, fill polygons, count 541,081 Overture places across London, smooth with k-rings, do the same in DuckDB's h3 extension, and compare hexagons with square grids of equal area.
-
Methane plume detection in Python with NASA EMIT: mapping and quantifying landfill plumes
Detect and quantify methane plumes in Python with NASA's EMIT imaging spectrometer: load open EMIT methane enhancement data from NASA VEDA without a login, separate three Dallas-Fort Worth landfill plumes from the noise, compute the integrated mass enhancement, estimate emission rates with ERA5 winds, and compare them with EPA greenhouse gas inventory figures.
-
Make a map in Python with matplotlib and cartopy: projections, classified choropleths, scale bars, north arrows and inset maps
A cartopy tutorial for publication-quality maps in Python: choose a projection, draw a classified choropleth of Census 2021 population density with a proper legend, add a scale bar, a true-north arrow, an inset locator map and gridlines, style it for light and dark backgrounds, export at 300 dpi to PNG, PDF and SVG, and shade the Copernicus DEM for Greater Manchester.
-
Earthquake damage mapping with Sentinel-1 in Python: log ratio and z-scores in Antakya
Map earthquake damage from free Sentinel-1 radar in Python. Compute the log ratio and a z-score against a pre-event stack for Antakya after the February 2023 Türkiye earthquakes, aggregate to a 200 m grid, and score the map against 730 destroyed buildings mapped in OpenStreetMap.
-
Raster to vector in Python: polygonize with rasterio, rasterize boundaries and convert a GeoTIFF to a Cloud Optimized GeoTIFF
Convert raster to vector in Python with rasterio.features.shapes on 10 m ESA WorldCover for Greater Manchester: polygonize water and built-up land, fix invalid polygons, simplify without gaps, rasterize ONS district boundaries for land-cover shares, then write and validate a Cloud Optimized GeoTIFF with rio-cogeo and measure HTTP range reads.
-
Rooftop solar potential from LiDAR in Python: pvlib, PVGIS and shading for every roof
Estimate rooftop solar potential in Python for 2,148 buildings in Manchester: take a 1 m Environment Agency LiDAR DSM and Overture Maps footprints, compute roof slope and aspect, trace horizon angles for shading from neighbours and trees, build a sun map from a PVGIS typical year with pvlib, check it against pvlib's Perez model and PVGIS, then estimate kWp and kWh per year per roof and rank the best roofs.
-
Sentinel-1 soil moisture in Python: change detection checked against COSMOS-UK
Estimate surface soil moisture from free Sentinel-1 radar in Python with the TU Wien change-detection method. Build dry and wet references per orbit, map relative soil moisture at 100 m, and test it honestly against four COSMOS-UK stations and their rain gauges.
-
Spatial joins in GeoPandas: sjoin, predicates, sjoin_nearest and point in polygon
A practical guide to spatial joins in GeoPandas with real Greater Manchester data: 13,187 NaPTAN bus stops, 1,386 OpenStreetMap schools, 215 ONS wards and 1,702 LSOAs. Covers point in polygon with sjoin, predicates, counts per polygon, how='left'/'inner'/'right', sjoin_nearest with max_distance, clip and overlay, and a timing comparison of a naive loop against the spatial index.
-
Coordinate reference systems in Python: EPSG codes, pyproj transforms and reprojecting vectors and rasters
A practical guide to coordinate reference systems in Python with pyproj, GeoPandas and rasterio, tested on real Greater Manchester data. Covers EPSG codes, WGS 84 vs British National Grid (EPSG:27700), the always_xy axis-order trap, OSTN15 vs Helmert datum shifts checked against 13,187 NaPTAN bus stops, measured area and distance errors in the wrong CRS, and reprojecting a GeoDataFrame and a Copernicus DEM raster.
-
Viewshed analysis in Python: line of sight and a wind farm ZTV with GDAL and numpy
Run a viewshed analysis in Python on a real DEM: compute what one wind turbine can see with gdal.ViewshedGenerate, rebuild the same line-of-sight algorithm in numpy, correct for earth curvature and refraction, build a cumulative zone of theoretical visibility (ZTV) from 26 turbines, draw line-of-sight profiles, and measure how much trees and buildings in a LiDAR DSM shrink a bare-earth viewshed.
-
Habitat mapping with Sentinel-2 in Python: spectral index rules and an honest accuracy assessment
Map blanket bog, heath, bracken, grassland, woodland, water and built-up land in the Peak District from 2023 Sentinel-2 seasonal composites with an eight-rule spectral index classifier, then assess it properly: a confusion matrix, user's and producer's accuracy, kappa and its caveats, block-bootstrap confidence intervals, and why a random train/test split overstates accuracy.
-
Canopy height map in Python: Meta 1 m and ETH 10 m maps checked against UK LiDAR (Forest of Dean)
Read the open Meta/WRI global 1 m canopy height map from AWS with no login, compare it with ETH Global Canopy Height 2020, and validate both against a canopy height model made from Environment Agency LiDAR in the Forest of Dean: CRS alignment, bias, RMSE, hexbin plots, height classes and honest limitations.
-
InSAR time series in Python: Svartsengi inflation cycles validated with GNSS
Build an SBAS InSAR time series in Python from 364 COMET LiCSAR Sentinel-1 interferograms of the Svartsengi volcano in Iceland. Check unwrapping with phase closure, invert pixel by pixel with weighted least squares, project GNSS east, north and up into the line of sight, and validate six inflation–eruption cycles against the SENG station.
-
SAR speckle filtering in Python: Lee, refined Lee, Frost and Gamma-MAP on Sentinel-1
Write the classic SAR speckle filters (boxcar, Lee, refined Lee, enhanced Lee, Frost and Gamma-MAP) in NumPy and SciPy, run them on Sentinel-1 over Rutland Water, and compare them by equivalent number of looks, edge preservation and speed.
-
Zonal statistics in Python: exactextract vs rasterstats, with partial pixels, land-cover fractions and a benchmark
Calculate zonal statistics in Python with exactextract and rasterstats on real data: ONS 2021 LSOA boundaries for Cumbria with GHSL population, the Copernicus DEM and ESA WorldCover. Covers partial-pixel weighting vs pixel centroids, CRS alignment, categorical land-cover fractions, a speed benchmark, GeoPackage export and choropleth maps.
-
ERA5 in Python with xarray: mapping the July 2022 UK heatwave
Read ERA5 climate reanalysis in Python with xarray from free, login-free cloud Zarr stores, clip it to the UK, compute area-weighted means and anomalies against 1991–2020, export a Cloud Optimised GeoTIFF and check it against Met Office stations.
-
Habitat classification with LightGBM in Python: Sentinel-2, Sentinel-1 and terrain fusion
Classify nine upland habitats in the Peak District with LightGBM on 77 multi-temporal features: seasonal Sentinel-2 bands and indices, Sentinel-1 VV and VH backscatter and Copernicus DEM terrain. Tune with spatial-fold cross-validation and class weights, run an S2 / S2+S1 / S2+S1+terrain ablation, explain the model with TreeSHAP and permutation importance, and compare it with a random forest and the Part 1 rules on the same test blocks.
-
Mogi model inversion in Python: magma depth and volume from InSAR at Svartsengi, Iceland
Invert Sentinel-1 InSAR for a volcanic source in Python: write Mogi point-source and Okada (1985) sill forward models in NumPy, downsample ascending and descending LOS maps with a quadtree, fit depth and volume change by weighted least squares with scipy.optimize, and estimate uncertainties with a Monte Carlo of correlated noise, compared with published results.
-
Sentinel-2 cloud and cloud-shadow masking in Python: SCL vs s2cloudless vs OmniCloudMask
Mask clouds and cloud shadows in Sentinel-2 L2A imagery with Python. Compare the Scene Classification Layer (SCL), s2cloudless, OmniCloudMask and a simple threshold over the Lake District, measure how far they agree, score them against 186 reference points and build a cloud-free median composite.
-
Snow cover mapping with NDSI in Python: Sentinel-2 and MODIS over the Cairngorms
Map snow cover in Python with the Normalised Difference Snow Index (NDSI) from Sentinel-2 and the daily MODIS snow product on the Microsoft Planetary Computer: choosing the NDSI threshold, separating snow from cloud, snow-covered area through the winter of 2025–26 in the Cairngorms, and the snowline by elevation and aspect with the Copernicus DEM.
-
AlphaEarth satellite embeddings in Python: few-shot habitat mapping, uncertainty and area estimates
Use Google DeepMind's AlphaEarth Foundations satellite embeddings, a geospatial foundation model, in Python without a login. Classify nine Peak District habitats with a linear model, compare a few-shot learning curve against raw Sentinel-2 bands, map uncertainty, check calibration, simulate active learning, and estimate habitat areas with confidence intervals from a stratified random sample (Olofsson et al. 2014).
-
Glacier velocity in Python with NASA ITS_LIVE: Jakobshavn Isbræ, 1985–2025
Map glacier velocity in Python with NASA's ITS_LIVE Zarr datacubes on AWS, with no login: velocity maps, a flowline traced from the velocity field, 40 years of speed profiles, seasonal and multi-year time series from 80,568 filtered image pairs, and strain rates and flow directions on Jakobshavn Isbræ (Sermeq Kujalleq), Greenland's fastest glacier.
-
InSAR volcano deformation in Python: mapping magma inflation at Svartsengi, Iceland, with Sentinel-1
Map volcano deformation with Sentinel-1 InSAR in Python: stream free COMET LiCSAR interferograms of the Svartsengi volcanic system in Iceland, read wrapped and unwrapped phase and coherence, count fringes, convert phase to line-of-sight millimetres, and combine ascending and descending tracks into vertical and east–west motion, checked against GNSS.
-
Object-based image analysis in Python: OBIA habitat classification with Sentinel-2
Segment 2023 Sentinel-2 seasonal composites of the Peak District into image objects with scikit-image SLIC and Felzenszwalb, tune the segment size against Living England boundaries, compute per-object statistics, shape and GLCM texture with NumPy, classify the objects with LightGBM and compare OBIA with a pixel-based classifier on accuracy, salt-and-pepper noise and patch counts. Then export the habitat objects as GeoPackage polygons.
-
Satellite image time series classification with deep learning: TempCNN and L-TAE in PyTorch on a CPU
Classify habitats from a full year of cloudy, irregular Sentinel-2 observations with two temporal neural networks in PyTorch: a 1D convolutional TempCNN and a lightweight temporal attention encoder (L-TAE) with day-of-year positional encoding. Compare masking with gap-filling, test against a LightGBM baseline on spatial blocks, read which dates matter from attention and occlusion, and predict a wall-to-wall 10 m map on a CPU.
-
Coastal erosion and shoreline change from Sentinel-2 in Python (Holderness, 2017–2026)
Measure shoreline change on one of Europe's fastest-eroding coasts with free Sentinel-2 imagery and Python: MNDWI and Otsu thresholding, sub-pixel shorelines with find_contours, shore-normal transects, a tide correction from open tide-gauge data, end-point and linear regression rates, and an honest comparison with East Riding of Yorkshire Council's cliff monitoring.
-
Deforestation detection in Python with Global Forest Change data (Rondônia, 2001–2025)
Map forest loss and deforestation in Python with the Hansen/UMD Global Forest Change v1.13 data: read the open tiles with no login, compute loss by year in hectares with correct pixel areas, compare Landsat and Sentinel-2 images before and after, and check the GFC loss year against Sentinel-2 NBR and MODIS burned area for central Rondônia, Brazil.
-
Hot spot analysis in Python: Getis-Ord Gi*, Moran's I and LISA on Birmingham road collisions
Run a hot spot analysis in Python with PySAL: aggregate 2025 STATS19 road collisions in Birmingham to a hexagon grid, test for clustering with global Moran's I, map Getis-Ord Gi* hot spots and LISA clusters with permutation inference and FDR correction, and see how spatial weights, the areal unit and exposure change the answer.
-
Deep learning inference on large rasters: sliding-window prediction, COGs and polygons in Python
Run a trained PyTorch U-Net over a whole Sentinel-2 scene of a city it has never seen: tiled sliding-window inference on a large GeoTIFF with overlapping, blended windows, a georeferenced Cloud Optimised GeoTIFF of classes and confidence, raster-to-vector polygons with rasterio.features.shapes in a GeoPackage, and an honest check against reference labels.
-
Kriging in Python with PyKrige: IDW vs ordinary kriging of UK NO₂ monitoring data
A practical spatial interpolation tutorial in Python: download real 2025 NO₂ annual means from the UK AURN network, fit spherical, exponential and Gaussian variograms, run ordinary kriging with PyKrige, compare it with inverse distance weighting, score both with leave-one-out cross-validation, map the kriging variance and add Sentinel-5P satellite NO₂ as a covariate.
-
U-Net satellite image segmentation in PyTorch: a TorchGeo and Sentinel-2 tutorial on a CPU
Train a U-Net to segment Sentinel-2 imagery into buildings, built-up land, trees, grass and water with PyTorch, segmentation_models_pytorch and TorchGeo's Sentinel-2 pretrained ResNet-18 weights, on a CPU in five minutes. Real loss curves, per-class IoU and F1, a confusion matrix and an honest look at what limits accuracy.
-
PMTiles vector tiles in Python and a MapLibre GL JS web map
Turn Overture Maps GeoParquet into PMTiles vector tiles with the GDAL that ships in pyogrio, no Tippecanoe needed, measure the zoom-level and size trade-offs, and serve an interactive MapLibre GL JS map of Bristol from static hosting.
-
Query Overture Maps with DuckDB spatial and GeoParquet in Python
Pull Bristol's 144,965 buildings and 20,727 places straight from the Overture Maps GeoParquet release with DuckDB, run spatial SQL joins and H3 aggregation, load the result into GeoPandas, write GeoParquet 1.1, and time it against Shapefile, GeoPackage and FlatGeobuf.
-
Training data for deep learning on satellite imagery: Sentinel-2 image chips in Python
Build a labelled deep learning dataset from free data: stream a Sentinel-2 L2A composite from the Planetary Computer, rasterise Overture building footprints over ESA WorldCover, cut georeferenced image chips, split them by spatial block so the test set does not leak, check the class balance and load it all as a TorchGeo dataset.
-
Sentinel-5P NO₂ in Python: mapping air pollution from TROPOMI over the UK and north-west Europe
Map nitrogen dioxide air pollution from Sentinel-5P TROPOMI Level-2 data in Python: stream a full year of orbits from the Planetary Computer, apply the qa_value > 0.75 filter, grid swaths to a regular raster, build monthly and seasonal composites, rank cities and measure the weekend drop, with an honest explanation of what a tropospheric column is.
-
InSAR ground subsidence in Python: one year of Mexico City sinking, from Sentinel-1
Map ground subsidence with Sentinel-1 InSAR in Python: stream 170 real interferograms of Mexico City, convert unwrapped phase to millimetres of line-of-sight motion, mask low coherence, invert a small-baseline network into a time series, and cross-check the velocity with MintPy.
-
Burn severity mapping with Sentinel-2 and dNBR in Python (Saddleworth Moor 2018)
Map wildfire burn severity in Python with free Sentinel-2 imagery: NBR calculation, cloud and water masking with SCL, dNBR with an offset, USGS severity classes and burned hectares, a burn perimeter, fire progression, RdNBR and an eight-year recovery record for the 2018 Saddleworth Moor fire.
-
Extract building footprints and heights from LiDAR in Python (USGS 3DEP, downtown Madison)
Turn classified LiDAR into building footprints with heights in Python: stream USGS 3DEP COPC tiles, build a DTM and a building nDSM from ASPRS class 6 points, polygonise and regularise the mask, split roofs at height steps, compute height statistics per building, score the result against OpenStreetMap with IoU, and export a GeoPackage and a 3D view.
-
Sentinel-1 crop monitoring in Python: SAR time series for wheat and oilseed rape
Monitor crops through cloud with free Sentinel-1 radar. Build VV, VH and VH/VV ratio time series for 247 fields in Cambridgeshire, beat speckle by field-averaging, read the growth signatures of winter wheat and oilseed rape, and compare with the gaps that cloud leaves in Sentinel-2 NDVI.
-
Segment Anything on satellite and aerial imagery in Python: a samgeo tutorial
Run Meta's Segment Anything Model on high-resolution aerial imagery with segment-geospatial (samgeo), on a CPU. Generate masks automatically, vectorise them to a GeoPackage, filter them by area, shape and NDVI to find buildings, and prompt SAM with points and boxes, checked against OpenStreetMap.
-
Land surface temperature from Landsat in Python: mapping London's urban heat island
Calculate land surface temperature from Landsat 8 and 9 Collection 2 Level-2 data in Python: ST_B10 scale and offset, QA_PIXEL cloud masking, LST by land cover and NDVI, an urban-rural transect, hotspots with OpenStreetMap and an honest comparison with air temperature, for London in the July 2022 heatwave.
-
Land cover classification in Python: a Sentinel-2 random forest with scikit-learn
Supervised land cover classification in Python: build seasonal Sentinel-2 composites with odc-stac, sample labels from ESA WorldCover, train a scikit-learn random forest, test it with spatial cross-validation and map Sheffield and the Peak District.
-
NDVI time series in Python with Sentinel-2, odc-stac and xarray
Build a five-year Sentinel-2 NDVI time series in Python: load a lazy dask datacube with odc-stac, mask cloud with SCL, make monthly composites, extract field curves and derive crop phenology.
-
Sentinel-1 ship detection in Python: a CA-CFAR detector off the Port of Rotterdam
Detect ships in free Sentinel-1 SAR imagery with a cell-averaging CFAR detector written in NumPy and SciPy: mask the land, set a threshold from a speckle model, cluster detections into targets, reject wind turbines, sidelobes and ambiguities, compare VV with VH and export the vessels as GeoJSON.
-
Watershed delineation in Python with pysheds: the River Derwent above Ladybower
Delineate a catchment and its stream network from a free DEM in Python with pysheds: fill pits and depressions, D8 flow direction, flow accumulation, snapping the pour point, catchment polygon and area, Strahler stream order and drainage density, all checked against the official NRFA boundary for the River Derwent at Yorkshire Bridge in the Peak District.
-
Monitoring reservoir water quality from space with Sentinel-2 and Python (Peak District case study)
Track chlorophyll, turbidity and peat colour proxies in four Peak District reservoirs with free Sentinel-2 imagery: cloud and water masking for small, dark upland lakes, NDCI and NDTI time series from 2019 to 2026, anomaly ranking, maps, tidy CSVs and an honest check against Environment Agency samples.
-
Sentinel-1 SAR flood mapping in Python: the 2022 Pakistan floods
Map the 2022 Sindh floods with free Sentinel-1 radar from the Microsoft Planetary Computer: convert backscatter to decibels, remove speckle with a Lee filter, pick a water threshold with Otsu's method, and measure the flood extent and how it receded over seven months.
-
LiDAR point clouds in Python with laspy: from a LAZ file to a DTM, DSM and canopy height model
Stream free USGS 3DEP lidar of Washington, D.C., read and inspect a LAZ file with laspy, remove noise, grid the points into a 1 m DTM, DSM and canopy height model, and use the result to measure the Washington Monument to within 44 cm.
-
From DEM to 3D-printable STL with Python and NumPy
Stream a Copernicus 30 m DEM of the Edale valley, reproject it to British National Grid, scale it to a 150 mm print and write a watertight binary STL using only NumPy, with checks that prove the mesh is printable.
-
Fast vector geoprocessing in Python: spatial indexes, overlays and modern file formats
Benchmark point-in-polygon joins, buffers, overlays and dissolves on 200,000 points with GeoPandas and Shapely 2, see why spatial indexes give a 100x+ speed-up, and compare Shapefile, GeoPackage, FlatGeobuf and GeoParquet.
-
Interactive web maps from Python with Folium and GeoPandas .explore()
Turn GeoDataFrames into shareable Leaflet web maps: a one-line choropleth with .explore(), then a hand-built Folium map with custom styling, tooltips, clustered markers, switchable basemaps and plugins — saved as a single HTML file.
-
Street network analysis with OSMnx: routes, isochrones and walking accessibility
Download Sheffield's walkable street network from OpenStreetMap with OSMnx, find the shortest walk across the city, draw 5–20 minute isochrones and map how far every street is from the nearest supermarket.
-
Measuring Lake Mead's shrinking shoreline with Sentinel-2, STAC and Python
Search seven summers of Sentinel-2 imagery with pystac-client, stream only the bands you need from Cloud Optimised GeoTIFFs, mask clouds, compute NDWI and MNDWI and measure how much of Lake Mead dried up between 2019 and 2025.
-
Terrain analysis in Python: stream, clip and analyse a DEM with rasterio and rioxarray
Read only the pixels you need from a cloud-hosted Copernicus DEM, reproject to British National Grid, derive hillshade, slope and aspect with NumPy, compute zonal statistics and draw an elevation profile — all in one script.
-
Getting started with GeoPandas: projections, spatial joins and choropleth maps
Load Natural Earth vector data with GeoPandas, reproject to an equal-area CRS, calculate population density, join cities to countries, dissolve to continents and draw publication-ready choropleths.
-
Visualising a Cycle Track in 3D
Turning a GPX track of the Wharncliffe Woods cycle route into a 3D scene and elevation profile in ArcGIS Pro.
-
Performing Bivariate Correlation Analysis using Census and Crime Data
Exploring the relationship between census variables and recorded crime with bivariate correlation analysis in ArcGIS Pro.
-
How to Add Google Maps and OpenStreetMap Layers to QGIS
Add OpenStreetMap, satellite and Google basemaps to QGIS 3.40 LTR, 3.44 and 4.x as XYZ Tiles connections: the correct tile URLs, the OpenStreetMap tile usage policy, the licence-safe ways to get Google, Esri and Sentinel-2 imagery, and the same basemaps from PyQGIS, folium, leafmap and contextily.
-
Creating a 3D Printed Digital Elevation Model in QGIS
From a digital elevation model to a physical 3D print: preparing terrain in QGIS and exporting it for slicing.
-
Monitoring Lake Mead Drought
A look at the dramatic fall in Lake Mead's water level during the 2022 drought.
Nothing matches all of those filters.
Try fewer filters, or search the whole site, which also covers the country data guides.
Learning paths
Each path runs from a first step to research-grade analysis, in an order where every tutorial builds on the one before. Jump in wherever the level suits you.
Python GIS foundations
Vector data, projections, joins and maps, then rasters, zonal statistics and cloud-native formats.
- Getting started with GeoPandas: projections, spatial joins and choropleth maps Beginner
- Coordinate reference systems in Python: EPSG codes, pyproj transforms and reprojecting vectors and rasters Beginner
- Spatial joins in GeoPandas: sjoin, predicates, sjoin_nearest and point in polygon Beginner
- Make a map in Python with matplotlib and cartopy: projections, classified choropleths, scale bars, north arrows and inset maps Beginner
- Interactive web maps from Python with Folium and GeoPandas .explore() Beginner
- Terrain analysis in Python: stream, clip and analyse a DEM with rasterio and rioxarray Intermediate
- Zonal statistics in Python: exactextract vs rasterstats, with partial pixels, land-cover fractions and a benchmark Intermediate
- Raster to vector in Python: polygonize with rasterio, rasterize boundaries and convert a GeoTIFF to a Cloud Optimized GeoTIFF Intermediate
- Fast vector geoprocessing in Python: spatial indexes, overlays and modern file formats Intermediate
- Query Overture Maps with DuckDB spatial and GeoParquet in Python Intermediate
Remote sensing and Earth observation
Search Sentinel-2 with STAC, mask clouds, build index time series, then measure fire, water, heat, snow and gas.
- Measuring Lake Mead's shrinking shoreline with Sentinel-2, STAC and Python Intermediate
- Sentinel-2 cloud and cloud-shadow masking in Python: SCL vs s2cloudless vs OmniCloudMask Intermediate
- NDVI time series in Python with Sentinel-2, odc-stac and xarray Intermediate
- Burn severity mapping with Sentinel-2 and dNBR in Python (Saddleworth Moor 2018) Intermediate
- Monitoring reservoir water quality from space with Sentinel-2 and Python (Peak District case study) Intermediate
- Land surface temperature from Landsat in Python: mapping London's urban heat island Intermediate
- Snow cover mapping with NDSI in Python: Sentinel-2 and MODIS over the Cairngorms Intermediate
- Sentinel-5P NO₂ in Python: mapping air pollution from TROPOMI over the UK and north-west Europe Intermediate
- Coastal erosion and shoreline change from Sentinel-2 in Python (Holderness, 2017–2026) Advanced
- Methane plume detection in Python with NASA EMIT: mapping and quantifying landfill plumes Advanced
SAR and InSAR
Radar from first principles: speckle, floods, ships and crops with Sentinel-1, then interferometry and source modelling.
- SAR speckle filtering in Python: Lee, refined Lee, Frost and Gamma-MAP on Sentinel-1 Intermediate
- Sentinel-1 SAR flood mapping in Python: the 2022 Pakistan floods Intermediate
- Sentinel-1 ship detection in Python: a CA-CFAR detector off the Port of Rotterdam Intermediate
- Sentinel-1 crop monitoring in Python: SAR time series for wheat and oilseed rape Intermediate
- Sentinel-1 soil moisture in Python: change detection checked against COSMOS-UK Intermediate
- Earthquake damage mapping with Sentinel-1 in Python: log ratio and z-scores in Antakya Intermediate
- InSAR ground subsidence in Python: one year of Mexico City sinking, from Sentinel-1 Advanced
- InSAR volcano deformation in Python: mapping magma inflation at Svartsengi, Iceland, with Sentinel-1 Advanced
- Mogi model inversion in Python: magma depth and volume from InSAR at Svartsengi, Iceland Advanced
- InSAR time series in Python: Svartsengi inflation cycles validated with GNSS Advanced
Machine learning and AI
From rule-based maps and random forests to U-Nets, transformers and foundation-model embeddings, validated honestly.
- Habitat mapping with Sentinel-2 in Python: spectral index rules and an honest accuracy assessment Beginner
- Land cover classification in Python: a Sentinel-2 random forest with scikit-learn Intermediate
- Habitat classification with LightGBM in Python: Sentinel-2, Sentinel-1 and terrain fusion Intermediate
- Segment Anything on satellite and aerial imagery in Python: a samgeo tutorial Intermediate
- Training data for deep learning on satellite imagery: Sentinel-2 image chips in Python Intermediate
- U-Net satellite image segmentation in PyTorch: a TorchGeo and Sentinel-2 tutorial on a CPU Advanced
- Deep learning inference on large rasters: sliding-window prediction, COGs and polygons in Python Advanced
- Object-based image analysis in Python: OBIA habitat classification with Sentinel-2 Advanced
- Satellite image time series classification with deep learning: TempCNN and L-TAE in PyTorch on a CPU Advanced
- AlphaEarth satellite embeddings in Python: few-shot habitat mapping, uncertainty and area estimates Advanced
Multi-part series
One project, followed from raw data to result over several tutorials. Read them in order.
-
AGS data in GIS
- Read and validate AGS4 ground investigation data with Python
- Boreholes on a map: AGS4 locations in British National Grid with QGIS
- 3D borehole logs from AGS4 data in PyVista and QGIS
- A geological cross-section from AGS4 boreholes and LIDAR in Python and QGIS
- A 3D ground model from AGS4 boreholes: kriged stratum surfaces in Python
- Visualising SPT N-values from AGS4 data in Python
- 3D-print a geological ground model: one watertight STL per stratum
-
3D-printed maps with Python
- From DEM to 3D-printable STL with Python and NumPy
- Emboss a GPX route on 3D-printed terrain with Python: raised, engraved or two-colour inlay
- Multi-colour 3D-printed terrain with Python: one STL per colour for AMS and MMU printers
- 3D print a city model with Python: OpenStreetMap buildings on Environment Agency LiDAR
- Tiling a large 3D-printed terrain model: jigsaw joints and dowel pins in Python
-
Cloud-native rasters in Python
-
Spatial regression in Python
-
Point pattern analysis in Python
-
Healthcare accessibility analysis in Python
-
Geospatial Python essentials
- Coordinate reference systems in Python: EPSG codes, pyproj transforms and reprojecting vectors and rasters
- Spatial joins in GeoPandas: sjoin, predicates, sjoin_nearest and point in polygon
- Raster to vector in Python: polygonize with rasterio, rasterize boundaries and convert a GeoTIFF to a Cloud Optimized GeoTIFF
-
AI habitat classification with Python
- Habitat mapping with Sentinel-2 in Python: spectral index rules and an honest accuracy assessment
- Habitat classification with LightGBM in Python: Sentinel-2, Sentinel-1 and terrain fusion
- Object-based image analysis in Python: OBIA habitat classification with Sentinel-2
- Satellite image time series classification with deep learning: TempCNN and L-TAE in PyTorch on a CPU
- AlphaEarth satellite embeddings in Python: few-shot habitat mapping, uncertainty and area estimates
-
Volcano deformation with InSAR in Python
-
Deep learning for satellite imagery with Python
-
Mapping NO₂ air pollution from space and from the ground
-
Cloud-native vector GIS with Python