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

Add a query parameter to enable modifying url of VectorTileLayer to filter features #263

Open
wants to merge 5 commits into
base: VectorTileLayer
Choose a base branch
from

Conversation

zifanw9
Copy link

@zifanw9 zifanw9 commented Nov 7, 2024

Why is this feature needed?

  • Allow users to interactively filter features in vector tile layer, given that hideout property is not introduced to vector tile layer yet

Why is current version not suitable for the needs?

  • Line 100 of VectorTileLayer.ts uses layer.setUrl(url) method, but this method is not available and will throw error
  • Joachim, the author of Leaflet.VectorTileLayer suggests
    • URL template string can have an extra {q} in addition to {x}, {y}, {z}
    • If we initialize a const URLSearchParams with a name such as q, and pass this const directly as an element in options when initializing VectorTileLayer, then we will be able to update the layer by modifying the key/value in the const q
    • To make the update take effect immediately, use layer.redraw() method

With the new changes, how to do the filter then?

The vector tile server needs to support CQL in order to perform filtering by passing different URL. Here I use TiPG for testing the functionality.

  1. Initialize the layer within the map container
dl.VectorTileLayer(url="https://<TileServerEndpointURL>/collections/<SCHEMA.TABLE>/tiles/WebMercatorQuad/{z}/{x}/{y}?{q}",
                             id="vectortile",
                             query={
                                    "properties": "res,comind,geometry",
                                    "filter-lang": "cql2-text",
                                    "filter": "res IS NULL AND comind IS NULL"
                                    })

Note in the above code, the url ends with ?{q}, this format must be strictly followed if you want to perform filtering. If you do not expect to use filtering functionality, you can choose to specify the url without ?{q} at the end.

Additionally, to perform filtering, the query parameter needs to be specified. This parameter assumes a dictionary-like object. In this example, "properties": "res,comind,geometry" means to select those three columns from the PostGIS table. "filter-lang": "cql2-text" specifies the filter language, and "filter": "res IS NULL AND comind IS NULL" is like a SQL where clause that selects only features with both res and comind fields having null values. Note that those specific query parameters are tile server dependent and please consult the documentation of the tile server that you are using. I also want to mention that TiPG seems to assume column names to be all lower case.

  1. Create a callback function to update the query parameter of VectorTileLayer
@callback(
    Output("vectortile","query"),
    [Input("map","zoom")],
    State("vectortile","query")
)
def updatebuildinglayer(zoomlevel,current_url):
    expected_url = {
        "properties": "res,comind,geometry",
        "filter-lang": "cql2-text",
        "filter": "res = '0'"
    }
    if expected_url != current_url:
        return expected_url
    
    return dash.no_update

@zifanw9 zifanw9 mentioned this pull request Nov 7, 2024
@zifanw9
Copy link
Author

zifanw9 commented Nov 8, 2024

Known issue that should be resolved
If there are two vector tile layers (corresponding to two PostGIS tables) applied with this query parameter, the queries will mess up. Currently it seems like only one vector tile layer with query parameter can be added to the map at once


Update: the issue seems to be resolved in my third commit, but I am not sure if the code follows the best practice and whether it will lead to other unseen issues

@zifanw9
Copy link
Author

zifanw9 commented Nov 13, 2024

I just made one more commit to add setStyle method, which can update the style dictionary or function used for a vector tile layer.

I give an example of changing the attribute to display in a callback function.

clientside_callback(
    '''
        function(zoomlevel) {
            let displayattribute = "avgdistnearbyhydrant";
            if (zoomlevel > 16) {
                displayattribute = "maxdistnearbyhydrant";
            }
            const style_function = function(feature,layername,zoomlevel,context) {
                                                let style_template = {
                                                    fillOpacity: 1, 
                                                    stroke: false,
                                                    radius: 5,
                                                    fillColor: "#808080"
                                                    };
                                                
                                                const _value = feature.properties[displayattribute];
                                                
                                                if (_value!=null) {
                                                    if (_value <= 500) { style_template.fillColor="#ffffb2"; }
                                                    else { style_template.fillColor="#bd0026"; }
                                                }

                                                if (zoomlevel <= 11) { style_template.radius=1; }
                                                else if (zoomlevel <= 12) { style_template.radius=2; }
                                                else if (zoomlevel <= 13) { style_template.radius=3; }
                                                else if (zoomlevel <= 14) { style_template.radius=4; }

                                                return style_template;
                                            };
            style_function.hideout = {"displayattribute": displayattribute};
            return style_function;
        }
    
    ''',
    Output("hydrantMain","style"),
    Input("homepagemap","zoom"),
    prevent_initial_call=True,
)

Maybe using this method to un-display certain features (e.g., set stroke and fill to false) in lieu of url filter that I wrote earlier in this PR is better, since setStyle does not require re-transmission of updated tiles.

Ideally it will be great if we could have a real hideout property in the context, but I do not know how to update the properties stored in the context

@zifanw9
Copy link
Author

zifanw9 commented Nov 15, 2024

I just made 5th commit to resolve a bug related to update the style of the layer to perform filtering.

Previously layer is not redrawn after layer.setStyle method is applied, and I suggest that the layer style typically is updated automatically.

I discover that there is a case in which the above assumption is not true:

  1. Assuming that I have a callback function that update the vector tile layer's style function to perform some filtering based on user input min and max values. Within the style_function like the one I give in my previous comment, the followings thing is needed
  const no_style = {
                      stroke : false,
                      fill : false
                    };

  if (_value!=null){
      if (_value >= mininclusion && _value <= maxinclusion) {}
      else {return no_style;}
  }
  1. I open the map in browser and apply some filtering on the layer (defining mininclusion and maxinclusion)
  2. I zoom out or zoom in the map so that the zoom level changed
  3. I reset/cancel the filter or change the filter with a different set of mininclusion/maxinclusion values (particularly the new filter values imply more features should be shown; if less features should be shown, a redraw is not necessary)
  4. The layer is not updated, and a redraw is required in this case

In my 5th commit, I add a property to track the zoom value when the style is changed, and redraw the layer if the zoom values is different between current and previous zoom. If the zoom level is the same between two style updates, I have not observed any issue so far.

Redraw probably should be avoided when possible for vector tile layer since it will re-request tiles from the tile server. Update the style of a vector tile layer itself does not seem to make duplicate requests of tiles, although new tiles will be requested when panning the map or zoom in and out of the map

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

Successfully merging this pull request may close these issues.

1 participant