rnmapbox / rnmapbox/maps

[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.

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

  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.

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.

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.