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

select feature on click is too slow #892

Closed
nininea1 opened this issue Sep 26, 2018 · 5 comments
Closed

select feature on click is too slow #892

nininea1 opened this issue Sep 26, 2018 · 5 comments

Comments

@nininea1
Copy link

nininea1 commented Sep 26, 2018

I'm using mapbox, with GeoJsonSource and symbollayer. When user clicks on feature it should change a color. I handle this logic with following code and it works, but it is too slow and takes several second to change icon color.

Here I configure symbol layer, add icon changelogic for 'PROPERTY_SELECTED':

mapBoxMap?.addLayer(SymbolLayer(markerStyleLayerIdentifier, markerSourceIdentifier)
                .withProperties(
                        PropertyFactory.iconImage(markerImage),
                        PropertyFactory.iconAllowOverlap(false),
                        PropertyFactory.iconImage(match(
                                get(PROPERTY_SELECTED), literal(0),
                                literal(markerImage),
                                literal(markerImageSelected)
                        ))
                ))

on map click features objects are update:

override fun onMapClick(point: LatLng) {
    val screenPoint = mapBoxMap?.projection?.toScreenLocation(point)
    var features = mapBoxMap?.queryRenderedFeatures(screenPoint
            ?: return, markerStyleLayerIdentifier)

    if ((features ?: return).isNotEmpty()) {
        var feature = features[0]
        showMarkerInfo(feature)
        doAsync {
            var featureList = featureCollection?.features()

            var id = feature.getNumberProperty(PROPERTY_STOP_ID)

            if (featureList != null) {
                for (i in 0 until featureList.size) {

                    var fId = featureList[i].getNumberProperty(PROPERTY_STOP_ID)

                    if (fId == id) {
                        featureList[i].properties()?.addProperty(PROPERTY_SELECTED, 1)
                    } else {
                        featureList[i].properties()?.addProperty(PROPERTY_SELECTED, 0)
                    }
                }

                uiThread {
                    refreshSource()
                }
            }
        }
    }
}

and refresh source :

private fun refreshSource() {
    var source = mapBoxMap?.getSource(markerSourceIdentifier) as GeoJsonSource?
    if (source != null && featureCollection != null) {
        source.setGeoJson(featureCollection)
    }
}

after refreshSource is called, it takes several time before icon update. In my case there are 2050 features is source. Is there any better way to implement it ? Or any way to optimise this solution ?

@LukasPaczos
Copy link
Contributor

Thanks for using Mapbox @nininea1! We are tracking performance improvements during GeoJsonSource update in mapbox/mapbox-gl-native#8484.

To answer your question - one approach that might improve performance is filtering. This requires a bit more work and might not help in your specific case but might still be worth a shot.

You can set up a second layer that takes the same source as a parameter but with iconImage set to markerImageSelected while the initial layer has iconImage set to markerImage. Then, you can add a filter with Layer#setFilter and define it to display only symbols with PROPERTY_STOP_ID that are selected or not, depending on the layer. You will need to maintain a list of selected features yourself in this case. Whenever a new feature is selected, you just need to reload the filter expression.

Example of such filter would be:

    Expression selectedExpression = Expression.any(
      Expression.eq(Expression.get(PROPERTY_STOP_ID), "selected_stop_id_1"),
      Expression.eq(Expression.get(PROPERTY_STOP_ID), "selected_stop_id_2"),
      ...
    );
    
    symbolLayer.setFilter(Expression.not(selectedExpression));

    selectedSymbolLayer.setFilter(selectedExpression);

To achieve similar effect you can also use match or switchCase expressions.

Let us know whether filtering helps, closing this one as not actionable.

@nininea1
Copy link
Author

p.s. the way I do selection works faster in iOS

@nininea1
Copy link
Author

nininea1 commented Sep 27, 2018

How should I refresh symbol layer? when I will change filter of the layer should I remove and add it again ?

I implemented it this way and it is much more fast and pretty :)

`

            var selectedLayer = mapBoxMap?.getLayer(markerSelectedStyleLayerIdentifier) as SymbolLayer?
            var id = feature.getNumberProperty(PROPERTY_STOP_ID)
            var selectedExpression = any(
                    eq(get(PROPERTY_STOP_ID), literal(id.toString()))
            )
            selectedLayer?.filter = selectedExpression
            mapBoxMap?.removeLayer(selectedLayer ?: return)
            mapBoxMap?.addLayer(selectedLayer ?: return)`

thanks !

@LukasPaczos
Copy link
Contributor

I'm glad that it helped! Quick follow-up question - you shouldn't need to re-add the layer, setting the filter should be enough to display only requested features, is that not working for you?

@nininea1
Copy link
Author

nininea1 commented Sep 27, 2018

yes, it works without re-add layer.
I retested it .

at first I added filter for number field , like this :

var selectedExpression = any(
eq(get(PROPERTY_STOP_ID), 227)
)

but it dons't work.

if I change it like this one it works:
var selectedExpression = any(
eq(get(PROPERTY_STOP_ID), "227")
)

filter works only for strings? I don't know.

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

2 participants