diff --git a/packages/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/CHANGELOG.md index 3c569ed01a79..76b3e23c693c 100644 --- a/packages/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.21+4 + +* Support projection methods to translate between screen and latlng coordinates. + ## 0.5.21+3 * Fix `myLocationButton` bug in `google_maps_flutter` iOS. diff --git a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java index 76e14faaf01e..6ca3f1ffbd73 100644 --- a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java +++ b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java @@ -201,11 +201,23 @@ static Object latLngToJson(LatLng latLng) { return Arrays.asList(latLng.latitude, latLng.longitude); } - private static LatLng toLatLng(Object o) { + static LatLng toLatLng(Object o) { final List data = toList(o); return new LatLng(toDouble(data.get(0)), toDouble(data.get(1))); } + static Point toPoint(Object o) { + Map screenCoordinate = (Map) o; + return new Point(screenCoordinate.get("x"), screenCoordinate.get("y")); + } + + static Map pointToJson(Point point) { + final Map data = new HashMap<>(2); + data.put("x", point.x); + data.put("y", point.y); + return data; + } + private static LatLngBounds toLatLngBounds(Object o) { if (o == null) { return null; diff --git a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index de6a1158023d..f2ea28e32412 100644 --- a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -17,6 +17,7 @@ import android.app.Application; import android.content.Context; import android.content.pm.PackageManager; +import android.graphics.Point; import android.os.Bundle; import android.util.Log; import android.view.View; @@ -226,6 +227,32 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) { } break; } + case "map#getScreenCoordinate": + { + if (googleMap != null) { + LatLng latLng = Convert.toLatLng(call.arguments); + Point screenLocation = googleMap.getProjection().toScreenLocation(latLng); + result.success(Convert.pointToJson(screenLocation)); + } else { + result.error( + "GoogleMap uninitialized", + "getScreenCoordinate called prior to map initialization", + null); + } + break; + } + case "map#getLatLng": + { + if (googleMap != null) { + Point point = Convert.toPoint(call.arguments); + LatLng latLng = googleMap.getProjection().fromScreenLocation(point); + result.success(Convert.latLngToJson(latLng)); + } else { + result.error( + "GoogleMap uninitialized", "getLatLng called prior to map initialization", null); + } + break; + } case "camera#move": { final CameraUpdate cameraUpdate = diff --git a/packages/google_maps_flutter/example/test_driver/google_maps.dart b/packages/google_maps_flutter/example/test_driver/google_maps.dart index 6f2eacd49c00..8bcf2e3f2f5d 100644 --- a/packages/google_maps_flutter/example/test_driver/google_maps.dart +++ b/packages/google_maps_flutter/example/test_driver/google_maps.dart @@ -564,4 +564,78 @@ void main() { final GoogleMapController controller = await controllerCompleter.future; await controller.setMapStyle(null); }); + + test('testGetLatLng', () async { + final Key key = GlobalKey(); + final Completer controllerCompleter = + Completer(); + + await pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + key: key, + initialCameraPosition: _kInitialCameraPosition, + onMapCreated: (GoogleMapController controller) { + controllerCompleter.complete(controller); + }, + ), + )); + + final GoogleMapController controller = await controllerCompleter.future; + + // We suspected a bug in the iOS Google Maps SDK caused the camera is not properly positioned at + // initialization. https://github.com/flutter/flutter/issues/24806 + // This temporary workaround fix is provided while the actual fix in the Google Maps SDK is + // still being investigated. + // TODO(cyanglaz): Remove this temporary fix once the Maps SDK issue is resolved. + // https://github.com/flutter/flutter/issues/27550 + await Future.delayed(const Duration(seconds: 3)); + + final LatLngBounds visibleRegion = await controller.getVisibleRegion(); + final LatLng topLeft = + await controller.getLatLng(const ScreenCoordinate(x: 0, y: 0)); + final LatLng northWest = LatLng( + visibleRegion.northeast.latitude, + visibleRegion.southwest.longitude, + ); + + expect(topLeft, northWest); + }); + + test('testScreenCoordinate', () async { + final Key key = GlobalKey(); + final Completer controllerCompleter = + Completer(); + + await pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + key: key, + initialCameraPosition: _kInitialCameraPosition, + onMapCreated: (GoogleMapController controller) { + controllerCompleter.complete(controller); + }, + ), + )); + + final GoogleMapController controller = await controllerCompleter.future; + + // We suspected a bug in the iOS Google Maps SDK caused the camera is not properly positioned at + // initialization. https://github.com/flutter/flutter/issues/24806 + // This temporary workaround fix is provided while the actual fix in the Google Maps SDK is + // still being investigated. + // TODO(cyanglaz): Remove this temporary fix once the Maps SDK issue is resolved. + // https://github.com/flutter/flutter/issues/27550 + await Future.delayed(const Duration(seconds: 3)); + + final LatLngBounds visibleRegion = await controller.getVisibleRegion(); + final LatLng northWest = LatLng( + visibleRegion.northeast.latitude, + visibleRegion.southwest.longitude, + ); + final ScreenCoordinate topLeft = + await controller.getScreenCoordinate(northWest); + + expect(topLeft, const ScreenCoordinate(x: 0, y: 0)); + }); } diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapController.m b/packages/google_maps_flutter/ios/Classes/GoogleMapController.m index 04a9f415e6fc..70a278af45de 100644 --- a/packages/google_maps_flutter/ios/Classes/GoogleMapController.m +++ b/packages/google_maps_flutter/ios/Classes/GoogleMapController.m @@ -8,7 +8,9 @@ #pragma mark - Conversion of JSON-like values sent via platform channels. Forward declarations. static NSDictionary* PositionToJson(GMSCameraPosition* position); +static NSDictionary* PointToJson(CGPoint point); static NSArray* LocationToJson(CLLocationCoordinate2D position); +static CGPoint ToCGPoint(NSDictionary* json); static GMSCameraPosition* ToOptionalCameraPosition(NSDictionary* json); static GMSCoordinateBounds* ToOptionalBounds(NSArray* json); static GMSCameraUpdate* ToCameraUpdate(NSArray* data); @@ -147,6 +149,26 @@ - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { message:@"getVisibleRegion called prior to map initialization" details:nil]); } + } else if ([call.method isEqualToString:@"map#getScreenCoordinate"]) { + if (_mapView != nil) { + CLLocationCoordinate2D location = [FLTGoogleMapJsonConversions toLocation:call.arguments]; + CGPoint point = [_mapView.projection pointForCoordinate:location]; + result(PointToJson(point)); + } else { + result([FlutterError errorWithCode:@"GoogleMap uninitialized" + message:@"getScreenCoordinate called prior to map initialization" + details:nil]); + } + } else if ([call.method isEqualToString:@"map#getLatLng"]) { + if (_mapView != nil && call.arguments) { + CGPoint point = ToCGPoint(call.arguments); + CLLocationCoordinate2D latlng = [_mapView.projection coordinateForPoint:point]; + result(LocationToJson(latlng)); + } else { + result([FlutterError errorWithCode:@"GoogleMap uninitialized" + message:@"getLatLng called prior to map initialization" + details:nil]); + } } else if ([call.method isEqualToString:@"map#waitForMap"]) { result(nil); } else if ([call.method isEqualToString:@"markers#update"]) { @@ -427,6 +449,13 @@ - (void)mapView:(GMSMapView*)mapView didLongPressAtCoordinate:(CLLocationCoordin }; } +static NSDictionary* PointToJson(CGPoint point) { + return @{ + @"x" : @((int)point.x), + @"y" : @((int)point.y), + }; +} + static NSDictionary* GMSCoordinateBoundsToJson(GMSCoordinateBounds* bounds) { if (!bounds) { return nil; @@ -460,6 +489,12 @@ static CLLocationCoordinate2D ToLocation(NSArray* data) { return json ? ToCameraPosition(json) : nil; } +static CGPoint ToCGPoint(NSDictionary* json) { + double x = ToDouble(json[@"x"]); + double y = ToDouble(json[@"y"]); + return CGPointMake(x, y); +} + static GMSCoordinateBounds* ToBounds(NSArray* data) { return [[GMSCoordinateBounds alloc] initWithCoordinate:ToLocation(data[0]) coordinate:ToLocation(data[1])]; diff --git a/packages/google_maps_flutter/lib/google_maps_flutter.dart b/packages/google_maps_flutter/lib/google_maps_flutter.dart index 91f037192255..5f3889f7b2f1 100644 --- a/packages/google_maps_flutter/lib/google_maps_flutter.dart +++ b/packages/google_maps_flutter/lib/google_maps_flutter.dart @@ -17,17 +17,18 @@ part 'src/bitmap.dart'; part 'src/callbacks.dart'; part 'src/camera.dart'; part 'src/cap.dart'; +part 'src/circle.dart'; +part 'src/circle_updates.dart'; part 'src/controller.dart'; part 'src/google_map.dart'; part 'src/joint_type.dart'; +part 'src/location.dart'; part 'src/marker.dart'; part 'src/marker_updates.dart'; -part 'src/location.dart'; part 'src/pattern_item.dart'; part 'src/polygon.dart'; part 'src/polygon_updates.dart'; part 'src/polyline.dart'; part 'src/polyline_updates.dart'; -part 'src/circle.dart'; -part 'src/circle_updates.dart'; +part 'src/screen_coordinate.dart'; part 'src/ui.dart'; diff --git a/packages/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/lib/src/controller.dart index ec77111bae9d..4ef8956c581a 100644 --- a/packages/google_maps_flutter/lib/src/controller.dart +++ b/packages/google_maps_flutter/lib/src/controller.dart @@ -208,4 +208,25 @@ class GoogleMapController { return LatLngBounds(northeast: northeast, southwest: southwest); } + + /// Return [ScreenCoordinate] of the [LatLng] in the current map view. + /// + /// A projection is used to translate between on screen location and geographic coordinates. + /// Screen location is in screen pixels (not display pixels) with respect to the top left corner + /// of the map, not necessarily of the whole screen. + Future getScreenCoordinate(LatLng latLng) async { + final Map point = await channel.invokeMapMethod( + 'map#getScreenCoordinate', latLng._toJson()); + return ScreenCoordinate(x: point['x'], y: point['y']); + } + + /// Returns [LatLng] corresponding to the [ScreenCoordinate] in the current map view. + /// + /// Returned [LatLng] corresponds to a screen location. The screen location is specified in screen + /// pixels (not display pixels) relative to the top left of the map, not top left of the whole screen. + Future getLatLng(ScreenCoordinate screenCoordinate) async { + final List latLng = await channel.invokeMethod>( + 'map#getLatLng', screenCoordinate._toJson()); + return LatLng(latLng[0], latLng[1]); + } } diff --git a/packages/google_maps_flutter/lib/src/screen_coordinate.dart b/packages/google_maps_flutter/lib/src/screen_coordinate.dart new file mode 100644 index 000000000000..83e57514c2a0 --- /dev/null +++ b/packages/google_maps_flutter/lib/src/screen_coordinate.dart @@ -0,0 +1,39 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of google_maps_flutter; + +/// Represents a point coordinate in the [GoogleMap]'s view. +/// +/// The screen location is specified in screen pixels (not display pixels) relative +/// to the top left of the map, not top left of the whole screen. (x, y) = (0, 0) +/// corresponds to top-left of the [GoogleMap] not the whole screen. +@immutable +class ScreenCoordinate { + const ScreenCoordinate({ + @required this.x, + @required this.y, + }); + + final int x; + final int y; + + dynamic _toJson() { + return { + "x": x, + "y": y, + }; + } + + @override + String toString() => '$runtimeType($x, $y)'; + + @override + bool operator ==(Object o) { + return o is ScreenCoordinate && o.x == x && o.y == y; + } + + @override + int get hashCode => hashValues(x, y); +} diff --git a/packages/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/pubspec.yaml index bdc41c873909..908fb5b250e6 100644 --- a/packages/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter -version: 0.5.21+3 +version: 0.5.21+4 dependencies: flutter: