Wikidata Ogham Sites — Interactive Map

About this notebook

This notebook fetches Ogham-stone records from Wikidata and displays their find-spots on two interactive Leaflet maps, each with an OpenStreetMap basemap. The first map shows individual stones as county-coloured markers with pop-up details; the second bins the same points into a hex grid and shades each hexagon by the number of stones it contains, making regional concentration visible at a glance.

It is a companion to wikidata-ogham-sites-live.qmd (bar-chart overview of the same dataset) and part of an Open Educational Resource series on knowledge graphs and linked open data. A local-Python variant of this notebook is available as wikidata-ogham-sites-map.ipynb.

What you’ll learn

  • How to extract geographic coordinates from a Wikidata SPARQL response. Coordinates in Wikidata are stored as WKT Point(lon lat) literals — a string format that needs a small parser before the values can be plotted.
  • How to build an interactive web map from Python in a browser-only environment, by bridging from Python to JavaScript and rendering through Leaflet.

Data-context notes

The query in this notebook only returns records that have coordinates (?item wdt:P625 ?geo, not OPTIONAL). This is the right call for a map — records without coordinates cannot be plotted — but it also filters out a non-trivial share of stones, depending on how well the data is curated at the time you run it. The overview notebook makes the opposite choice, making coordinates optional, to give a complete picture of counts per county. The difference between those two result sets is itself an instructive thing to look at.

A second subtlety: Wikidata stores coordinates in WGS 84 (EPSG:4326), the same coordinate reference system Leaflet and OpenStreetMap expect. No reprojection is needed. If you later query an endpoint that uses a different projection (e.g. some national cultural-heritage registries), you will need to reproject.

Tooling notes

Rendering an interactive map in a browser-only notebook is slightly unusual, because the Python libraries most people know for this (folium, geopandas, contextily) do not work well in Pyodide: folium expects to write HTML files, geopandas has heavy binary dependencies (GDAL, GEOS, PROJ), and contextily needs server-side HTTP. The local .ipynb variant of this notebook happily uses folium; the browser variant instead builds an HTML snippet with embedded Leaflet JavaScript and returns it as the cell’s output.

There is one additional browser-specific constraint worth knowing about: quarto-live runs Python inside a Web Worker, which has no DOM access. That means js.document, js.eval() targeting the DOM, and any direct manipulation of page elements from Python will fail with ReferenceError: document is not defined. The working pattern is the one used below: Python builds an HTML string containing the map container and the Leaflet-initialisation script, then returns it as the cell value. quarto-live inserts that HTML into the page on the main thread, where the browser has full DOM access and runs the embedded JavaScript normally.

Note

On first load, your browser downloads the Python runtime (Pyodide, ~10 MB) and the Leaflet library (~150 KB). Please allow a moment for these to initialise.

Step 1 — Defining the SPARQL query

We ask Wikidata for every Ogham stone, its find-spot, its county, and its coordinate location. The structure is almost identical to the overview notebook, with the one difference noted above: coordinates are required.

Step 2 — Loading the data

The parse_wkt_point helper defined above converts each Point(lon lat) literal into a (lat, lon) tuple. Note the ordering: WKT is lon lat, but Leaflet (and most mapping libraries) expect lat, lon. Getting this the wrong way round is one of the most common mistakes when plotting spatial data — the kind of bug where everything “works” but all your points end up in the wrong hemisphere.

Step 3a — Map with county-coloured markers

We build the first map as an HTML string in Python and return it as the cell’s value via a _repr_html_-reprable object. quarto-live inserts the HTML on the main thread, where the browser happily runs the embedded <script> with full DOM access.

This first map loads only core Leaflet, plus the Leaflet.fullscreen plugin for the maximise-to-fullscreen button under the zoom controls.

A layer control in the top-right corner (expand it by hovering or tapping) offers four base layers — OpenStreetMap as the default, the Humanitarian OSM style, Esri satellite imagery, and Esri’s world terrain base — and one overlay per county, each with its colour swatch in the label. Unchecking a county hides just its markers, so you can isolate regions interactively. All provider URLs and attributions are taken from the leaflet-providers registry.

Step 3b — Map with hex-binned density grid

The second map shows the same data as a hex-binned density grid: we tile the area with regular hexagons, count how many stones fall into each hex, and colour each cell by its count. Empty hexagons are not drawn, so the result is a compact “honeycomb” that makes regional concentration immediately obvious — the same question a heatmap answers, but on aggregated data rather than a blurred point cloud.

Why hex bins instead of a heatmap?

Kernel-density heatmaps (e.g. leaflet.heat) are visually attractive but they have two well-known problems in a teaching context:

  1. They’re a canvas-rendered blur. The result depends on zoom level, the chosen kernel radius, and the container size; resizing or fullscreening the map can silently break the rendering. Hex bins, by contrast, are plain Leaflet polygons — they resize cleanly and behave like any other map layer.
  2. They hide the underlying counts. A heatmap shows relative intensity on an arbitrary colour ramp; a hex bin shows “this hexagon contains N stones” and you can click it to verify. For an OER, that transparency matters.

Hex binning is a small but useful GIS pattern. It also matters that hexagons tile the plane with only three distinct edge directions (rectangles have two, triangles six) and that every neighbour is exactly the same distance away — which makes them the preferred choice for spatial aggregation in tools like h3, kepler.gl, and geopandas / QGIS.

A small projection trick

We compute the hex grid in a trivial “equirectangular” projection: longitude becomes x, and latitude becomes y ⋅ cos(φ₀) where φ₀ is a reference latitude (here, the mean latitude of our data). This makes the hexagons roughly equal-area at the latitude of interest, which is all we need for a continental-scale visualisation. For a global map or proper geodesy you would want pyproj — but that is not pre-bundled in Pyodide, and for Ireland at ~53° N the shortcut is entirely adequate.

Step 4 — Exploring the data

The cells below are a free playground — filter the DataFrame by county, compute per-county aggregates, or extend the query yourself.


Part of an Open Educational Resource series on knowledge graphs and linked open data, produced in the context of NFDI4Objects.