LoginSignup
7
4

More than 3 years have passed since last update.

google mapをflutter webで使う

Last updated at Posted at 2020-08-03

公式のパッケージであるgoogle_maps_flutterは2020/8/1時点で、webに対応してません。iOS、androidでmapが表示されていたとしても、webではmapが表示されません。
そこで、javascript apiのrapperであるgoogle_mapsを使用して、map表示を試みてみました。
google_maps_flutterとgoogle_mapsとの実装の違いで混乱する方がいらっしゃるかと思ったので、代表的な機能のgoogle_mapsでの実装方法をまとめました。

スクリーンショット 2020-08-05 0.19.34.png

使用環境

sample0.dart
Doctor summary (to see all details, run flutter doctor -v):
[] Flutter (Channel beta, 1.20.0-7.3.pre, on Mac OS X 10.15.5 19F101, locale ja-JP)

[] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[] Xcode - develop for iOS and macOS (Xcode 11.6)
[] Chrome - develop for the web
[] Android Studio (version 4.0)
[] VS Code (version 1.47.0)
[] Connected device (2 available)

javascript apiのimport

index.htmlにjavascript apiのimportします。GCPで取得したapi keyをqueryに指定してください。

index.html
<script src="https://maps.googleapis.com/maps/api/js?key=[api key]"></script>

packageのimport

google mapを表示するための必要なpackageをimportします。

sample1.dart
import 'package:google_maps/google_maps.dart' as jsMap;
import 'dart:ui' as ui;
import 'dart:html';

map本体

html elementをdartに内に埋め込むHtmlElementViewを使用して、mapを表示します。

3行目のplatformViewRegistryは静的解析で未定義のエラーが出ますが、無視してください。かなり気持ち悪いですが、ちゃんと動きますので。

HtmlElementViewで作ったhtml elementはhtmlIdで一意となるので、再表示する際はhtmlIdを異なるものにしてください。そうしないと直前の表示が残ってしまいます。

sample2.dart
Widget _map() {
  final String htmlId = "map";
  ui.platformViewRegistry.registerViewFactory(htmlId, (int viewId) {
    final mapOptions = gMap.MapOptions()
      ..zoom = 15.0
      ..center = gMap.LatLng(35.7560423, 139.7803552);

    final elem = DivElement()..id = htmlId;
    final map = gMap.GMap(elem, mapOptions);
    return elem;
  });
  return HtmlElementView(viewType: htmlId);
}

mapにIconを表示する

Icon objectを作って、Maker objectに指定します。Maker objectにはmap objectも追加します。Iconは複数表示可能です。ただこのやり方では、Iconのrotationが指定できません。(flutter mobileやswiftでは可能)
rotationの指定方法はsample5.dartを参考ください。

sample3.dart
final _icon = gMap.Icon()
  ..scaledSize = gMap.Size(40, 40)
  ..url =
            "https://lh3.googleusercontent.com/ogw/ADGmqu_RzXtbUv4nHU9XjdbNtDNQ5XAIlOh_1jJNci48=s64-c-mo";

gMap.Marker(gMap.MarkerOptions()
  ..anchorPoint = gMap.Point(0.5, 0.5)
  ..icon = _icon
  ..position = gMap.LatLng(35.7560423, 139.7803552)
  ..map = map
  ..title = htmlId);

Icon imageに、pubspec.yamlでassetsに指定したimageを使いたい場合は、pubspec.yamlで指定してるpathの先頭にassets/を追加します。url propertyを参照してください。

sample4.dart
final _icon = gMap.Icon()
  ..scaledSize = gMap.Size(40, 40)
  ..url = "assets/[pubspec.yamlで指定しているassetsのpath]";

Iconを回す

Google Maps JavaScript APIを使ったことがある人はご存知だと思いますが、
Iconのrotationを変更するにはsvgのpath指定でmarkerのIconを作成する必要がります。
sample5.dartでは正方形を45°傾けています。path property に正方形を適用しています。rotaion property に回転の角度である45°を適用しています。

sample5.dart
final svgMarker = gMap.GSymbol()
  ..rotation = 45
  ..path = 'M 10 10 H 90 V 90 H 10 L 10 10'
  ..fillColor = 'blue'
  ..fillOpacity = 0.8
  ..scale = 1
  ..strokeColor = 'blue'
  ..strokeWeight = 14;

  gMap.Marker(gMap.MarkerOptions()
    ..position = map.center
    ..icon = svgMarker
    ..map = map);

