公式のパッケージである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での実装方法をまとめました。
使用環境
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に指定してください。
<script src="https://maps.googleapis.com/maps/api/js?key=[api key]"></script>
packageのimport
google mapを表示するための必要なpackageをimportします。
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を異なるものにしてください。そうしないと直前の表示が残ってしまいます。
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を参考ください。
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を参照してください。
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°を適用しています。
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では、デフォルトで表示される地図上のコントローラーを非表示にしています。
final mapOptions = gMap.MapOptions()
..mapTypeControl = false
..fullscreenControl = false
..streetViewControl = false
..zoomControl = false
..zoom = 15.0
..center = gMap.LatLng(35.7560423, 139.7803552);
mapTypeControl で表示非表示を制御します。
fullscreenControl で表示非表示を制御します。
streetViewControl で表示非表示を制御します。
zoomControl で表示非表示を制御します。
polyLineを引く
google map directions apiは使わず、2点間を繋ぐpolyLineをひきました。
Polyline objectのpath propertyに繋ぎたい緯度経度を設定します。
Polyline objectのmap propertyにmap objectを設定すれば完了です。
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が必要だったので、onCenterChanged、onDragstart、onDragendを使いました。javascript apiで使用できるeventはおそらく使えそうです。GMapのpropertyを漁ってみてください。
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を指定すれば表示されます。複数表示可能です。
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を指定すれば表示されます。
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で書き直して使いました。
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
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:html、dart:jsがmobileでは使えないからのようです。
firestore系のpackageがweb用のpackageをimportしているのですが、それらもmobileでのbuildの妨げになります。mobileとwebのsourceは分けて管理するのがいいかもしれません。
追記
Support Flutter Cross-Platformに記載の方法を使えば、webとmobileでimportするpackageを制御できます。