NFDI4Objects KG — Ogham Sites on a Map
About this notebook
This notebook takes the same NFDI4Objects Knowledge Graph dataset that the companion notebook n4okg-ogham-sites-county-live.qmd visualises as counts, and plots each individual Ogham site on two interactive Leaflet maps. The first map shows individual sites as county-coloured markers with pop-up details; the second shows the same points styled by stone count — marker size and colour both encode how many stones were disclosed at each site, making regional concentration visible at a glance.
It is part of an Open Educational Resource series on knowledge graphs and linked open data, and is designed to stand on its own: you do not need to have read any other notebook in the series to follow along. A local-Python variant of this notebook is available as n4okg-ogham-sites-map.ipynb.
Why this dataset?
Ogham sites from the N4O KG’s collection/9 are a good fit for a map teaching example because:
- Each site has a GeoSPARQL
hasGeometry/asWKTliteral with explicit WKT point coordinates — a common, well-specified encoding that generalises to many other knowledge graphs. - The stone-count-per-site metadata is rich enough for two different map treatments: a categorical view coloured by county, and a density-weighted heatmap.
- The sites concentrate in just two Irish regions, so the map is geographically interesting without being sparse.
Data-context notes
Two subtleties about coordinates that we handle here and that generalise to other GeoSPARQL-using KGs:
- WKT is
lon lat, Leaflet islat, 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. Our parser swaps the order explicitly. - Some endpoints uppercase the
POINTkeyword, some don’t. Wikidata writesPoint(...), the N4O KG writesPOINT(...). Our parser is case-insensitive, which is a reasonable defensive posture when working across endpoints.
A third point worth mentioning: N4O KG data is contributed by many projects under shared infrastructure, each with its own ontology. The Ogham ontology (oghamonto:) is separate from the NFDI4Objects core vocabulary. This is normal for domain-rich knowledge graphs and one of the reasons SPARQL is expressive: you can mix vocabularies freely in a single query.
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 companion 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.
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.
The N4O KG is a research prototype. If this notebook fails to load data with a network error, the endpoint may be temporarily unreachable or may not allow cross-origin browser requests from this page’s domain. The local .ipynb companion is not affected by this and is always a reliable fallback.
Step 1 — Defining the SPARQL query
The query below asks the N4O KG for every oghamonto:OghamSite, its label, its GeoSPARQL geometry, the County it lies in, and a count of catalogued Ogham stones (oghamonto:OghamStone_CIIC) disclosed at that site. We group by site — one row per site with its aggregated stone count.
Two notes on query design that generalise to other knowledge-graph queries:
geo:hasGeometry/geo:asWKTuses SPARQL property-path notation to traverse two predicates in a single triple pattern. It is equivalent to introducing an intermediate variable, but shorter.COUNT(DISTINCT ?stone)— not justCOUNT(?stone). A stone could in principle be linked through more than one path;DISTINCTmakes sure we count each stone once.
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 styled by stone count
The second map shows the same sites as Step 3a, but here the stone count per site drives the visual encoding: each marker’s radius is proportional to the count, and its colour follows a continuous viridis gradient from low (dark purple) to high (bright yellow). The two encodings reinforce each other — busy sites stand out clearly, whereas in a density heatmap they would blur together with their neighbours.
A design note on why this is not a heatmap: heatmaps are best at answering “where are points concentrated in space?”, not “where are the large values?”. Since our dataset has well-separated sites with very uneven counts (one site with 30+ stones, many with just one or two), a density heatmap weighted by count drowns the low-count sites in a hazy background and makes the high-count ones barely distinguishable. Proportional circles are the honest visualisation here.
The map re-uses the four base layers from Step 3a and adds the same fullscreen control. Because the overlays are SVG CircleMarkers (not a canvas heatmap), no special redraw handling is needed on fullscreen toggle.
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. Remember: in this notebook one row = one site, with a count column holding the number of Ogham stones disclosed there.
This notebook is part of the Open Educational Resources of NFDI4Objects.