地図上のコントローラーを非表示にする

sample7.dartでは、デフォルトで表示される地図上のコントローラーを非表示にしています。

sample7.dart
  final mapOptions = gMap.MapOptions()
  ..mapTypeControl = false
  ..fullscreenControl = false
  ..streetViewControl = false
  ..zoomControl = false
  ..zoom = 15.0
  ..center = gMap.LatLng(35.7560423, 139.7803552);

mapTypeControl で表示非表示を制御します。スクリーンショット 2020-08-02 11.26.47.png
fullscreenControl で表示非表示を制御します。スクリーンショット 2020-08-02 11.26.54.png
streetViewControl で表示非表示を制御します。スクリーンショット 2020-08-02 11.27.00.png
zoomControl で表示非表示を制御します。スクリーンショット 2020-08-02 11.27.06.png

polyLineを引く

google map directions apiは使わず、2点間を繋ぐpolyLineをひきました。
Polyline objectのpath propertyに繋ぎたい緯度経度を設定します。
Polyline objectのmap propertyにmap objectを設定すれば完了です。

sample8.dart
final polyline = gMap.Polyline(gMap.PolylineOptions()
  ..path = [
  gMap.LatLng(35.7560423, 139.7803552),
  gMap.LatLng(35.7713573, 139.7754953)
  ]
  ..strokeColor = "#75A9FF"
  ..strokeOpacity = 1.0
  ..strokeWeight = 3);
polyline.map = map;            

Cameraのeventをハンドリングする

Streamでmapに対するeventを取得できます。
私はmapのcenterが動いた時のeventと、mapをdrag開始した時のevent、dragを終了した時のeventが必要だったので、onCenterChangedonDragstartonDragendを使いました。javascript apiで使用できるeventはおそらく使えそうです。GMapのpropertyを漁ってみてください。

sample9.dart
map.onCenterChanged.listen((event) {
  print(map.center.lat);
  print(map.center.lng);
  print(map.zoom);
});

map.onDragstart.listen((event) {});

map.onDragend.listen((event) {});


Circleを表示する

CircleOptions objectにcenterの緯度経度と半径であるradius(m)、あとmap objectを指定すれば表示されます。複数表示可能です。

sample10.dart
final areaCircle = gMap.CircleOptions()
  ..map = map
  ..center = gMap.LatLng(35.7560423, 139.7803552)
  ..radius = 400;
gMap.Circle(areaCircle);

Polygonを表示する

PolygonOptions objectに表示したい矩形の頂点を、LatLngのListとしてpathsパラメータに設定します。そして、map objectを指定すれば表示されます。

sample11.dart
final areaPolygon = gMap.PolygonOptions()
  ..strokeColor = "#000000"
  ..fillColor = '#000000'
  ..fillOpacity = 0.3
  ..paths = [
    gMap.LatLng(35.7560423, 139.7803552),
    gMap.LatLng(35.7560424, 139.7803554),
    gMap.LatLng(35.7560425, 139.7803555),
    gMap.LatLng(35.7560426, 139.7803556),
  ]
  ..map = store.map;
gMap.Polygon(areaPolygon);

自分の位置を取得する

pub.devで公開されているlocationは、webでは使えませんでした。
ググった結果、ここを参考にすれば、簡単に位置情報が取得できます。

Geolocatorを使って、緯度経度から2点間の距離を算出する

pub.devで公開されているgeolocatorが、webでは使えません。phpで該当の実装を行っている方がいたので、それをdartで書き直して使いました。

sample12.dart
import 'dart:math';

