fleaflet / flutter_map

A versatile mapping package for Flutter. Simple and easy to learn, yet completely customizable and configurable, it's the best choice for mapping in your Flutter app.
https://pub.dev/packages/flutter_map
BSD 3-Clause "New" or "Revised" License
2.76k stars 863 forks source link

[FEATURE] Interactive `Polygon`s #385

Closed ibisoda closed 9 months ago

ibisoda commented 5 years ago

Hi,

I'm trying to reimplement an existing Leaflet-based app using flutter. In that app, the developer used Polygons to display the outline of a building, which when clicked/tapped shows additional information. and changes the polygon.

I'm trying to replicate that function, however I noticed there is no onTap Listener, at least for Polygons. Is there a different way to listen to tap events?

matyhaty commented 5 years ago

We are also looking at a onTap of a polygon / live.

Would be very helpful to us

We have some developers here also, so if someone can point us in the right direction we are happy to contribute

neokree commented 5 years ago

From what I found it exist a onTap method, settable from the MapOptions. However this returns a LatLng object of the position tapped, so it seems like that it is not "binded" to any Polygon, Line, Marker etc.

neokree commented 5 years ago

Just for anyone interested, I just translated in dart a function to find if a position is inside a Polygon

/// Translated from PHP
/// Source: https://assemblysys.com/php-point-in-polygon-algorithm/
bool _pointInPolygon(LatLng position, Polygon polygon) {
  // Check if the point sits exactly on a vertex
  var vertexPosition = polygon.points
      .firstWhere((point) => point == position, orElse: () => null);
  if (vertexPosition != null) {
    return true;
  }

  // Check if the point is inside the polygon or on the boundary
  int intersections = 0;
  var verticesCount = polygon.points.length;

  for (int i = 1; i < verticesCount; i++) {
    LatLng vertex1 = polygon.points[i - 1];
    LatLng vertex2 = polygon.points[i];

    // Check if point is on an horizontal polygon boundary
    if (vertex1.latitude == vertex2.latitude &&
        vertex1.latitude == position.latitude &&
        position.longitude > min(vertex1.longitude, vertex2.longitude) &&
        position.longitude < max(vertex1.longitude, vertex2.longitude)) {
      return true;
    }

    if (position.latitude > min(vertex1.latitude, vertex2.latitude) &&
        position.latitude <= max(vertex1.latitude, vertex2.latitude) &&
        position.longitude <= max(vertex1.longitude, vertex2.longitude) &&
        vertex1.latitude != vertex2.latitude) {
      var xinters = (position.latitude - vertex1.latitude) *
              (vertex2.longitude - vertex1.longitude) /
              (vertex2.latitude - vertex1.latitude) +
          vertex1.longitude;
      if (xinters == position.longitude) {
        // Check if point is on the polygon boundary (other than horizontal)
        return true;
      }
      if (vertex1.longitude == vertex2.longitude ||
          position.longitude <= xinters) {
        intersections++;
      }
    }
  }

  // If the number of edges we passed through is odd, then it's in the polygon.
  return intersections % 2 != 0;
}
synw commented 5 years ago

This lib can do it as well. The problem is that this kind of brute force geofencing is really slow so not user friendly. If you have a lot of polygons it is not appropriate. It would be much better to have an on tap callback.

spvalencia commented 5 years ago

It would be great to have onTap event for polygons and polylines. I also need that feature but I have not be able to find a good way to implemented.

MichalMisiaszek commented 5 years ago

Interestingly enough the same authors created map_view plugin which uses static Google Maps and provide listeners for polygon layers. @johnpryan how difficult it to add to flutter_map ?

MichalMisiaszek commented 5 years ago

Just for anyone interested, I just translated in dart a function to find if a position is inside a Polygon

