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 860 forks source link

Open Street Map / Mapbox attribution example #671

Closed Xennis closed 4 years ago

Xennis commented 4 years ago

I could find nowhere a good example for a good looking attribution. That's why I want to share my solution to enable others because correct attribution is a crucial part when using OSM and tile providers:

image

The logo is in the left corner with the "i" icon. On click this alert dialog opens. This is close to the Mabbox Android SDK attribution. The code

The attribution plugin that uses the packages flutter_svg (the SVG itself can be found on the linked page above) and url_launcher:

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_map/plugin_api.dart';
import 'package:flutter_svg/svg.dart';
import 'package:url_launcher/url_launcher.dart';

class AttributionOptions extends LayerOptions {
  final Color color;
  final String logoAssetName;

  AttributionOptions({
    @required this.logoAssetName,
    this.color = Colors.blueGrey,
  }) : assert(logoAssetName != null);
}

class AttributionLayer extends StatelessWidget {
  final AttributionOptions options;
  final MapState map;
  final Stream<void> stream;

  AttributionLayer(this.options, this.map, this.stream);

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.bottomLeft,
      child: Row(
        children: [
          Text("   "),  // FIXME: Use proper spacing
          SvgPicture.asset(
            options.logoAssetName,
            semanticsLabel: 'Mapbox',
            width: 80,
            color: options.color,
          ),
          IconButton(
              icon: Icon(
                Icons.info,
                color: options.color,
              ),
              onPressed: () {
                showDialog(
                    context: context,
                    builder: (context) => AlertDialog(
                          title: Text('Mapbox Map'),
                          content: SingleChildScrollView(
                            child: ListBody(
                              children: <Widget>[
                                RichText(
                                  text: TextSpan(
                                    text: '© Mapbox\n',
                                    style: TextStyle(color: Colors.blue),
                                    recognizer: TapGestureRecognizer()
                                      ..onTap = () {
                                        launch(
                                            'https://www.mapbox.com/about/maps/');
                                      },
                                  ),
                                ),
                                RichText(
                                  text: TextSpan(
                                    text: '© OpenStreetMap\n',
                                    style: TextStyle(color: Colors.blue),
                                    recognizer: TapGestureRecognizer()
                                      ..onTap = () {
                                        launch(
                                            'http://www.openstreetmap.org/copyright');
                                      },
                                  ),
                                ),
                                RichText(
                                  text: TextSpan(
                                    text: 'Improve this map',
                                    style: TextStyle(color: Colors.blue),
                                    recognizer: TapGestureRecognizer()
                                      ..onTap = () {
                                        launch(
                                            'https://www.mapbox.com/map-feedback/');
                                      },
                                  ),
                                ),
                              ],
                            ),
                          ),
                          actions: [
                            FlatButton(
                                child: Text("OK"),
                                onPressed: () {
                                  Navigator.of(context).pop();
                                }),
                          ],
                        ));
              })
        ],
      ),
    );
  }
}

class AttributionPlugin extends MapPlugin {
  @override
  Widget createLayer(
      LayerOptions options, MapState mapState, Stream<void> stream) {
    return AttributionLayer(options, mapState, stream);
  }

  @override
  bool supportsLayer(LayerOptions options) {
    return options is AttributionOptions;
  }
}

Usage

                    FlutterMap(
                      options: MapOptions(
                        plugins: [
                          AttributionPlugin(),
                        ],
                      ),
                      layers: [
                        [...] 
                        AttributionOptions(
                            logoAssetName: "assets/mapbox-logo.svg"),
                      ])
                    )

Probably someone will ask:

I've no intention to create a proper plugin for that. I my eyes that simply does make sense because the attribution is so different from tile provider to tile provider. Also within the same tile provider it differs; for example for Mapbox there a case were additional attributions for telemetry and satellite images are needed. You maybe want to translate or not translate the text, maybe place it some other corner, ... Basically the common thing is: "Put some widget somewhere". What would be 3 lines of code. Instead of maintaining these in an extra plugin with all the overhead it probably makes more sense to enhance flutter_map to take a Widget in the MapOptions.

Like mentioned above, I only wanted to share the example. The issue can be closed.

an99990 commented 2 years ago

hey @Xennis , i just used your attribution, thank you for sharing. I noticed that by zooming the map the attribution seem to be moving too ? sometimes you would see it and other it would just not be there. Do you know how i can fix it ? thank you

Xennis commented 2 years ago

Hi @an99990 back than I didn't have this issue. But flutter_map changed a lot in between. One guess out of the blue: not the zooming is the issue but the rotation. You can define these days layers as non-rotatable.

an99990 commented 2 years ago

hi again @Xennis , like you said it was the rotation. Thank you for the super fast answer. Happy new year to you !

here is the fix :

 nonRotatedLayers: [
        AttributionOptions(),]