mapbox / mapbox-gl-js

Interactive, thoroughly customizable maps in the browser, powered by vector tiles and WebGL
https://docs.mapbox.com/mapbox-gl-js/
Other
11.04k stars 2.21k forks source link

Using an object in the properties of a feature in Styles Expressions #11685

Open dragonfist453 opened 2 years ago

dragonfist453 commented 2 years ago

Motivation

I was using a layer which has a property in the properties called "layer_properties" that contains an object containing further detailed properties unique to the layer type. For example, we have:

{
  "geometry": {
    "coordinates": [
      -169.4,
      -47.5
    ],
  "type": "Point"
  },
  "id": "JET_STREAM_POINT_DATA_fid--476b30b2_17f98bc5c3d_-5d37",
  "properties": {
    "validTime": 1649397600000,
    "valid_time_str": "08Apr22 06:00Z",
    "layer_properties": {
      "jet_wind_level": "290",
      "jet_wind_speed": "90"
    },
    "forecast_period": "018HR",
    "layer_name": "JET_STREAM_POINT_DATA"
  },
  "type": "Feature"
}

Using the existing API, there is no easy way to access jet_wind_level or jet_wind_speed inside layer_properties as get will give me layer_properties as a string like "{jet_wind_level: 290, jet_wind_speed: 90}". This does not help me much and I need to implement an algorithm in styles API that slices the string to get me the value given attribute.

A lot of layers for weather have these sort of composite objects not necessarily named "layer_properties" that may need to be accessed.

Design

It helps a lot for data that could be provided this way and it will make it a lot more powerful. There should be no drawbacks as it just makes the styles expressions API work for more versatile use-cases.

Mock-Up

It could be tried as a ['get-composite', 'jet_wind_level', 'layer_properties'] (as per above given example) to provide the string "290" by accessing the object conveniently. This should ease up and make this wonderful API much more convenient to use.

Alternative followed for this problem

For others facing this problem, I had to implement a simple slicing function that looks complex like so:

const sliceAttrValueFromCompositeObject = (attribute: string, compositeObjectKey: string, endchar: string, separator: string = ':'): Array<any> => {
  return [
    'slice', // Slice the sliced string with the given indices to get only the value part for a given attribute
    ['slice',['get', compositeObjectKey],['index-of', attribute+separator, ['get', compositeObjectKey]]], // Slice string such that we only have the JSON from the attribute till the end so that we only operate on that
    ['+', ['index-of', attribute+separator, ['slice',['get', compositeObjectKey],['index-of', attribute+separator, ['get', compositeObjectKey]]]], ['length', attribute+separator]], // Get the index of the attribute name in the sliced string and then add the length of the attribute+separator to go to the value part
    ['index-of', endchar, ['slice',['get', compositeObjectKey],['index-of', attribute+separator, ['get', compositeObjectKey]]]] // Get the index of the nearest end char to slice till
  ]
}

const getAttrFromCompositeObject = (attribute: string, compositeObjectKey: string = 'layer_properties', separator: string = ':'): Array<any> => {
  return [
    'case', // If there is a comma present in the sliced string, slice till ',' else slice till '}' (which notifies that this is last or only attribute in JSON)
    ['!=', ['index-of', ',', ['slice',['get', compositeObjectKey],['index-of', attribute+separator, ['get', compositeObjectKey]]]], -1],
    sliceAttrValueFromCompositeObject(attribute, compositeObjectKey, ',', separator),
    sliceAttrValueFromCompositeObject(attribute, compositeObjectKey, '}', separator)
  ]
}

Using this as getAttrFromCompositeObject('jet_wind_speed') would get me the data from layer_properties but this only works for text-field or icon-image or for any place that takes a value type. When a string is asked, like in a "formatted" type, it will not work and that does lead to problems. If this could be completely solved by adding a feature to access composite objects, it would make the API very powerful for anybody using it

mourner commented 2 years ago

See also #2434 for a similar request, with a lengthy discussion. The problem is that GeoJSON is converted into vector tiles for processing & rendering, and the vector tile specification does not support nested properties — fixing this is a pretty difficult architectural problem, more than it appears at first.