Skip to content

fix(thumbnails): render dataset card maps instead of collapsing them - #5

Open
a5dur wants to merge 1 commit into
ckan-2.12from
fix/dataset-item-map-thumbnails
Open

fix(thumbnails): render dataset card maps instead of collapsing them#5
a5dur wants to merge 1 commit into
ckan-2.12from
fix/dataset-item-map-thumbnails

Conversation

@a5dur

@a5dur a5dur commented Aug 20, 2026

Copy link
Copy Markdown

Map thumbnails on /dataset never appeared. Two independent causes, both confirmed against the live sandbox deployment.

1. Every container was zero width

Browser diagnostic on /dataset:

"firstDiv": { "w": 0, "h": 125, "alreadyInitialised": true, "childNodes": 2 }

alreadyInitialised: true with two child nodes means Leaflet did build the map. It was 125px tall and 0px wide.

snippets/package_item.html puts .dataset-item-map next to the dataset content in a flex row. The element is empty at layout time β€” Leaflet fills it with absolutely positioned panes β€” so its min-content width is 0 and the default flex-shrink lets the sibling collapse it entirely.

.dataset-item-map { flex: 0 0 125px; }

Plus an invalidateSize() after render, because Leaflet caches the zero size it measured at init and won't recover on its own.

Verified live in the console β€” setting flex: 0 0 125px and dispatching resize made the tiles appear.

2. One dataset blanked all of them

Uncaught (in promise) Error: Bounds are not valid.
    at createMapImage (bf92fc20-gztr_theme.js:56:9)

createMapImage() awaited every card in a single unguarded sequence, so the first throw abandoned the rest. gztr_spatial_full_with_geometry returns features with a null geometry when it can't resolve one; L.geoJSON then yields an empty layer and fitBounds raises.

Each card now builds in its own try/catch, invalid bounds fall back to the configured default view, and the action returns None explicitly on failure instead of falling off the end.

Why that feature was unresolvable

Three spatial_full shapes exist in the wild:

Shape collection id geometry
Current top-level, collection id top-level resolved from STAC
Older nested under properties.collection, keyed by location top-level int must be resolved
Oldest absent absent inline

The action only understood the first. The offending dataset had the second:

{ "id": 31, "properties": { "OBJECTID": 31, "NAMELSAD": "Bernalillo County",
    "collection": { "properties": { "location": "nm_counties.geojson", ... } } } }

feature.get("collection") returned None, so the STAC lookup was skipped and the feature came back geometry-less.

Now factored into _resolve_feature_ref, which:

  • handles all three shapes ("nm_counties.geojson" β†’ nm_counties)
  • refuses collections that aren't installed, rather than letting stac_item_show 500 on them
  • casts the id to a string β€” stac_item_show compares it as a quoted SQL literal (WHERE item.id = '31'), so an int id silently matches zero rows

Verification

ckanext/gztr/tests/test_resolve_feature_ref.py β€” 5 cases covering all three shapes plus drawn-features and unknown-collection. Imports the function without loading CKAN, so it runs standalone:

$ python ckanext/gztr/tests/test_resolve_feature_ref.py
  ok  test_current_shape
  ok  test_drawn_features_passes_through
  ok  test_legacy_nested_collection
  ok  test_oldest_shape_has_no_collection_at_all
  ok  test_unknown_collection_is_not_looked_up
5 passed

Against the 5 real production payloads β€” the four with inline geometry need no lookup, and the one that was throwing now resolves:

dataset57286                     inline geometry, no lookup needed
usgs-surface-water-sub-basins    inline geometry, no lookup needed
dataset45248                     inline geometry, no lookup needed
dataset16822                     inline geometry, no lookup needed
water-authority-data-gap-...     RESOLVES -> ('nm_counties', '31')

nm_counties/31 matches its NAMELSAD: "Bernalillo County".

Also

Dropped two console.log calls that dumped full geometry payloads to the browser console on every page load.

Not in this PR

before_dataset_index in plugin.py has the same legacy-shape problem β€” it does feature["collection"] (subscript, not .get()) and raises KeyError on the oldest shape, which makes search-index rebuild silently drop those datasets out of bbox search. Same root cause, but a separate symptom; happy to fold it in here or do it separately.

πŸ€– Generated with Claude Code

The map thumbnails on /dataset never appeared. Two independent causes,
both confirmed against the sandbox deployment.

1. Every container was zero width. snippets/package_item.html places
   .dataset-item-map next to the dataset content in a flex row. The
   element is empty at layout time -- Leaflet fills it with absolutely
   positioned panes -- so its min-content width is 0 and the default
   flex-shrink lets the sibling collapse it entirely. Leaflet built the
   maps (the containers had _leaflet_id set and two child nodes) at
   125px tall and 0px wide, so nothing was ever visible. Pinned the
   track size with `flex: 0 0 125px` and added an invalidateSize() call,
   since Leaflet caches the zero size it measured at init.

2. One dataset blanked all of them. createMapImage() awaited every card
   in a single unguarded sequence, so the first throw abandoned the
   rest. gztr_spatial_full_with_geometry returns features with a null
   geometry when it cannot resolve one, L.geoJSON then yields an empty
   layer, and fitBounds raises "Bounds are not valid." Each card now
   builds inside its own try/catch, an invalid bounds falls back to the
   configured default view rather than throwing, and the action returns
   None explicitly on failure instead of falling off the end.

The unresolvable feature came from a spatial_full shape the action did
not recognise. Three shapes exist in the wild: the current one, with
`collection` and `id` at the top level; an older one nesting the whole
config.json entry under properties.collection, keyed by `location`
("nm_counties.geojson" -> "nm_counties"); and the oldest, a bare GeoJSON
Feature with inline geometry and no collection reference at all. The
lookup is now factored into _resolve_feature_ref, which handles all
three, refuses collections that are not installed rather than letting
stac_item_show 500 on them, and casts the id to a string because
stac_item_show compares it as a quoted SQL literal -- an int id silently
matches no rows.

Also dropped the two console.log calls that dumped full geometry
payloads to the browser console on every page load.

tests/test_resolve_feature_ref.py covers all three shapes plus the drawn
feature and unknown collection cases; it imports the function without
loading CKAN so it runs standalone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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