mapbox / mapbox-maps-ios

Interactive, thoroughly customizable maps for iOS powered by vector tiles and Metal
https://www.mapbox.com/mapbox-mobile-sdk
Other
462 stars 150 forks source link

feature-state styling not working. #2046

Open FezVrasta opened 10 months ago

FezVrasta commented 10 months ago

Environment

Observed behavior and steps to reproduce

I have created a custom map style on Mapbox Studio and uploaded a GeoJSON with several multipolygon features, the file looks like this:

{
  "type": "FeatureCollection",
  "name": "AD_micro-regions",
  "crs": {
    "type": "name",
    "properties": {
      "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
    }
  },
  "features": [
    {
      "type": "Feature",
      "id": "AD-01_low_high",
      "properties": {
        "id": "AD-01",
        "elevation": "low_high"
      },
      "geometry": {
        "type": "MultiPolygon",
        "coordinates": [...]
      }
    },
    ... other fetures
  ]

I defined a custom styling for the layer I created so that each feature will be colored according to the danger_level feature state (1, 2, 3, 4, 5):

[
  "match",
  ["feature-state", "danger_level"],
  [1],
  "rgb(134, 231, 75)",
  [2],
  "rgb(249, 219, 73)",
  [3],
  "rgb(235, 134, 52)",
  [4],
  "rgb(216, 69, 49)",
  [5],
  "rgb(184, 64, 46)",
  "rgba(0, 0, 0, 0)"
]

Now my plan would be to set the danger_level for each feature from the iOS SDK, but I can't really find a way to do it.

I tried using setFeatureState but it has no effect:

map.setFeatureState(
  sourceId: "composite",
  //sourceLayerId: "micro-regions_elevation-2p16jz",
  featureId: "AD-01_low_high",
  state: ["danger_level": 4],
  callback: { result in
    // This returns `success(<null>)`
    print(result)

    map.getFeatureState(
      sourceId: "composite",
      featureId: "AD-01_low_high",
      callback: { state in
        // This correctly prints `success(["danger_level": 4])`
        // but the map style is not updated
        print(state)
      }
    )                        
  }
)

The code above has no effect, the state seems to be set correctly but no updates happen on the rendered map.

If I also specify the sourceLayerId, the second print statement returns an empty dictionary.

If I print map.allSourceIdentifiers I only see this:

[
  MapboxMaps.SourceInfo(id: "composite", type: MapboxMaps.SourceType(rawValue: "vector")),
  MapboxMaps.SourceInfo(id: "6D9AE", type: MapboxMaps.SourceType(rawValue: "geojson"))
]

My map has two geojson layers, not one, and 6D9AE changes id on each render. I tried using map.allSourceIdentifiers.last!.id as sourceId for the setFeatureState call but it had no effect.

I tried to retrieve the layer with map.layer(withId: "micro-regions_elevation") and it correctly returned the layer data:

FillLayer(id: "micro-regions_elevations", type: MapboxMaps.LayerType(rawValue: "fill"), filter: nil, source: Optional("composite"), sourceLayer: Optional("micro-regions_elevation-2p16jz"), slot: nil, minZoom: nil, maxZoom: nil, visibility: MapboxMaps.Value<MapboxMaps.Visibility>.constant(MapboxMaps.Visibility.visible), fillSortKey: nil, fillAntialias: nil, fillColor: Optional(MapboxMaps.Value<MapboxMaps.StyleColor>.expression([match, [get, danger_level], 1, [rgba, 134.0, 231.00001525878906, 75.0, 1.0], 2, [rgba, 249.00001525878906, 219.00001525878906, 73.0, 1.0], 3, [rgba, 235.00001525878906, 134.0, 52.000003814697266, 1.0], 4, [rgba, 216.00001525878906, 69.0, 49.000003814697266, 1.0], 5, [rgba, 184.0, 64.0, 46.0, 1.0], [rgba, 0.0, 0.0, 0.0, 0.0]])), fillColorTransition: nil, fillEmissiveStrength: nil, fillEmissiveStrengthTransition: nil, fillOpacity: Optional(MapboxMaps.Value<Swift.Double>.constant(0.5)), fillOpacityTransition: nil, fillOutlineColor: Optional(MapboxMaps.Value<MapboxMaps.StyleColor>.constant(MapboxMaps.StyleColor(rawValue: "rgba(0.00, 0.00, 0.00, 0.00)"))), fillOutlineColorTransition: nil, fillPattern: nil, fillTranslate: nil, fillTranslateTransition: nil, fillTranslateAnchor: nil)

But I still get the same result.

I also tried this alternative fill color formula wit the same result though:

[
  "case",
  ["==", ["feature-state", "danger_level"], 1],
  "rgb(134, 231, 75)",
  ["==", ["feature-state", "danger_level"], 2],
  "rgb(249, 219, 73)",
  ["==", ["feature-state", "danger_level"], 3],
  "rgb(235, 134, 52)",
  ["==", ["feature-state", "danger_level"], 4],
  "rgb(216, 69, 49)",
  ["==", ["feature-state", "danger_level"], 5],
  "rgb(184, 64, 46)",
  "rgba(0, 0, 0, 0)"
]

This is my map style ID for reference: mapbox://styles/fezvrasta/clol7m5uj008q01qocvyg1fzx

What am I doing wrong?

persidskiy commented 10 months ago

Hi @FezVrasta , thank you for the report!

I managed to reproduce the issue locally, but in my case the polygon changes the color if I pan the map for a while after the state update. Can you please confirm if you see the same result?

Internal bug to track https://mapbox.atlassian.net/browse/MAPSNAT-1585

FezVrasta commented 10 months ago

Thanks for the reply. In my case the color never changes.

persidskiy commented 10 months ago

@FezVrasta Did you try to use "6D9AE" geojson source id? The "composite" is a Mapbox vector source that drives the basemap

FezVrasta commented 10 months ago

I tried both sources (I literally forEached on every available source and run the same logic on each)

Also, the 6D9AE ID changes on each load so, even if it worked, it would not be usable on a production app.

I also wonder why this code returns a state, shouldn't it error if I try to set state on a non-existent feature?

map.setFeatureState(
  sourceId: "composite",
  //sourceLayerId: "micro-regions_elevation-2p16jz",
  featureId: "AD-01_low_high",
  state: ["danger_level": 4],
  callback: { result in
    // This returns `success(<null>)`
    print(result)

    map.getFeatureState(
      sourceId: "composite",
      featureId: "AD-01_low_high",
      callback: { state in
        // This correctly prints `success(["danger_level": 4])`
        // but the map style is not updated
        print(state)
      }
    )                        
  }
)
persidskiy commented 9 months ago

I also wonder why this code returns a state, shouldn't it error if I try to set state on a non-existent feature?

In some cases the geojson can me loaded later than the state is set.

Also, the 6D9AE ID changes on each load so, even if it worked, it would not be usable on a production app.

This may be a reason for your issue, for example you set the feature state for one Source ID, then load a new source with the different ID. The state won't be transferred int this case

FezVrasta commented 9 months ago

This may be a reason for your issue, for example you set the feature state for one Source ID, then load a new source with the different ID. The state won't be transferred int this case

I'm not loading any sources manually, I'm only using the ones baked in the style.

persidskiy commented 8 months ago

@FezVrasta Hi, can you please check if the issue is resolved in v11.0.0 stable release?

FezVrasta commented 8 months ago

It'll take some time as I'm focusing on other things currently, I'll let you know as soon I test it.

FezVrasta commented 6 months ago

I'm sorry, I realized I won't have time to look into this for the foreseeable future. Feel free to close the issue if you think the bug has been addressed already.