Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a z-index like property? #7016

Closed
gabimoncha opened this issue Jul 24, 2018 · 14 comments
Closed

Is there a z-index like property? #7016

gabimoncha opened this issue Jul 24, 2018 · 14 comments

Comments

@gabimoncha
Copy link

gabimoncha commented Jul 24, 2018

mapbox-gl-js version: 0.45.0

Question

Is there a z-index like property for layers?

I had one big geojson vector layer, which I had to split based on its gender property because it was to big and the zoom extent from which it was visible wasn't enough. But because it has many points, depending which layer comes lasts, it overlaps on the other one. In some case this is needed, but in my particular case, I need them to be at the same z-index (let's use this css property for now).

Is there a property for layers that does this, because I couldn't find it in the docs? If not, have you think of implementing this feature? I am pretty sure mapbox users would be very happy to have this feature.

@mourner
Copy link
Member

mourner commented Jul 24, 2018

Currently there's no such property, and the draw order fully depends on the layer order. We're looking into introducing a z-index-like property as a part of #4225, but that will take a while to get implemented.

@mourner mourner closed this as completed Jul 24, 2018
@andrewharvey
Copy link
Collaborator

possibly a duplicate of #1349

@areichman
Copy link

areichman commented Jul 26, 2018

@mourner Using the same vector tile as the source, I see a reversed rendering order when using a circle layer versus a symbol layer. Does that make sense or would you expect them to be the same?

In my case, I have time sequential data and there may be two markers with different timestamps at the same location. The circle layer shows the newest marker on top while the symbol layer shows the oldest on top. When clicking on each layer, queryRenderedFeatures returns them in reverse order of each other too.

Edit: I realize now the original question was in regards to layer order., whereas my comment is about feature order within a layer.

@mourner
Copy link
Member

mourner commented Jul 26, 2018

@areichman not sure — maybe @ChrisLoer can chime in on this.

@ChrisLoer
Copy link
Contributor

Hi @areichman, I believe the difference you're seeing between circle layers and symbol layers is because we sort the rendering order of symbol features within a layer based on their y-position: #470. The idea there is that when you have a lot of overlapping icons, consistently sorting them based on y-position makes them look less "jumbled".

#4361 (#1349 mentioned by @andrewharvey is another duplicate) proposes adding explicit sort order control, which I think would be the best way to address this. But for some purposes it might make sense to just turn off the y-position sorting for symbol layers (when you're providing the data for the symbol layer and you control the order of the data to match your desired render order).

@cs09g
Copy link
Contributor

cs09g commented Nov 28, 2018

@ChrisLoer

when you're providing the data for the symbol layer and you control the order of the data to match your desired render order

How can I add data for order and how to use those for render order? Okay, first, I set order value in fields in tilejson, then what should I do or How the library detects it? is there a reserved keyword for render order?

@ryanhamley
Copy link
Contributor

@cs09g Setting the symbol-z-order style spec property to source will prevent sorting on symbol layers, which means they will be rendered in the same order as your source data. It's not possible at this time to sort other layer types in this way.

@cs09g
Copy link
Contributor

cs09g commented Nov 29, 2018

@ryanhamley There's no order of data. sources is the same. it is objects. how does it recognize its features rendering order? so, how it can be sorted, by what value of layer or each feature?
I have many features in a layer and want to give priority(z-index) for each feature in case they overlaps.

@ryanhamley
Copy link
Contributor

If your source is a FeatureCollection with an array of features, then setting symbol-z-order: 'source' on the style will render the features in the order they appear in the array, instead of sorting them by their y position on the viewport.

@kukiel
Copy link

kukiel commented Mar 12, 2020

Hi guys,
is there any plan to introduce some ordering mechanism (like z-index)? I do have many layers of different types and proper order plays key role. I must say I am having hard time to keep the right order whenever introducing new layer or when there is a need to reorder layers.

Maybe there is some new API I am not aware of or some tips on how to manage order. Will appreciate an update on this topic.

@areichman
Copy link

areichman commented Mar 12, 2020

@kukiel I usually create some dummy GeoJSON layers and then reference them using the beforeId attribute when adding layers.

e.g.

map.addSource('empty', {
  type: 'geojson',
  data: { type: 'FeatureCollection', features: [] }
});

const zIndex2 = map.addLayer({
  id: 'z-index-2',
  type: 'symbol',
  source: 'empty'
});

const zIndex1 = map.addLayer({
  id: 'z-index-1',
  type: 'symbol',
  source: 'empty'
}, 'z-index-2'); // place this layer below zIndex2

const polygons = map.addLayer({
  id: 'my-polygons',
  type: 'symbol',
  source: myPolygons
}, 'z-index-1'); // place the polygons on the bottom

