rnmapbox / maps

A Mapbox react native module for creating custom maps
MIT License
2.22k stars 839 forks source link

Consistent and reliable layer hierarchy #1477

Closed m3co-code closed 1 year ago

m3co-code commented 3 years ago

This is rather a conceptual problem we're experiencing and not a code error or similar. Therefore, the issue template wasn't applicable so far. Please just let me know if I should do something differently!


We're trying to achieve a consistent order of layers in our App. Assume that we have the following layers we add.

Now it's clear that we can define this belowLayerID or aboveLayerID. The problem we're having is that user location is only conditionally rendered in our App. What happens now when zone markers specify it as aboveLayerID but it doesn't exist? I'd assume it moves to the same level as zone polygon which is not what we want.

We want to be deterministic no matter whether some layers of the "layer-chain" are rendered or not.

We were also looking into layerIndex. But here the behaviour was also pretty surprising and dynamic. At least it appeared to us like this. Using very high numbers (10000+) for the lowest element did result in Mapbox errors (some out of bounds exception). It was working somehow when we used numbers below 116, but also then it was sometimes even below mapbox rendered layers.

Is there some idiomatic way how we can do this? Can you recommend an approach for us?

We also did not find proper docs for layerIndex. Why is there a maximum number that we can give? We assumed it to be similar to z-index, but apparently there is something else behind it.

Thanks already everyone for making it that far reading through our issue! :D The example is of course simplified, there are many layers we are rendering in our App and it's rather complicated.

ferdicus commented 3 years ago

hmm, I think it also doesn't help, that it is behaving differently on iOS and Android πŸ˜…

Can you maybe provide a small example without any external dependencies within our /example app to reproduce this?

Thanks in advance πŸ™‡πŸΏ

m3co-code commented 3 years ago

Hi @ferdicus, sorry for opening the issue and then not coming back for 10 days from my side... πŸ’

I can try to create such an example application. It will, however, take a few days until I find a slot doing that. Hope to be able to provide it this Friday.

mwood23 commented 3 years ago

I'm running into this issue too. My has 25+ map layers and I would like to order them in a specific way. aboveLayerID and belowLayerID doesn't appear to work if the layers aren't visible. layerIndex doesn't work unless you know the precise number of layers which I won't since the user choose them (it fires a warning an appends at the end). This is the app I'm working on (https://www.campsitetonight.app/). Sometimes the coverage or weather maps pop over top of the results πŸ˜…

I can get an example together if that would help? I looked in the source, but I'm not familiar enough with Java or Objective C to know what's happening.

mfazekas commented 3 years ago

Ideally you can introduce dummy empty layers just for ordering, that will not be shown/hidden. Then you put other layers above/bellow those layers.

mwood23 commented 3 years ago

@mfazekas Could you provide a code snippet of how this would be implemented? I'm having trouble figuring out how to do that.

Dauria1 commented 2 years ago

@mfazekas Could you provide a code snippet of how this would be implemented? I'm having trouble figuring out how to do that.

Did you ever get any further with these issues? I am running into the same problems.

Cmoen11 commented 2 years ago

any updates?? :)

tomskopek commented 2 years ago

I came up with this for my case.

I'm not sure this is the "proper way" to do things, but it seems to be working for me. To be honest, I'm never quite sure I'm using this library correctly - so if you see something funky in my code, I would be grateful if you point it out... For example, is this the right way to handle onPress? Is this the right way to deal with showing/hiding a layer? πŸ€·β€β™‚οΈ

Hope this helps someone!

I believe the trick is that the conditional layers need to be the ones that contains the above/below layer logic. If you try to do aboveLayerId={'aConditionalLayer'}, you run into problems when it is not there. This is just a guess based on my testing.

CleanShot 2022-02-28 at 22 34 59@2x

     const renderDetails =     zoomLevel.current > 11 && someOtherConditions
     ....

        <MapboxGL.MapView>
          <MapboxGL.ShapeSource
            id={'dataSource'}
            shape={myData}
            hitbox={{ width: 0, height: 0 }}
            onPress={(shape: OnPressEvent) => {
              // shape.features returns an array of nearby features that match the hitbox
              const feature = shape.features[0];
              doSomething(feature.id);
            }}
          >
            <MapboxGL.CircleLayer id="dotsLayer" />     // <-------- my bottom layer
            {renderDetails && (                        // <---- a layer that is only shown on some conditions
              <MapboxGL.SymbolLayer
                id="detailsLayer"                       // <------   my middle layer
                aboveLayerID={'dotsLayer'}               // <-------  note this
                belowLayerID={'searchPinLayer'}           // <-------  note this
              />          
            )}
          </MapboxGL.ShapeSource>
          <MapboxGL.ShapeSource id={'searchPinShapeSource'} shape={searchPin}>
            <MapboxGL.SymbolLayer
              id="searchPinLayer"             // <------- my top layer
              style={layerStyles.searchPin}
            />
          </MapboxGL.ShapeSource>
        </MapboxGL.MapView>
stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

mfazekas commented 1 year ago

Something like this can be tried, where we have persistent empty separator layers and we position above/below those.

import React from 'react';
import { Button } from 'react-native';
import {
  MapView,
  ShapeSource,
  SymbolLayer,
  CircleLayer,
  Camera,
} from '@rnmapbox/maps';

const styles = {
  mapView: { flex: 1 },
  circleLayer: {
    circleRadiusTransition: { duration: 5000, delay: 0 },
    circleColor: '#ff0000',
  },
};

const emptyFeatures = {
  type: 'FeatureCollection',
  features: [],
};

const features = {
  type: 'FeatureCollection',
  features: [
    {
      type: 'Feature',
      id: 'a-feature',
      properties: {
        icon: 'example',
        text: 'example-icon-and-label',
      },
      geometry: {
        type: 'Point',
        coordinates: [-74.00597, 40.71427],
      },
    },
    {
      type: 'Feature',
      id: 'b-feature',
      properties: {
        text: 'just-label',
      },
      geometry: {
        type: 'Point',
        coordinates: [-74.001097, 40.71527],
      },
    },
    {
      type: 'Feature',
      id: 'c-feature',
      properties: {
        icon: 'example',
      },
      geometry: {
        type: 'Point',
        coordinates: [-74.00697, 40.72427],
      },
    },
  ],
};

function moveBy(features, d) {
  return {
    type: 'FeatureCollection',
    features: features.features.map((i) => ({
      ...i,
      geometry: {
        ...i.geometry,
        coordinates: [
          i.geometry.coordinates[0] + d,
          i.geometry.coordinates[1] + d,
        ],
      },
    })),
  };
}

const features1 = moveBy(features, 0.0001);
const features2 = moveBy(features, 0.0002);
const features3 = moveBy(features, 0.0003);

class BugReportExample extends React.Component {
  state = {
    radius: 20,
  };

  render() {
    const circleLayerStyle = {
      ...styles.circleLayer,
      ...{ circleRadius: this.state.radius },
    };

    return (
      <>
        <Button
          title="Grow"
          onPress={() => this.setState({ radius: this.state.radius + 20 })}
        />
        <MapView style={styles.mapView}>
          <Camera centerCoordinate={[-74.00597, 40.71427]} zoomLevel={14} />
          <ShapeSource id="separator-source" shape={emptyFeatures}>
            <CircleLayer id={'separator-1'} />
            <CircleLayer id={'separator-2'} />
            <CircleLayer id={'separator-3'} />
          </ShapeSource>

          {true && (
            <ShapeSource id={'shape-source-id-0'} shape={features}>
              <CircleLayer
                id={'circle-layer0'}
                style={{ circleRadius: 20, circleColor: 'red' }}
                belowLayerID={'separator-2'}
              />
            </ShapeSource>
          )}

          {true && (
            <ShapeSource id={'shape-source-id-1'} shape={features2}>
              <CircleLayer
                id={'circle-layer1'}
                style={{ circleRadius: 20, circleColor: 'green' }}
                aboveLayerID={'separator-2'}
              />
            </ShapeSource>
          )}
          {true && (
            <ShapeSource id={'shape-source-id-2'} shape={features3}>
              <CircleLayer
                id={'circle-layer3'}
                style={{ circleRadius: 20, circleColor: 'blue' }}
                aboveLayerID={'separator-3'}
              />
            </ShapeSource>
          )}
        </MapView>
      </>
    );
  }
}

export default BugReportExample;
mfazekas commented 1 year ago

FWIW with mapbox 10 we have option to specify the layer position with an index: android iOS

We could either implement this as:

 <CircleLayer
    layerPosition={12}
    ...
 />

or

 <CircleLayer
    at={12}
    ...
 />

Sorry this is already implemented, but the position is absolute.

CarlosAlbertoTI commented 1 year ago

FWIW with mapbox 10 we have option to specify the layer position with an index: android iOS

We could either implement this as:

 <CircleLayer
    layerPosition={12}
    ...
 />

or

 <CircleLayer
    at={12}
    ...
 />

How can I use this? Could you create a simple example?

mfazekas commented 1 year ago

@CarlosAlbertoTI sorry I've updated my comment it's implemented as layerIndex but it's the absolute layer index, so it might now work for you. See https://github.com/rnmapbox/maps/blob/main/docs/BackgroundLayer.md#layerindex