/// Translated from PHP
/// Source: https://assemblysys.com/php-point-in-polygon-algorithm/
bool _pointInPolygon(LatLng position, Polygon polygon) {
  // Check if the point sits exactly on a vertex
  var vertexPosition = polygon.points
      .firstWhere((point) => point == position, orElse: () => null);
  if (vertexPosition != null) {
    return true;
  }

  // Check if the point is inside the polygon or on the boundary
  int intersections = 0;
  var verticesCount = polygon.points.length;

  for (int i = 1; i < verticesCount; i++) {
    LatLng vertex1 = polygon.points[i - 1];
    LatLng vertex2 = polygon.points[i];

    // Check if point is on an horizontal polygon boundary
    if (vertex1.latitude == vertex2.latitude &&
        vertex1.latitude == position.latitude &&
        position.longitude > min(vertex1.longitude, vertex2.longitude) &&
        position.longitude < max(vertex1.longitude, vertex2.longitude)) {
      return true;
    }

    if (position.latitude > min(vertex1.latitude, vertex2.latitude) &&
        position.latitude <= max(vertex1.latitude, vertex2.latitude) &&
        position.longitude <= max(vertex1.longitude, vertex2.longitude) &&
        vertex1.latitude != vertex2.latitude) {
      var xinters = (position.latitude - vertex1.latitude) *
              (vertex2.longitude - vertex1.longitude) /
              (vertex2.latitude - vertex1.latitude) +
          vertex1.longitude;
      if (xinters == position.longitude) {
        // Check if point is on the polygon boundary (other than horizontal)
        return true;
      }
      if (vertex1.longitude == vertex2.longitude ||
          position.longitude <= xinters) {
        intersections++;
      }
    }
  }

  // If the number of edges we passed through is odd, then it's in the polygon.
  return intersections % 2 != 0;
}

I used diffrent trick. Each polygon in lat/lng space I convert into a path in x/y space. Then using 2d geometry functions of path I can check if given point is inside specific path (which I can connect to specific polygon). Works pretty fast.

synw commented 5 years ago

Interesting. Do you have some code that we could see? I'm trying to do more or less the same thing using the geohex encoding format

MichalMisiaszek commented 5 years ago

Interesting. Do you have some code that we could see? I'm trying to do more or less the same thing using the geohex encoding format

So I am using some classes from actual map plugin to convert points from lat/lng to x/y. I iterate over all polygons and store paths in List in the same order.

import 'dart:ui' as ui;

 ui.Path convertPolygon(Polygon polygon) {
    CustomPoint _customPoint;
    List<Offset> _offsets = new List<Offset>();
    ui.Path _polygonPath = new ui.Path();
    polygon.points.forEach((point) {
      _customPoint =  _mapOptions.crs.latLngToPoint(point, _mapOptions.zoom);
      _offsets.add(new Offset(_customPoint.x, _customPoint.y));
    });
    _polygonPath.addPolygon(_offsets, true);
    return _polygonPath;
  }

and then:

  Polygon getPolygonWithPosition(LatLng _position)  {
    CustomPoint _customPoint = _mapOptions.crs.latLngToPoint(_position, _mapOptions.zoom);
    Offset _offset = new Offset(_customPoint.x, _customPoint.y);
    int _index = -1;
    _polygonPaths.forEach((path) {
      _index ++;
      if (path.contains(_offset)) {
        return;
      }
    });
    return _polygons[index];
  }
GregorySech commented 5 years ago

PSA getPolygonWithPosition does not compile, you need to convert to the classic for(var path in _polygonPaths) loop.

MichalMisiaszek commented 5 years ago

PSA getPolygonWithPosition does not compile, you need to convert to the classic for(var path in _polygonPaths) loop.

I just changed code in answer from original. Let me fix it. Fixed.

GregorySech commented 5 years ago

I do not enjoy being pedantic but using return does not stop functional forEach, the intent is clear tho. EDIT: int _index = _polygonPaths.indexWhere((path) => path.contains(_offset)); Could be an alternative to forEach.

aleffabricio commented 5 years ago

It would be great to have onTap event for polygons and polylines. I also need that feature but I have not be able to find a good way to implemented.

Hello, @spvalencia, this library has been implemented https://github.com/synw/geodraw, sometimes it helps you in some way.

spvalencia commented 5 years ago

Hi @aleffabricio. Thank you so much. I'm going to check it out.

joandervieira commented 4 years ago

I found this useful plugin for flutter_map: https://pub.dev/packages/map_controller

matyhaty commented 4 years ago

@joandervieira Thank you

X-SLAYER commented 4 years ago

check this package geodesy it have a method to check if a given geo point is in the a polygon

Anup2712 commented 4 years ago

I have done ontap of polygon and show info window using map controller

FlutterMap(
        mapController: mapController,
        options: MapOptions(
          center: LatLng(20.1754, 84.4053),
          zoom: 8,
          onTap: (latlng) {
            showDialog(
              context: context,
              builder: (context) {
                return Dialog(
                  child: Container(
                    height: MediaQuery.of(context).size.height * 0.3,
                    width: MediaQuery.of(context).size.width * 0.2,
                    decoration: BoxDecoration(
                      border: Border.all(
                        color: Colors.blue,
                        width: 8,
                      ),
                    ),
                    child: Align(
                      alignment: Alignment.topLeft,
                      child: Column(
                        children: <Widget>[ 
                          Text(
                            "Latitude : " + plotlat + "",
                            style: _textStyleinfo,
                          ),
                          Divider(
                            color: Colors.black54,
                          ),
                          Text(
                            "Longitude : " + plotlon + "",
                            style: _textStyleinfo,
                          ),
                        ],
                      ),
                    ),
                  ),
                );
              },
            );
          },
        ),
      ),
iRELGE commented 4 years ago

you can use my code i used to know if point is inside polygon i hope it help you. soon i'll share i code that show you a pop up on map if point is inside polygon: inside pubspec.yaml: flutter_map and geodesy.

`class PolylinePage extends StatelessWidget { static const String route = 'polyline'; Geodesy geodesy = Geodesy(); @override Widget build(BuildContext context) { // List of point to draw polygon var points = [ LatLng(51.5, -0.09), LatLng(53.3498, -6.2603), LatLng(48.8566, 2.3522), ]; //List of point to draw second polygon var pointsGradient = [ LatLng(55.5, -0.09), LatLng(54.3498, -6.2603), LatLng(52.8566, 2.3522), LatLng(55.5, -0.09), LatLng(55.5, -0.09), ];

var polygones = <Polygon>[
  Polygon(points: points, color: Colors.red),
  Polygon(points: pointsGradient)
];

void onPolygon(LatLng point) {

  polygones.forEach((element) {
    // if point is on the polygon isGeoPointInPolygon iS true
    bool isGeoPointInPolygon =
        geodesy.isGeoPointInPolygon(point, element.points);
    if (isGeoPointInPolygon == true) {
      print(element.points);
    }
  });
}

return Scaffold(
  appBar: AppBar(title: Text('Polylines')),
  drawer: buildDrawer(context, PolylinePage.route),
  body: Padding(
    padding: EdgeInsets.all(8.0),
    child: Column(
      children: [
        Padding(
          padding: EdgeInsets.only(top: 8.0, bottom: 8.0),
          child: Text('Polylines'),
        ),
        Flexible(
          child: FlutterMap(
            options: MapOptions(
            ///use on tap to get point coordinates
              onTap: onPolygon,
              plugins: [
           /// import plugin
                TappablePolylineMapPlugin(),
              ],
              center: LatLng(51.5, -0.09),
              zoom: 5.0,
            ),
            layers: [
              TileLayerOptions(
                  urlTemplate:
                      'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
                  subdomains: ['a', 'b', 'c']),
             // to draw polygon on map
              PolygonLayerOptions(polygons: polygones),

            ],
          ),
        ),
      ],
    ),
  ),
);

} }`

Anup2712 commented 4 years ago

thank you

With Regards,Anupam DasMobile Application DeveloperSPARC Pvt. Ltd.

On Mon, Oct 19, 2020 at 3:48 AM RabieEL notifications@github.com wrote:

you can use my code i used to know if point is inside polygon i hope it help you. soon i'll share i code that show you a pop up on map if point is inside polygon: inside pubspec.yaml: flutter_map and geodesy `class PolylinePage extends StatelessWidget { static const String route = 'polyline'; Geodesy geodesy = Geodesy(); @override https://github.com/override Widget build(BuildContext context) { // List of point to draw polygon var points = [ LatLng(51.5, -0.09), LatLng(53.3498, -6.2603), LatLng(48.8566, 2.3522), ]; //List of point to draw second polygon var pointsGradient = [ LatLng(55.5, -0.09), LatLng(54.3498, -6.2603), LatLng(52.8566, 2.3522), LatLng(55.5, -0.09), LatLng(55.5, -0.09), ];

var polygones = [ Polygon(points: points, color: Colors.red), Polygon(points: pointsGradient) ];

void onPolygon(LatLng point) {

polygones.forEach((element) {

// if point is on the polygon isGeoPointInPolygon if true bool isGeoPointInPolygon = geodesy.isGeoPointInPolygon(point, element.points); if (isGeoPointInPolygon == true) { print(element.points); } }); }

return Scaffold( appBar: AppBar(title: Text('Polylines')), drawer: buildDrawer(context, PolylinePage.route), body: Padding( padding: EdgeInsets.all(8.0), child: Column( children: [ Padding( padding: EdgeInsets.only(top: 8.0, bottom: 8.0), child: Text('Polylines'), ), Flexible( child: FlutterMap( options: MapOptions( ///use on tap to get point coordinates onTap: onPolygon, plugins: [ /// import plugin TappablePolylineMapPlugin(), ], center: LatLng(51.5, -0.09), zoom: 5.0, ), layers: [ TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png http://tile.openstreetmap.org/%7Bz%7D/%7Bx%7D/%7By%7D.png', subdomains: ['a', 'b', 'c']), // to draw polygon on map PolygonLayerOptions(polygons: polygones),

        ],
      ),
    ),
  ],
),

), );

} }`

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/fleaflet/flutter_map/issues/385#issuecomment-711431985, or unsubscribe https://github.com/notifications/unsubscribe-auth/AJ2HXFGBY5QMPBWTBI5SWGDSLNSUBANCNFSM4ILA5RIQ .

kotrotko commented 3 years ago

@iRELGE Thank you so much for this! You saved me a lot of time and energy. If you have more publications on this issue, please, share them with me.

iRELGE commented 3 years ago

@kotrotko your welcome ill share it soon ,ill share how to show popup if you clock on polygon if you need it

iRELGE commented 3 years ago

@kotrotko please can you marke this issue as closed

kotrotko commented 3 years ago

I'm so sorry status closed is not available now((

iRELGE commented 3 years ago

@kotrotko its ok bro my its about Agriculture draw field and follow the interventions if you have any question contact me

kotrotko commented 3 years ago

👍🏻

On 28 Feb 2021, at 19:32, RabieEL notifications@github.com wrote:

marke

github-actions[bot] commented 3 years ago

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

github-actions[bot] commented 3 years ago

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

barbalex commented 3 years ago

This issue seems rather basic and would probably be very useful to many devs. I don't think it should be closed.

matthiscock commented 3 years ago

also agree, thats a work around not addressing the core issue

rodrigoleao commented 3 years ago

I fully agree with @barbalex, this issue seems rather basic and having to scroll through a list of polygons and verify that a point is in is brute force and greatly limits usage. Unfortunately, we didn't find simple things like putting a label in the center of a polygon in a simple way, which is quite used in any gis app.

karen1au commented 2 years ago

really hope this feature can be implemented in flutter_map, most other map libraries have this feature

ibrierley commented 2 years ago

Doesn't the solution listed earlier work for you ?

aytunch commented 2 years ago

A Polygon is basically a CustomPainter widget right? Can't we expose a onTap function inside the Polygon class and internally add a GestureDetector to the render implementation

JaffaKetchup commented 2 years ago

@aytunch Might try to do some general cleanup tomorrow, so might investigate this then as well. Just got a lot on my plate at the moment!

aytunch commented 2 years ago

Thanks @JaffaKetchup for the quick response. Going through lots of polygons and using geodesy's isInPolygon method makes it very slow to decide which polygon is pressed. This would be a nice improvement :D

github-actions[bot] commented 2 years ago

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

JaffaKetchup commented 2 years ago

Sorry I haven't been active on this thread for a while. I never got round to looking what we could do properly. I'll try and do it today (towards the end), now things have calmed down a bit.

JaffaKetchup commented 2 years ago

Ok, so I've been looking at this file: https://github.dev/fleaflet/flutter_map/blob/aec827495aa5a5278fbd88b09b0e1d5b67dee7fa/lib/src/layer/polygon_layer.dart#L63 ... and it appears that it should just be possible to wrap with a GestureDetector(). However, the tricky bit will be send the event up the tree, as I've not had experience with this before. @ibrierley or @aytunch do you have any ideas?

ibrierley commented 2 years ago

I'm not about much until Monday now to test anything, but taking a quick peek at the code, I think all the Gesture stuff should probably exist in https://github.com/fleaflet/flutter_map/tree/master/lib/src/gestures

What I'm wondering though, is if thinking about gestures is a redherring...I don't think (but maybe wrong these days!) we can intercept a gesture on a shape or whatever, it would probably only be the whole canvas anyway ? So what does it get us (I may be thinking about this wrong though!) Wouldn't we still just have a point that we need to figure out if its contained in some poly ?

If that's the case, then we already have an onTap (or whatever) mapEvent set via the onTap param, or I guess listen via a mapController, and the main issue is efficiently figuring if the event is contained within an area ? (I do have some thoughts on that, but may need to be part of a bigger solution to be really efficient, how many polys may there be...).

Note, I may not be making sense :D.

aytunch commented 2 years ago

Hi @JaffaKetchup thanks for looking in to this.

At a quick glance I think we can create optional uniqueId and onTap fields to the Polygon class

final String uniqueId;
final Function(String) onTap;

and in line 114 we can wrap each Polygon widget with a GestureDetector:

          polygons.add(
            GestureDetector(
              onTap: () {
                 if(polygon.onTap != null)
                   polygon.onTap(polygon.id);
              },
              CustomPaint(
                painter: PolygonPainter(polygon),
                size: size,
              ),
            ),
          );

We can add other onTap methods of GestureDetector like onLongPress etc to the API as well.

One concern I have is if CustomPaint plays well with gesture detectors. I have a feeling that it might receive all the taps that fall in to its bounding box. In that case there are codes which tell if the touch point falls inside of a Polygon.

aytunch commented 2 years ago

@ibrierley I think if we use the whole map's onTap then we will need to check if users tap location corresponds to any of our maybe thousands of Polygons.

If we lower this logic to the Polygon layer, then it would be much performant I think.

aytunch commented 2 years ago

And the top level Api would look like this (PolygonLayerOptions instead of PolylineLayerOptions)

                          PolylineLayerOptions(
                            polylines: [
                              Polyline(
                                id: "polygon1",
                                onTap: (id) => print("polygon with id: $id is pressed!"),
                                points: pointsGradient,
                                strokeWidth: 4.0,
                                gradientColors: [
                                  Color(0xffE40203),
                                  Color(0xffFEED00),
                                  Color(0xff007E2D),
                                ],
                              ),
                            ],
                          ),
JaffaKetchup commented 2 years ago

Seems like a good idea. Can't make a PR right now, but feel free to if you want!

ibrierley commented 2 years ago

I'm probably misunderstanding how the gesturedetector works a bit. But I'm assuming if you have 100 polygons in this case, you would end up with 100 overlaid gesturedetectors on each whole canvas which will all register an onTap ?

JaffaKetchup commented 2 years ago

Yes, you are right. I'm not sure what that will do to performance, but I guess we'll have to find out.

aytunch commented 2 years ago

@ibrierley yes exactly. Do you think this would be a problem in terms of performance? Unless a Polygon region is pressed, the onTap will not run. The heavy calculation part is implementing the algorithm which finds if a point is inside of a polygon. And instead of iterating through 100 polygons to see which one(s) are pressed, We will only check if the onTap location is inside of that specific polygon. There are cases where 2 polygons might be overlapping. In this case I don't know how GestureDetectors can return both of those polygons. This is a valid concern.

But in terms of performance I don't think wrapping polygons with GestureDetectors would have an impact.

ibrierley commented 2 years ago

Ah I think the bit I'm misunderstanding is the "Unless a Polygon region is pressed, the onTap will not run", do we know this is the case even for one poly that it would only register a tap on the shape and not on the space next to it on the same Canvas ?

aytunch commented 2 years ago

Ah I think the bit I'm misunderstanding is the "Unless a Polygon region is pressed, the onTap will not run", do we know this is the case even for one poly that it would only register a tap on the shape and not on the space next to it on the same Canvas ?

@ibrierley I need to look at CustomPainter class and see if it allows us to register a tap according to its exact shape not the bounding box. Did I understand your concern right? You think that a Polygon's GestureDetector might take more area than the actual Polygon and this would mess up other gestures of the map?

ibrierley commented 2 years ago

Yes, my understanding (more from how Canvas worked vs svg in a browser) is that the Canvas doesn't know anything about the objects displayed, so it can't do that. However, Flutter does do some funky things with it's rendering, so it's quite possibly I'm wrong on this and it either does it, or there's a way around that.