Fix/geotiff autofit unresolvable projection - #174
Merged
Conversation
…ojection Dynamic map layers painted, then vanished, on a dashboard whose rasters are UTM 15N. The layers kept the right feature count, stayed visible at full opacity with the correct z-index -- they were simply drawn far off screen. Features are parsed into the map's projection when they are added, so a later change of view projection leaves them holding the outgoing projection's numbers. The GeoTIFF auto-fit adopts the raster's projection and calls map.setView, which is legitimate: OpenLayers 10.4 ships a UTM projection factory, so EPSG:32615 resolves and transforms correctly. What was missing is that nothing moved the features already on the map. Web Mercator coordinates around (-10078522, 1629726) were then read as UTM metres, which is thousands of kilometres away. reprojectVectorFeatures walks the map's vector sources and transforms their geometries, and the auto-fit calls it whenever the adopted projection differs from the outgoing one. Tile sources are skipped: they have no geometry to move. The deferred swap in runtimeLayerFetcher now reads the projection when it fires rather than when the fetch resolved. An auto-fit can land in between, and parsing into a projection the map has already left produces the same stranding. Investigated with temporary logging rather than by inspection: three earlier theories -- the layer being rebuilt empty, the features being cleared, and a z-order conflict -- were each disproved by it. A fourth, that OpenLayers could not resolve the projection at all, was disproved by checking ol/proj, which resolves UTM codes through a built-in factory. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… tile Replaces the approach in 547dd1b. Raising the fill-viewport tile while a control was open did clear the overlapping grid items, but the map is opaque, so those items disappeared for as long as the control stayed open. On a dashboard where widgets are deliberately floated over a full-screen map that is most of the dashboard. The legend and the other grid items need to be visible at the same time. Only the control floats now. A new FloatingMapControl leaves an anchor in place carrying the caller's existing positioning CSS, and portals the control itself to document.body pinned to whatever rectangle that anchor occupies. The offsets stay in one place, and a non-fill map lands exactly where it always did. This is the only way out: position:fixed makes the tile a stacking context, and no descendant z-index can escape one -- so the control has to leave the tile in the DOM. It follows PopupModal, the app's other portal: createPortal into document.body with position:fixed. Deliberately not react-bootstrap Overlay, which is the right tool for a popover anchored to a trigger but whose popper flip/shift would move a control that must stay pinned to a map corner. Applied to the legend, the layer control, and the map's error alert. The coordinate readout is left alone: it renders only under `dataviewerViz`, where nothing overlaps the map. The alert is pinned on both sides, so the anchor spans a real width and the floated copy carries it across rather than shrinking to its content. z-index 1029 clears grid items and Bootstrap dropdowns (1000) and stays below $zindex-fixed 1030, $zindex-modal-backdrop 1050 and $zindex-modal 1055 as configured here, so the fixed header and any modal still cover it. The DashboardItem :has() rule, the data-map-control-open attributes and the tests that asserted them are all removed with the approach they served. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Raise fill-viewport map controls above overlapping tiles; stop rendering empty headings
Three commits, 10 files, +229 / −6. Two unrelated concerns — the branch name only describes the first. They are separate commits and cherry-pick cleanly if you would rather split them.
1. A fill-viewport map's legend and layer control were hidden by overlapping tiles
547dd1bfSymptom
Opening a map's legend or layer control drew it behind any grid item overlapping the map — but only when the map filled the viewport, and only outside edit mode.
Cause
fillViewportActivemakes the itemposition: fixed, and a fixed element creates a stacking context even atz-index: auto. That seals the item's subtree in. The legend, layer control, error alert and coordinate readout all setz-index: 1000, but inside a stacking context that only orders them against each other — never against another grid item. The tile paints as one unit ingridItemsorder, so any tile ordered after the map covered the map's own controls.Edit mode was unaffected because
fillViewportActiveis gated on!isEditing, leaving the itemposition: relativeand therefore not a stacking context — which is exactly why the controls behaved there. That observation is what identified the bug; an earlier theory about z-index ties with Bootstrap dropdowns was wrong precisely because it would have behaved identically in both modes.Fix
No descendant
z-indexcan escape a stacking context, so the item itself is raised while a control is open, lifting the whole subtree. Each control flags its own container withdata-map-control-openwhile expanded, and a:has()rule scoped to the fill-viewport branch raises the item to1029.Two consequences, both deliberate:
1029clears dropdowns (1000) and sticky (1020) but stays under the fixed header (1030) and all modal chrome (backdrop 1040, modal 1050, popover 1070, tooltip 1080, app alerts 1081), so a modal still covers the map.LayerControlContaineralso gains thearia-labelit was missing, matching the legend's — which is how the test reaches it without the direct DOM accesstesting-library/no-node-accessforbids.2 & 3. Optional headings rendered as empty elements
3133b15d,0ec875b4titleis optional in thetablereturn shape;titleanddescriptionare both optional incard. Both renderers ignored that:A plugin returning only
datatherefore got an empty<h2>, or an empty<h3>+ empty<p>+Header's own1.5remmargin, as blank space above its content. Guarded now, the waysubtitlealready was. The Card wrapper is guarded too — dropping only the fields would leave the margin behind.Headercarries adata-testidso its absence is observable: an empty wrapper renders no text, so no query could otherwise tell it apart from no wrapper at all.Verification
131 suites, 2503 tests. Lint and Prettier clean.
Each new test was mutation-checked against the committed code — the mutation must fail the suite and the baseline must restore clean:
DataTable's titleCard's titleCard's Header wrapperCard's descriptionLimitations, stated plainly
:has()in computed styles, so the raise cannot be read back throughgetComputedStyle. The tests assert the injected rule instead: that it ships, what it raises to, that it stays below modal chrome, and that the element actually carries the generated class. They cannot prove paint order — this wants a quick look in a browser with a legend open over an overlapping tile before merging.Card's description is not caught. It only produces an empty<p>when a title exists without a description, and Testing Library has no query that distinguishes an empty paragraph from no paragraph without direct DOM access. The guard is correct; it is simply untested.Card's title (masked by the outer wrapper guard until a description-without-title case was added) and always rendering the Header (invisible to text queries until the testid was added).Notes
npm run build) to appear in the Django-served app.:has()is baseline-supported (Chrome 105+, Safari 15.4+, Firefox 121+).card-type plugin drop its title cleanly, which is the immediate motivation — but they apply to every table and card plugin.