const points = map.addLayer({
  id: 'my-real-points',
  type: 'symbol',
  source: myPoints
}, 'z-index-2'); // place the points above the polygons

Edit: I do this when I have lots of different layers that may be toggled on/off, so the dummy layers give me something I can always refer to. In the example above, if my-polygons and my-points are always on the map, you can use the beforeId to place the polygons directly underneath the points without the extra overhead of the zIndex* layers.

@kukiel
Copy link

kukiel commented Mar 13, 2020

@areichman thanks for sharing, I was actually thinking of something similar but was also hoping maybe there is/will be something built-in.

@JoeDuncko
Copy link

JoeDuncko commented Oct 14, 2022

For my use case - where I have a known number of possible GeoJSON layers that can appear in a known order - I ended up just always rendering the sources and layers, but passing in an empty but valid GeoJSON object when I want to hide the layers. It remains to be seen how reliable this is.

{
    type: "FeatureCollection",
    features: [],
}

@timautin
Copy link

timautin commented Aug 18, 2023

Building upon @areichman 's answer, I did this (it's in Flutter):

static const minZIndex = 0;
static const maxZIndex = 100;

void _initZIndexes() async {

  await mapController!.addSource("empty", const GeojsonSourceProperties(data: { 'type': 'FeatureCollection', 'features': [] } ));
  for (var i = maxZIndex; i >= minZIndex; i--) {
    mapController!.addLayer("empty", "z-index-$i", const SymbolLayerProperties(), belowLayerId: i == 100 ? "" : "z-index-${i + 1}");
  }
}

Future<void> _addPolygon(String id, { List<LatLng> coordinates = const [], String fillColor = "#ff0000", double fillOpacity = 1, String strokeColor = "#ff0000", double strokeWidth = 1, double strokeOffset = 0, double strokeOpacity = 1, int zIndex = 0}) async {

  if (coordinates.length < 3) { throw Exception("Polygon must have at least 3 coordinates."); }
  if (zIndex > maxZIndex || zIndex < minZIndex) { throw Exception("Z-index must be in the [$minZIndex..$maxZIndex] range."); }

  // Convert the List<LatLng> to a closed array of lon/lat pairs:
  List<List<double>> lonLatCoordinates = [];
  for (var coordinate in coordinates) {
    lonLatCoordinates.add([coordinate.longitude, coordinate.latitude]);
  }
  lonLatCoordinates.add([coordinates[0].longitude, coordinates[0].latitude]);

  await mapController!.addSource("$id-source", GeojsonSourceProperties(data: {
    'type': 'FeatureCollection',
    'features': [
      {
        'type': 'Feature',
        'geometry': {
          "coordinates": [lonLatCoordinates],
          "type": "Polygon"
        }
      }
    ]
  }));

  await mapController!.addFillLayer("$id-source", "$id-fill-layer", FillLayerProperties(
      fillColor: fillColor,
      fillOpacity: fillOpacity,
      fillOutlineColor: strokeWidth == 1 && strokeOpacity == 1 && strokeOffset == 0 ? strokeColor : fillColor
    ),
    belowLayerId: "z-index-$zIndex"
  );

  if (strokeWidth != 1 || strokeOpacity != 1 || strokeOffset != 0) {

    log.info("draw border");

    await mapController!.addLineLayer("$id-source", "$id-stroke-layer", LineLayerProperties(
        lineColor: strokeColor,
        lineWidth: strokeWidth,
        lineOpacity: strokeOpacity,
        lineOffset: strokeOffset
      ),
      belowLayerId: "z-index-$zIndex"
    );
  }
}

void _removePolygon(String id) async {

  await mapController!.removeLayer("$id-fill-layer");
  await mapController!.removeLayer("$id-stroke-layer");
  await mapController!.removeSource("$id-source");
}

It can then be used like this:

// Init z-index layers (once):
_initZIndexes();

// Draw polygons:
_addPolygon("rect1", coordinates: [
    const LatLng(43.5, 6),
    const LatLng(43, 7),
    const LatLng(43, 8),
    const LatLng(44, 8),
    const LatLng(44, 7)
  ],
  fillColor: "#ff0000",
  fillOpacity: 0.5,
  strokeColor: "#0000ff",
  strokeWidth: 4,
  strokeOffset: -2,
  strokeOpacity: 1,
  zIndex: 0
);

_addPolygon("rect2", coordinates: [
    const LatLng(43.5, 7.5),
    const LatLng(43.5, 8.5),
    const LatLng(44.5, 8.5),
    const LatLng(44.5, 7.5)
  ],
  fillColor: "#ffff00",
  fillOpacity: 1,
  strokeColor: "#00ffff",
  strokeWidth: 4,
  strokeOffset: -2,
  strokeOpacity: 1,
  zIndex: 1
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants