flutter_map icon indicating copy to clipboard operation
flutter_map copied to clipboard

[BUG] Failure to calculate `LatLngBounds.center` for some points

Open afrish opened this issue 2 years ago • 15 comments
trafficstars

What is the bug?

When an object of class LatLngBounds is created with some values, the subsequent call of the getter "center" on the object causes an AssertionError

How can we reproduce it?

In the real-world scenario, we're using flutter_map_marker_cluster library, which is trying to draw a cluster marker, during this process the "center" getter is called, and the whole layer rendering fails with an AssertionError.

Here is a small test to reproduce the problem:

import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:test/test.dart';

void main() {
  group('LatLngBounds', () {
    test('should calculate center point #1', () async {
      final bounds = LatLngBounds(const LatLng(-77.45, -171.16), const LatLng(46.64, 25.88));
      final center = bounds.center;
      expect(center.latitude, greaterThanOrEqualTo(-90));
      expect(center.latitude, lessThanOrEqualTo(90));
      expect(center.longitude, greaterThanOrEqualTo(-180));
      expect(center.longitude, lessThanOrEqualTo(180));
    });
    test('should calculate center point #2', () async {
      final bounds = LatLngBounds(const LatLng(-0.87, -179.86), const LatLng(84.92, 23.86));
      final center = bounds.center;
      expect(center.latitude, greaterThanOrEqualTo(-90));
      expect(center.latitude, lessThanOrEqualTo(90));
      expect(center.longitude, greaterThanOrEqualTo(-180));
      expect(center.longitude, lessThanOrEqualTo(180));
    });
  });
}

and the result is:

dart:core                                              _AssertionError._throwNew
package:latlong2/latlong/LatLng.dart 34:16             new LatLng
package:flutter_map/src/geo/latlng_bounds.dart 121:12  LatLngBounds.center
test/latlng_bounds_test.dart 9:29                      main.<fn>.<fn>

'package:latlong2/latlong/LatLng.dart': Failed assertion: line 34 pos 16: 'longitude >= -180 && longitude <= 180': is not true.

dart:core                                              _AssertionError._throwNew
package:latlong2/latlong/LatLng.dart 34:16             new LatLng
package:flutter_map/src/geo/latlng_bounds.dart 121:12  LatLngBounds.center
test/latlng_bounds_test.dart 17:29                     main.<fn>.<fn>

'package:latlong2/latlong/LatLng.dart': Failed assertion: line 34 pos 16: 'longitude >= -180 && longitude <= 180': is not true.

Do you have a potential solution?

No response

Platforms

flutter_map-5.0.0

Severity

Erroneous: Prevents normal functioning and causes errors in the console

afrish avatar Oct 13 '23 08:10 afrish

Running the tests on flutter_map-6.0.0 gives the same result:

dart:core                                              _AssertionError._throwNew
package:latlong2/latlong/LatLng.dart 34:16             new LatLng
package:flutter_map/src/geo/latlng_bounds.dart 111:12  LatLngBounds.center
test/latlng_bounds_test.dart 9:29                      main.<fn>.<fn>

'package:latlong2/latlong/LatLng.dart': Failed assertion: line 34 pos 16: 'longitude >= -180 && longitude <= 180': is not true.

dart:core                                              _AssertionError._throwNew
package:latlong2/latlong/LatLng.dart 34:16             new LatLng
package:flutter_map/src/geo/latlng_bounds.dart 111:12  LatLngBounds.center
test/latlng_bounds_test.dart 17:29                     main.<fn>.<fn>

'package:latlong2/latlong/LatLng.dart': Failed assertion: line 34 pos 16: 'longitude >= -180 && longitude <= 180': is not true.

afrish avatar Oct 13 '23 09:10 afrish

So, it may be worth adding some output in there, see what the actual value is, and how it got to that.

ibrierley avatar Oct 13 '23 09:10 ibrierley

So, it may be worth adding some output in there, see what the actual value is, and how it got to that.

Here are the values that are calculated inside the "center" getter:

LatLngBounds.center: -27.2752295399207, -326.54519557683335
LatLngBounds.center: 46.84991848571311, -182.08004769921172

Sorry, there is some serious math inside, I have no idea how it got there :)

afrish avatar Oct 13 '23 09:10 afrish

The -326 and -182 are wrong, but the others seems sensible possibly.

JaffaKetchup avatar Oct 13 '23 09:10 JaffaKetchup

The -326 and -182 are wrong, but the others seems sensible possibly.

That's right, but the values -326 and -182 are calculated inside the LatLngBounds.center getter from the correct values I give it. That's why my assumption is that there is a defect inside LatLngBounds.center getter.

afrish avatar Oct 13 '23 09:10 afrish

Able to produce this simply by defining the initialCameraFit on map options, not somewhere else like OnMapReady as in #1684 so these two issues may be related somehow.

        initialCameraFit: CameraFit.coordinates(
          coordinates: [initialCenter],
        ),

My initialCenter variable is Brisbane, AU:

Map initial center: LatLng(latitude:-27.470125, longitude:153.021072)

The error was:

The following assertion was thrown building LayoutBuilder: 'package:latlong2/latlong/LatLng.dart': Failed assertion: line 33 pos 16: 'latitude >= -90 && latitude <= 90': is not true.

