amay077 / Xamarin.Forms.GoogleMaps

Map library for Xamarin.Forms using Google maps API
https://www.nuget.org/packages/Xamarin.Forms.GoogleMaps/
MIT License
546 stars 346 forks source link

Map.Region acting strangely in CameraMoving event #658

Open CalamityDeadshot opened 4 years ago

CalamityDeadshot commented 4 years ago

VERSIONS

PLATFORMS

ACTUAL BEHAVIOR

Greetings. I am using SkiaSharp canvas on top of the GoogleMaps (with Clustering library) view like this:

<Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <maps:ClusteredMap x:Name="map"
                               CameraMoving="Map_CameraMoving"
                               VerticalOptions="FillAndExpand"
                               HorizontalOptions="FillAndExpand"
            <skia:SKCanvasView x:Name="MapCanvas"
                               PaintSurface="Draw"
                               VerticalOptions="FillAndExpand"
                               HorizontalOptions="FillAndExpand"/>
</Grid>

And my goal is to draw some text on top of pins constantly - including map movement. So I call MapCanvas.InvalidateSurface(); 30 times every second (for 30 FPS) and execute the following code in Draw():

private void Draw(object sender, SKPaintSurfaceEventArgs args)
        {
            SKImageInfo info = args.Info;
            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;

            canvas.Clear();

            SKPaint paint = new SKPaint {
                Color = Color.Accent.ToSKColor(),
                TextSize = 64.0f,
                IsAntialias = true,
                IsStroke = false,
                Style = SKPaintStyle.Fill
            };
            try {
                foreach (var pin in map.Pins) {
                    float topLeftLng = (float)map.Region.FarLeft.Longitude;
                    float topLeftLat = (float)map.Region.FarLeft.Latitude;
                    float topRightLng = (float)map.Region.FarRight.Longitude;
                    float bottomLeftLat = (float)map.Region.NearLeft.Latitude;
                    float leftMapEdge = topLeftLng;
                    float rightMapEdge = topRightLng;
                    float topMapEdge = topLeftLat;
                    float bottomMapEdge = bottomLeftLat;
                    if (leftMapEdge > pin.Position.Longitude || // check if pin is outside the visible region
                        rightMapEdge < pin.Position.Longitude ||
                        topMapEdge < pin.Position.Latitude ||
                        bottomMapEdge > pin.Position.Latitude) continue;
                    float pinScreenX = Mapper.Map((float)pin.Position.Longitude, topLeftLng, topRightLng, 0, info.Width);
                    float pinScreenY = Mapper.Map((float)pin.Position.Latitude, topLeftLat, bottomLeftLat, 0, info.Height);
                    canvas.DrawText("Some text here", pinScreenX, pinScreenY, paint);
                }
            } catch (Exception e) {
                Debug.WriteLine(e.Message);
            }
        }

As you can see, I use Map's Region property to find the right screen coords to write text. And to make this text "follow" pins when user moves the map, I need Region to update exactly this frequently, but it doesn't - so I get laggy labels which update its position only when map moves slow enough. But I didn't name this issue like this for nothing - strange things happen with the following code:

private void Map_CameraMoving(object sender, CameraMovingEventArgs e)
        {
            Debug.WriteLine(map.Region.ToString());
        }

In debug mode Region starts updating much more frequently, and my labels follow their pins better (with some lags though). This doesn't work if I launch the app not through IDE but through tapping its icon on the device, because the Debug.WriteLine gets ignored while not debugging. If instead I use map.Region.ToString() in the UI itself, labels still lag as if Map_CameraMoving wasn't there in the first place.

EXPECTED BEHAVIOR

Basically I want Region to update at least 30 times a second to draw labels and move them smoothly, or at least a hacky way to make it update more frequently which doesn't use debugging.

HOW TO REPRODUCE

  1. Download SkiaSharp, SkiaSharp.Views and SkiaSharp.Views.Forms from NuGet (all three are v1.68.0)
  2. Use the layout provided in the first block of code.
  3. Start a while (true) loop where you call
    MapCanvas.InvalidateSurface();
    await Task.Delay(TimeSpan.FromSeconds(1.0 / 30));
  4. Use the Draw method provided in the second block of code.
  5. Implement Map method:
    public static float Map(float value, float fromSource, float toSource, float fromTarget, float toTarget)
        {
            return (value - fromSource) / (toSource - fromSource) * (toTarget - fromTarget) + fromTarget;
        }
  6. Try moving map with and without logging its Region object in Map_CameraMoving.