Skip to content

Commit 92df872

Browse files
iskakaushikPark Sung Min
authored and
Park Sung Min
committed
[google_maps_flutter] Add Projection methods to google_maps (flutter#2108)
[google_maps_flutter] Add Projection methods to google_maps This doesn't yet expose the full projection functionality but addresses the most common use-cases to translate screen coordinates to latlngs. Issue: flutter/flutter#37959
1 parent abe2f64 commit 92df872

File tree

9 files changed

+218
-5
lines changed

9 files changed

+218
-5
lines changed

packages/google_maps_flutter/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.5.21+4
2+
3+
* Support projection methods to translate between screen and latlng coordinates.
4+
15
## 0.5.21+3
26

37
* Fix `myLocationButton` bug in `google_maps_flutter` iOS.

packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,23 @@ static Object latLngToJson(LatLng latLng) {
201201
return Arrays.asList(latLng.latitude, latLng.longitude);
202202
}
203203

204-
private static LatLng toLatLng(Object o) {
204+
static LatLng toLatLng(Object o) {
205205
final List<?> data = toList(o);
206206
return new LatLng(toDouble(data.get(0)), toDouble(data.get(1)));
207207
}
208208

209+
static Point toPoint(Object o) {
210+
Map<String, Integer> screenCoordinate = (Map<String, Integer>) o;
211+
return new Point(screenCoordinate.get("x"), screenCoordinate.get("y"));
212+
}
213+
214+
static Map<String, Integer> pointToJson(Point point) {
215+
final Map<String, Integer> data = new HashMap<>(2);
216+
data.put("x", point.x);
217+
data.put("y", point.y);
218+
return data;
219+
}
220+
209221
private static LatLngBounds toLatLngBounds(Object o) {
210222
if (o == null) {
211223
return null;

packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import android.app.Application;
1818
import android.content.Context;
1919
import android.content.pm.PackageManager;
20+
import android.graphics.Point;
2021
import android.os.Bundle;
2122
import android.util.Log;
2223
import android.view.View;
@@ -226,6 +227,32 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
226227
}
227228
break;
228229
}
230+
case "map#getScreenCoordinate":
231+
{
232+
if (googleMap != null) {
233+
LatLng latLng = Convert.toLatLng(call.arguments);
234+
Point screenLocation = googleMap.getProjection().toScreenLocation(latLng);
235+
result.success(Convert.pointToJson(screenLocation));
236+
} else {
237+
result.error(
238+
"GoogleMap uninitialized",
239+
"getScreenCoordinate called prior to map initialization",
240+
null);
241+
}
242+
break;
243+
}
244+
case "map#getLatLng":
245+
{
246+
if (googleMap != null) {
247+
Point point = Convert.toPoint(call.arguments);
248+
LatLng latLng = googleMap.getProjection().fromScreenLocation(point);
249+
result.success(Convert.latLngToJson(latLng));
250+
} else {
251+
result.error(
252+
"GoogleMap uninitialized", "getLatLng called prior to map initialization", null);
253+
}
254+
break;
255+
}
229256
case "camera#move":
230257
{
231258
final CameraUpdate cameraUpdate =

packages/google_maps_flutter/example/test_driver/google_maps.dart

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,4 +564,78 @@ void main() {
564564
final GoogleMapController controller = await controllerCompleter.future;
565565
await controller.setMapStyle(null);
566566
});
567+
568+
test('testGetLatLng', () async {
569+
final Key key = GlobalKey();
570+
final Completer<GoogleMapController> controllerCompleter =
571+
Completer<GoogleMapController>();
572+
573+
await pumpWidget(Directionality(
574+
textDirection: TextDirection.ltr,
575+
child: GoogleMap(
576+
key: key,
577+
initialCameraPosition: _kInitialCameraPosition,
578+
onMapCreated: (GoogleMapController controller) {
579+
controllerCompleter.complete(controller);
580+
},
581+
),
582+
));
583+
584+
final GoogleMapController controller = await controllerCompleter.future;
585+
586+
// We suspected a bug in the iOS Google Maps SDK caused the camera is not properly positioned at
587+
// initialization. https://github.com/flutter/flutter/issues/24806
588+
// This temporary workaround fix is provided while the actual fix in the Google Maps SDK is
589+
// still being investigated.
590+
// TODO(cyanglaz): Remove this temporary fix once the Maps SDK issue is resolved.
591+
// https://github.com/flutter/flutter/issues/27550
592+
await Future<dynamic>.delayed(const Duration(seconds: 3));
593+
594+
final LatLngBounds visibleRegion = await controller.getVisibleRegion();
595+
final LatLng topLeft =
596+
await controller.getLatLng(const ScreenCoordinate(x: 0, y: 0));
597+
final LatLng northWest = LatLng(
598+
visibleRegion.northeast.latitude,
599+
visibleRegion.southwest.longitude,
600+
);
601+
602+
expect(topLeft, northWest);
603+
});
604+
605+
test('testScreenCoordinate', () async {
606+
final Key key = GlobalKey();
607+
final Completer<GoogleMapController> controllerCompleter =
608+
Completer<GoogleMapController>();
609+
610+
await pumpWidget(Directionality(
611+
textDirection: TextDirection.ltr,
612+
child: GoogleMap(
613+
key: key,
614+
initialCameraPosition: _kInitialCameraPosition,
615+
onMapCreated: (GoogleMapController controller) {
616+
controllerCompleter.complete(controller);
617+
},
618+
),
619+
));
620+
621+
final GoogleMapController controller = await controllerCompleter.future;
622+
623+
// We suspected a bug in the iOS Google Maps SDK caused the camera is not properly positioned at
624+
// initialization. https://github.com/flutter/flutter/issues/24806
625+
// This temporary workaround fix is provided while the actual fix in the Google Maps SDK is
626+
// still being investigated.
627+
// TODO(cyanglaz): Remove this temporary fix once the Maps SDK issue is resolved.
628+
// https://github.com/flutter/flutter/issues/27550
629+
await Future<dynamic>.delayed(const Duration(seconds: 3));
630+
631+
final LatLngBounds visibleRegion = await controller.getVisibleRegion();
632+
final LatLng northWest = LatLng(
633+
visibleRegion.northeast.latitude,
634+
visibleRegion.southwest.longitude,
635+
);
636+
final ScreenCoordinate topLeft =
637+
await controller.getScreenCoordinate(northWest);
638+
639+
expect(topLeft, const ScreenCoordinate(x: 0, y: 0));
640+
});
567641
}

packages/google_maps_flutter/ios/Classes/GoogleMapController.m

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
#pragma mark - Conversion of JSON-like values sent via platform channels. Forward declarations.
99

1010
static NSDictionary* PositionToJson(GMSCameraPosition* position);
11+
static NSDictionary* PointToJson(CGPoint point);
1112
static NSArray* LocationToJson(CLLocationCoordinate2D position);
13+
static CGPoint ToCGPoint(NSDictionary* json);
1214
static GMSCameraPosition* ToOptionalCameraPosition(NSDictionary* json);
1315
static GMSCoordinateBounds* ToOptionalBounds(NSArray* json);
1416
static GMSCameraUpdate* ToCameraUpdate(NSArray* data);
@@ -147,6 +149,26 @@ - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
147149
message:@"getVisibleRegion called prior to map initialization"
148150
details:nil]);
149151
}
152+
} else if ([call.method isEqualToString:@"map#getScreenCoordinate"]) {
153+
if (_mapView != nil) {
154+
CLLocationCoordinate2D location = [FLTGoogleMapJsonConversions toLocation:call.arguments];
155+
CGPoint point = [_mapView.projection pointForCoordinate:location];
156+
result(PointToJson(point));
157+
} else {
158+
result([FlutterError errorWithCode:@"GoogleMap uninitialized"
159+
message:@"getScreenCoordinate called prior to map initialization"
160+
details:nil]);
161+
}
162+
} else if ([call.method isEqualToString:@"map#getLatLng"]) {
163+
if (_mapView != nil && call.arguments) {
164+
CGPoint point = ToCGPoint(call.arguments);
165+
CLLocationCoordinate2D latlng = [_mapView.projection coordinateForPoint:point];
166+
result(LocationToJson(latlng));
167+
} else {
168+
result([FlutterError errorWithCode:@"GoogleMap uninitialized"
169+
message:@"getLatLng called prior to map initialization"
170+
details:nil]);
171+
}
150172
} else if ([call.method isEqualToString:@"map#waitForMap"]) {
151173
result(nil);
152174
} else if ([call.method isEqualToString:@"markers#update"]) {
@@ -427,6 +449,13 @@ - (void)mapView:(GMSMapView*)mapView didLongPressAtCoordinate:(CLLocationCoordin
427449
};
428450
}
429451

