Skip to content

feat: serve old map images from IIIF Image API services - #145

Merged
kochizufan merged 5 commits into
train/2026-12from
dec26/m13-t2-iiif-image
Oct 28, 2026
Merged

kochizufan merged 5 commits into
train/2026-12from
dec26/m13-t2-iiif-image

Conversation

@kochizufan

@kochizufan kochizufan commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Serve an old map's image straight from a IIIF Image API service. A map setting can carry delivery: { type: "iiifImage", url }; Core reads the service's info.json and requests tiles in the IIIF URL form instead of tiles/{mapID}/{z}/{x}/{y}.jpg.

dist/ is not rebuilt in this PR yet; a build: rebuild dist commit is added right before merging.

What changes

  • src/iiif/delivery.ts (new; not exported from src/index)
    • checkIiifDelivery(info, expected?) — pure check (no fetch) whether Core can serve the image: { ok, version, level, tiling: "arbitrary" | "level0-256" } or { ok: false, reason }.
    • resolveIiifDelivery(delivery, expected, opts?) — fetches info.json (credentials: "omit", 10 s timeout, 1 MiB limit) and returns a tileUrlFunction for HistMap (plus minZoom for level 0 tiles). Tiles outside the image return the transparent PNG.
    • IiifDeliveryError with reason (bad-url, fetch-failed, timeout, too-large, not-json, unsupported-version, unsupported-level0, size-mismatch).
    • Parsing info.json and building the request URL (region, size, quality, format) is left entirely to OpenLayers (ol/format IIIFInfo, ol/source IIIF). Only the tile grid is Maplat's own HistMap grid; Core does not build IIIF URLs itself.
  • src/source_ex.ts: mapSourceFactory resolves delivery on both the map-setting path and the noload path. Because OpenLayers' UrlTile regenerates the URL function from url (which HistMap fills in from mapID), the IIIF function is re-applied on the created source with setTileUrlFunction. url/urls next to delivery are dropped with a warning. IIIF maps are never registered with the tile cache (getMapCacheEnable is false).
  • src/index.ts: a map whose IIIF image cannot be used no longer fails the whole startup. It is logged with console.error, left out of the raw sourceLoaded list and reported as { mapID, usable: false } in the element's sourceLoaded detail; the other maps start normally. If the initial map (startFrom or the restored map) is not usable, startup falls back to the default map with a warning (this also covers maps dropped because the GL library is missing).
  • vite.config.ts: ol/format is external (like ol/source), so OpenLayers' IIIF code is not bundled.
  • Level 0 services with tiles other than 256 px, sizes-only level 0 and Image API 1 are reported as unusable for now.
  • Demo: new region niigata — the 1920 Map of Niigata City from the National Diet Library (public domain), shown directly from the NDL IIIF image service (canvas 3; alignment is provisional, 6 control points). A copy with a broken delivery shows that startup continues; a panel section lists recent old-map tile requests and the cache status.
  • README (en/ja): section "Serving a map image from a IIIF Image API service (delivery)". CHANGELOG: one line under Added.

Tests

  • spec/iiif-delivery-grid.spec.ts: for every tile of every zoom of the grid built by HistMap_tin (including one ring outside the image), the requested region equals the tile's extent mapped back to image pixels and clipped to the image, the requested size equals ceil(clipped size / scale) and is at most 256, every tile touching the image is requested and none outside it. Run on four info.json fixtures: NDL (v2, level 1, 1024 tiles), iiif.io example (v3, level 1, 512 tiles), Stanford (v2, level 2) and a hand-written level 0 with 256 tiles. Fixed URL expectations are listed in the spec.
  • spec/iiif-delivery.spec.ts: the check's cases and the resolver's failure modes (URL form, fetch failure, timeout, size limit by header and by streamed bytes, non-JSON, v1, size mismatch, @id mismatch warning, minZoom).
  • spec/iiif-source-factory.spec.ts: both factory paths with the same fixtures (tile URL on the created source with a mapID, no tile cache, url + delivery, failures as IiifDeliveryError).
  • spec/iiif-unusable-source.spec.ts: startup with an unusable IIIF map, including when it is the initial map.
  • e2e/iiif-image.spec.ts: the demo region with NDL responses served from fixtures — map shown, every tile request in the IIIF form, not cached, no manifest request.
  • e2e/iiif-live.spec.ts: 5 requests to NDL (info.json and 4 tiles); runs only with MAPLAT_IIIF_LIVE=1, skipped in CI.
  • Also checked locally: the same grid measurement passes with OpenLayers 9.2.4 (the peer lower bound).

🤖 Generated with Claude Code

kochizufan and others added 5 commits October 28, 2026 18:15
Add src/iiif/delivery.ts with checkIiifDelivery, resolveIiifDelivery and
IiifDeliveryError. Parsing of info.json and the request URL syntax are
delegated to ol/format IIIFInfo and ol/source IIIF; the tile grid stays
Maplat's HistMap grid. ol/format is kept external in the build.

Tests cover every tile of the product grid (region, size, no missing or
extra requests) for v2 level1, v3 level1, v2 level2 and level0 256 tiles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… when it fails

- mapSourceFactory resolves delivery { type: "iiifImage" } on both the
  map-setting path and the noload path, and re-applies the IIIF tile URL
  function on the created source (OpenLayers regenerates it from url).
- IIIF maps are never registered to the tile cache.
- A map whose IIIF image cannot be used is reported as usable: false in
  sourceLoaded and the other maps start normally. If the initial map is
  not usable, startup falls back to the default map with a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vice

- New demo region "niigata": the 1920 Map of Niigata City from the National
  Diet Library, displayed directly from its IIIF Image API service (canvas 3).
  Alignment is provisional (6 control points).
- A copy with a broken delivery URL shows that an unusable map does not stop
  startup. The new panel section lists recent old-map tile requests and
  whether the current map is cached.
- e2e: the demo map is shown with tiles in the IIIF request form, no tile
  cache and no manifest request (served from fixtures). A live check against
  NDL runs only with MAPLAT_IIIF_LIVE=1.
- README (en/ja) section on delivery and a CHANGELOG entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ternal

- Level 0 services with 256 px tiles are served only when their scale
  factors are 1, 2, 4, ... without gaps; otherwise unsupported-level0.
  minZoom is never negative.
- A missing or non-integer width/height (in info.json or in the expected
  size) is reported as size-mismatch instead of an OpenLayers error.
- The record of unusable maps moves out of the MaplatApp declaration into
  a module-level WeakMap, so it no longer appears in index.d.ts. The demo
  reads the logged errors instead of Core internals.
- README: only <maplat-app> reports unusable maps as usable: false; the
  MaplatApp sourceLoaded detail leaves them out.
- Tests: every failure reason on both factory paths; the live check also
  covers the image service used by the demo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kochizufan
kochizufan marked this pull request as ready for review October 28, 2026 12:37
@kochizufan
kochizufan merged commit 0c1bc10 into train/2026-12 Oct 28, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant