tlserver / flutter_map_location_marker

A flutter map plugin for displaying device current location.
https://pub.dev/packages/flutter_map_location_marker
BSD 3-Clause "New" or "Revised" License
97 stars 82 forks source link

The orientation of marker heading is clockwise 90 deg in ios but correct in android #112

Closed jesussmile closed 16 hours ago

jesussmile commented 2 months ago

So, i wrote the code for both ios and android , in android it works fine where as in ios the rotation of the marker is clockwise 90

A

## Decleration ##
  late final Stream<Position?> _geolocatorStream;
#### initState ####
 const factory = LocationMarkerDataStreamFactory();

    _geolocatorStream =
        factory.defaultPositionStreamSource().asBroadcastStream();
    _geolocatorStream.listen((position) {
      if (position != null) {
        setState(() {
          polyAirPlaneRoute.add(LatLng(position.latitude, position.longitude));
          //print(polyAirPlaneRoute);
          _altitude = ((position.altitude * 3.281).toStringAsFixed(0));
          //convert _altitude to nearest value of 10 for example if it is 4287 then it should be 4290
          double alt = ((position.altitude * 3.281).roundToDouble() / 10)
                  .roundToDouble() *
              10;
          Provider.of<ProviderService>(context, listen: false)
              .setAltitudeFactor(alt, context);
          // Start a periodic timer that increases the altitude
          // by 100 every second until it reaches 15000
          _speed = ((position.speed * 1.94384).toStringAsFixed(0));
          _accuracy = ("${position.accuracy.toStringAsFixed(0)} m");
          _heading = (position.heading.toStringAsFixed(0));
          _speedAccuracy = (" ${position.speedAccuracy.toStringAsFixed(1)}");
          var now = DateTime.now();
          var formatterTime = DateFormat('kk:mm:ss');
          _curTime = formatterTime.format(now);
          _timeStamp =
              ("${position.timestamp!.hour}:${position.timestamp!.minute}:${position.timestamp!.second}");
        });
      }
    });

###### Implementation #######

 IgnorePointer(
    child: CurrentLocationLayer(
        positionStream:
        factory.fromGeolocatorPositionStream(
            stream: _geolocatorStream,
            // headingStream: factory.defaultHeadingStreamSource(),
        ),
        style: LocationMarkerStyle(
            markerDirection: MarkerDirection.heading,
            markerSize: const Size.square(55),
                marker: AvatarGlow(
                    //showTwoGlows: true,
                    glowColor:
                    const Color.fromARGB(255, 107, 107, 195),
                        endRadius: 500,
                        child: SvgPicture.asset(
                            "assets/plane2.svg",
                            // color: const Color.fromARGB(
                            //     255, 57, 239, 78),
                            height: 45,
                        ),
                ),
        ),
    ),
),
tlserver commented 2 months ago

This problem should be related to flutter_compass, please report there.

jesussmile commented 2 months ago

and the heading ? gives -1 degrees from the stream ?

tlserver commented 2 months ago

What do you mean? Are you using real device or emulator?

jesussmile commented 2 months ago

ofcourse a real device, _heading = (position.heading.toStringAsFixed(0)); in the code above at times returns back the value '-1' in android its fine, the issue lies with ios.

tlserver commented 2 months ago

I cannot repoduce this issue. Please provide more details. And you may try to run Custom Stream Example in your devices. If the issue still occur, modify that exmaple at line 99:

                  _headingStreamController.add(
                    LocationMarkerHeading(
                      heading: -1,
                      accuracy: pi * 0.2,
                    ),
                  );
jesussmile commented 1 month ago

The issue is how ios perceives heading. it is actually a course. https://github.com/Baseflow/flutter-geolocator/issues/1281

jesussmile commented 1 month ago

https://github.com/tlserver/flutter_map_location_marker/assets/11044978/e95f927f-2278-4df1-8359-61d59e5bb189

How can i align the heading of my marker based on the course and not compass then ? One way is to constantly compare the current position with the last position and align the heading which i did previously but its too much unnecessary computation and can i extract gps altitude, heading , ground speed etc directly from this plugin? I end up using geolocator for that. If you check the attached video . The heading is not based on the course rather the compass

jesussmile commented 1 month ago

So, I did sth like this to get the course irrespective of the compass heading so my aircraft/ vehicle is correctly aligned to the path , Obviously i can't use HeadingSector etc.. as they are based on the compass.

import 'dart:io';
import 'dart:math';

import 'package:avatar_glow/avatar_glow.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
import 'package:geolocator/geolocator.dart';
import 'package:latlong2/latlong.dart';
// import 'dart:math' as Math;

class GeoStream extends StatefulWidget {
  @override
  State<GeoStream> createState() => _GeoStreamState();
}

class _GeoStreamState extends State<GeoStream> {
  final _positionStream = Geolocator.getPositionStream(
    locationSettings: const LocationSettings(
      accuracy: LocationAccuracy.bestForNavigation,
      //distanceFilter: 0,
      //timeLimit: Duration(minutes: 1),
    ),
  );
  Position? _previousPosition;
  bool _trackMe = false;
  final MapController _mapController = MapController();
  static double getHeading(
      double startLat, double startLong, double endLat, double endLong) {
    final double dLon = endLong - startLong;
    final double y = sin(dLon) * cos(endLat);
    final double x =
        cos(startLat) * sin(endLat) - sin(startLat) * cos(endLat) * cos(dLon);
    double heading = atan2(y, x);

    heading = 360 - (heading * 180 / pi) % 360;

    if (Platform.isIOS) heading = heading * (pi / 180);

    return heading;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Geolocator stand alone'),
      ),
      body: StreamBuilder<Position>(
        stream: _positionStream,
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return const Center(child: CircularProgressIndicator());
          }

