maplibre / maplibre-native

MapLibre Native - Interactive vector tile maps for iOS, Android and other platforms.
https://maplibre.org
BSD 2-Clause "Simplified" License
1.03k stars 299 forks source link

Label Blinking Issue at Integer Zoom Levels with animateCamera #2477

Open geolives-contact opened 4 months ago

geolives-contact commented 4 months ago

Describe the bug Starting with MapLibre v11, when the map zoom is set to an exact integer value (such as 15.0, 16.0 or 17.0 for instance), some labels blink when animateCamera is called. The issue does not occur when using moveCamera or when the zoom is set between integer values (e.g., 16.4 or 17.1).

Here are some videos to demonstrate the issue:

To Reproduce

  1. Download and use this sample activity file: SimpleMapActivity.kt.zip
  2. This activity file contains code to run animateCamera at short intervals with the zoom set exactly to 15.0.
  3. If needed, we can create a branch with this edit in our fork.

Expected behavior On the MapLibre v10 version, labels didn't blink when zoom are set at integer values.

Platform information (please complete the following information):

louwers commented 4 months ago

Thanks for the great bug report with reproduction!

That's a really strange bug, but we'll have a look at it.

I can reproduce this with other styles as well.

louwers commented 3 months ago

I tried iOS, doesn't seem to be a problem there:

struct SimpleMap: UIViewRepresentable {

    func makeUIView(context _: Context) -> MLNMapView {
        let mapView = MLNMapView(frame: .zero, styleURL: VERSATILES_COLORFUL_STYLE)
        mapView.zoomLevel = 15.0

        mapView.centerCoordinate = CLLocationCoordinate2D(latitude: 50.15, longitude: 5.2)

        startTimer(mapView: mapView)
        return mapView
    }

    func updateUIView(_: MLNMapView, context _: Context) {}

    private func startTimer(mapView: MLNMapView) {
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { i in
            // Update coordinates
            var currentCenter = mapView.centerCoordinate

            // Update coordinates
            currentCenter.latitude += 0.001
            currentCenter.longitude += 0.001

            // Animate camera to new coordinates
            let camera = MLNMapCamera(lookingAtCenter: currentCenter, altitude: mapView.camera.altitude, pitch: mapView.camera.pitch, heading: mapView.camera.heading)
//            mapView.setCamera(camera, withDuration: 1, animationTimingFunction: CAMediaTimingFunction(name: .easeInEaseOut))
//            mapView.setCamera(camera, withDuration: 1, animationTimingFunction: CAMediaTimingFunction(name: .easeInEaseOut))
            mapView.fly(to: camera)
        }
    }
}
louwers commented 3 months ago

I think we're losing a lot of accuracy on Android somewhere.

constexpr double kEpsilon = 1e-9;
// To avoid flickering issue due to "zoom = 13.9999999..".
double roundForAccuracy(double x) {
    double round_x = std::round(x);
    double diff = std::abs(round_x - x);
    if (diff < kEpsilon && diff > 0) {
        return round_x;
    } else {
        return x;
    }
}

is meant to account for this problem but the zoom goes from 15.0 to 14.993487604307171 during the animation

louwers commented 3 months ago

@geolives-contact animateCamera uses flyTo which is implemented using the Smooth and efficient zooming and panning algorithm. So it zooms out a little, with longer distances and a longer animation duration this is more pronounced. The zooming out causes (re)placement.

This looks related: https://github.com/maplibre/maplibre-native/issues/16

geolives-contact commented 3 months ago

Thank you for your prompt feedback on this issue!

From what I understand, the symbol flickers because the zooming/panning algorithm briefly changes the zoom level from 15.0 to 14.999, then back to 15.0 when the camera reaches its destination.

The real problem seems to be why the labels flicker during these zoom level transitions. It’s particularly strange since some labels do not flicker at all.

I will investigate this further next week, specifically to see if the arrangement of layers in the styleJSON affect this behavior.

louwers commented 3 months ago

@geolives-contact Yes I think it's a bug, with such a small zoom change I don't expect such dramatic label placement changes. I also don't see it on iOS for some reason.

I see this problem in other styles as well, so I don't think it's related to your style. The solution is probably in the placement code but it quite complex so it may be hard to find the problem.