[Bug]: HeatmapLayer is missing the LayerPropsCommon codepart, so on iOS it never receives sourceLayer or filter
Nobody has claimed this yet.
- Dominant language
- Kotlin
- Stars
- 2.9k
- Forks
- 947
- Avg merge
- 6d 37m
- Merged PRs (30d)
- 1
Description
Mapbox Version
11.15.2
React Native Version
0.81.5
Platform
iOS
@rnmapbox/maps version
10.2.6
Standalone component to reproduce
I have not reproduced this at runtime. Per .github/REPRODUCING.md rule 5 I'm saying so up front, and flagging the consequence below as inferred rather than observed. What I did verify is a structural difference in this repo's own source, which should be quick to confirm.
The shape it would take — a heatmap over a vector tile source:
import React from 'react';
import { View } from 'react-native';
import Mapbox from '@rnmapbox/maps';
const TILES = 'https://example.com/tiles/{z}/{x}/{y}.pbf';
export default function BugReportExample() {
return (
<View style={{ flex: 1 }}>
<Mapbox.MapView style={{ flex: 1 }}>
<Mapbox.Camera zoomLevel={10} centerCoordinate={[-81.69, 41.5]} />
<Mapbox.VectorSource id="tileset" tileUrlTemplates={[TILES]}>
<Mapbox.HeatmapLayer
id="heat"
sourceLayerID="my_points"
filter={['==', ['get', 'kind'], 'a']}
style={{ heatmapRadius: 30, heatmapOpacity: 1 }}
/>
</Mapbox.VectorSource>
</Mapbox.MapView>
</View>
);
}
Neither sourceLayerID nor filter reaches the native layer on iOS — see below for why.
Swapping HeatmapLayer for CircleLayer, everything else equal, should render.
Expected: the heatmap renders from my_points, filtered.
Inferred actual on iOS: nothing renders, because the layer has no source-layer to read. A ShapeSource should be unaffected, since source is set by the constructor and sourceLayer is meaningless there.
Observed behavior and steps to reproduce
RNMBXHeatmapLayer.swift is the only vector-capable layer without the @{codepart-replace-start(LayerPropsCommon.codepart-swift.ejs, ...)} block. Checked against main today:
| Layer | codepart marker |
|---|---|
| Circle | present |
| Fill | present |
| Line | present |
| Symbol | present |
| Raster | present |
| FillExtrusion | present |
| Heatmap | absent |
That block is what assigns sourceLayer, source and filter under RNMBX_11. Its absence matters because RNMBXLayer.setOptions has its entire body inside #if !RNMBX_11:
func setOptions(_ layer: inout Layer) {
setBaseOptions(&layer)
#if !RNMBX_11
if let sourceLayerID = sourceLayerID { layer.sourceLayer = sourceLayerID }
...
if let filter = filter, filter.count > 0 { ... }
#endif
}
Since the podspec always compiles -D RNMBX_11, that is a no-op in every current build. RNMBXHeatmapLayer.makeLayer calls exactly that no-op and nothing else:
override func makeLayer(style: Style) throws -> Layer {
let _ : VectorSource = try self.layerWithSourceID(in: style)
#if RNMBX_11
var layer: Layer = LayerType(id: self.id!, source: self.sourceID!)
#else
var layer: Layer = LayerType(id: self.id!)
#endif
setOptions(&layer)
return layer
}
By contrast RNMBXCircleLayer.makeLayer assigns layer.sourceLayer directly and carries the codepart. So a heatmap ends up with source (from the constructor) but never source-layer and never filter.
Two consequences, the second arguably worse than the first:
- On a
VectorSourcethe layer has no source-layer to read, so it should draw nothing. filteris never applied at all — so filtering a heatmap would silently do nothing rather than fail visibly.
Android looks unaffected: RNMBXHeatmapLayer.kt calls layer.sourceLayer(mSourceLayerID!!) directly.
This may explain some older iOS-side "heatmap doesn't show" reports, though the ones I found (#3474, #3163) are Android crashes and look unrelated.
Expected behavior
HeatmapLayer honours sourceLayerID and filter on iOS, as the other layer types do.
Notes / preliminary analysis
The fix appears to be adding the codepart marker to RNMBXHeatmapLayer.swift and running yarn generate. As I read scripts/codepart-replace.mjs, generation scans for the marker rather than working from a manifest, so it can't introduce a missing block on its own — the marker has to be added to the file once.
Instantiating the existing template with {layerType:"Heatmap"} produces a block that diffs identically against RNMBXCircleLayer's once the type name is normalised, which is the change we're carrying locally as a patch-package patch. Happy to open a PR with it, but I'd rather not send a fix I haven't verified against a reproducer — that seems to be exactly what REPRODUCING.md is asking contributors not to do. We expect to have this on a device shortly; if it behaves as described I can follow up with before/after evidence, or you may well prefer to just make the one-line change yourselves.
Caveats, stated plainly:
- Everything above is from reading source, not running it. The cause (missing marker, no-op
setOptions) is verified against your source; the effect (blank layer, ignored filter) is inferred. - I did not build the
/exampleapp. It needs an Xcode/pods setup and Mapbox credentials that this environment doesn't have, and the finding is structural rather than timing-dependent, so a reproducer seemed unlikely to change the diagnosis. I'd have run it otherwise. - I have not checked whether
RasterParticleLayer,HillshadeLayeror the newer layer types have the same gap.
Additional links and references
ios/RNMBX/codeparts/LayerPropsCommon.codepart-swift.ejs— the templateios/RNMBX/RNMBXCircleLayer.swift— a layer that has the blockios/RNMBX/RNMBXHeatmapLayer.swift— the one that doesn'tscripts/codepart-replace.mjs— marker-driven generation
Thanks for .github/REPRODUCING.md, by the way — the #4252/#4253 example is a sharp illustration, and it's the reason this is an issue and not a speculative PR.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with ios/RNMBX/RNMBXHeatmapLayer.swift and compare its generated-property marker with ios/RNMBX/RNMBXCircleLayer.swift and LayerPropsCommon.codepart-swift.ejs. Review scripts/codepart-replace.mjs, run yarn generate, and verify the generated iOS layer honors sourceLayerID and filter for a VectorSource without changing ShapeSource behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, react-native, swift
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100