          final position = snapshot.data!;
          if (_trackMe) {
            _mapController.move(
              LatLng(position.latitude, position.longitude),
              _mapController.camera.zoom,
            );
          }

          double? bearing;
          if (_previousPosition != null) {
            bearing = getHeading(
              _previousPosition!.latitude,
              _previousPosition!.longitude,
              position.latitude,
              position.longitude,
            );
            //print('Device course: $bearing');
          }
          _previousPosition = position;

          return FlutterMap(
            mapController: _mapController,
            options: MapOptions(
              initialCenter: LatLng(position.latitude, position.longitude),
              initialZoom: 1,
              minZoom: 0,
              maxZoom: 19,
            ),
            children: [
              TileLayer(
                urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
                userAgentPackageName:
                    'net.tlserver6y.flutter_map_location_marker.example',
                maxZoom: 19,
              ),
              // MarkerLayer(markers: markers),
              CurrentLocationLayer(
                alignPositionOnUpdate: AlignOnUpdate.always,
                alignDirectionOnUpdate: AlignOnUpdate.never,
                style: LocationMarkerStyle(
                  showHeadingSector: false,
                  marker: DefaultLocationMarker(
                    child: AvatarGlow(
                      glowColor: Colors.blue,
                      glowRadiusFactor: 0.5,
                      child: Transform.rotate(
                        angle: bearing ?? 0,
                        child: const Icon(
                          Icons.navigation,
                          color: Colors.white,
                        ),
                      ),
                    ),
                  ),
                  markerSize: const Size(40, 40),
                  // markerDirection: MarkerDirection.,
                ),
              ),
            ],
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _trackMe = !_trackMe;
          });
        },
        child: Icon(_trackMe ? Icons.stop : Icons.play_arrow),
      ),
    );
  }
}
tlserver commented 1 month ago

No, you absolutely can use HeadingSector. Check out the positionStream, headingStream and the Custom stream example.

jesussmile commented 1 month ago

Ofcourse! But it's based on magnetic compass. I an afraid I am not able to explain myself properly. If u don't mind let me break it down. In your plugin the heading of the navigation is based on the device orientation. So if I point my device to south and travel north the HeadingSector will show south when I am navigating towards south. Generally while navigating it is better to use heading from a location stream rather than from compass. So far this plugin is getting all relevant information about heading from the compass. It would be nice if u could get a course instead.. that is what most navigation apps do. So your device can point any direction but you heading will always be correct as now it is following the course rather than the compass.

tlserver commented 1 month ago

Generally while navigating it is better to use heading from a location stream rather than from compass.

Since geolocation always returns {heading: -1} on iOS devices, which you have also mentioned that, heading from the location stream is not reliable.

In your plugin the heading of the navigation is based on the device orientation.

It is true that the default value of heading is based on device orientation but you are NOT limited to using device orientation as heading. The default implementation receives the device's position from the geolocator package and the device's heading from the flutter_compass package, but with type conversion, streams from other sources are also supported.

It would be nice if u could get a course instead.. that is what most navigation apps do. So your device can point any direction but you heading will always be correct as now it is following the course rather than the compass.

This feature is currently supported. This plugin is highly flexible. The displaying logic and data handling logic are separated. The CurrentLocationLayer accepts a positionStream to control the marker position and a headingStream to control the rotation of the marker. Therefore, you can create your own stream to provide the data you want. Again, check out the CustomStreamExample. This example shows how to provide a position stream and a heading stream NOT from device sensors but from a UI joystick.

Also, the AnimatedLocationMarkerLayer can be used if you prefer setState over emitting data to streams. For this case, check out the NoStreamExample.

How can i align the heading of my marker based on the course and not compass then? One way is to constantly compare the current position with the last position and align the heading which i did previously but its too much unnecessary computation

I do not think this is too much unnecessary computation; simple sine and cosine calculations only require a few compute resources. If this simple calculation happen less than 10 times per second, it should be un-noticeable. But if you already have heading value from other library, you may emit it directly into the heading stream to avoid computation.

and can i extract gps altitude, heading , ground speed etc directly from this plugin?

No, you cannot. The widget is only for displaying something on the screen but not for providing data. Instead, the widget receives data or data stream from constructor, but it smartly uses the data provided from other libraries as default values, so that you can have a flexible zero-config widget. See also FAQ.

I end up using geolocator for that.

Yes, this is the recommended way to obtain geolocation data. Then, you can consider providing the geolocation data and the calculated heading to CurrentLocationLayer or AnimatedLocationMarkerLayer. See also DefaultStreamExample

bencollinsuk commented 1 month ago

I also have the same problem and it means my app looks totally broken to my users. What I really need is a complete example of how to fix this.

I don't really understand how I can switch from using the magnetic heading when stationary, to the course heading when moving?

tlserver commented 1 month ago

I don't have time to give you a full example, sorry. The recommandation is using StreamController to emit the values you want into a stream and put this stream to headingStream of CurrentLocationLayer. See also DefaultStreamExample for how to get the default magnetic heading stream.

github-actions[bot] commented 1 week ago

This issue is stale because it has been open for 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] commented 16 hours ago

This issue was closed because it has been stalled for 7 days with no activity.