From 3d3490e537d75a793f6b4f45abee1ed9610717f1 Mon Sep 17 00:00:00 2001 From: victor Date: Fri, 26 Jul 2019 21:23:06 +0300 Subject: [PATCH] #24213 add support for projection methods toScreenLocation() and fromScreenLocation() --- .../flutter/plugins/googlemaps/Convert.java | 10 +- .../googlemaps/GoogleMapController.java | 1153 +++++++++-------- .../example/lib/map_coordinates.dart | 69 +- .../ios/Classes/GoogleMapController.m | 34 + .../lib/src/controller.dart | 29 +- .../google_maps_flutter/lib/src/location.dart | 23 + 6 files changed, 730 insertions(+), 588 deletions(-) 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 da737bdc15ac..a59ec719d9db 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,7 +201,13 @@ static Object latLngToJson(LatLng latLng) { return Arrays.asList(latLng.latitude, latLng.longitude); } - private static LatLng toLatLng(Object o) { + + static Object pointToJson(Point resp) { + return Arrays.asList(resp.x,resp.y); + } + + + static LatLng toLatLng(Object o) { final List data = toList(o); return new LatLng(toDouble(data.get(0)), toDouble(data.get(1))); } @@ -587,4 +593,6 @@ private static Cap toCap(Object o) { throw new IllegalArgumentException("Cannot interpret " + o + " as Cap"); } } + + } 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 fe0d3d7c3e48..ad25a251c669 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,9 +17,11 @@ 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; + import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.GoogleMapOptions; @@ -33,10 +35,12 @@ import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.Polygon; import com.google.android.gms.maps.model.Polyline; + import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.platform.PlatformView; + import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -44,9 +48,11 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -/** Controller of a single GoogleMaps MapView instance. */ +/** + * Controller of a single GoogleMaps MapView instance. + */ final class GoogleMapController - implements Application.ActivityLifecycleCallbacks, + implements Application.ActivityLifecycleCallbacks, GoogleMap.OnCameraIdleListener, GoogleMap.OnCameraMoveListener, GoogleMap.OnCameraMoveStartedListener, @@ -62,593 +68,596 @@ final class GoogleMapController GoogleMap.OnMapLongClickListener, PlatformView { - private static final String TAG = "GoogleMapController"; - private final int id; - private final AtomicInteger activityState; - private final MethodChannel methodChannel; - private final PluginRegistry.Registrar registrar; - private final MapView mapView; - private GoogleMap googleMap; - private boolean trackCameraPosition = false; - private boolean myLocationEnabled = false; - private boolean myLocationButtonEnabled = false; - private boolean indoorEnabled = true; - private boolean disposed = false; - private final float density; - private MethodChannel.Result mapReadyResult; - private final int registrarActivityHashCode; - private final Context context; - private final MarkersController markersController; - private final PolygonsController polygonsController; - private final PolylinesController polylinesController; - private final CirclesController circlesController; - private List initialMarkers; - private List initialPolygons; - private List initialPolylines; - private List initialCircles; - - GoogleMapController( - int id, - Context context, - AtomicInteger activityState, - PluginRegistry.Registrar registrar, - GoogleMapOptions options) { - this.id = id; - this.context = context; - this.activityState = activityState; - this.registrar = registrar; - this.mapView = new MapView(context, options); - this.density = context.getResources().getDisplayMetrics().density; - methodChannel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/google_maps_" + id); - methodChannel.setMethodCallHandler(this); - this.registrarActivityHashCode = registrar.activity().hashCode(); - this.markersController = new MarkersController(methodChannel); - this.polygonsController = new PolygonsController(methodChannel); - this.polylinesController = new PolylinesController(methodChannel, density); - this.circlesController = new CirclesController(methodChannel); - } - - @Override - public View getView() { - return mapView; - } - - void init() { - switch (activityState.get()) { - case STOPPED: - mapView.onCreate(null); - mapView.onStart(); - mapView.onResume(); - mapView.onPause(); - mapView.onStop(); - break; - case PAUSED: - mapView.onCreate(null); - mapView.onStart(); - mapView.onResume(); - mapView.onPause(); - break; - case RESUMED: - mapView.onCreate(null); - mapView.onStart(); - mapView.onResume(); - break; - case STARTED: - mapView.onCreate(null); + private static final String TAG = "GoogleMapController"; + private final int id; + private final AtomicInteger activityState; + private final MethodChannel methodChannel; + private final PluginRegistry.Registrar registrar; + private final MapView mapView; + private GoogleMap googleMap; + private boolean trackCameraPosition = false; + private boolean myLocationEnabled = false; + private boolean myLocationButtonEnabled = false; + private boolean indoorEnabled = true; + private boolean disposed = false; + private final float density; + private MethodChannel.Result mapReadyResult; + private final int registrarActivityHashCode; + private final Context context; + private final MarkersController markersController; + private final PolygonsController polygonsController; + private final PolylinesController polylinesController; + private final CirclesController circlesController; + private List initialMarkers; + private List initialPolygons; + private List initialPolylines; + private List initialCircles; + + GoogleMapController( + int id, + Context context, + AtomicInteger activityState, + PluginRegistry.Registrar registrar, + GoogleMapOptions options) { + this.id = id; + this.context = context; + this.activityState = activityState; + this.registrar = registrar; + this.mapView = new MapView(context, options); + this.density = context.getResources().getDisplayMetrics().density; + methodChannel = + new MethodChannel(registrar.messenger(), "plugins.flutter.io/google_maps_" + id); + methodChannel.setMethodCallHandler(this); + this.registrarActivityHashCode = registrar.activity().hashCode(); + this.markersController = new MarkersController(methodChannel); + this.polygonsController = new PolygonsController(methodChannel); + this.polylinesController = new PolylinesController(methodChannel, density); + this.circlesController = new CirclesController(methodChannel); + } + + @Override + public View getView() { + return mapView; + } + + void init() { + switch (activityState.get()) { + case STOPPED: + mapView.onCreate(null); + mapView.onStart(); + mapView.onResume(); + mapView.onPause(); + mapView.onStop(); + break; + case PAUSED: + mapView.onCreate(null); + mapView.onStart(); + mapView.onResume(); + mapView.onPause(); + break; + case RESUMED: + mapView.onCreate(null); + mapView.onStart(); + mapView.onResume(); + break; + case STARTED: + mapView.onCreate(null); + mapView.onStart(); + break; + case CREATED: + mapView.onCreate(null); + break; + case DESTROYED: + // Nothing to do, the activity has been completely destroyed. + break; + default: + throw new IllegalArgumentException( + "Cannot interpret " + activityState.get() + " as an activity state"); + } + registrar.activity().getApplication().registerActivityLifecycleCallbacks(this); + mapView.getMapAsync(this); + } + + private void moveCamera(CameraUpdate cameraUpdate) { + googleMap.moveCamera(cameraUpdate); + } + + private void animateCamera(CameraUpdate cameraUpdate) { + googleMap.animateCamera(cameraUpdate); + } + + private CameraPosition getCameraPosition() { + return trackCameraPosition ? googleMap.getCameraPosition() : null; + } + + @Override + public void onMapReady(GoogleMap googleMap) { + this.googleMap = googleMap; + this.googleMap.setIndoorEnabled(this.indoorEnabled); + googleMap.setOnInfoWindowClickListener(this); + if (mapReadyResult != null) { + mapReadyResult.success(null); + mapReadyResult = null; + } + googleMap.setOnCameraMoveStartedListener(this); + googleMap.setOnCameraMoveListener(this); + googleMap.setOnCameraIdleListener(this); + googleMap.setOnMarkerClickListener(this); + googleMap.setOnPolygonClickListener(this); + googleMap.setOnPolylineClickListener(this); + googleMap.setOnCircleClickListener(this); + googleMap.setOnMapClickListener(this); + googleMap.setOnMapLongClickListener(this); + updateMyLocationSettings(); + markersController.setGoogleMap(googleMap); + polygonsController.setGoogleMap(googleMap); + polylinesController.setGoogleMap(googleMap); + circlesController.setGoogleMap(googleMap); + updateInitialMarkers(); + updateInitialPolygons(); + updateInitialPolylines(); + updateInitialCircles(); + } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + switch (call.method) { + case "map#waitForMap": + if (googleMap != null) { + result.success(null); + return; + } + mapReadyResult = result; + break; + case "map#update": { + Convert.interpretGoogleMapOptions(call.argument("options"), this); + result.success(Convert.cameraPositionToJson(getCameraPosition())); + break; + } + case "map#getVisibleRegion": { + if (googleMap != null) { + LatLngBounds latLngBounds = googleMap.getProjection().getVisibleRegion().latLngBounds; + result.success(Convert.latlngBoundsToJson(latLngBounds)); + } else { + result.error( + "GoogleMap uninitialized", + "getVisibleRegion called prior to map initialization", + null); + } + break; + } + case "camera#move": { + final CameraUpdate cameraUpdate = + Convert.toCameraUpdate(call.argument("cameraUpdate"), density); + moveCamera(cameraUpdate); + result.success(null); + break; + } + case "camera#animate": { + final CameraUpdate cameraUpdate = + Convert.toCameraUpdate(call.argument("cameraUpdate"), density); + animateCamera(cameraUpdate); + result.success(null); + break; + } + case "markers#update": { + Object markersToAdd = call.argument("markersToAdd"); + markersController.addMarkers((List) markersToAdd); + Object markersToChange = call.argument("markersToChange"); + markersController.changeMarkers((List) markersToChange); + Object markerIdsToRemove = call.argument("markerIdsToRemove"); + markersController.removeMarkers((List) markerIdsToRemove); + result.success(null); + break; + } + case "polygons#update": { + Object polygonsToAdd = call.argument("polygonsToAdd"); + polygonsController.addPolygons((List) polygonsToAdd); + Object polygonsToChange = call.argument("polygonsToChange"); + polygonsController.changePolygons((List) polygonsToChange); + Object polygonIdsToRemove = call.argument("polygonIdsToRemove"); + polygonsController.removePolygons((List) polygonIdsToRemove); + result.success(null); + break; + } + case "polylines#update": { + Object polylinesToAdd = call.argument("polylinesToAdd"); + polylinesController.addPolylines((List) polylinesToAdd); + Object polylinesToChange = call.argument("polylinesToChange"); + polylinesController.changePolylines((List) polylinesToChange); + Object polylineIdsToRemove = call.argument("polylineIdsToRemove"); + polylinesController.removePolylines((List) polylineIdsToRemove); + result.success(null); + break; + } + case "circles#update": { + Object circlesToAdd = call.argument("circlesToAdd"); + circlesController.addCircles((List) circlesToAdd); + Object circlesToChange = call.argument("circlesToChange"); + circlesController.changeCircles((List) circlesToChange); + Object circleIdsToRemove = call.argument("circleIdsToRemove"); + circlesController.removeCircles((List) circleIdsToRemove); + result.success(null); + break; + } + case "map#isCompassEnabled": { + result.success(googleMap.getUiSettings().isCompassEnabled()); + break; + } + case "map#isMapToolbarEnabled": { + result.success(googleMap.getUiSettings().isMapToolbarEnabled()); + break; + } + case "map#getMinMaxZoomLevels": { + List zoomLevels = new ArrayList<>(2); + zoomLevels.add(googleMap.getMinZoomLevel()); + zoomLevels.add(googleMap.getMaxZoomLevel()); + result.success(zoomLevels); + break; + } + case "map#isZoomGesturesEnabled": { + result.success(googleMap.getUiSettings().isZoomGesturesEnabled()); + break; + } + case "map#isScrollGesturesEnabled": { + result.success(googleMap.getUiSettings().isScrollGesturesEnabled()); + break; + } + case "map#isTiltGesturesEnabled": { + result.success(googleMap.getUiSettings().isTiltGesturesEnabled()); + break; + } + case "map#isRotateGesturesEnabled": { + result.success(googleMap.getUiSettings().isRotateGesturesEnabled()); + break; + } + case "map#isMyLocationButtonEnabled": { + result.success(googleMap.getUiSettings().isMyLocationButtonEnabled()); + break; + } + case "map#setStyle": { + String mapStyle = (String) call.arguments; + boolean mapStyleSet; + if (mapStyle == null) { + mapStyleSet = googleMap.setMapStyle(null); + } else { + mapStyleSet = googleMap.setMapStyle(new MapStyleOptions(mapStyle)); + } + ArrayList mapStyleResult = new ArrayList<>(2); + mapStyleResult.add(mapStyleSet); + if (!mapStyleSet) { + mapStyleResult.add( + "Unable to set the map style. Please check console logs for errors."); + } + result.success(mapStyleResult); + break; + } + case "map#fromScreenLocation": { + List point = (List) call.argument("point"); + Point p = new Point(point.get(0), point.get(1)); + LatLng latLng = googleMap.getProjection().fromScreenLocation(p); + final Map arguments = new HashMap<>(2); + + arguments.put("position", Convert.latLngToJson(latLng)); + result.success(arguments); + break; + } + + case "map#toScreenLocation": { + List point = (List) call.argument("position"); + LatLng p = Convert.toLatLng(point); + Point resp = googleMap.getProjection().toScreenLocation(p); + final Map arguments = new HashMap<>(2); + arguments.put("point" , Convert.pointToJson(resp)); + result.success(arguments); + break; + } + default: + result.notImplemented(); + } + } + + @Override + public void onMapClick(LatLng latLng) { + final Map arguments = new HashMap<>(2); + arguments.put("position", Convert.latLngToJson(latLng)); + methodChannel.invokeMethod("map#onTap", arguments); + } + + @Override + public void onMapLongClick(LatLng latLng) { + final Map arguments = new HashMap<>(2); + arguments.put("position", Convert.latLngToJson(latLng)); + methodChannel.invokeMethod("map#onLongPress", arguments); + } + + @Override + public void onCameraMoveStarted(int reason) { + final Map arguments = new HashMap<>(2); + boolean isGesture = reason == GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE; + arguments.put("isGesture", isGesture); + methodChannel.invokeMethod("camera#onMoveStarted", arguments); + } + + @Override + public void onInfoWindowClick(Marker marker) { + markersController.onInfoWindowTap(marker.getId()); + } + + @Override + public void onCameraMove() { + if (!trackCameraPosition) { + return; + } + final Map arguments = new HashMap<>(2); + arguments.put("position", Convert.cameraPositionToJson(googleMap.getCameraPosition())); + methodChannel.invokeMethod("camera#onMove", arguments); + } + + @Override + public void onCameraIdle() { + methodChannel.invokeMethod("camera#onIdle", Collections.singletonMap("map", id)); + } + + @Override + public boolean onMarkerClick(Marker marker) { + return markersController.onMarkerTap(marker.getId()); + } + + @Override + public void onPolygonClick(Polygon polygon) { + polygonsController.onPolygonTap(polygon.getId()); + } + + @Override + public void onPolylineClick(Polyline polyline) { + polylinesController.onPolylineTap(polyline.getId()); + } + + @Override + public void onCircleClick(Circle circle) { + circlesController.onCircleTap(circle.getId()); + } + + @Override + public void dispose() { + if (disposed) { + return; + } + disposed = true; + methodChannel.setMethodCallHandler(null); + mapView.onDestroy(); + registrar.activity().getApplication().unregisterActivityLifecycleCallbacks(this); + } + + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + if (disposed || activity.hashCode() != registrarActivityHashCode) { + return; + } + mapView.onCreate(savedInstanceState); + } + + @Override + public void onActivityStarted(Activity activity) { + if (disposed || activity.hashCode() != registrarActivityHashCode) { + return; + } mapView.onStart(); - break; - case CREATED: - mapView.onCreate(null); - break; - case DESTROYED: - // Nothing to do, the activity has been completely destroyed. - break; - default: - throw new IllegalArgumentException( - "Cannot interpret " + activityState.get() + " as an activity state"); - } - registrar.activity().getApplication().registerActivityLifecycleCallbacks(this); - mapView.getMapAsync(this); - } - - private void moveCamera(CameraUpdate cameraUpdate) { - googleMap.moveCamera(cameraUpdate); - } - - private void animateCamera(CameraUpdate cameraUpdate) { - googleMap.animateCamera(cameraUpdate); - } - - private CameraPosition getCameraPosition() { - return trackCameraPosition ? googleMap.getCameraPosition() : null; - } - - @Override - public void onMapReady(GoogleMap googleMap) { - this.googleMap = googleMap; - this.googleMap.setIndoorEnabled(this.indoorEnabled); - googleMap.setOnInfoWindowClickListener(this); - if (mapReadyResult != null) { - mapReadyResult.success(null); - mapReadyResult = null; - } - googleMap.setOnCameraMoveStartedListener(this); - googleMap.setOnCameraMoveListener(this); - googleMap.setOnCameraIdleListener(this); - googleMap.setOnMarkerClickListener(this); - googleMap.setOnPolygonClickListener(this); - googleMap.setOnPolylineClickListener(this); - googleMap.setOnCircleClickListener(this); - googleMap.setOnMapClickListener(this); - googleMap.setOnMapLongClickListener(this); - updateMyLocationSettings(); - markersController.setGoogleMap(googleMap); - polygonsController.setGoogleMap(googleMap); - polylinesController.setGoogleMap(googleMap); - circlesController.setGoogleMap(googleMap); - updateInitialMarkers(); - updateInitialPolygons(); - updateInitialPolylines(); - updateInitialCircles(); - } - - @Override - public void onMethodCall(MethodCall call, MethodChannel.Result result) { - switch (call.method) { - case "map#waitForMap": - if (googleMap != null) { - result.success(null); - return; + } + + @Override + public void onActivityResumed(Activity activity) { + if (disposed || activity.hashCode() != registrarActivityHashCode) { + return; } - mapReadyResult = result; - break; - case "map#update": - { - Convert.interpretGoogleMapOptions(call.argument("options"), this); - result.success(Convert.cameraPositionToJson(getCameraPosition())); - break; + mapView.onResume(); + } + + @Override + public void onActivityPaused(Activity activity) { + if (disposed || activity.hashCode() != registrarActivityHashCode) { + return; } - case "map#getVisibleRegion": - { - if (googleMap != null) { - LatLngBounds latLngBounds = googleMap.getProjection().getVisibleRegion().latLngBounds; - result.success(Convert.latlngBoundsToJson(latLngBounds)); - } else { - result.error( - "GoogleMap uninitialized", - "getVisibleRegion called prior to map initialization", - null); - } - break; + mapView.onPause(); + } + + @Override + public void onActivityStopped(Activity activity) { + if (disposed || activity.hashCode() != registrarActivityHashCode) { + return; } - case "camera#move": - { - final CameraUpdate cameraUpdate = - Convert.toCameraUpdate(call.argument("cameraUpdate"), density); - moveCamera(cameraUpdate); - result.success(null); - break; + mapView.onStop(); + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + if (disposed || activity.hashCode() != registrarActivityHashCode) { + return; } - case "camera#animate": - { - final CameraUpdate cameraUpdate = - Convert.toCameraUpdate(call.argument("cameraUpdate"), density); - animateCamera(cameraUpdate); - result.success(null); - break; + mapView.onSaveInstanceState(outState); + } + + @Override + public void onActivityDestroyed(Activity activity) { + if (disposed || activity.hashCode() != registrarActivityHashCode) { + return; } - case "markers#update": - { - Object markersToAdd = call.argument("markersToAdd"); - markersController.addMarkers((List) markersToAdd); - Object markersToChange = call.argument("markersToChange"); - markersController.changeMarkers((List) markersToChange); - Object markerIdsToRemove = call.argument("markerIdsToRemove"); - markersController.removeMarkers((List) markerIdsToRemove); - result.success(null); - break; + mapView.onDestroy(); + } + + // GoogleMapOptionsSink methods + + @Override + public void setCameraTargetBounds(LatLngBounds bounds) { + googleMap.setLatLngBoundsForCameraTarget(bounds); + } + + @Override + public void setCompassEnabled(boolean compassEnabled) { + googleMap.getUiSettings().setCompassEnabled(compassEnabled); + } + + @Override + public void setMapToolbarEnabled(boolean mapToolbarEnabled) { + googleMap.getUiSettings().setMapToolbarEnabled(mapToolbarEnabled); + } + + @Override + public void setMapType(int mapType) { + googleMap.setMapType(mapType); + } + + @Override + public void setTrackCameraPosition(boolean trackCameraPosition) { + this.trackCameraPosition = trackCameraPosition; + } + + @Override + public void setRotateGesturesEnabled(boolean rotateGesturesEnabled) { + googleMap.getUiSettings().setRotateGesturesEnabled(rotateGesturesEnabled); + } + + @Override + public void setScrollGesturesEnabled(boolean scrollGesturesEnabled) { + googleMap.getUiSettings().setScrollGesturesEnabled(scrollGesturesEnabled); + } + + @Override + public void setTiltGesturesEnabled(boolean tiltGesturesEnabled) { + googleMap.getUiSettings().setTiltGesturesEnabled(tiltGesturesEnabled); + } + + @Override + public void setMinMaxZoomPreference(Float min, Float max) { + googleMap.resetMinMaxZoomPreference(); + if (min != null) { + googleMap.setMinZoomPreference(min); } - case "polygons#update": - { - Object polygonsToAdd = call.argument("polygonsToAdd"); - polygonsController.addPolygons((List) polygonsToAdd); - Object polygonsToChange = call.argument("polygonsToChange"); - polygonsController.changePolygons((List) polygonsToChange); - Object polygonIdsToRemove = call.argument("polygonIdsToRemove"); - polygonsController.removePolygons((List) polygonIdsToRemove); - result.success(null); - break; + if (max != null) { + googleMap.setMaxZoomPreference(max); } - case "polylines#update": - { - Object polylinesToAdd = call.argument("polylinesToAdd"); - polylinesController.addPolylines((List) polylinesToAdd); - Object polylinesToChange = call.argument("polylinesToChange"); - polylinesController.changePolylines((List) polylinesToChange); - Object polylineIdsToRemove = call.argument("polylineIdsToRemove"); - polylinesController.removePolylines((List) polylineIdsToRemove); - result.success(null); - break; + } + + @Override + public void setPadding(float top, float left, float bottom, float right) { + if (googleMap != null) { + googleMap.setPadding( + (int) (left * density), + (int) (top * density), + (int) (right * density), + (int) (bottom * density)); } - case "circles#update": - { - Object circlesToAdd = call.argument("circlesToAdd"); - circlesController.addCircles((List) circlesToAdd); - Object circlesToChange = call.argument("circlesToChange"); - circlesController.changeCircles((List) circlesToChange); - Object circleIdsToRemove = call.argument("circleIdsToRemove"); - circlesController.removeCircles((List) circleIdsToRemove); - result.success(null); - break; + } + + @Override + public void setZoomGesturesEnabled(boolean zoomGesturesEnabled) { + googleMap.getUiSettings().setZoomGesturesEnabled(zoomGesturesEnabled); + } + + @Override + public void setMyLocationEnabled(boolean myLocationEnabled) { + if (this.myLocationEnabled == myLocationEnabled) { + return; } - case "map#isCompassEnabled": - { - result.success(googleMap.getUiSettings().isCompassEnabled()); - break; + this.myLocationEnabled = myLocationEnabled; + if (googleMap != null) { + updateMyLocationSettings(); } - case "map#isMapToolbarEnabled": - { - result.success(googleMap.getUiSettings().isMapToolbarEnabled()); - break; + } + + @Override + public void setMyLocationButtonEnabled(boolean myLocationButtonEnabled) { + if (this.myLocationButtonEnabled == myLocationButtonEnabled) { + return; } - case "map#getMinMaxZoomLevels": - { - List zoomLevels = new ArrayList<>(2); - zoomLevels.add(googleMap.getMinZoomLevel()); - zoomLevels.add(googleMap.getMaxZoomLevel()); - result.success(zoomLevels); - break; + this.myLocationButtonEnabled = myLocationButtonEnabled; + if (googleMap != null) { + updateMyLocationSettings(); } - case "map#isZoomGesturesEnabled": - { - result.success(googleMap.getUiSettings().isZoomGesturesEnabled()); - break; + } + + @Override + public void setInitialMarkers(Object initialMarkers) { + this.initialMarkers = (List) initialMarkers; + if (googleMap != null) { + updateInitialMarkers(); } - case "map#isScrollGesturesEnabled": - { - result.success(googleMap.getUiSettings().isScrollGesturesEnabled()); - break; + } + + private void updateInitialMarkers() { + markersController.addMarkers(initialMarkers); + } + + @Override + public void setInitialPolygons(Object initialPolygons) { + this.initialPolygons = (List) initialPolygons; + if (googleMap != null) { + updateInitialPolygons(); } - case "map#isTiltGesturesEnabled": - { - result.success(googleMap.getUiSettings().isTiltGesturesEnabled()); - break; + } + + private void updateInitialPolygons() { + polygonsController.addPolygons(initialPolygons); + } + + @Override + public void setInitialPolylines(Object initialPolylines) { + this.initialPolylines = (List) initialPolylines; + if (googleMap != null) { + updateInitialPolylines(); } - case "map#isRotateGesturesEnabled": - { - result.success(googleMap.getUiSettings().isRotateGesturesEnabled()); - break; + } + + private void updateInitialPolylines() { + polylinesController.addPolylines(initialPolylines); + } + + @Override + public void setInitialCircles(Object initialCircles) { + this.initialCircles = (List) initialCircles; + if (googleMap != null) { + updateInitialCircles(); } - case "map#isMyLocationButtonEnabled": - { - result.success(googleMap.getUiSettings().isMyLocationButtonEnabled()); - break; + } + + private void updateInitialCircles() { + circlesController.addCircles(initialCircles); + } + + @SuppressLint("MissingPermission") + private void updateMyLocationSettings() { + if (hasLocationPermission()) { + // The plugin doesn't add the location permission by default so that apps that don't need + // the feature won't require the permission. + // Gradle is doing a static check for missing permission and in some configurations will + // fail the build if the permission is missing. The following disables the Gradle lint. + //noinspection ResourceType + googleMap.setMyLocationEnabled(myLocationEnabled); + googleMap.getUiSettings().setMyLocationButtonEnabled(myLocationButtonEnabled); + } else { + // TODO(amirh): Make the options update fail. + // https://github.com/flutter/flutter/issues/24327 + Log.e(TAG, "Cannot enable MyLocation layer as location permissions are not granted"); } - case "map#setStyle": - { - String mapStyle = (String) call.arguments; - boolean mapStyleSet; - if (mapStyle == null) { - mapStyleSet = googleMap.setMapStyle(null); - } else { - mapStyleSet = googleMap.setMapStyle(new MapStyleOptions(mapStyle)); - } - ArrayList mapStyleResult = new ArrayList<>(2); - mapStyleResult.add(mapStyleSet); - if (!mapStyleSet) { - mapStyleResult.add( - "Unable to set the map style. Please check console logs for errors."); - } - result.success(mapStyleResult); - break; + } + + private boolean hasLocationPermission() { + return checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED + || checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) + == PackageManager.PERMISSION_GRANTED; + } + + private int checkSelfPermission(String permission) { + if (permission == null) { + throw new IllegalArgumentException("permission is null"); } - default: - result.notImplemented(); - } - } - - @Override - public void onMapClick(LatLng latLng) { - final Map arguments = new HashMap<>(2); - arguments.put("position", Convert.latLngToJson(latLng)); - methodChannel.invokeMethod("map#onTap", arguments); - } - - @Override - public void onMapLongClick(LatLng latLng) { - final Map arguments = new HashMap<>(2); - arguments.put("position", Convert.latLngToJson(latLng)); - methodChannel.invokeMethod("map#onLongPress", arguments); - } - - @Override - public void onCameraMoveStarted(int reason) { - final Map arguments = new HashMap<>(2); - boolean isGesture = reason == GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE; - arguments.put("isGesture", isGesture); - methodChannel.invokeMethod("camera#onMoveStarted", arguments); - } - - @Override - public void onInfoWindowClick(Marker marker) { - markersController.onInfoWindowTap(marker.getId()); - } - - @Override - public void onCameraMove() { - if (!trackCameraPosition) { - return; - } - final Map arguments = new HashMap<>(2); - arguments.put("position", Convert.cameraPositionToJson(googleMap.getCameraPosition())); - methodChannel.invokeMethod("camera#onMove", arguments); - } - - @Override - public void onCameraIdle() { - methodChannel.invokeMethod("camera#onIdle", Collections.singletonMap("map", id)); - } - - @Override - public boolean onMarkerClick(Marker marker) { - return markersController.onMarkerTap(marker.getId()); - } - - @Override - public void onPolygonClick(Polygon polygon) { - polygonsController.onPolygonTap(polygon.getId()); - } - - @Override - public void onPolylineClick(Polyline polyline) { - polylinesController.onPolylineTap(polyline.getId()); - } - - @Override - public void onCircleClick(Circle circle) { - circlesController.onCircleTap(circle.getId()); - } - - @Override - public void dispose() { - if (disposed) { - return; - } - disposed = true; - methodChannel.setMethodCallHandler(null); - mapView.onDestroy(); - registrar.activity().getApplication().unregisterActivityLifecycleCallbacks(this); - } - - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - if (disposed || activity.hashCode() != registrarActivityHashCode) { - return; - } - mapView.onCreate(savedInstanceState); - } - - @Override - public void onActivityStarted(Activity activity) { - if (disposed || activity.hashCode() != registrarActivityHashCode) { - return; - } - mapView.onStart(); - } - - @Override - public void onActivityResumed(Activity activity) { - if (disposed || activity.hashCode() != registrarActivityHashCode) { - return; - } - mapView.onResume(); - } - - @Override - public void onActivityPaused(Activity activity) { - if (disposed || activity.hashCode() != registrarActivityHashCode) { - return; - } - mapView.onPause(); - } - - @Override - public void onActivityStopped(Activity activity) { - if (disposed || activity.hashCode() != registrarActivityHashCode) { - return; - } - mapView.onStop(); - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - if (disposed || activity.hashCode() != registrarActivityHashCode) { - return; - } - mapView.onSaveInstanceState(outState); - } - - @Override - public void onActivityDestroyed(Activity activity) { - if (disposed || activity.hashCode() != registrarActivityHashCode) { - return; - } - mapView.onDestroy(); - } - - // GoogleMapOptionsSink methods - - @Override - public void setCameraTargetBounds(LatLngBounds bounds) { - googleMap.setLatLngBoundsForCameraTarget(bounds); - } - - @Override - public void setCompassEnabled(boolean compassEnabled) { - googleMap.getUiSettings().setCompassEnabled(compassEnabled); - } - - @Override - public void setMapToolbarEnabled(boolean mapToolbarEnabled) { - googleMap.getUiSettings().setMapToolbarEnabled(mapToolbarEnabled); - } - - @Override - public void setMapType(int mapType) { - googleMap.setMapType(mapType); - } - - @Override - public void setTrackCameraPosition(boolean trackCameraPosition) { - this.trackCameraPosition = trackCameraPosition; - } - - @Override - public void setRotateGesturesEnabled(boolean rotateGesturesEnabled) { - googleMap.getUiSettings().setRotateGesturesEnabled(rotateGesturesEnabled); - } - - @Override - public void setScrollGesturesEnabled(boolean scrollGesturesEnabled) { - googleMap.getUiSettings().setScrollGesturesEnabled(scrollGesturesEnabled); - } - - @Override - public void setTiltGesturesEnabled(boolean tiltGesturesEnabled) { - googleMap.getUiSettings().setTiltGesturesEnabled(tiltGesturesEnabled); - } - - @Override - public void setMinMaxZoomPreference(Float min, Float max) { - googleMap.resetMinMaxZoomPreference(); - if (min != null) { - googleMap.setMinZoomPreference(min); - } - if (max != null) { - googleMap.setMaxZoomPreference(max); - } - } - - @Override - public void setPadding(float top, float left, float bottom, float right) { - if (googleMap != null) { - googleMap.setPadding( - (int) (left * density), - (int) (top * density), - (int) (right * density), - (int) (bottom * density)); - } - } - - @Override - public void setZoomGesturesEnabled(boolean zoomGesturesEnabled) { - googleMap.getUiSettings().setZoomGesturesEnabled(zoomGesturesEnabled); - } - - @Override - public void setMyLocationEnabled(boolean myLocationEnabled) { - if (this.myLocationEnabled == myLocationEnabled) { - return; - } - this.myLocationEnabled = myLocationEnabled; - if (googleMap != null) { - updateMyLocationSettings(); - } - } - - @Override - public void setMyLocationButtonEnabled(boolean myLocationButtonEnabled) { - if (this.myLocationButtonEnabled == myLocationButtonEnabled) { - return; - } - this.myLocationButtonEnabled = myLocationButtonEnabled; - if (googleMap != null) { - updateMyLocationSettings(); - } - } - - @Override - public void setInitialMarkers(Object initialMarkers) { - this.initialMarkers = (List) initialMarkers; - if (googleMap != null) { - updateInitialMarkers(); - } - } - - private void updateInitialMarkers() { - markersController.addMarkers(initialMarkers); - } - - @Override - public void setInitialPolygons(Object initialPolygons) { - this.initialPolygons = (List) initialPolygons; - if (googleMap != null) { - updateInitialPolygons(); - } - } - - private void updateInitialPolygons() { - polygonsController.addPolygons(initialPolygons); - } - - @Override - public void setInitialPolylines(Object initialPolylines) { - this.initialPolylines = (List) initialPolylines; - if (googleMap != null) { - updateInitialPolylines(); - } - } - - private void updateInitialPolylines() { - polylinesController.addPolylines(initialPolylines); - } - - @Override - public void setInitialCircles(Object initialCircles) { - this.initialCircles = (List) initialCircles; - if (googleMap != null) { - updateInitialCircles(); - } - } - - private void updateInitialCircles() { - circlesController.addCircles(initialCircles); - } - - @SuppressLint("MissingPermission") - private void updateMyLocationSettings() { - if (hasLocationPermission()) { - // The plugin doesn't add the location permission by default so that apps that don't need - // the feature won't require the permission. - // Gradle is doing a static check for missing permission and in some configurations will - // fail the build if the permission is missing. The following disables the Gradle lint. - //noinspection ResourceType - googleMap.setMyLocationEnabled(myLocationEnabled); - googleMap.getUiSettings().setMyLocationButtonEnabled(myLocationButtonEnabled); - } else { - // TODO(amirh): Make the options update fail. - // https://github.com/flutter/flutter/issues/24327 - Log.e(TAG, "Cannot enable MyLocation layer as location permissions are not granted"); - } - } - - private boolean hasLocationPermission() { - return checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) - == PackageManager.PERMISSION_GRANTED - || checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) - == PackageManager.PERMISSION_GRANTED; - } - - private int checkSelfPermission(String permission) { - if (permission == null) { - throw new IllegalArgumentException("permission is null"); - } - return context.checkPermission( - permission, android.os.Process.myPid(), android.os.Process.myUid()); - } - - public void setIndoorEnabled(boolean indoorEnabled) { - this.indoorEnabled = indoorEnabled; - } + return context.checkPermission( + permission, android.os.Process.myPid(), android.os.Process.myUid()); + } + + public void setIndoorEnabled(boolean indoorEnabled) { + this.indoorEnabled = indoorEnabled; + } } diff --git a/packages/google_maps_flutter/example/lib/map_coordinates.dart b/packages/google_maps_flutter/example/lib/map_coordinates.dart index fd707fba9313..7014bbe29518 100644 --- a/packages/google_maps_flutter/example/lib/map_coordinates.dart +++ b/packages/google_maps_flutter/example/lib/map_coordinates.dart @@ -5,14 +5,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; + import 'page.dart'; -const CameraPosition _kInitialPosition = - CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0); +const CameraPosition _kInitialPosition = CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0); class MapCoordinatesPage extends Page { MapCoordinatesPage() : super(const Icon(Icons.map), 'Map coordinates'); - + @override Widget build(BuildContext context) { return const _MapCoordinatesBody(); @@ -21,27 +21,33 @@ class MapCoordinatesPage extends Page { class _MapCoordinatesBody extends StatefulWidget { const _MapCoordinatesBody(); - + @override State createState() => _MapCoordinatesBodyState(); } class _MapCoordinatesBodyState extends State<_MapCoordinatesBody> { + _MapCoordinatesBodyState(); - + GoogleMapController mapController; LatLngBounds _visibleRegion = LatLngBounds( southwest: const LatLng(0, 0), northeast: const LatLng(0, 0), ); - + + bool _pointInitialized = false; + LatLng _latLng = const LatLng(0, 0); + Point _point = const Point(x: 0, y: 0); + @override Widget build(BuildContext context) { final GoogleMap googleMap = GoogleMap( onMapCreated: onMapCreated, + onTap: _displayTapCoordinates, initialCameraPosition: _kInitialPosition, ); - + final List columnChildren = [ Padding( padding: const EdgeInsets.all(10.0), @@ -54,22 +60,31 @@ class _MapCoordinatesBodyState extends State<_MapCoordinatesBody> { ), ), ]; - + if (mapController != null) { final String currentVisibleRegion = 'VisibleRegion:' '\nnortheast: ${_visibleRegion.northeast},' '\nsouthwest: ${_visibleRegion.southwest}'; columnChildren.add(Center(child: Text(currentVisibleRegion))); columnChildren.add(_getVisibleRegionButton()); + final String currentClickedPoint = 'Click on map to get screen coords:' + '\nx: ${_point.x},' + '\ny: ${_point.y}'; + columnChildren.add(Center(child: Text(currentClickedPoint))); + columnChildren.add(_getCoordinateOfClick()); + columnChildren.add(Center( + child: Text("Lat/Lng of point on screen" + "\nlat: ${_latLng.latitude}" + "\nlng: ${_latLng.longitude}"))); } - + return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: columnChildren, ); } - + void onMapCreated(GoogleMapController controller) async { final LatLngBounds visibleRegion = await controller.getVisibleRegion(); setState(() { @@ -77,15 +92,22 @@ class _MapCoordinatesBodyState extends State<_MapCoordinatesBody> { _visibleRegion = visibleRegion; }); } - + + void _displayTapCoordinates(LatLng latLng) async { + final Point screenPoint = await mapController.toScreenLocation(latLng); + setState(() { + _pointInitialized = true; + _point = screenPoint; + }); + } + Widget _getVisibleRegionButton() { return Padding( padding: const EdgeInsets.all(8.0), child: RaisedButton( - child: const Text('Get Visible Region Bounds'), + child: const Text('Get Visible Region'), onPressed: () async { - final LatLngBounds visibleRegion = - await mapController.getVisibleRegion(); + final LatLngBounds visibleRegion = await mapController.getVisibleRegion(); setState(() { _visibleRegion = visibleRegion; }); @@ -93,4 +115,23 @@ class _MapCoordinatesBodyState extends State<_MapCoordinatesBody> { ), ); } + + Widget _getCoordinateOfClick() { + return Padding( + padding: const EdgeInsets.all(8.0), + child: RaisedButton( + child: const Text('Get Coordinates of click'), + onPressed: () async { + if (!_pointInitialized) { + Scaffold.of(context).showSnackBar( + SnackBar(content: Text("Please click on the map first"))); + } + final LatLng latLng = await mapController.fromScreenLocation(_point); + setState(() { + _latLng = latLng; + }); + }, + ), + ); + } } diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapController.m b/packages/google_maps_flutter/ios/Classes/GoogleMapController.m index f7fcef1a29e2..2fa1e31bc9af 100644 --- a/packages/google_maps_flutter/ios/Classes/GoogleMapController.m +++ b/packages/google_maps_flutter/ios/Classes/GoogleMapController.m @@ -13,6 +13,7 @@ static GMSCoordinateBounds* ToOptionalBounds(NSArray* json); static GMSCameraUpdate* ToCameraUpdate(NSArray* data); static NSDictionary* GMSCoordinateBoundsToJson(GMSCoordinateBounds* bounds); +static NSDictionary* CGPointToJson(CGPoint* point); static void InterpretMapOptions(NSDictionary* data, id sink); static double ToDouble(NSNumber* data) { return [FLTGoogleMapJsonConversions toDouble:data]; } @@ -147,6 +148,28 @@ - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { message:@"getVisibleRegion called prior to map initialization" details:nil]); } + } else if ([call.method isEqualToString:@"map#fromScreenLocation"]) { + if (_mapView != nil) { + NSArray* arr = call.arguments[@"point"]; + CGPoint point = [FLTGoogleMapJsonConversions toPoint:arr]; + CLLocationCoordinate2D coords = [_mapView.projection coordinateForPoint:point]; + result(@{@"position" : LocationToJson(coords)}); + } else { + result([FlutterError errorWithCode:@"GoogleMap uninitialized" + message:@"fromScreenLocation called prior to map initialization" + details:nil]); + } + } else if ([call.method isEqualToString:@"map#toScreenLocation"]) { + if (_mapView != nil) { + NSArray* arr = call.arguments[@"position"]; + CLLocationCoordinate2D coords = [FLTGoogleMapJsonConversions toLocation:arr]; + CGPoint extractedExpr = [_mapView.projection pointForCoordinate:(coords)]; + result(CGPointToJson(&extractedExpr)); + } else { + result([FlutterError errorWithCode:@"GoogleMap uninitialized" + message:@"toScreenLocation called prior to map initialization" + details:nil]); + } } else if ([call.method isEqualToString:@"map#waitForMap"]) { result(nil); } else if ([call.method isEqualToString:@"markers#update"]) { @@ -426,6 +449,17 @@ - (void)mapView:(GMSMapView*)mapView didLongPressAtCoordinate:(CLLocationCoordin }; } + +static NSDictionary* CGPointToJson(CGPoint* point) { + if (!point) { + return nil; + } + return @{ + @"point" : @[ @((int) point->x), @((int) point->y) ] + }; +} + + static float ToFloat(NSNumber* data) { return [FLTGoogleMapJsonConversions toFloat:data]; } static CLLocationCoordinate2D ToLocation(NSArray* data) { diff --git a/packages/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/lib/src/controller.dart index 97899b9909f8..cc50d959d54b 100644 --- a/packages/google_maps_flutter/lib/src/controller.dart +++ b/packages/google_maps_flutter/lib/src/controller.dart @@ -194,7 +194,7 @@ class GoogleMapController { throw MapStyleException(successAndError[1]); } } - + /// Return [LatLngBounds] defining the region that is visible in a map. Future getVisibleRegion() async { final Map latLngBounds = @@ -204,4 +204,31 @@ class GoogleMapController { return LatLngBounds(northeast: northeast, southwest: southwest); } + + /// Return [LatLng] defining corresponding to the point coordinates on the screen + /// The screen location is in screen pixels (not display pixels) relative + /// to the top left of the map (not of the whole screen). + Future fromScreenLocation(Point point) async { + final Map latLngBounds = + await channel.invokeMapMethod('map#fromScreenLocation',{ + 'point': point._toJson(), + }); + + print(latLngBounds); + return LatLng._fromJson(latLngBounds['position']); + } + + /// Return [Point] defining the point on the screen corresponding to LatLng + /// The screen location is in screen pixels (not display pixels) relative + /// to the top left of the map (not of the whole screen). + Future toScreenLocation(LatLng latlng) async { + final Map point = + await channel.invokeMapMethod('map#toScreenLocation',{ + 'position': latlng._toJson(), + }); + + print(point); + return Point._fromJson(point['point']); + } } + diff --git a/packages/google_maps_flutter/lib/src/location.dart b/packages/google_maps_flutter/lib/src/location.dart index f0c6b623ab71..aba4f98aecdd 100644 --- a/packages/google_maps_flutter/lib/src/location.dart +++ b/packages/google_maps_flutter/lib/src/location.dart @@ -121,3 +121,26 @@ class LatLngBounds { @override int get hashCode => hashValues(southwest, northeast); } + + +/// A point on the screen described by x,y coordinates +class Point { + + final int x; + + final int y; + + const Point({@required this.x,@required this.y}); + + dynamic _toJson() { + return [x, y]; + } + + static Point _fromJson(dynamic json) { + if (json == null) { + return null; + } + return Point(x: json[0], y:json[1]); + } + +}