class ManualGeolocator {
  //2地点間の距離(m)を求める
  //ヒュベニの公式から求めるバージョン
  //mode 測地系 true:世界(default) false:日本
  //@return double 距離(m)
  static double distanceBetween(lat1, lon1, lat2, lon2, {mode = true}) {
    // 緯度経度をラジアンに変換
    final radLat1 = lat1 * pi / 180; // 緯度1
    final radLon1 = lon1 * pi / 180; // 経度1
    final radLat2 = lat2 * pi / 180; // 緯度2
    final radLon2 = lon2 * pi / 180; // 経度2

    // 緯度差
    final radLatDiff = radLat1 - radLat2;

    // 経度差算
    final radLonDiff = radLon1 - radLon2;

    // 平均緯度
    final radLatAve = (radLat1 + radLat2) / 2.0;

    // 測地系による値の違い
    final a = mode ? 6378137.0 : 6377397.155; // 赤道半径
    final b = mode ? 6356752.314140356 : 6356078.963; // 極半径
    final e2 = mode ? 0.00669438002301188 : 0.00667436061028297; // 第一離心率^2
    final a1e2 = mode ? 6335439.32708317 : 6334832.10663254; // 赤道上の子午線曲率半径

    final sinLat = sin(radLatAve);
    final w2 = 1.0 - e2 * (sinLat * sinLat);
    final m = a1e2 / (sqrt(w2) * w2); // 子午線曲率半径M
    final n = a / sqrt(w2); // 卯酉線曲率半径

    final t1 = m * radLatDiff;
    final t2 = n * cos(radLatAve) * radLonDiff;
    final dist = sqrt((t1 * t1) + (t2 * t2));

    return dist;
  }
}

Geocoderを使って、緯度経度から住所を取得する

pub.devで公開されているgeocoderが、webでは使えません。
なので、web APIのGeocoding APIを使いました。

Geocoding APIのreturn値は大きなjsonです。jsonからdartのmodel classを簡単に作ってくれるwebサービスがあるので紹介しておきます。ここ

Complete example

sample13.dart
Widget _map() {
    final String htmlId = "map";
    ui.platformViewRegistry.registerViewFactory(htmlId, (int viewId) {
      final mapOptions = gMap.MapOptions()
        ..zoom = 15.0
        ..center = gMap.LatLng(35.7560423, 139.7803552);

      final elem = DivElement()..id = htmlId;
      final map = gMap.GMap(elem, mapOptions);

      final _icon = gMap.Icon()
        ..scaledSize = gMap.Size(40, 40)
        ..url =
            "https://lh3.googleusercontent.com/ogw/ADGmqu_RzXtbUv4nHU9XjdbNtDNQ5XAIlOh_1jJNci48=s64-c-mo";

      gMap.Marker(gMap.MarkerOptions()
        ..anchorPoint = gMap.Point(0.5, 0.5)
        ..icon = _icon
        ..position = gMap.LatLng(35.7560423, 139.7803552)
        ..map = map
        ..title = htmlId);

      gMap.Marker(gMap.MarkerOptions()
        ..anchorPoint = gMap.Point(0.5, 0.5)
        ..icon = _icon
        ..position = gMap.LatLng(35.7713573, 139.7754953)
        ..map = map
        ..title = htmlId);

      final polyline = gMap.Polyline(gMap.PolylineOptions()
        ..path = [
          gMap.LatLng(35.7560423, 139.7803552),
          gMap.LatLng(35.7713573, 139.7754953)
        ]
        ..strokeColor = "#75A9FF"
        ..strokeOpacity = 1.0
        ..strokeWeight = 3);
      polyline.map = map;

      final areaCircle = gMap.CircleOptions()
        ..map = map
        ..center = gMap.LatLng(35.7560423, 139.7803552)
        ..radius = 400;
      gMap.Circle(areaCircle);

      map.onCenterChanged.listen((event) {
        print(map.center.lat);
        print(map.center.lng);
        print(map.zoom);
      });

      map.onDragstart.listen((event) {});

      map.onDragend.listen((event) {});

      final svgMarker = gMap.GSymbol()
        ..rotation = 45
        ..path = 'M 10 10 H 90 V 90 H 10 L 10 10'
        ..fillColor = 'blue'
        ..fillOpacity = 0.8
        ..scale = 1
        ..strokeColor = 'blue'
        ..strokeWeight = 14;

      gMap.Marker(gMap.MarkerOptions()
        ..position = map.center
        ..icon = svgMarker
        ..map = map);

      return elem;
    });
    return HtmlElementView(viewType: htmlId);
  }

できそうでできなかったこと

custom styleを適用すること

GMapのStyledMapTypeOptionsやStyledMapTypeを試してみましたが、styleは変更できませんでした。

その他、flutter webで気をつけること

####flutter web用のpackageをimportしているとiOS、androidのbuildでこける####
最初にimportしたdart:htmldart:jsがmobileでは使えないからのようです。
firestore系のpackageがweb用のpackageをimportしているのですが、それらもmobileでのbuildの妨げになります。mobileとwebのsourceは分けて管理するのがいいかもしれません。

追記

Support Flutter Cross-Platformに記載の方法を使えば、webとmobileでimportするpackageを制御できます。

7
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4