diff --git a/.gitignore b/.gitignore index be2c76329..c530f7be0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ android/.project .vscode/ .idea/ +android/.settings/org.eclipse.buildship.core.prefs diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index 6aa97a90a..000000000 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -connection.project.dir=../example/android -eclipse.preferences.version=1 diff --git a/android/src/main/java/com/mapbox/mapboxgl/Convert.java b/android/src/main/java/com/mapbox/mapboxgl/Convert.java index 852000f8e..c6759d59e 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/Convert.java +++ b/android/src/main/java/com/mapbox/mapboxgl/Convert.java @@ -184,10 +184,10 @@ static void interpretMapboxMapOptions(Object o, MapboxMapOptionsSink sink) { if (compassEnabled != null) { sink.setCompassEnabled(toBoolean(compassEnabled)); } -// final Object mapType = data.get("mapType"); -// if (mapType != null) { -// sink.setMapType(toInt(mapType)); -// } + final Object styleString = data.get("styleString"); + if (styleString != null) { + sink.setStyleString(toString(styleString)); + } final Object minMaxZoomPreference = data.get("minMaxZoomPreference"); if (minMaxZoomPreference != null) { final List zoomPreferenceData = toList(minMaxZoomPreference); diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java index 29f83ead3..dc28ff6cb 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java @@ -10,6 +10,7 @@ import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.maps.MapboxMapOptions; +import com.mapbox.mapboxsdk.maps.Style; import io.flutter.plugin.common.PluginRegistry; import java.util.concurrent.atomic.AtomicInteger; @@ -22,11 +23,12 @@ class MapboxMapBuilder implements MapboxMapOptionsSink { .attributionEnabled(false); private boolean trackCameraPosition = false; private boolean myLocationEnabled = false; + private String styleString = Style.MAPBOX_STREETS; MapboxMapController build( int id, Context context, AtomicInteger state, PluginRegistry.Registrar registrar) { final MapboxMapController controller = - new MapboxMapController(id, context, state, registrar, options); + new MapboxMapController(id, context, state, registrar, options, styleString); controller.init(); controller.setMyLocationEnabled(myLocationEnabled); controller.setTrackCameraPosition(trackCameraPosition); @@ -49,10 +51,11 @@ public void setCameraTargetBounds(LatLngBounds bounds) { //options.latLngBoundsForCameraTarget(bounds); } -// @Override -// public void setMapType(int mapType) { -// options.mapType(mapType); -// } + @Override + public void setStyleString(String styleString) { + this.styleString = styleString; + //options. styleString(styleString); + } @Override public void setMinMaxZoomPreference(Float min, Float max) { diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index f5491643b..5b38da731 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -21,6 +21,7 @@ import android.support.annotation.NonNull; import android.util.Log; import android.view.View; +import android.graphics.PointF; import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.geometry.LatLngBounds; @@ -34,6 +35,16 @@ import com.mapbox.mapboxsdk.camera.CameraUpdate; import com.mapbox.mapboxsdk.maps.Style; +import com.mapbox.geojson.Feature; +import com.mapbox.mapboxsdk.style.expressions.Expression; + +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.location.LocationComponent; +import com.mapbox.mapboxsdk.location.modes.CameraMode; +import com.mapbox.mapboxsdk.location.modes.RenderMode; +import com.mapbox.mapboxsdk.style.layers.RasterLayer; +import com.mapbox.mapboxsdk.style.sources.RasterSource; + import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry; @@ -42,6 +53,11 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import java.util.List; +import java.util.ArrayList; + +import android.graphics.PointF; +import android.graphics.RectF; /** Controller of a single MapboxMaps MapView instance. */ final class MapboxMapController @@ -51,7 +67,7 @@ final class MapboxMapController MapboxMap.OnCameraMoveStartedListener, MapboxMap.OnInfoWindowClickListener, // MapboxMap.OnMarkerClickListener,//todo: deprecated in 7 - // MapboxMap.OnMapClickListener,//dddd + MapboxMap.OnMapClickListener, MapboxMapOptionsSink, MethodChannel.MethodCallHandler, com.mapbox.mapboxsdk.maps.OnMapReadyCallback, @@ -72,18 +88,22 @@ final class MapboxMapController private MethodChannel.Result mapReadyResult; private final int registrarActivityHashCode; private final Context context; + private final String styleStringInitial; + LocationComponent locationComponent = null; MapboxMapController( int id, Context context, AtomicInteger activityState, PluginRegistry.Registrar registrar, - MapboxMapOptions options) { + MapboxMapOptions options, + String styleStringInitial) { Mapbox.getInstance(context, getAccessToken(context)); this.id = id; this.context = context; this.activityState = activityState; this.registrar = registrar; + this.styleStringInitial = styleStringInitial; this.mapView = new MapView(context, options); // this.markers = new HashMap<>(); this.density = context.getResources().getDisplayMetrics().density; @@ -192,7 +212,7 @@ private CameraPosition getCameraPosition() { @Override public void onMapReady(MapboxMap mapboxMap) { this.mapboxMap = mapboxMap; - mapboxMap.setStyle(Style.MAPBOX_STREETS); + // mapboxMap.setStyle(Style.MAPBOX_STREETS); mapboxMap.setOnInfoWindowClickListener(this); if (mapReadyResult != null) { mapReadyResult.success(null); @@ -202,7 +222,41 @@ public void onMapReady(MapboxMap mapboxMap) { mapboxMap.addOnCameraMoveListener(this); mapboxMap.addOnCameraIdleListener(this); //mapboxMap.setOnMarkerClickListener(this); - updateMyLocationEnabled(); + mapboxMap.addOnMapClickListener(this); + setStyleString(styleStringInitial); + // updateMyLocationEnabled(); + } + + @Override + public void setStyleString(String styleString) { + //check if json, url or plain string: + if (styleString==null || styleString.isEmpty()) { + Log.e(TAG,"setStyleString - string empty or null"); + } else if (styleString.startsWith("{") || styleString.startsWith("[")){ + mapboxMap.setStyle(new Style.Builder().fromJson(styleString), onStyleLoadedCallback); + } else { + mapboxMap.setStyle(new Style.Builder().fromUrl(styleString), onStyleLoadedCallback); + } + } + + Style.OnStyleLoaded onStyleLoadedCallback = new Style.OnStyleLoaded() { + @Override + public void onStyleLoaded(@NonNull Style style) { + enableLocationComponent(); + } + }; + + @SuppressWarnings( {"MissingPermission"}) + private void enableLocationComponent() { + if (hasLocationPermission()) { + locationComponent = mapboxMap.getLocationComponent(); + locationComponent.activateLocationComponent(context, mapboxMap.getStyle()); + locationComponent.setLocationComponentEnabled(true); + locationComponent.setCameraMode(CameraMode.TRACKING); + locationComponent.setRenderMode(RenderMode.COMPASS); + } else { + Log.e(TAG, "missing location permissions"); + } } @Override @@ -241,6 +295,37 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) { result.success(null); break; } + case "map#queryRenderedFeatures": + { + Map reply = new HashMap<>(); + List features; + + String[] layerIds = ((List) call.argument("layerIds")).toArray(new String[0]); + + String filter = (String) call.argument("filter"); + + Expression filterExpression = filter == null ? null : new Expression(filter); + if (call.hasArgument("x")) { + Double x = call.argument("x"); + Double y = call.argument("y"); + PointF pixel = new PointF(x.floatValue(), y.floatValue()); + features = mapboxMap.queryRenderedFeatures(pixel, filterExpression, layerIds); + } else { + Double left = call.argument("left"); + Double top = call.argument("top"); + Double right = call.argument("right"); + Double bottom = call.argument("bottom"); + RectF rectF = new RectF(left.floatValue(), top.floatValue(), right.floatValue(), bottom.floatValue()); + features = mapboxMap.queryRenderedFeatures(rectF, filterExpression, layerIds); + } + List featuresJson = new ArrayList<>(); + for (Feature feature : features) { + featuresJson.add(feature.toJson()); + } + reply.put("features", featuresJson); + result.success(reply); + break; + } // case "marker#add": // { // final MarkerBuilder markerBuilder = newMarkerBuilder(); @@ -313,12 +398,27 @@ public void onCameraIdle() { // return (markerController != null && markerController.onTap()); // } + @Override + public boolean onMapClick(@NonNull LatLng point) { + PointF pointf = mapboxMap.getProjection().toScreenLocation(point); + final Map arguments = new HashMap<>(5); + arguments.put("x", pointf.x); + arguments.put("y", pointf.y); + arguments.put("lng", point.getLongitude()); + arguments.put("lat", point.getLatitude()); + methodChannel.invokeMethod("map#onMapClick", arguments); + return true; + } + @Override public void dispose() { if (disposed) { return; } disposed = true; + if (locationComponent != null) { + locationComponent.setLocationComponentEnabled(false); + } mapView.onDestroy(); registrar.activity().getApplication().unregisterActivityLifecycleCallbacks(this); } @@ -391,12 +491,6 @@ public void setCompassEnabled(boolean compassEnabled) { mapboxMap.getUiSettings().setCompassEnabled(compassEnabled); } -// @Override -// public void setMapType(int mapType) { -// throw new UnsupportedOperationException("setMapType"); -// //mapboxMap.setMapType(mapType); -// } - @Override public void setTrackCameraPosition(boolean trackCameraPosition) { this.trackCameraPosition = trackCameraPosition; @@ -445,7 +539,9 @@ public void setMyLocationEnabled(boolean myLocationEnabled) { } private void updateMyLocationEnabled() { - //throw new UnsupportedOperationException("updateMyLocationEnabled") + // if (locationComponent != null) { + // locationComponent.setLocationComponentEnabled(this.myLocationEnabled); + // } } private boolean hasLocationPermission() { diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapOptionsSink.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapOptionsSink.java index cbcd86610..c128b9dd6 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapOptionsSink.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapOptionsSink.java @@ -12,7 +12,8 @@ interface MapboxMapOptionsSink { void setCompassEnabled(boolean compassEnabled); -// void setMapType(int mapType); + // TODO: styleString is not actually a part of options. consider moving + void setStyleString(String styleString); void setMinMaxZoomPreference(Float min, Float max); diff --git a/example/lib/map_ui.dart b/example/lib/map_ui.dart index 4974206f9..dee2e59d0 100644 --- a/example/lib/map_ui.dart +++ b/example/lib/map_ui.dart @@ -42,7 +42,7 @@ class MapUiBodyState extends State { bool _compassEnabled = true; CameraTargetBounds _cameraTargetBounds = CameraTargetBounds.unbounded; MinMaxZoomPreference _minMaxZoomPreference = MinMaxZoomPreference.unbounded; - MapType _mapType = MapType.normal; + String _styleString = MapboxStyles.MAPBOX_STREETS; bool _rotateGesturesEnabled = true; bool _scrollGesturesEnabled = true; bool _tiltGesturesEnabled = true; @@ -114,14 +114,12 @@ class MapUiBodyState extends State { ); } - Widget _mapTypeCycler() { - final MapType nextType = - MapType.values[(_mapType.index + 1) % MapType.values.length]; + Widget _setStyleToSatellite() { return FlatButton( - child: Text('change map type to $nextType'), + child: Text('change map style to Satellite'), onPressed: () { setState(() { - _mapType = nextType; + _styleString = MapboxStyles.SATELLITE; }); }, ); @@ -191,12 +189,19 @@ class MapUiBodyState extends State { compassEnabled: _compassEnabled, cameraTargetBounds: _cameraTargetBounds, minMaxZoomPreference: _minMaxZoomPreference, - mapType: _mapType, + styleString: _styleString, rotateGesturesEnabled: _rotateGesturesEnabled, scrollGesturesEnabled: _scrollGesturesEnabled, tiltGesturesEnabled: _tiltGesturesEnabled, zoomGesturesEnabled: _zoomGesturesEnabled, myLocationEnabled: _myLocationEnabled, + onMapClick: (point, latLng) async { + print("${point.x},${point.y} ${latLng.latitude}/${latLng.longitude}"); + List features = await mapController.queryRenderedFeatures(point, [],null); + if (features.length>0) { + print(features[0]); + } + } ); final List columnChildren = [ @@ -226,7 +231,7 @@ class MapUiBodyState extends State { Text(_isMoving ? '(Camera moving)' : '(Camera idle)'), _compassToggler(), _latLngBoundsToggler(), - _mapTypeCycler(), + _setStyleToSatellite(), _zoomBoundsToggler(), _rotateToggler(), _scrollToggler(), diff --git a/lib/mapbox_gl.dart b/lib/mapbox_gl.dart index 8caf54cca..bfc09518d 100644 --- a/lib/mapbox_gl.dart +++ b/lib/mapbox_gl.dart @@ -5,6 +5,7 @@ library mapbox_gl; import 'dart:async'; +import 'dart:math'; import 'dart:ui'; import 'package:flutter/foundation.dart'; diff --git a/lib/src/controller.dart b/lib/src/controller.dart index 297b2f63d..13b9eeb4e 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -4,6 +4,8 @@ part of mapbox_gl; +typedef void OnMapClickCallback(Point point, LatLng coordinates); + /// Controller for a single MapboxMap instance running on the host platform. /// /// Change listeners are notified upon changes to any of @@ -18,7 +20,7 @@ part of mapbox_gl; /// Marker tap events can be received by adding callbacks to [onMarkerTapped]. class MapboxMapController extends ChangeNotifier { MapboxMapController._( - this._id, MethodChannel channel, CameraPosition initialCameraPosition) + this._id, MethodChannel channel, CameraPosition initialCameraPosition, {this.onMapClick}) : assert(_id != null), assert(channel != null), _channel = channel { @@ -27,16 +29,18 @@ class MapboxMapController extends ChangeNotifier { } static Future init( - int id, CameraPosition initialCameraPosition) async { + int id, CameraPosition initialCameraPosition, {OnMapClickCallback onMapClick}) async { assert(id != null); final MethodChannel channel = MethodChannel('plugins.flutter.io/mapbox_maps_$id'); await channel.invokeMethod('map#waitForMap'); - return MapboxMapController._(id, channel, initialCameraPosition); + return MapboxMapController._(id, channel, initialCameraPosition, onMapClick: onMapClick); } final MethodChannel _channel; + final OnMapClickCallback onMapClick; + /// Callbacks to receive tap events for markers placed on this map. final ArgumentCallbacks onMarkerTapped = ArgumentCallbacks(); @@ -90,6 +94,15 @@ class MapboxMapController extends ChangeNotifier { _isCameraMoving = false; notifyListeners(); break; + case 'map#onMapClick': + final double x = call.arguments['x']; + final double y = call.arguments['y']; + final double lng = call.arguments['lng']; + final double lat = call.arguments['lat']; + if (onMapClick != null) { + onMapClick(Point(x, y), LatLng(lat, lng)); + } + break; default: throw MissingPluginException(); } @@ -214,4 +227,42 @@ class MapboxMapController extends ChangeNotifier { }); _markers.remove(id); } + + Future queryRenderedFeatures( + Point point, List layerIds, String filter) async { + try { + final Map reply = await _channel.invokeMethod( + 'map#queryRenderedFeatures', + { + 'x': point.x, + 'y': point.y, + 'layerIds': layerIds, + 'filter': filter, + }, + ); + return reply['features']; + } on PlatformException catch (e) { + return new Future.error(e); + } + } + + Future queryRenderedFeaturesInRect(Rect rect, List layerIds, String filter) async { + try { + final Map reply = await _channel.invokeMethod( + 'map#queryRenderedFeatures', + { + 'left': rect.left, + 'top': rect.top, + 'right': rect.right, + 'bottom': rect.bottom, + 'layerIds': layerIds, + 'filter': filter, + }, + ); + return reply['features']; + } on PlatformException catch (e) { + return new Future.error(e); + } + } + } diff --git a/lib/src/mapbox_map.dart b/lib/src/mapbox_map.dart index a3c368062..9bc5a04cc 100644 --- a/lib/src/mapbox_map.dart +++ b/lib/src/mapbox_map.dart @@ -13,7 +13,7 @@ class MapboxMap extends StatefulWidget { this.gestureRecognizers, this.compassEnabled = true, this.cameraTargetBounds = CameraTargetBounds.unbounded, - this.mapType = MapType.normal, + this.styleString, this.minMaxZoomPreference = MinMaxZoomPreference.unbounded, this.rotateGesturesEnabled = true, this.scrollGesturesEnabled = true, @@ -21,6 +21,7 @@ class MapboxMap extends StatefulWidget { this.tiltGesturesEnabled = true, this.trackCameraPosition = false, this.myLocationEnabled = false, + this.onMapClick, }) : assert(initialCameraPosition != null); final MapCreatedCallback onMapCreated; @@ -33,9 +34,11 @@ class MapboxMap extends StatefulWidget { /// Geographical bounding box for the camera target. final CameraTargetBounds cameraTargetBounds; - - /// Type of map tiles to be rendered. - final MapType mapType; + + /// Style URL or Style JSON + /// Can be a MapboxStyle constant, any Mapbox Style URL, + /// or a StyleJSON (https://docs.mapbox.com/mapbox-gl-js/style-spec/) + final String styleString; /// Preferred bounds for the camera zoom level. /// @@ -93,6 +96,8 @@ class MapboxMap extends StatefulWidget { /// were not claimed by any other gesture recognizer. final Set> gestureRecognizers; + final OnMapClickCallback onMapClick; + @override State createState() => _MapboxMapState(); } @@ -157,7 +162,7 @@ class _MapboxMapState extends State { Future onPlatformViewCreated(int id) async { final MapboxMapController controller = - await MapboxMapController.init(id, widget.initialCameraPosition); + await MapboxMapController.init(id, widget.initialCameraPosition, onMapClick: widget.onMapClick); _controller.complete(controller); if (widget.onMapCreated != null) { widget.onMapCreated(controller); @@ -173,7 +178,7 @@ class _MapboxMapOptions { _MapboxMapOptions({ this.compassEnabled, this.cameraTargetBounds, - this.mapType, + this.styleString, this.minMaxZoomPreference, this.rotateGesturesEnabled, this.scrollGesturesEnabled, @@ -187,7 +192,7 @@ class _MapboxMapOptions { return _MapboxMapOptions( compassEnabled: map.compassEnabled, cameraTargetBounds: map.cameraTargetBounds, - mapType: map.mapType, + styleString: map.styleString, minMaxZoomPreference: map.minMaxZoomPreference, rotateGesturesEnabled: map.rotateGesturesEnabled, scrollGesturesEnabled: map.scrollGesturesEnabled, @@ -202,7 +207,7 @@ class _MapboxMapOptions { final CameraTargetBounds cameraTargetBounds; - final MapType mapType; + final String styleString; final MinMaxZoomPreference minMaxZoomPreference; @@ -229,7 +234,7 @@ class _MapboxMapOptions { addIfNonNull('compassEnabled', compassEnabled); addIfNonNull('cameraTargetBounds', cameraTargetBounds?._toJson()); - addIfNonNull('mapType', mapType?.index); + addIfNonNull('styleString', styleString); addIfNonNull('minMaxZoomPreference', minMaxZoomPreference?._toJson()); addIfNonNull('rotateGesturesEnabled', rotateGesturesEnabled); addIfNonNull('scrollGesturesEnabled', scrollGesturesEnabled); diff --git a/lib/src/ui.dart b/lib/src/ui.dart index f6d649174..a0e895a50 100644 --- a/lib/src/ui.dart +++ b/lib/src/ui.dart @@ -4,25 +4,56 @@ part of mapbox_gl; -/// Type of map tiles to display. -// Enum constants must be indexed to match the corresponding int constants of -// the Android platform API, see -// -enum MapType { - /// Do not display map tiles. - none, - - /// Normal tiles (traffic and labels, subtle terrain information). - normal, - - /// Satellite imaging tiles (aerial photos) - satellite, - - /// Terrain tiles (indicates type and height of terrain) - terrain, - - /// Hybrid tiles (satellite images with some labels/overlays) - hybrid, +class MapboxStyles { + static const String MAPBOX_STREETS = "mapbox://styles/mapbox/streets-v11"; + /** + * Outdoors: A general-purpose style tailored to outdoor activities. Using this constant means + * your map style will always use the latest version and may change as we improve the style. + */ + static const String OUTDOORS = "mapbox://styles/mapbox/outdoors-v11"; + + /** + * Light: Subtle light backdrop for data visualizations. Using this constant means your map + * style will always use the latest version and may change as we improve the style. + */ + static const String LIGHT = "mapbox://styles/mapbox/light-v10"; + + /** + * Dark: Subtle dark backdrop for data visualizations. Using this constant means your map style + * will always use the latest version and may change as we improve the style. + */ + static const String DARK = "mapbox://styles/mapbox/dark-v10"; + + /** + * Satellite: A beautiful global satellite and aerial imagery layer. Using this constant means + * your map style will always use the latest version and may change as we improve the style. + */ + static const String SATELLITE = "mapbox://styles/mapbox/satellite-v9"; + + /** + * Satellite Streets: Global satellite and aerial imagery with unobtrusive labels. Using this + * constant means your map style will always use the latest version and may change as we + * improve the style. + */ + static const String SATELLITE_STREETS = "mapbox://styles/mapbox/satellite-streets-v11"; + + /** + * Traffic Day: Color-coded roads based on live traffic congestion data. Traffic data is currently + * available in + * these select + * countries. Using this constant means your map style will always use the latest version and + * may change as we improve the style. + */ + static const String TRAFFIC_DAY = "mapbox://styles/mapbox/traffic-day-v2"; + + /** + * Traffic Night: Color-coded roads based on live traffic congestion data, designed to maximize + * legibility in low-light situations. Traffic data is currently available in + * these select + * countries. Using this constant means your map style will always use the latest version and + * may change as we improve the style. + */ + static const String TRAFFIC_NIGHT = "mapbox://styles/mapbox/traffic-night-v2"; } /// Bounds for the map camera target.