452+
static NSDictionary* PointToJson(CGPoint point) {
453+
return @{
454+
@"x" : @((int)point.x),
455+
@"y" : @((int)point.y),
456+
};
457+
}
458+
430459
static NSDictionary* GMSCoordinateBoundsToJson(GMSCoordinateBounds* bounds) {
431460
if (!bounds) {
432461
return nil;
@@ -460,6 +489,12 @@ static CLLocationCoordinate2D ToLocation(NSArray* data) {
460489
return json ? ToCameraPosition(json) : nil;
461490
}
462491

492+
static CGPoint ToCGPoint(NSDictionary* json) {
493+
double x = ToDouble(json[@"x"]);
494+
double y = ToDouble(json[@"y"]);
495+
return CGPointMake(x, y);
496+
}
497+
463498
static GMSCoordinateBounds* ToBounds(NSArray* data) {
464499
return [[GMSCoordinateBounds alloc] initWithCoordinate:ToLocation(data[0])
465500
coordinate:ToLocation(data[1])];

packages/google_maps_flutter/lib/google_maps_flutter.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,18 @@ part 'src/bitmap.dart';
1717
part 'src/callbacks.dart';
1818
part 'src/camera.dart';
1919
part 'src/cap.dart';
20+
part 'src/circle.dart';
21+
part 'src/circle_updates.dart';
2022
part 'src/controller.dart';
2123
part 'src/google_map.dart';
2224
part 'src/joint_type.dart';
25+
part 'src/location.dart';
2326
part 'src/marker.dart';
2427
part 'src/marker_updates.dart';
25-
part 'src/location.dart';
2628
part 'src/pattern_item.dart';
2729
part 'src/polygon.dart';
2830
part 'src/polygon_updates.dart';
2931
part 'src/polyline.dart';
3032
part 'src/polyline_updates.dart';
31-
part 'src/circle.dart';
32-
part 'src/circle_updates.dart';
33+
part 'src/screen_coordinate.dart';
3334
part 'src/ui.dart';

packages/google_maps_flutter/lib/src/controller.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,25 @@ class GoogleMapController {
208208

209209
return LatLngBounds(northeast: northeast, southwest: southwest);
210210
}
211+
212+
/// Return [ScreenCoordinate] of the [LatLng] in the current map view.
213+
///
214+
/// A projection is used to translate between on screen location and geographic coordinates.
215+
/// Screen location is in screen pixels (not display pixels) with respect to the top left corner
216+
/// of the map, not necessarily of the whole screen.
217+
Future<ScreenCoordinate> getScreenCoordinate(LatLng latLng) async {
218+
final Map<String, int> point = await channel.invokeMapMethod<String, int>(
219+
'map#getScreenCoordinate', latLng._toJson());
220+
return ScreenCoordinate(x: point['x'], y: point['y']);
221+
}
222+
223+
/// Returns [LatLng] corresponding to the [ScreenCoordinate] in the current map view.
224+
///
225+
/// Returned [LatLng] corresponds to a screen location. The screen location is specified in screen
226+
/// pixels (not display pixels) relative to the top left of the map, not top left of the whole screen.
227+
Future<LatLng> getLatLng(ScreenCoordinate screenCoordinate) async {
228+
final List<dynamic> latLng = await channel.invokeMethod<List<dynamic>>(
229+
'map#getLatLng', screenCoordinate._toJson());
230+
return LatLng(latLng[0], latLng[1]);
231+
}
211232
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
part of google_maps_flutter;
6+
7+
/// Represents a point coordinate in the [GoogleMap]'s view.
8+
///
9+
/// The screen location is specified in screen pixels (not display pixels) relative
10+
/// to the top left of the map, not top left of the whole screen. (x, y) = (0, 0)
11+
/// corresponds to top-left of the [GoogleMap] not the whole screen.
12+
@immutable
13+
class ScreenCoordinate {
14+
const ScreenCoordinate({
15+
@required this.x,
16+
@required this.y,
17+
});
18+
19+
final int x;
20+
final int y;
21+
22+
dynamic _toJson() {
23+
return <String, int>{
24+
"x": x,
25+
"y": y,
26+
};
27+
}
28+
29+
@override
30+
String toString() => '$runtimeType($x, $y)';
31+
32+
@override
33+
bool operator ==(Object o) {
34+
return o is ScreenCoordinate && o.x == x && o.y == y;
35+
}
36+
37+
@override
38+
int get hashCode => hashValues(x, y);
39+
}

packages/google_maps_flutter/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: google_maps_flutter
22
description: A Flutter plugin for integrating Google Maps in iOS and Android applications.
33
author: Flutter Team <[email protected]>
44
homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter
5-
version: 0.5.21+3
5+
version: 0.5.21+4
66

77
dependencies:
88
flutter:

0 commit comments

Comments
 (0)