I've dug into the assertation error and strangely the both values are given NaN double types as I can see when debugging:

Given values: lat=NaN, lng=NaN

I can confirm it works as expected without any error, when I pass the same variable as a standalone parameter to MapOptions like before:

initialCenter: initialCenter,

Hope this helps. Version 6 upgrades are awesome, thank you so much to everyone contributed!

kirpit avatar Oct 18 '23 15:10 kirpit

Hi @kirpit, Thanks, this might be a missing puzzle piece. If you've got a moment, can you describe your display setup and window setup - number, size, resolution, position, that kind of thing?

JaffaKetchup avatar Oct 18 '23 17:10 JaffaKetchup

That may be behaving strangely because having one point in a CameraFit doesn't really make sense.

JaffaKetchup avatar Oct 18 '23 17:10 JaffaKetchup

Hi @kirpit, Thanks, this might be a missing puzzle piece. If you've got a moment, can you describe your display setup and window setup - number, size, resolution, position, that kind of thing?

Absolutely, as much as I can..

I'm using an actual physical device Xiaomi Mi 10 (Android API 33) and according to specs here, it has 6.67 inch - 1080x2340 - 24 bit display (sounds right as it's the attached screenshot size): https://www.devicespecifications.com/en/model/3af652f8

https://github.com/fleaflet/flutter_map/assets/1009931/7d16cdc5-a6df-4672-9af8-8e0675940420

I'm not doing anything fancy on the map (yet), couple of markers and "go to my location" stack button on top right that works with flutter_map_location_marker package no issues. There is only an AppBar within a Scaffold and its body is entirely FlutterMap widget without any FutureBuilder etc..

However, I can reproduce the same exact error (with NaN values) in an LG Nexus 5 (1080x1920) in the device simulator: https://www.devicespecifications.com/en/model/d23f3709

https://github.com/fleaflet/flutter_map/assets/1009931/063c9f7d-e1dc-42d1-baa6-85551d17bdcd

Let me know if I can provide anything else.

Edit: Also, those are my versions and the doctor says everything is fine..

Dart 3.1.0 Flutter 3.12.0 latlong2: ^0.9.0

kirpit avatar Oct 18 '23 17:10 kirpit

I've got a new implementation that seems to be almost the same as the current one, but it passes the first test. The second test however, still doesn't pass. The issue is that we're working at the edge of the projection again (-179.86 is just asking for trouble :D).

JaffaKetchup avatar Oct 18 '23 20:10 JaffaKetchup

I've got a new implementation that seems to be almost the same as the current one, but it passes the first test. The second test however, still doesn't pass. The issue is that we're working at the edge of the projection again (-179.86 is just asking for trouble :D).

I was wondering whether there is a way to prove that the result of those calculations is actually correct (not only between the bounds, but in the middle between the two points). Since I faced this problem while using the flutter_map_marker_cluster library, I was wondering how to check that the clusters were placed correctly, and that lead me to that LatLngBounds.center method

afrish avatar Oct 19 '23 06:10 afrish

Is there currently a workaround to adjust the zoom level based on the bounds or is the only option to downgrade to flutter_map v5?

lukbukkit avatar Nov 22 '23 16:11 lukbukkit

@LukBukkit I'm not sure what you're referring to?

JaffaKetchup avatar Nov 22 '23 16:11 JaffaKetchup

Hi, thanks for your fast response, and I'm sorry for my inadequate description of the error. I've got multiple coordinates, which I'm showing as a route using a polyling layer on a map. I planned to set the map's initial position using CameraFit.bounds to make the whole route visible. However, due to this issue, I get the same error as @kirpit when applying the CameraFit using the map's property initialCameraFit.

Using the property LatLngBounds.fromPoints(routePositions).center, I can center the map based on the points without an error. But so far, I haven't found a way to set the map's initialZoom based on a list of points that don't involve CameraFit. Is there any workaround for this issue right now, i.e., to set a map's zoom level based on a list of LatLng points? I can't use a uniform zoom level, as my routes have different lengths.

lukbukkit avatar Nov 27 '23 10:11 lukbukkit

LatLngBounds calculates the midpoint between the bounding box on a sphere. This doesn't cause issues on smaller bounding boxes but will have issues if the direct way between those two points is near the edges This example shows the direkt way between -77.45, -171.16 and 46.64, 25.88:

As we use it to calculate the midpoint on the flat projection, we should use a simple flat approach to calculate the midpoint too:

  LatLng get center =>
      LatLng(((north - south) / 2) + south, ((east - west) / 2) + west);

Still evaluating if this is the only occurence of this bug.

josxha avatar Nov 27 '23 14:11 josxha

In the code (LatLngBounds.center) I found a link with the following comment about "midpoint":

The longitude can be normalised to −180…+180 using (lon+540)%360-180

Seems to solve the initial issue, right? I've tried, and found "decent" not-crashing values. I have to say that the results are a bit counterintuitive, as @josxha noticed, because of the sphere.

Should we code a clean sphericalCenter with the current code plus the (lon+540)%360-180 fix, and a simpleCenter that would be the half sum of each corner coordinates?

monsieurtanuki avatar Mar 15 '24 13:03 monsieurtanuki