Kortagerð (Mapmaking) — Iceland Maps

SkillDev tools

Generate Iceland maps from cached LMI data — static (matplotlib) and interactive (Leaflet), with ISN93 derived cache.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Kortagerð (Mapmaking) — Iceland Maps skill

What this skill tells your AI

The instructions your AI receives, as published by jokull/icelandic-data in .agents/skills/kortagerd/SKILL.md and read by ahel’s review.

Generate high-quality maps of Iceland using cached LMI geodata. Supports interactive HTML (Leaflet) and static PNG/SVG (geopandas + matplotlib).

Quick Start

# Ensure geodata is cached
uv run python scripts/lmi.py download

# Interactive map (full-screen, layer toggles, popups)
uv run python scripts/kortagerð.py html -o reports/map.html

# Static map (publication-quality PNG)
uv run python scripts/kortagerð.py static -o reports/map.png

# Zoomed to capital area with Reykjavík highlighted
uv run python scripts/kortagerð.py static --bounds capital --highlight "Reykjavíkurborg" -o reports/rvk.png

# Overlay custom points from CSV
uv run python scripts/kortagerð.py static --points data/my_locations.csv -o reports/custom.png

Bounding Box Presets

NameAreaBounds [W, S, E, N]
icelandFull country (default)-24.7, 63.2, -13.1, 66.6
capital / reykjavikGreater Reykjavík-22.1, 63.95, -21.3, 64.25
southwestReykjanes to Hekla-22.5, 63.6, -19.5, 64.5
northSkagafjörður to Húsavík-20.0, 65.2, -15.0, 66.6
eastEastfjords-16.0, 64.2, -13.3, 65.8
westfjordsWestfjords-24.7, 65.0, -21.0, 66.6
southSouth coast-21.0, 63.2, -17.5, 64.3
akureyriAkureyri area-18.4, 65.5, -17.7, 65.8

Cached Layers (in data/geodata/)

FileWhatWhen to use
Landmask.geojsonIceland land polygonAlways — base fill for any map
CoastalLine.geojsonCoastlineBorder outline
AdministrativeUnit_level2.geojson128 municipalitiesChoropleth, boundaries, --highlight
RoadLines.geojsonRoad networkInfrastructure, transport maps
WatercourseLine.geojsonRivers (28k features)Hydrology (skip for perf if not needed)
Lake_Reservoir.geojsonLakesHydrology, geography
LandIceArea.geojsonGlaciersTerrain, geography
BuiltupAreaPoints.geojson98 settlements + popCity labels, settlement maps
NatureParkArea.geojsonNational parksEnvironmental, tourism maps
IslandArea.geojsonIslandsOffshore geography
Airport_Airfield_points.geojsonAirportsTransport maps
Port.geojsonHarborsMaritime, transport maps

Caching strategy (for raster-overlay maps)

Vector layers are pre-cached at Tier 1 (data/geodata/). For maps that overlay rasters (e.g. Copernicus HRL Grassland), three more tiers exist under data/cache/ to avoid re-downloading and re-projecting on every render.

