maplibre / maplibre-gl-js

MapLibre GL JS - Interactive vector tile maps in the browser
https://maplibre.org/maplibre-gl-js/docs/
Other
6.41k stars 689 forks source link

Feature Properties Transform #4198

Open wipfli opened 4 months ago

wipfli commented 4 months ago

The things that a user can do with MapLibre style expressions based on feature properties is limited to the implemented styling language. While the style language has support for basic math, logic, and rudimentary string operations, many things cannot be done easily.

For example, it is hard to format a number to the locale India with 3 significant digits, e.g.: turn 1434389.043 to 14,30,000.

Another example: it is not possible to extract a value from a JSON encoded dictionary.

Both of these tasks are trivial to achieve in JavaScript or any other programming language and we usually motivate users to do such transformation at tile generation time.

In the past, we rejected a proposal to reference user-defined javascript functions from the style.json.

What I would like to introduce instead is a user-defined Feature Properties Transform, a callback which gets executed during vector tile parsing.

The Feature Properties Transform gets executed on every feature and has access to the following data:

With this information, one can easily filter based on source and source-layer, like people do usually in style.json documents and since the transform has access to all feature properties, it can do arbitrary logic and execute javascript on the properties dictionary. This is very powerful and opens the way to an alternative way of styling maps, namely more styling in JavaScript and less in the styling language.

Proof-of-concept implementation: #4199

HarelM commented 4 months ago

I think this is a very good idea for a hook. We need to remember that allowing things to be only in javascript will cause issues with parity between native and web. This is true for add protocol as well, but addProtocol forces you to put something "Wrong" in the style as opposed to this feature which you might just get an empty label and this can "fail" siliently.

In any case, these hooks are important and people using them should know that they create issues with parity.

One thing to keep in mind looking at the PR is that the transform is done in the worker thread while the map API is available in the main thread, and passing a method to the worker is very hard (you can probably pass very basic method, without the context of the main thread). So there needs to be a way to pass a method to the worker, which is only using importScript, which needs a javascript file or a string converted to dataURI.

In any case, I think adding that hook, since it is in the worker can be done differently maybe, using some "noop" method you override in the worker tile class - i.e something like

class WorkerTile {
...
    transfromFeature(feature) = return feature // this is a new noop method

}

and call this code in the worker context:

WorkerTile.prototype.transfromFeature = (f) => {//do something else with f and return it }

IDK, worth prototyping to see what's the easiest approach.

wipfli commented 4 months ago

Thanks for the feedback.

Here is an example how to use the proof-of-concept: https://github.com/wipfli/maplibre-feature-properties-transform-example

And you are right @HarelM the setFeaturePropertiesTransform function should not be available in the main thread, only in the worker...

HarelM commented 4 months ago

This looks elegant. Nice work!

neodescis commented 4 months ago

This would be great! I just went to a lot of work to implement color hashing of features based on some feature value. Without this, I had to get a list of unique values up-front from another endpoint, hash those, and put them all into a (sometimes giant) case expression. With this addition, it would be just a few lines of code.