Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/google_maps_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Integer> screenCoordinate = (Map<String, Integer>) o;
return new Point(screenCoordinate.get("x"), screenCoordinate.get("y"));
}

static Map<String, Integer> pointToJson(Point point) {
final Map<String, Integer> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -226,6 +227,32 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
}
break;
}
case "map#getScreenCoordinate":
{
if (googleMap != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subjective, optional: Consider using early returns/guard clauses here instead to reduce nesting.

https://testing.googleblog.com/2017/06/code-health-reduce-nesting-reduce.html

You're doing this in multiple places, so it may even be worth extracting this out into a helper method that takes result and calls error for you and returns false if map isn't initialized. That way your checks could just be one liners guarding each method. This is just a totally optional style subjection though.

if (!assertGoogleMapInitialized(/*methodName=*/"getVisibleRegion", result)) {
  return;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting approach. I like the idea. I will revisit this in a follow-up PR.

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 =
Expand Down
74 changes: 74 additions & 0 deletions packages/google_maps_flutter/example/test_driver/google_maps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();

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<dynamic>.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<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();

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<dynamic>.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));
});
}
35 changes: 35 additions & 0 deletions packages/google_maps_flutter/ios/Classes/GoogleMapController.m
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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"]) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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])];
Expand Down
7 changes: 4 additions & 3 deletions packages/google_maps_flutter/lib/google_maps_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
21 changes: 21 additions & 0 deletions packages/google_maps_flutter/lib/src/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be useful information on ScreenInformation's DartDoc, too. I think it would be helpful to also say on the x and y members how the coordinate system works, eg is 0,0 top left or center?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this.

/// of the map, not necessarily of the whole screen.
Future<ScreenCoordinate> getScreenCoordinate(LatLng latLng) async {
final Map<String, int> point = await channel.invokeMapMethod<String, int>(
'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<LatLng> getLatLng(ScreenCoordinate screenCoordinate) async {
final List<dynamic> latLng = await channel.invokeMethod<List<dynamic>>(
'map#getLatLng', screenCoordinate._toJson());
return LatLng(latLng[0], latLng[1]);
}
}
39 changes: 39 additions & 0 deletions packages/google_maps_flutter/lib/src/screen_coordinate.dart
Original file line number Diff line number Diff line change
@@ -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 <String, int>{
"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);
}
2 changes: 1 addition & 1 deletion packages/google_maps_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: google_maps_flutter
description: A Flutter plugin for integrating Google Maps in iOS and Android applications.
author: Flutter Team <[email protected]>
homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter
version: 0.5.21+3
version: 0.5.21+4

dependencies:
flutter:
Expand Down