TierPathBuilt byWhatSpeedup
1data/geodata/*.geojsonscripts/lmi.py downloadLMI WFS vectors (~50 MB)
2data/raw/lmi_hrl/*.tifscripts/lmi_hrl.py fetch grasslandSource HRL GeoTIFFs (~860 MB)
3data/cache/rasters/*.tifscripts/build_cache.py rastersLZW + ISN93-projected GeoTIFFs (~9 MB each — 98× smaller)skip 30 s reproject per render
4data/cache/constants.jsonscripts/build_cache.py constantsIceland total area + 4-CRS bbox + per-source SHA-256 + grassland areaskip 1.5 s polygon area + 826 MB scan per render
5data/cache/arrays/*.npywritten automatically on first map renderDecoded probability arrays (e.g. GRAVPI)skip 5 s RGB-decode + reproject

Render scripts read these tiers via scripts/utils/cache.py:

from scripts.utils.cache import iceland_constants, cached_raster, CacheMissingError

try:
    K = iceland_constants()
    iceland_km2 = K["iceland_total_area_km2"]
except CacheMissingError as e:
    print(e.hint)         # tells you the build_cache.py command to run

Tier 3+4 are explicit (build_cache.py all); Tier 5 is opportunistic (populated on first render). Sources are SHA-256 fingerprinted in constants.json so scripts/build_cache.py status flags stale entries when an upstream raster changes.

Benchmark

# Cold (full re-download): ~120 s for grassland
# Warm-raw (rebuild Tier 3+4): ~10 s for grassland
# Warm (steady state): ~3-6 s
uv run python scripts/bench_maps.py run --mode warm
uv run python scripts/bench_maps.py history

Results land in data/cache/benchmarks.json with timestamps and a delta vs. the last warm baseline.

Tests

# Cache-source consistency + map-output smoke tests
uv run pytest tests/test_cache_consistency.py tests/test_maps_render.py -v

Troubleshooting

  • "Missing data/cache/constants.json" → run scripts/build_cache.py all
  • "STALE — source changed" in status → run scripts/build_cache.py rasters
  • Render is slow despite cache → check scripts/build_cache.py status; the tier-5 .npy file may be missing for grassland_probability_heatmap.py (auto-rebuilds on next run).

Python Code Templates

Load a layer with geopandas

import geopandas as gpd
from pathlib import Path

GEODATA = Path("data/geodata")
land = gpd.read_file(GEODATA / "Landmask.geojson")
municipalities = gpd.read_file(GEODATA / "AdministrativeUnit_level2.geojson")

Simple choropleth map

import geopandas as gpd
import matplotlib.pyplot as plt
from pathlib import Path

GEODATA = Path("data/geodata")
land = gpd.read_file(GEODATA / "Landmask.geojson")
munic = gpd.read_file(GEODATA / "AdministrativeUnit_level2.geojson")

# Merge your data onto municipalities
# munic = munic.merge(your_data, left_on="namn", right_on="municipality")

fig, ax = plt.subplots(figsize=(12, 8), facecolor="#f0f4f8")
ax.set_facecolor("#c8d6e5")
land.plot(ax=ax, color="#f5f0e6", edgecolor="#2d3436", linewidth=0.6)
munic.plot(ax=ax, column="your_metric", cmap="YlOrRd", legend=True,
           edgecolor="#636e72", linewidth=0.3, alpha=0.8)
ax.set_xlim(-24.7, -13.1)
ax.set_ylim(63.2, 66.6)
ax.set_aspect(1 / 0.42)  # latitude correction at 65°N
ax.set_title("Your Title")
plt.tight_layout()
fig.savefig("reports/choropleth.png", dpi=200, bbox_inches="tight")

Overlay custom points

import polars as pl
import geopandas as gpd
import matplotlib.pyplot as plt

GEODATA = Path("data/geodata")
land = gpd.read_file(GEODATA / "Landmask.geojson")

# Your point data
pts = pl.read_csv("data/processed/your_points.csv")

fig, ax = plt.subplots(figsize=(12, 8))
land.plot(ax=ax, color="#f5f0e6", edgecolor="#2d3436", linewidth=0.6)
ax.scatter(pts["lon"], pts["lat"], c="#e74c3c", s=20, zorder=10)

Interactive HTML with custom data overlay

import json
from pathlib import Path

GEODATA = Path("data/geodata")

# Load base layers
with open(GEODATA / "Landmask.geojson") as f:
    landmask = json.load(f)

# Build Leaflet HTML with your data embedded as JSON
# Follow the pattern in scripts/kortagerð.py cmd_html()

R Code Templates

Load layers with sf

library(sf)
library(ggplot2)

geodata <- "data/geodata"
land <- st_read(file.path(geodata, "Landmask.geojson"))
munic <- st_read(file.path(geodata, "AdministrativeUnit_level2.geojson"))
roads <- st_read(file.path(geodata, "RoadLines.geojson"))
glaciers <- st_read(file.path(geodata, "LandIceArea.geojson"))
lakes <- st_read(file.path(geodata, "Lake_Reservoir.geojson"))

Basic ggplot2 map

ggplot() +
  geom_sf(data = land, fill = "#f5f0e6", color = "#2d3436", linewidth = 0.3) +
  geom_sf(data = glaciers, fill = "#dfe6e9", color = "#b2bec3", linewidth = 0.2) +
  geom_sf(data = lakes, fill = "#74b9ff", color = "#0984e3", linewidth = 0.2) +
  geom_sf(data = roads, color = "#e17055", linewidth = 0.3) +
  coord_sf(xlim = c(-24.7, -13.1), ylim = c(63.2, 66.6)) +
  theme_minimal() +
  labs(title = "Iceland")

Choropleth with municipality data

# Merge your data onto municipalities
munic_data <- munic %>%
  left_join(your_data, by = c("namn" = "municipality"))

ggplot() +
  geom_sf(data = land, fill = "#f5f0e6", color = "#2d3436", linewidth = 0.3) +
  geom_sf(data = munic_data, aes(fill = your_metric), color = "#636e72", linewidth = 0.2) +
  scale_fill_viridis_c() +
  coord_sf(xlim = c(-24.7, -13.1), ylim = c(63.2, 66.6)) +
  theme_minimal()

Color Palette

Standard colors used by kortagerð.py:

ElementFillStroke
Ocean#c8d6e5
Land#f5f0e6#2d3436
Glaciers#dfe6e9#b2bec3
Lakes#74b9ff#0984e3
Rivers#0984e3
Major roads#d63031
Minor roads#e17055
Nature parks#a8e6cf#00b894
Municipalities#b2bec3 (dashed)
Highlight#ffeaa7#fdcb6e
Settlements#2d3436#fff

Tips

  • Latitude correction: At 65°N, set ax.set_aspect(1/0.42) in matplotlib or the map will appear stretched
  • WatercourseLine has 28k features — skip for faster rendering on zoomed maps
  • Municipality names are in the namn column of AdministrativeUnit_level2.geojson
  • Settlement population is in the ppl column of BuiltupAreaPoints.geojson
  • Road classification: filter by rtt field (1-3 = highways, 4-10 = regional, >10 = local)
  • For extra layers not pre-cached: uv run python scripts/lmi.py fetch ERM:WetlandArea

Signals

GitHub stars
53
Forks
4
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
kortagerd
Source
github.com/jokull/icelandic-data