[Bug]: HeatmapLayer is missing the LayerPropsCommon codepart, so on iOS it never receives sourceLayer or filter

Open Beginner friendly
#4,268 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
ios, react-native, swift
Domain
mobile

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.

Description

bug 🪲
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:

  1. On a VectorSource the layer has no source-layer to read, so it should draw nothing.
  2. 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.

Dominant language
Kotlin
Stars
2.9k
Forks
947
Avg merge
8d 4h
Merged PRs (30d)
2

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from rnmapbox/maps

All issues in rnmapbox/maps

Similar issues

More Kotlin issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.