smo_scottish_lidar
A pure Ruby gem for listing and downloading Scottish Public Sector LiDAR data from the Registry of Open Data on AWS. Built by Sebastian Madrid Ontiveros to support hydraulic modellers in Scotland working on 1D-2D flood risk assessments and model build workflows.
No external dependencies. No AWS CLI. No credentials. Uses only Ruby stdlib (net/http, uri, fileutils). Compatible with InfoWorks ICM 2027 embedded Ruby.
What is this data?
The Scottish Government has made LiDAR survey data publicly available through an S3 bucket (srsp-open-data). The dataset covers most of Scotland across five survey phases plus a dedicated Outer Hebrides survey. Each phase includes:
- DSM - Digital Surface Model (includes buildings, trees, structures)
- DTM - Digital Terrain Model (bare earth, vegetation removed)
- LAZ - Raw LiDAR point cloud in compressed LAS format
Files are organised by OS National Grid square (e.g. NS, NT, NO, NN) and are free to download.
| Phase | Area covered |
|---|---|
| phase-1 | Central Scotland |
| phase-2 | South and East Scotland |
| phase-3 | North and West Scotland |
| phase-4 | Additional coverage |
| phase-5 | Latest survey phase |
| outer-hebrides | Western Isles (25cm and 50cm resolution) |
Installation
gem install smo_scottish_lidar
require "smo_scottish_lidar"
Quick start
require "smo_scottish_lidar"
# List all Phase 1 DSM tiles in the NS grid square
lister = SmoScottishLidar::Lister.new
lister.summary("phase-1", "dsm", grid_square: "NS")
# Download a single tile
downloader = SmoScottishLidar::Downloader.new
downloader.download_file("phase-1", "dsm", "NS56_1M_DSM_PHASE1.tif",
destination: "/tmp/lidar"
)
# Batch download all NS tiles for Phase 1 DSM
downloader.download("phase-1", "dsm",
destination: "/tmp/lidar/phase-1/dsm",
grid_square: "NS"
)
API reference
SmoScottishLidar::Lister
Lists available files from the S3 bucket. All filtering is done client-side after fetching the S3 listing.
lister = SmoScottishLidar::Lister.new(verbose: false)
lister.list(phase, type, grid_square: nil, resolution: nil)
Returns an Array<Hash> of matching files. Each hash contains:
| Key | Type | Description |
|---|---|---|
:key |
String | Full S3 object key |
:filename |
String | Bare filename (e.g. NS56_1M_DSM_PHASE1.tif) |
:size |
Integer | File size in bytes |
:last_modified |
String | ISO 8601 timestamp |
files = lister.list("phase-1", "dsm", grid_square: "NS")
files.each { |f| puts "#{f[:filename]} #{f[:size]} bytes" }
lister.summary(phase, type, grid_square: nil, resolution: nil)
Prints a formatted table to stdout and returns the same Array<Hash>.
lister.summary("phase-2", "dtm", grid_square: "NT")
lister.summary("outer-hebrides", "dsm", resolution: "50cm")
SmoScottishLidar::Downloader
Downloads files from the S3 bucket. Streams in chunks to avoid loading large files into memory.
downloader = SmoScottishLidar::Downloader.new(verbose: false)
downloader.download(phase, type, destination:, ...)
Downloads all tiles matching the given filters. Returns a summary hash { downloaded: [...], skipped: [...], failed: [...] }.
downloader.download(
"phase-1", "dsm",
destination: "/tmp/lidar/phase-1/dsm",
grid_square: "NS", # optional. nil downloads everything
skip_existing: true, # skip files already on disk at the correct size
dry_run: false # set true to preview without downloading
)
| Option | Default | Description |
|---|---|---|
destination: |
required | Local directory to save files into |
grid_square: |
nil |
OS grid square filter, e.g. "NS", "NT" |
resolution: |
nil |
Outer Hebrides only, e.g. "25cm", "50cm", "4ppm", "16ppm" |
skip_existing: |
true |
Skip files that already exist locally at the correct size |
dry_run: |
false |
Print what would be downloaded without downloading |
downloader.download_file(phase, type, filename, destination:, resolution: nil)
Downloads a single tile by exact filename.
downloader.download_file(
"phase-1", "dsm", "NS56_1M_DSM_PHASE1.tif",
destination: "/tmp/lidar"
)
SmoScottishLidar.prefix_for(phase, type, resolution: nil)
Returns the S3 prefix string for a given phase and type. Useful if you need to build custom queries.
SmoScottishLidar.prefix_for("phase-1", "dsm")
# => "lidar/phase-1/dsm/27700/gridded/"
SmoScottishLidar.prefix_for("outer-hebrides", "dtm", resolution: "50cm")
# => "lidar/outer-hebrides/2019/dtm/50cm/27700/gridded/"
Valid phases and types
SmoScottishLidar::PHASES
# => ["phase-1", "phase-2", "phase-3", "phase-4", "phase-5", "outer-hebrides"]
SmoScottishLidar::DATASET_TYPES
# => ["dsm", "dtm", "laz"]
Outer Hebrides resolutions
| Type | Available resolutions |
|---|---|
| dsm | "25cm" (default), "50cm" |
| dtm | "25cm" (default), "50cm" |
| laz | "4ppm" (default), "16ppm" |
Examples
The examples/ directory contains ready-to-run scripts:
| Script | Description |
|---|---|
demo.rb |
Full walkthrough of all features |
phase_1_dsm.rb |
List Phase 1 DSM tiles |
phase_1_dtm.rb |
List Phase 1 DTM tiles |
phase_1_laz.rb |
List Phase 1 LAZ tiles |
phase_2_dsm.rb ... |
One script per phase and type |
outer_hebrides_dsm.rb |
Outer Hebrides DSM |
outer_hebrides_dtm.rb |
Outer Hebrides DTM |
outer_hebrides_laz.rb |
Outer Hebrides LAZ |
download_individual_tile.rb |
Download a single named tile |
download_batch_tiles.rb |
Batch download with grid square filter |
Run any script with:
ruby examples/phase_1_dsm.rb
Typical workflow for hydraulic modelling
require "smo_scottish_lidar"
downloader = SmoScottishLidar::Downloader.new(verbose: true)
# 1. Check what is available for your catchment (e.g. NS and NS grid squares)
lister = SmoScottishLidar::Lister.new
lister.summary("phase-1", "dtm", grid_square: "NS")
# 2. Dry run first to confirm file sizes and count
downloader.download("phase-1", "dtm",
destination: "/projects/my_catchment/lidar/dtm",
grid_square: "NS",
dry_run: true
)
# 3. Download for real
downloader.download("phase-1", "dtm",
destination: "/projects/my_catchment/lidar/dtm",
grid_square: "NS",
dry_run: false
)
# 4. If the download is interrupted, re-run the same command.
# Files already on disk at the correct size are skipped automatically.
Support
If this gem saves you time on a project, you can buy me a coffee.
License
MIT. Copyright (c) 2024 Sebastian Madrid Ontiveros.
