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
VectorSource the layer has no source-layer to read, so it should draw nothing.
filter is 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
/example app. 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, HillshadeLayer or the newer layer types have the same gap.
Additional links and references
ios/RNMBX/codeparts/LayerPropsCommon.codepart-swift.ejs — the template
ios/RNMBX/RNMBXCircleLayer.swift — a layer that has the block
ios/RNMBX/RNMBXHeatmapLayer.swift — the one that doesn't
scripts/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.
Mapbox Version
11.15.2
React Native Version
0.81.5
Platform
iOS
@rnmapbox/mapsversion10.2.6
Standalone component to reproduce
I have not reproduced this at runtime. Per
.github/REPRODUCING.mdrule 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:
Neither
sourceLayerIDnorfilterreaches the native layer on iOS — see below for why.Swapping
HeatmapLayerforCircleLayer, 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-layerto read. AShapeSourceshould be unaffected, sincesourceis set by the constructor andsourceLayeris meaningless there.Observed behavior and steps to reproduce
RNMBXHeatmapLayer.swiftis the only vector-capable layer without the@{codepart-replace-start(LayerPropsCommon.codepart-swift.ejs, ...)}block. Checked againstmaintoday:That block is what assigns
sourceLayer,sourceandfilterunderRNMBX_11. Its absence matters becauseRNMBXLayer.setOptionshas its entire body inside#if !RNMBX_11:Since the podspec always compiles
-D RNMBX_11, that is a no-op in every current build.RNMBXHeatmapLayer.makeLayercalls exactly that no-op and nothing else:By contrast
RNMBXCircleLayer.makeLayerassignslayer.sourceLayerdirectly and carries the codepart. So a heatmap ends up withsource(from the constructor) but neversource-layerand neverfilter.Two consequences, the second arguably worse than the first:
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.ktcallslayer.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
HeatmapLayerhonourssourceLayerIDandfilteron iOS, as the other layer types do.Notes / preliminary analysis
The fix appears to be adding the codepart marker to
RNMBXHeatmapLayer.swiftand runningyarn generate. As I readscripts/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 againstRNMBXCircleLayer's once the type name is normalised, which is the change we're carrying locally as apatch-packagepatch. 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:
setOptions) is verified against your source; the effect (blank layer, ignored filter) is inferred./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.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 generationThanks 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.