From 1f969376beb87989c083e190576cb8bc73c136cf Mon Sep 17 00:00:00 2001 From: morvagergely <51244648+morvagergely@users.noreply.github.com> Date: Thu, 12 May 2022 13:16:20 +0200 Subject: [PATCH 01/22] Document layer zoom limits (#1028) --- lib/src/controller.dart | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/src/controller.dart b/lib/src/controller.dart index 1d4583f13..70df6971a 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -340,6 +340,10 @@ class MapboxMapController extends ChangeNotifier { /// If [enableInteraction] is set the layer is considered for touch or drag /// events. [sourceLayer] is used to selected a specific source layer from /// Vector source. + /// [minzoom] is the minimum (inclusive) zoom level at which the layer is + /// visible. + /// [maxzoom] is the maximum (exclusive) zoom level at which the layer is + /// visible. /// [filter] determines which features should be rendered in the layer. /// Filters are written as [expressions]. /// @@ -376,6 +380,10 @@ class MapboxMapController extends ChangeNotifier { /// If [enableInteraction] is set the layer is considered for touch or drag /// events. [sourceLayer] is used to selected a specific source layer from /// Vector source. + /// [minzoom] is the minimum (inclusive) zoom level at which the layer is + /// visible. + /// [maxzoom] is the maximum (exclusive) zoom level at which the layer is + /// visible. /// [filter] determines which features should be rendered in the layer. /// Filters are written as [expressions]. /// @@ -412,6 +420,10 @@ class MapboxMapController extends ChangeNotifier { /// If [enableInteraction] is set the layer is considered for touch or drag /// events. [sourceLayer] is used to selected a specific source layer from /// Vector source. + /// [minzoom] is the minimum (inclusive) zoom level at which the layer is + /// visible. + /// [maxzoom] is the maximum (exclusive) zoom level at which the layer is + /// visible. /// [filter] determines which features should be rendered in the layer. /// Filters are written as [expressions]. /// @@ -448,6 +460,10 @@ class MapboxMapController extends ChangeNotifier { /// If [enableInteraction] is set the layer is considered for touch or drag /// events. [sourceLayer] is used to selected a specific source layer from /// Vector source. + /// [minzoom] is the minimum (inclusive) zoom level at which the layer is + /// visible. + /// [maxzoom] is the maximum (exclusive) zoom level at which the layer is + /// visible. /// [filter] determines which features should be rendered in the layer. /// Filters are written as [expressions]. /// @@ -482,7 +498,11 @@ class MapboxMapController extends ChangeNotifier { /// /// Setting [belowLayerId] adds the new layer below the given id. /// [sourceLayer] is used to selected a specific source layer from - /// Raster source + /// Raster source. + /// [minzoom] is the minimum (inclusive) zoom level at which the layer is + /// visible. + /// [maxzoom] is the maximum (exclusive) zoom level at which the layer is + /// visible. Future addRasterLayer( String sourceId, String layerId, RasterLayerProperties properties, {String? belowLayerId, @@ -509,7 +529,11 @@ class MapboxMapController extends ChangeNotifier { /// /// Setting [belowLayerId] adds the new layer below the given id. /// [sourceLayer] is used to selected a specific source layer from - /// Raster source + /// Raster source. + /// [minzoom] is the minimum (inclusive) zoom level at which the layer is + /// visible. + /// [maxzoom] is the maximum (exclusive) zoom level at which the layer is + /// visible. Future addHillshadeLayer( String sourceId, String layerId, HillshadeLayerProperties properties, {String? belowLayerId, @@ -1129,6 +1153,10 @@ class MapboxMapController extends ChangeNotifier { /// [HillshadeLayerProperties]. /// [sourceLayer] is used to selected a specific source layer from Vector /// source. + /// [minzoom] is the minimum (inclusive) zoom level at which the layer is + /// visible. + /// [maxzoom] is the maximum (exclusive) zoom level at which the layer is + /// visible. /// [filter] determines which features should be rendered in the layer. /// Filters are written as [expressions]. /// [filter] is not supported by RasterLayer and HillshadeLayer. From 47b77e74cf261cf1ca75938481a0736eb5ca0492 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Thu, 19 May 2022 12:08:11 +0200 Subject: [PATCH 02/22] [release] update changelog for v0.16.0 (#1052) --- CHANGELOG.md | 37 +++++++++++++++++++++-- mapbox_gl_platform_interface/CHANGELOG.md | 13 ++++++-- mapbox_gl_web/CHANGELOG.md | 13 +++++++- 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1163ad7b7..dcc46455e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ +## 0.16.0, May 19, 2022 +* Fix type issues in query rendered features in rect [#862](https://github.com/flutter-mapbox-gl/maps/pull/862) +* Annotation manager moved to dart [#779](https://github.com/flutter-mapbox-gl/maps/pull/779) +* Add java formatting [#863](https://github.com/flutter-mapbox-gl/maps/pull/863) +* Fix urls parsing [#868](https://github.com/flutter-mapbox-gl/maps/pull/868) +* Fix for MissingPluginException when using downloadOfflineRegion [#864](https://github.com/flutter-mapbox-gl/maps/pull/864) +* Fix issue with map disposal on web [#895](https://github.com/flutter-mapbox-gl/maps/pull/895) +* Fix for rescale issues on web [#896](https://github.com/flutter-mapbox-gl/maps/pull/896) +* Fix android build issues [#904](https://github.com/flutter-mapbox-gl/maps/pull/904) +* Upgraded mapbox gl js to 2.7.0 [#889](https://github.com/flutter-mapbox-gl/maps/pull/889) +* Add updateContentInsets on Android [#903](https://github.com/flutter-mapbox-gl/maps/pull/903) +* Fix MapController's queryRendered* methods not considering layerIds [#870](https://github.com/flutter-mapbox-gl/maps/pull/870) +* Adding function in annotation manager to remove multiple annotations [#931](https://github.com/flutter-mapbox-gl/maps/pull/931) +* Update "Setting up" section of README [#918](https://github.com/flutter-mapbox-gl/maps/pull/918) +* Add support for layer zoom limits [#934](https://github.com/flutter-mapbox-gl/maps/pull/934) +* Add locationComponent#getLastLocation [#922](https://github.com/flutter-mapbox-gl/maps/pull/922) +* Add and default to Hybrid composition on Android [#916](https://github.com/flutter-mapbox-gl/maps/pull/916) +* Fix location puck getting hidden [#956](https://github.com/flutter-mapbox-gl/maps/pull/956) +* Don't call locationComponent if it's null [#966](https://github.com/flutter-mapbox-gl/maps/pull/966) +* Do nothing and correctly return if layer and source to remove are not there [](https://github.com/flutter-mapbox-gl/maps/pull/961) +* Fixed LocationTracking issue delayed permissions [#958](https://github.com/flutter-mapbox-gl/maps/pull/958) +* Fix for LocationComponent update issue [#969](https://github.com/flutter-mapbox-gl/maps/pull/969) +* Features with non string ids will not update properly [#970](https://github.com/flutter-mapbox-gl/maps/pull/970) +* Update added shapes by layer when setting a feature [#972](https://github.com/flutter-mapbox-gl/maps/pull/972) +* Allows modifying http headers [#977](https://github.com/flutter-mapbox-gl/maps/pull/977) +* Additional documentation [#986](https://github.com/flutter-mapbox-gl/maps/pull/986) +* Disabled hybrid composition do to various issues [#992](https://github.com/flutter-mapbox-gl/maps/pull/992) +* Implement layer filtering [#997](https://github.com/flutter-mapbox-gl/maps/pull/997) +* Drag event types support added [#987](https://github.com/flutter-mapbox-gl/maps/pull/987) +* Remove duplicated code [#1026](https://github.com/flutter-mapbox-gl/maps/pull/1026) +* Support filtering on addLayer [#1024](https://github.com/flutter-mapbox-gl/maps/pull/1024) +* Document layer zoom limits [#1028](https://github.com/flutter-mapbox-gl/maps/pull/1028) + ## 0.15.0, January 13, 2022 -* Callbacks added to onFeatureTapped will now also get the position (`Point`) and the location (`LatLng`) of the click passed when called [#798](https://github.com/flutter-mapbox-gl/maps/pull/798) +* Callbacks added to onFeatureTapped will now also get the position (`Point`) and the location (`LatLng`) of the click passed when called [#798](https://github.com/flutter-mapbox-gl/maps/pull/798) * Fixed layer based feature selection [#765](https://github.com/flutter-mapbox-gl/maps/pull/765) * Implement the changePosition function for place_fill example [#778](https://github.com/flutter-mapbox-gl/maps/pull/778) * Invoke onPause method of MapView in onPause lifecycle [#782](https://github.com/flutter-mapbox-gl/maps/pull/782) @@ -24,7 +57,7 @@ * Fixed issue with return type of remove source on web [#854](https://github.com/flutter-mapbox-gl/maps/pull/854) ## 0.14.0, November 13, 2021 -* Remove memory leaks by disposing internal components [#706](https://github.com/tobrun/flutter-mapbox-gl/pull/706) +* Remove memory leaks by disposing internal components [#706](https://github.com/tobrun/flutter-mapbox-gl/pull/706) * Improved annotation click order [#748](https://github.com/tobrun/flutter-mapbox-gl/pull/748) * Add support for Layers, properties and expressions backed by GeoJsonSource [#723](https://github.com/tobrun/flutter-mapbox-gl/pull/723) * Add attribution button gravity, position normally [#731](https://github.com/tobrun/flutter-mapbox-gl/pull/731) diff --git a/mapbox_gl_platform_interface/CHANGELOG.md b/mapbox_gl_platform_interface/CHANGELOG.md index 9c4cda24a..c187c4f50 100644 --- a/mapbox_gl_platform_interface/CHANGELOG.md +++ b/mapbox_gl_platform_interface/CHANGELOG.md @@ -1,10 +1,19 @@ +## 0.16.0, May 19, 2022 +* Annotation manager moved to dart [#779](https://github.com/flutter-mapbox-gl/maps/pull/779) +* Fix issue with map disposal on web [#895](https://github.com/flutter-mapbox-gl/maps/pull/895) +* Add support for layer zoom limits [#934](https://github.com/flutter-mapbox-gl/maps/pull/934) +* Add and default to Hybrid composition on Android [#916](https://github.com/flutter-mapbox-gl/maps/pull/916) +* Implement layer filtering [#997](https://github.com/flutter-mapbox-gl/maps/pull/997) +* Drag event types support added [#987](https://github.com/flutter-mapbox-gl/maps/pull/987) +* Support filtering on addLayer [#1024](https://github.com/flutter-mapbox-gl/maps/pull/1024) + ## 0.15.0, January 13, 2022 -* Callbacks added to onFeatureTapped will now also get the position (`Point`) and the location (`LatLng`) of the click passed when called [#798](https://github.com/flutter-mapbox-gl/maps/pull/798) +* Callbacks added to onFeatureTapped will now also get the position (`Point`) and the location (`LatLng`) of the click passed when called [#798](https://github.com/flutter-mapbox-gl/maps/pull/798) * Fix web issues with style loaded, feature tap, add promoteId, pointer change issue [#785](https://github.com/flutter-mapbox-gl/maps/pull/785) * Full style source support [#797](https://github.com/flutter-mapbox-gl/maps/pull/797) ## 0.14.0, November 13, 2021 -* Remove memory leaks by disposing internal components [#706](https://github.com/tobrun/flutter-mapbox-gl/pull/706) +* Remove memory leaks by disposing internal components [#706](https://github.com/tobrun/flutter-mapbox-gl/pull/706) * Add support for Layers, properties and expressions backed by GeoJsonSource [#723](https://github.com/tobrun/flutter-mapbox-gl/pull/723) * Add attribution button gravity, position normally [#731](https://github.com/tobrun/flutter-mapbox-gl/pull/731) * Remove MapboxGlPlatform.getInstance [#710](https://github.com/tobrun/flutter-mapbox-gl/pull/710) diff --git a/mapbox_gl_web/CHANGELOG.md b/mapbox_gl_web/CHANGELOG.md index a8330bde4..854f294c6 100644 --- a/mapbox_gl_web/CHANGELOG.md +++ b/mapbox_gl_web/CHANGELOG.md @@ -1,5 +1,16 @@ +## 0.16.0, May 19, 2022 +* Fix type issues in query rendered features in rect [#862](https://github.com/flutter-mapbox-gl/maps/pull/862) +* Annotation manager moved to dart [#779](https://github.com/flutter-mapbox-gl/maps/pull/779) +* Fix issue with map disposal on web [#895](https://github.com/flutter-mapbox-gl/maps/pull/895) +* Fix for rescale issues on web [#896](https://github.com/flutter-mapbox-gl/maps/pull/896) +* Upgraded mapbox gl js to 2.7.0 [#889](https://github.com/flutter-mapbox-gl/maps/pull/889) +* Add support for layer zoom limits [#934](https://github.com/flutter-mapbox-gl/maps/pull/934) +* Implement layer filtering [#997](https://github.com/flutter-mapbox-gl/maps/pull/997) +* Drag event types support added [#987](https://github.com/flutter-mapbox-gl/maps/pull/987) +* Support filtering on addLayer [#1024](https://github.com/flutter-mapbox-gl/maps/pull/1024) + ## 0.15.0, January 13, 2022 -* Callbacks added to onFeatureTapped will now also get the position (`Point`) and the location (`LatLng`) of the click passed when called [#798](https://github.com/flutter-mapbox-gl/maps/pull/798) +* Callbacks added to onFeatureTapped will now also get the position (`Point`) and the location (`LatLng`) of the click passed when called [#798](https://github.com/flutter-mapbox-gl/maps/pull/798) * Fix web issues with style loaded, feature tap, add promoteId, pointer change issue [#785](https://github.com/flutter-mapbox-gl/maps/pull/785) * Add check for Dart formatting [#803](https://github.com/flutter-mapbox-gl/maps/pull/803) * Remove unnecessary print of style height and width [#847](https://github.com/flutter-mapbox-gl/maps/pull/847) From 734b70701dbbfb967154e0547d3474e2a1478e95 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Mon, 23 May 2022 16:42:55 +0200 Subject: [PATCH 03/22] [release] release v0.16.0 (#1053) --- mapbox_gl_platform_interface/pubspec.yaml | 2 +- mapbox_gl_web/pubspec.yaml | 2 +- pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mapbox_gl_platform_interface/pubspec.yaml b/mapbox_gl_platform_interface/pubspec.yaml index 6cbdc932b..c3eae26be 100644 --- a/mapbox_gl_platform_interface/pubspec.yaml +++ b/mapbox_gl_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: mapbox_gl_platform_interface description: A common platform interface for the mapbox_gl plugin. -version: 0.15.0 +version: 0.16.0 homepage: https://github.com/tobrun/flutter-mapbox-gl dependencies: diff --git a/mapbox_gl_web/pubspec.yaml b/mapbox_gl_web/pubspec.yaml index e6e736718..769890992 100644 --- a/mapbox_gl_web/pubspec.yaml +++ b/mapbox_gl_web/pubspec.yaml @@ -1,6 +1,6 @@ name: mapbox_gl_web description: Web platform implementation of mapbox_gl -version: 0.15.0 +version: 0.16.0 homepage: https://github.com/tobrun/flutter-mapbox-gl flutter: diff --git a/pubspec.yaml b/pubspec.yaml index d99a102e5..ea086f30b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: mapbox_gl description: A Flutter plugin for integrating Mapbox Maps inside a Flutter application on Android, iOS and web platfroms. -version: 0.15.0 +version: 0.16.0 homepage: https://github.com/tobrun/flutter-mapbox-gl dependencies: From b1479f405596a3571c76b4628fabbf9b1196fe90 Mon Sep 17 00:00:00 2001 From: Felix Mittermeier <100692288+felix-mittermeier@users.noreply.github.com> Date: Mon, 30 May 2022 17:57:31 +0200 Subject: [PATCH 04/22] Improved map resizing (#1061) * Improved map resizing * Added functionality to trigger the map resize event from outside the package * Fixed pipeline --- .github/workflows/flutter_ci.yml | 8 +++- README.md | 12 +++++- example/web/index.html | 2 +- lib/src/controller.dart | 15 +++++++ .../lib/mapbox_gl_platform_interface.dart | 1 - .../lib/src/mapbox_gl_platform_interface.dart | 3 ++ .../lib/src/method_channel_mapbox_gl.dart | 6 +++ mapbox_gl_web/lib/mapbox_gl_web.dart | 2 +- .../lib/src/mapbox_web_gl_platform.dart | 41 +++++++++++++++---- pubspec.lock | 15 +++++-- 10 files changed, 87 insertions(+), 18 deletions(-) diff --git a/.github/workflows/flutter_ci.yml b/.github/workflows/flutter_ci.yml index b6707163e..2df64cee2 100644 --- a/.github/workflows/flutter_ci.yml +++ b/.github/workflows/flutter_ci.yml @@ -63,7 +63,9 @@ jobs: - uses: actions/setup-java@v1 with: java-version: '12.x' - - uses: subosito/flutter-action@v1 + - uses: subosito/flutter-action@v2 + with: + flutter-version: '2.10.5' - run: flutter pub get - name: Build example APK run: cd example && flutter build apk @@ -80,7 +82,9 @@ jobs: - uses: actions/setup-java@v1 with: java-version: '12.x' - - uses: subosito/flutter-action@v1 + - uses: subosito/flutter-action@v2 + with: + flutter-version: '2.10.5' - run: flutter pub get - name: build iOS package run: | diff --git a/README.md b/README.md index ad6887d49..ebbf3b077 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,16 @@ curl: (22) The requested URL returned error: 401 Unauthorized Include the JavaScript and CSS files in the `` of your `index.html` file: ``` - - + + + + ``` *Note: Look for latest version in [Mapbox GL JS documentation](https://docs.mapbox.com/mapbox-gl-js/guides/).* diff --git a/example/web/index.html b/example/web/index.html index 4d19c8101..f6098b72b 100644 --- a/example/web/index.html +++ b/example/web/index.html @@ -15,7 +15,7 @@ - + example diff --git a/lib/src/controller.dart b/lib/src/controller.dart index 70df6971a..ba49d28ef 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -257,6 +257,21 @@ class MapboxMapController extends ChangeNotifier { notifyListeners(); } + /// Triggers a resize event for the map on web (ignored on Android or iOS). + /// + /// Checks first if a resize is required or if it looks like it is already correctly resized. + /// If it looks good, the resize call will be skipped. + /// + /// To force resize map (without any checks) have a look at forceResizeWebMap() + void resizeWebMap() { + _mapboxGlPlatform.resizeWebMap(); + } + + /// Triggers a hard map resize event on web and does not check if it is required or not. + void forceResizeWebMap() { + _mapboxGlPlatform.forceResizeWebMap(); + } + /// Starts an animated change of the map camera position. /// /// The returned [Future] completes after the change has been started on the diff --git a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart index bf59ae3bb..62fa0b607 100644 --- a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart @@ -9,7 +9,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -import 'package:meta/meta.dart' show visibleForTesting; part 'src/annotation.dart'; part 'src/callbacks.dart'; diff --git a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart index 49cacae91..f2469e6fc 100644 --- a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart @@ -58,6 +58,9 @@ abstract class MapboxGlPlatform { Future matchMapLanguageWithDeviceDefault(); + void resizeWebMap(); + void forceResizeWebMap(); + Future updateContentInsets(EdgeInsets insets, bool animated); Future setMapLanguage(String language); Future setTelemetryEnabled(bool enabled); diff --git a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart index 38da956cf..dbaf0482d 100644 --- a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart +++ b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -678,4 +678,10 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { 'geojsonFeature': jsonEncode(geojsonFeature) }); } + + @override + void forceResizeWebMap() {} + + @override + void resizeWebMap() {} } diff --git a/mapbox_gl_web/lib/mapbox_gl_web.dart b/mapbox_gl_web/lib/mapbox_gl_web.dart index a6296a824..bec769c43 100644 --- a/mapbox_gl_web/lib/mapbox_gl_web.dart +++ b/mapbox_gl_web/lib/mapbox_gl_web.dart @@ -13,7 +13,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Element; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:mapbox_gl_platform_interface/mapbox_gl_platform_interface.dart'; diff --git a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart index 8a506b8a5..12358d8f9 100644 --- a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart +++ b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart @@ -24,6 +24,7 @@ class MapboxWebGlPlatform extends MapboxGlPlatform String? _navigationControlPosition; NavigationControl? _navigationControl; + Timer? lastResizeObserverTimer; @override Widget buildView( @@ -47,8 +48,10 @@ class MapboxWebGlPlatform extends MapboxGlPlatform ui.platformViewRegistry.registerViewFactory( 'plugins.flutter.io/mapbox_gl_$identifier', (int viewId) { _mapElement = DivElement() - ..style.width = '100%' - ..style.height = '100%'; + ..style.position = 'absolute' + ..style.top = '0' + ..style.bottom = '0' + ..style.width = '100%'; callback(viewId); return _mapElement; }); @@ -59,7 +62,6 @@ class MapboxWebGlPlatform extends MapboxGlPlatform await _addStylesheetToShadowRoot(_mapElement); if (_creationParams.containsKey('initialCameraPosition')) { var camera = _creationParams['initialCameraPosition']; - _dragEnabled = _creationParams['dragEnabled'] ?? true; if (_creationParams.containsKey('accessToken')) { @@ -82,16 +84,31 @@ class MapboxWebGlPlatform extends MapboxGlPlatform _map.on('movestart', _onCameraMoveStarted); _map.on('move', _onCameraMove); _map.on('moveend', _onCameraIdle); - _map.on('resize', _onMapResize); + _map.on('resize', (_) => _onMapResize()); _map.on('styleimagemissing', _loadFromAssets); if (_dragEnabled) { _map.on('mouseup', _onMouseUp); _map.on('mousemove', _onMouseMove); } + + _initResizeObserver(); } Convert.interpretMapboxMapOptions(_creationParams['options'], this); } + void _initResizeObserver() { + final resizeObserver = ResizeObserver((entries, observer) { + // The resize observer might be called a lot of times when the user resizes the browser window with the mouse for example. + // Due to the fact that the resize call is quite expensive it should not be called for every triggered event but only the last one, like "onMoveEnd". + // But because there is no event type for the end, there is only the option to spawn timers and cancel the previous ones if they get overwritten by a new event. + lastResizeObserverTimer?.cancel(); + lastResizeObserverTimer = Timer(Duration(milliseconds: 50), () { + _onMapResize(); + }); + }); + resizeObserver.observe(document.body as Element); + } + void _loadFromAssets(Event event) async { final imagePath = event.id; final ByteData bytes = await rootBundle.load(imagePath); @@ -340,12 +357,12 @@ class MapboxWebGlPlatform extends MapboxGlPlatform void _onStyleLoaded(_) { _mapReady = true; - _map.resize(); + _onMapResize(); onMapStyleLoadedPlatform(null); } - void _onMapResize(Event e) { - Timer(Duration(microseconds: 10), () { + void _onMapResize() { + Timer(Duration(), () { var container = _map.getContainer(); var canvas = _map.getCanvas(); var widthMismatch = canvas.clientWidth != container.clientWidth; @@ -949,4 +966,14 @@ class MapboxWebGlPlatform extends MapboxGlPlatform } } } + + @override + void resizeWebMap() { + _onMapResize(); + } + + @override + void forceResizeWebMap() { + _map.resize(); + } } diff --git a/pubspec.lock b/pubspec.lock index 4aab14388..5b21d9f78 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -16,7 +16,7 @@ packages: source: hosted version: "1.2.0" collection: - dependency: transitive + dependency: "direct main" description: name: collection url: "https://pub.dartlang.org" @@ -59,21 +59,28 @@ packages: name: mapbox_gl_dart url: "https://pub.dartlang.org" source: hosted - version: "0.2.0-nullsafety.0" + version: "0.2.1" mapbox_gl_platform_interface: dependency: "direct main" description: path: mapbox_gl_platform_interface relative: true source: path - version: "0.14.0" + version: "0.16.1" mapbox_gl_web: dependency: "direct main" description: path: mapbox_gl_web relative: true source: path - version: "0.14.0" + version: "0.16.1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" meta: dependency: transitive description: From 3496907955cd4b442e4eb905d67e8d46692174f1 Mon Sep 17 00:00:00 2001 From: morvagergely <51244648+morvagergely@users.noreply.github.com> Date: Tue, 31 May 2022 15:03:18 +0200 Subject: [PATCH 05/22] Add duration for animateCamera (#1066) --- example/lib/animate_camera.dart | 1 + ios/Classes/MapboxMapController.swift | 3 ++- lib/src/controller.dart | 7 +++++-- .../lib/src/mapbox_gl_platform_interface.dart | 2 +- .../lib/src/method_channel_mapbox_gl.dart | 3 ++- mapbox_gl_web/lib/mapbox_gl_web.dart | 1 + .../lib/src/mapbox_web_gl_platform.dart | 20 +++++++++++++++++-- 7 files changed, 30 insertions(+), 7 deletions(-) diff --git a/example/lib/animate_camera.dart b/example/lib/animate_camera.dart index 36f6a9c41..ec9cd55c9 100644 --- a/example/lib/animate_camera.dart +++ b/example/lib/animate_camera.dart @@ -79,6 +79,7 @@ class AnimateCameraState extends State { CameraUpdate.newLatLng( const LatLng(56.1725505, 10.1850512), ), + duration: Duration(seconds: 5), ) .then((result) => print( "mapController.animateCamera() returned $result")); diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index e1d55b8ab..703b68c31 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -336,8 +336,9 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma animationTimingFunction: CAMediaTimingFunction(name: CAMediaTimingFunctionName .easeInEaseOut)) result(nil) + } else { + mapView.setCamera(camera, animated: true) } - mapView.setCamera(camera, animated: true) } result(nil) diff --git a/lib/src/controller.dart b/lib/src/controller.dart index ba49d28ef..bb3d3e7b3 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -274,12 +274,15 @@ class MapboxMapController extends ChangeNotifier { /// Starts an animated change of the map camera position. /// + /// [duration] is the amount of time, that the transition animation should take. + /// /// The returned [Future] completes after the change has been started on the /// platform side. /// It returns true if the camera was successfully moved and false if the movement was canceled. /// Note: this currently always returns immediately with a value of null on iOS - Future animateCamera(CameraUpdate cameraUpdate) async { - return _mapboxGlPlatform.animateCamera(cameraUpdate); + Future animateCamera(CameraUpdate cameraUpdate, + {Duration? duration}) async { + return _mapboxGlPlatform.animateCamera(cameraUpdate, duration: duration); } /// Instantaneously re-position the camera. diff --git a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart index f2469e6fc..ca1f9252d 100644 --- a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart @@ -51,7 +51,7 @@ abstract class MapboxGlPlatform { OnPlatformViewCreatedCallback onPlatformViewCreated, Set>? gestureRecognizers); Future updateMapOptions(Map optionsUpdate); - Future animateCamera(CameraUpdate cameraUpdate); + Future animateCamera(CameraUpdate cameraUpdate, {Duration? duration}); Future moveCamera(CameraUpdate cameraUpdate); Future updateMyLocationTrackingMode( MyLocationTrackingMode myLocationTrackingMode); diff --git a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart index dbaf0482d..ea83b082a 100644 --- a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart +++ b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -211,9 +211,10 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { } @override - Future animateCamera(cameraUpdate) async { + Future animateCamera(cameraUpdate, {Duration? duration}) async { return await _channel.invokeMethod('camera#animate', { 'cameraUpdate': cameraUpdate.toJson(), + 'duration': duration?.inMilliseconds, }); } diff --git a/mapbox_gl_web/lib/mapbox_gl_web.dart b/mapbox_gl_web/lib/mapbox_gl_web.dart index bec769c43..70756d3be 100644 --- a/mapbox_gl_web/lib/mapbox_gl_web.dart +++ b/mapbox_gl_web/lib/mapbox_gl_web.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:html'; // ignore: unused_import import 'dart:js'; +import 'dart:js_util'; import 'dart:math'; import 'dart:typed_data'; import 'dart:ui' as ui; diff --git a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart index 12358d8f9..ed2918556 100644 --- a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart +++ b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart @@ -194,9 +194,25 @@ class MapboxWebGlPlatform extends MapboxGlPlatform } @override - Future animateCamera(CameraUpdate cameraUpdate) async { + Future animateCamera(CameraUpdate cameraUpdate, + {Duration? duration}) async { final cameraOptions = Convert.toCameraOptions(cameraUpdate, _map); - _map.flyTo(cameraOptions); + + final around = getProperty(cameraOptions, 'around'); + final bearing = getProperty(cameraOptions, 'bearing'); + final center = getProperty(cameraOptions, 'center'); + final pitch = getProperty(cameraOptions, 'pitch'); + final zoom = getProperty(cameraOptions, 'zoom'); + + _map.flyTo({ + if (around.jsObject != null) 'around': around, + if (bearing != null) 'bearing': bearing, + if (center.jsObject != null) 'center': center, + if (pitch != null) 'pitch': pitch, + if (zoom != null) 'zoom': zoom, + if (duration != null) 'duration': duration.inMilliseconds, + }); + return true; } From 019ed848e5516f414745fa8c1bb9ca567600e59c Mon Sep 17 00:00:00 2001 From: Felix Horvat Date: Wed, 28 Sep 2022 17:30:39 +0200 Subject: [PATCH 06/22] Add makefile for formatting (#1181) * refactor and fix ci the file * add makefile for formatting and installing formatting tools --- .github/workflows/flutter_ci.yml | 112 ++++++++++++++++--------------- .gitignore | 6 ++ Makefile | 13 ++++ install_formatting_tools.sh | 21 ++++++ pubspec.lock | 4 +- 5 files changed, 100 insertions(+), 56 deletions(-) create mode 100644 Makefile create mode 100755 install_formatting_tools.sh diff --git a/.github/workflows/flutter_ci.yml b/.github/workflows/flutter_ci.yml index 2df64cee2..4f16f71fd 100644 --- a/.github/workflows/flutter_ci.yml +++ b/.github/workflows/flutter_ci.yml @@ -8,69 +8,73 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions/setup-java@v1 - with: - java-version: '12.x' - - uses: subosito/flutter-action@v1 - - run: flutter pub get - - name: Lint analysis - run: cd example && flutter analyze - + - uses: actions/checkout@v1 + - uses: actions/setup-java@v1 + with: + java-version: "12.x" + - uses: subosito/flutter-action@v1 + with: + flutter-version: "2.10.5" + - run: flutter pub get + - name: Lint analysis + run: cd example && flutter analyze + check-dart-formatting: name: "Check Dart formatting" runs-on: ubuntu-latest - + steps: - - uses: actions/checkout@v1 - - uses: subosito/flutter-action@v1 - - name: Check Dart formatting - run: flutter format --set-exit-if-changed . + - uses: actions/checkout@v1 + - uses: subosito/flutter-action@v1 + - name: Check Dart formatting + run: flutter format --set-exit-if-changed . check-swift-formatting: name: "Check Swift formatting" runs-on: ubuntu-latest - + steps: - - uses: actions/checkout@v1 - - name: get SwiftFormat - run: wget https://github.com/nicklockwood/SwiftFormat/releases/download/0.48.18/swiftformat_linux.zip - - run: unzip swiftformat_linux.zip - - run: chmod +x swiftformat_linux - - name: Check Swift formatting - run: ./swiftformat_linux --swiftversion 4.2 --maxwidth 100 --lint ios + - uses: actions/checkout@v1 + - name: get SwiftFormat + run: ./install_formatting_tools.sh + - name: Check Swift formatting + run: ./swiftformat --swiftversion 4.2 --maxwidth 100 --lint ios check-java-formatting: name: "Check Java formatting" runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v1 - - name: get google-java-format - run: wget https://github.com/google/google-java-format/releases/download/v1.13.0/google-java-format-1.13.0-all-deps.jar - - run: java --version - - name: Check Java formatting - run: java -jar google-java-format-1.13.0-all-deps.jar --set-exit-if-changed -n $(find . -type f -name "*.java") + steps: + - uses: actions/checkout@v1 + - name: get google-java-format + run: ./install_formatting_tools.sh + - name: Check Java formatting + run: > + java + --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + -jar google-java-format-1.13.0-all-deps.jar --set-exit-if-changed -n $(find . -type f -name "*.java") build-android: environment: ANDROID_CI_DOWNLOADS_TOKEN name: "Build Android apk" runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: actions/setup-java@v1 - with: - java-version: '12.x' - - uses: subosito/flutter-action@v2 - with: - flutter-version: '2.10.5' - - run: flutter pub get - - name: Build example APK - run: cd example && flutter build apk - env: - SDK_REGISTRY_TOKEN: ${{ secrets.SDK_REGISTRY_ANDROID}} + - uses: actions/checkout@v1 + - uses: actions/setup-java@v1 + with: + java-version: "12.x" + - uses: subosito/flutter-action@v2 + with: + flutter-version: "2.10.5" + - run: flutter pub get + - name: Build example APK + run: cd example && flutter build apk + env: + SDK_REGISTRY_TOKEN: ${{ secrets.SDK_REGISTRY_ANDROID}} build-iOS: environment: ANDROID_CI_DOWNLOADS_TOKEN @@ -81,10 +85,10 @@ jobs: - uses: actions/checkout@v1 - uses: actions/setup-java@v1 with: - java-version: '12.x' + java-version: "12.x" - uses: subosito/flutter-action@v2 with: - flutter-version: '2.10.5' + flutter-version: "2.10.5" - run: flutter pub get - name: build iOS package run: | @@ -102,12 +106,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions/setup-java@v1 - with: - java-version: '12.x' - - uses: subosito/flutter-action@v1 - - run: flutter config --enable-web - - run: flutter pub get - - name: Build web - run: cd example && flutter build web + - uses: actions/checkout@v1 + - uses: actions/setup-java@v1 + with: + java-version: "12.x" + - uses: subosito/flutter-action@v1 + - run: flutter config --enable-web + - run: flutter pub get + - name: Build web + run: cd example && flutter build web diff --git a/.gitignore b/.gitignore index b7d74c826..64487b8f3 100644 --- a/.gitignore +++ b/.gitignore @@ -98,10 +98,15 @@ unlinked_spec.ds **/macos/Flutter/Flutter-Debug.xcconfig **/macos/Flutter/Flutter-Release.xcconfig **/macos/Flutter/Flutter-Profile.xcconfig +__MACOSX/ # Coverage coverage/ +# Binaries +google-java-format-1.13.0-all-deps.jar +swiftformat + # Symbols app.*.symbols @@ -112,3 +117,4 @@ app.*.symbols !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages !/dev/ci/**/Gemfile.lock + diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..93faecdf7 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +format: + java \ + --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ + -jar google-java-format-1.13.0-all-deps.jar -r $(shell find . -type f -name "*.java") + flutter format . + ./swiftformat --swiftversion 4.2 --maxwidth 100 ios + +install_formatting: + ./install_formatting_tools.sh diff --git a/install_formatting_tools.sh b/install_formatting_tools.sh new file mode 100755 index 000000000..e9aad1960 --- /dev/null +++ b/install_formatting_tools.sh @@ -0,0 +1,21 @@ +echo "Cleanup" +rm swiftformat +rm -r __MACOSX +rm google-java-format-1.13.0-all-deps.jar + +if [ $(uname) = "Linux" ] +then + echo "Install for Linux" + wget https://github.com/nicklockwood/SwiftFormat/releases/download/0.48.18/swiftformat_linux.zip + unzip swiftformat_linux.zip + rm swiftformat_linux.zip + mv swiftformat_linux swiftformat +else + echo "Install for MAC" + wget https://github.com/nicklockwood/SwiftFormat/releases/download/0.48.18/swiftformat.zip + unzip swiftformat.zip + rm swiftformat.zip +fi +chmod +x swiftformat + +wget https://github.com/google/google-java-format/releases/download/v1.13.0/google-java-format-1.13.0-all-deps.jar diff --git a/pubspec.lock b/pubspec.lock index 5b21d9f78..23ff03728 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -66,14 +66,14 @@ packages: path: mapbox_gl_platform_interface relative: true source: path - version: "0.16.1" + version: "0.16.0" mapbox_gl_web: dependency: "direct main" description: path: mapbox_gl_web relative: true source: path - version: "0.16.1" + version: "0.16.0" material_color_utilities: dependency: transitive description: From 3e490a8fc42b6b47374b3932fa49061293b709ee Mon Sep 17 00:00:00 2001 From: Hung Tran <32390731+hungtrn75@users.noreply.github.com> Date: Wed, 28 Sep 2022 22:54:17 +0700 Subject: [PATCH 07/22] Create a static map snapshot (#1076) * android impl * take snapshot ios * config for each platform interface * take snapshot in ios * take snap in android && example * android hybrid composition * ios test * test with android hybrid composition * Update README.md * use JPG instead of PNG with writeToDisk option & remove unused funtions * render example result with base64 option * remove team ID in example * iOS: use JPG instead of PNG * rename funtion * test flutter ci * test ci * ci: check swift formatting * ci: test check java formatting * ci: check java formatting * ci: test check java formatting * document for take snapshot feature * revert ci config * migration: jpeg with base64 option * docs: web support * feat: web support with base64 option * ci: test github ci * lint: ignore unnecessary_import * ci: reverse config --- README.md | 19 ++ android/build.gradle | 1 + .../java/com/mapbox/mapboxgl/BitmapUtils.java | 57 ++++++ .../com/mapbox/mapboxgl/GeoJSONUtils.java | 38 ++++ .../mapbox/mapboxgl/MapboxMapController.java | 89 +++++++++- example/.gitignore | 1 + .../android/app/src/main/AndroidManifest.xml | 1 + .../app/src/main/res/values/styles.xml | 3 + example/ios/Runner.xcodeproj/project.pbxproj | 15 +- example/ios/Runner/AppDelegate.swift | 16 +- example/ios/Runner/Info.plist | 2 + example/lib/generated_plugin_registrant.dart | 1 + example/lib/line.dart | 1 + example/lib/main.dart | 21 +-- example/lib/place_fill.dart | 1 + example/lib/place_source.dart | 1 + example/lib/place_symbol.dart | 1 + example/lib/take_snapshot.dart | 165 ++++++++++++++++++ example/macos/Runner/AppDelegate.swift | 6 +- example/macos/Runner/MainFlutterWindow.swift | 16 +- ios/Classes/MapboxMapController.swift | 114 ++++++++++++ ios/Classes/RNMBImageUtils.swift | 26 +++ lib/mapbox_gl.dart | 1 + lib/src/controller.dart | 10 ++ .../lib/mapbox_gl_platform_interface.dart | 2 + .../lib/src/mapbox_gl_platform_interface.dart | 2 + .../lib/src/method_channel_mapbox_gl.dart | 12 ++ .../lib/src/snapshot.dart | 155 ++++++++++++++++ .../lib/src/mapbox_web_gl_platform.dart | 19 ++ pubspec.lock | 10 +- 30 files changed, 754 insertions(+), 52 deletions(-) create mode 100644 android/src/main/java/com/mapbox/mapboxgl/BitmapUtils.java create mode 100644 android/src/main/java/com/mapbox/mapboxgl/GeoJSONUtils.java create mode 100644 example/lib/take_snapshot.dart create mode 100644 ios/Classes/RNMBImageUtils.swift create mode 100644 mapbox_gl_platform_interface/lib/src/snapshot.dart diff --git a/README.md b/README.md index ebbf3b077..6ca9d0fc7 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ - [Map Styles](#map-styles) - [Offline Sideloading](#offline-sideloading) - [Downloading Offline Regions](#downloading-offline-regions) + - [Create a static map snapshot](#create-a-static-map-snapshot) - [Location features](#location-features) - [Android](#android) - [iOS](#ios) @@ -206,7 +207,25 @@ An offline region is a defined region of a map that is available for use in cond downloadOfflineRegionStream(offlineRegion, onEvent); ``` +## Create a static map snapshot +The snapshotManager generates static raster images of the map. +Each snapshot image depicts a portion of a map defined by an SnapshotOptions object you provide. + +* Call `takeSnapshot` with predefined `SnapshotOptions` + +``` + final renderBox = mapKey.currentContext?.findRenderObject() as RenderBox; + + final snapshotOptions = SnapshotOptions( + width: renderBox.size.width, + height: renderBox.size.height, + writeToDisk: true, + withLogo: false, + ); + + final uri = await mapController?.takeSnapshot(snapshotOptions); +``` ## Location features ### Android diff --git a/android/build.gradle b/android/build.gradle index 1430207bc..88f4d5bd1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -57,6 +57,7 @@ android { implementation "com.mapbox.mapboxsdk:mapbox-android-plugin-localization-v9:0.12.0" implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-offline-v9:0.7.0' implementation 'com.squareup.okhttp3:okhttp:4.9.0' + implementation 'com.mapbox.mapboxsdk:mapbox-sdk-turf:5.1.0' } compileOptions { sourceCompatibility 1.8 diff --git a/android/src/main/java/com/mapbox/mapboxgl/BitmapUtils.java b/android/src/main/java/com/mapbox/mapboxgl/BitmapUtils.java new file mode 100644 index 000000000..c91044e51 --- /dev/null +++ b/android/src/main/java/com/mapbox/mapboxgl/BitmapUtils.java @@ -0,0 +1,57 @@ +package com.mapbox.mapboxgl; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.util.Base64; +import android.util.Log; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** Created by nickitaliano on 10/9/17. */ +public class BitmapUtils { + private static final String LOG_TAG = "BitmapUtils"; + + public static String createTempFile(Context context, Bitmap bitmap) { + File tempFile = null; + FileOutputStream outputStream = null; + + try { + tempFile = File.createTempFile(LOG_TAG, ".jpeg", context.getCacheDir()); + outputStream = new FileOutputStream(tempFile); + } catch (IOException e) { + Log.w(LOG_TAG, e.getLocalizedMessage()); + } + + if (tempFile == null) { + return null; + } + + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); + closeSnapshotOutputStream(outputStream); + return Uri.fromFile(tempFile).toString(); + } + + public static String createBase64(Bitmap bitmap) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); + byte[] bitmapBytes = outputStream.toByteArray(); + closeSnapshotOutputStream(outputStream); + String base64Prefix = "data:image/jpeg;base64,"; + return base64Prefix + Base64.encodeToString(bitmapBytes, Base64.NO_WRAP); + } + + private static void closeSnapshotOutputStream(OutputStream outputStream) { + if (outputStream == null) { + return; + } + try { + outputStream.close(); + } catch (IOException e) { + Log.w(LOG_TAG, e.getLocalizedMessage()); + } + } +} diff --git a/android/src/main/java/com/mapbox/mapboxgl/GeoJSONUtils.java b/android/src/main/java/com/mapbox/mapboxgl/GeoJSONUtils.java new file mode 100644 index 000000000..db3b3ed51 --- /dev/null +++ b/android/src/main/java/com/mapbox/mapboxgl/GeoJSONUtils.java @@ -0,0 +1,38 @@ +package com.mapbox.mapboxgl; + +import com.mapbox.geojson.Feature; +import com.mapbox.geojson.FeatureCollection; +import com.mapbox.geojson.Geometry; +import com.mapbox.geojson.GeometryCollection; +import com.mapbox.geojson.Point; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.geometry.LatLngBounds; +import com.mapbox.turf.TurfMeasurement; +import java.util.ArrayList; +import java.util.List; + +public class GeoJSONUtils { + public static LatLng toLatLng(Point point) { + if (point == null) { + return null; + } + return new LatLng(point.latitude(), point.longitude()); + } + + private static GeometryCollection toGeometryCollection(List features) { + ArrayList geometries = new ArrayList<>(); + geometries.ensureCapacity(features.size()); + for (Feature feature : features) { + geometries.add(feature.geometry()); + } + return GeometryCollection.fromGeometries(geometries); + } + + public static LatLngBounds toLatLngBounds(FeatureCollection featureCollection) { + List features = featureCollection.features(); + + double[] bbox = TurfMeasurement.bbox(toGeometryCollection(features)); + + return LatLngBounds.from(bbox[3], bbox[2], bbox[1], bbox[0]); + } +} diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index f45556c9f..fe2a9f4db 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -37,6 +37,7 @@ import com.mapbox.android.telemetry.TelemetryEnabler; import com.mapbox.geojson.Feature; import com.mapbox.geojson.FeatureCollection; +import com.mapbox.geojson.Point; import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdate; @@ -58,6 +59,8 @@ import com.mapbox.mapboxsdk.maps.Style; import com.mapbox.mapboxsdk.offline.OfflineManager; import com.mapbox.mapboxsdk.plugins.localization.LocalizationPlugin; +import com.mapbox.mapboxsdk.snapshotter.MapSnapshotter; +import com.mapbox.mapboxsdk.storage.FileSource; import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.CircleLayer; import com.mapbox.mapboxsdk.style.layers.FillExtrusionLayer; @@ -85,6 +88,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; /** Controller of a single MapboxMaps MapView instance. */ @SuppressLint("MissingPermission") @@ -108,6 +112,9 @@ final class MapboxMapController private final float density; private final Context context; private final String styleStringInitial; + private final Set interactiveFeatureLayerIds; + private final Map addedFeaturesByLayer; + private final Map mSnapshotterMap; private MapView mapView; private MapboxMap mapboxMap; private boolean trackCameraPosition = false; @@ -124,13 +131,8 @@ final class MapboxMapController private Style style; private Feature draggedFeature; private AndroidGesturesManager androidGesturesManager; - private LatLng dragOrigin; private LatLng dragPrevious; - - private Set interactiveFeatureLayerIds; - private Map addedFeaturesByLayer; - private LatLngBounds bounds = null; Style.OnStyleLoaded onStyleLoadedCallback = new Style.OnStyleLoaded() { @@ -174,7 +176,7 @@ public void onStyleLoaded(@NonNull Style style) { if (dragEnabled) { this.androidGesturesManager = new AndroidGesturesManager(this.mapView.getContext(), false); } - + this.mSnapshotterMap = new HashMap<>(); methodChannel = new MethodChannel(messenger, "plugins.flutter.io/mapbox_maps_" + id); methodChannel.setMethodCallHandler(this); } @@ -1185,6 +1187,81 @@ public void onFailure(@NonNull Exception exception) { result.success(null); break; } + case "snapshot#takeSnapshot": + { + FileSource.getInstance(context).activate(); + MapSnapshotter.Options snapShotOptions = + new MapSnapshotter.Options( + (int) call.argument("width"), (int) call.argument("height")); + + snapShotOptions.withLogo((boolean) call.argument("withLogo")); + Style.Builder styleBuilder = new Style.Builder(); + if (call.hasArgument("styleUri")) { + styleBuilder.fromUri((String) call.argument("styleUri")); + } else if (call.hasArgument("styleJson")) { + styleBuilder.fromJson((String) call.argument("styleJson")); + } else { + if (style == null) { + result.error( + "STYLE IS NULL", + "The style is null. Has onStyleLoaded() already been invoked?", + null); + } + styleBuilder.fromUri(style.getUri()); + } + snapShotOptions.withStyleBuilder(styleBuilder); + if (call.hasArgument("bounds")) { + FeatureCollection bounds = FeatureCollection.fromJson((String) call.argument("bounds")); + snapShotOptions.withRegion(GeoJSONUtils.toLatLngBounds(bounds)); + } else if (call.hasArgument("centerCoordinate")) { + Feature centerPoint = Feature.fromJson((String) call.argument("centerCoordinate")); + CameraPosition cameraPosition = + new CameraPosition.Builder() + .target(GeoJSONUtils.toLatLng((Point) centerPoint.geometry())) + .tilt((double) call.argument("pitch")) + .bearing((double) call.argument("heading")) + .zoom((double) call.argument("zoomLevel")) + .build(); + snapShotOptions.withCameraPosition(cameraPosition); + } else { + snapShotOptions.withRegion(mapboxMap.getProjection().getVisibleRegion().latLngBounds); + } + + final MapSnapshotter snapshotter = new MapSnapshotter(context, snapShotOptions); + final String snapshotterID = UUID.randomUUID().toString(); + mSnapshotterMap.put(snapshotterID, snapshotter); + + snapshotter.start( + snapshot -> { + Bitmap bitmap = snapshot.getBitmap(); + + String result1; + if ((boolean) call.argument("writeToDisk")) { + result1 = BitmapUtils.createTempFile(context, bitmap); + } else { + result1 = BitmapUtils.createBase64(bitmap); + } + + if (result1 == null) { + result.error( + "NO_RESULT", + "Could not generate snapshot, please check Android logs for more info.", + null); + return; + } + + result.success(result1); + mSnapshotterMap.remove(snapshotterID); + }, + new MapSnapshotter.ErrorHandler() { + @Override + public void onError(String error) { + result.error("SNAPSHOT_ERROR", error, null); + mSnapshotterMap.remove(snapshotterID); + } + }); + break; + } default: result.notImplemented(); } diff --git a/example/.gitignore b/example/.gitignore index 47e0b4d62..92b28e840 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -9,6 +9,7 @@ .buildlog/ .history .svn/ +.fvm/ # IntelliJ related *.iml diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index bba79f17c..519763b2e 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ + @drawable/launch_background + diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index c40f8995b..bfc583f29 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -381,10 +381,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -512,10 +509,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -539,10 +533,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 70693e4a8..175629709 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,13 +1,13 @@ -import UIKit import Flutter +import UIKit @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } } diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 8fbf2ef03..922b790c3 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -51,5 +51,7 @@ Shows your location on the map and helps improve the map NSLocationAlwaysUsageDescription Shows your location on the map and helps improve the map + CADisableMinimumFrameDurationOnPhone + diff --git a/example/lib/generated_plugin_registrant.dart b/example/lib/generated_plugin_registrant.dart index ae1e051c7..91d1beadb 100644 --- a/example/lib/generated_plugin_registrant.dart +++ b/example/lib/generated_plugin_registrant.dart @@ -4,6 +4,7 @@ // ignore_for_file: directives_ordering // ignore_for_file: lines_longer_than_80_chars +// ignore_for_file: depend_on_referenced_packages import 'package:device_info_plus_web/device_info_plus_web.dart'; import 'package:location_web/location_web.dart'; diff --git a/example/lib/line.dart b/example/lib/line.dart index cc8e541df..1dd281b90 100644 --- a/example/lib/line.dart +++ b/example/lib/line.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +// ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/material.dart'; diff --git a/example/lib/main.dart b/example/lib/main.dart index 2b6e4c899..e28938b2c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,32 +4,32 @@ import 'dart:io'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:location/location.dart'; -import 'package:mapbox_gl_example/custom_marker.dart'; -import 'package:mapbox_gl_example/full_map.dart'; -import 'package:mapbox_gl_example/offline_regions.dart'; -import 'package:mapbox_gl_example/place_batch.dart'; -import 'package:mapbox_gl_example/layer.dart'; -import 'package:mapbox_gl_example/sources.dart'; -import 'package:device_info_plus/device_info_plus.dart'; +import 'package:mapbox_gl/mapbox_gl.dart'; import 'animate_camera.dart'; import 'annotation_order_maps.dart'; +import 'click_annotations.dart'; +import 'custom_marker.dart'; import 'full_map.dart'; +import 'layer.dart'; import 'line.dart'; import 'local_style.dart'; import 'map_ui.dart'; import 'move_camera.dart'; -import 'click_annotations.dart'; +import 'offline_regions.dart'; import 'page.dart'; +import 'place_batch.dart'; import 'place_circle.dart'; +import 'place_fill.dart'; import 'place_source.dart'; import 'place_symbol.dart'; -import 'place_fill.dart'; import 'scrolling_map.dart'; -import 'package:mapbox_gl/mapbox_gl.dart'; +import 'sources.dart'; +import 'take_snapshot.dart'; final List _allPages = [ MapUiPage(), @@ -48,6 +48,7 @@ final List _allPages = [ AnnotationOrderPage(), CustomMarkerPage(), BatchAddPage(), + TakeSnapPage(), ClickAnnotationPage(), Sources() ]; diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart index 5b6420968..b323307d3 100644 --- a/example/lib/place_fill.dart +++ b/example/lib/place_fill.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +// ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/material.dart'; diff --git a/example/lib/place_source.dart b/example/lib/place_source.dart index 426a7760d..10a1b45c2 100644 --- a/example/lib/place_source.dart +++ b/example/lib/place_source.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +// ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/material.dart'; diff --git a/example/lib/place_symbol.dart b/example/lib/place_symbol.dart index 2edc59051..055d27c9e 100644 --- a/example/lib/place_symbol.dart +++ b/example/lib/place_symbol.dart @@ -5,6 +5,7 @@ import 'dart:async'; // ignore: unnecessary_import import 'dart:core'; import 'dart:math'; +// ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/material.dart'; diff --git a/example/lib/take_snapshot.dart b/example/lib/take_snapshot.dart new file mode 100644 index 000000000..49288a9b0 --- /dev/null +++ b/example/lib/take_snapshot.dart @@ -0,0 +1,165 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:mapbox_gl/mapbox_gl.dart'; + +import 'main.dart'; +import 'page.dart'; + +class TakeSnapPage extends ExamplePage { + TakeSnapPage() : super(const Icon(Icons.camera_alt), 'Take snapshot'); + + @override + Widget build(BuildContext context) { + return const TakeSnapshot(); + } +} + +class TakeSnapshot extends StatefulWidget { + const TakeSnapshot(); + + @override + State createState() => FullMapState(); +} + +class FullMapState extends State { + FullMapState(); + + MapboxMapController? mapController; + final mapKey = GlobalKey(); + String? snapshotResult; + + void _onMapCreated(MapboxMapController controller) { + mapController = controller; + } + + void _onTakeSnapshot([bool writeToDisk = true]) async { + final renderBox = mapKey.currentContext?.findRenderObject() as RenderBox; + + final snapshotOptions = SnapshotOptions( + width: renderBox.size.width, + height: renderBox.size.height, + writeToDisk: writeToDisk, + withLogo: false, + ); + final result = await mapController?.takeSnapshot(snapshotOptions); + debugPrint("result: $result"); + _setResult(result); + } + + void _onTakeSnapshotWithBounds() async { + final renderBox = mapKey.currentContext?.findRenderObject() as RenderBox; + final bounds = await mapController?.getVisibleRegion(); + + final snapshotOptions = SnapshotOptions( + width: renderBox.size.width, + height: renderBox.size.height, + writeToDisk: true, + withLogo: false, + bounds: bounds, + ); + final uri = await mapController?.takeSnapshot(snapshotOptions); + + _setResult(uri); + } + + void _onTakeSnapshotWithCameraPosition() async { + final renderBox = mapKey.currentContext?.findRenderObject() as RenderBox; + + final snapshotOptions = SnapshotOptions( + width: renderBox.size.width, + height: renderBox.size.height, + writeToDisk: true, + withLogo: false, + centerCoordinate: LatLng(40.79796, -74.126410), + zoomLevel: 12, + pitch: 30, + heading: 20, + ); + final uri = await mapController?.takeSnapshot(snapshotOptions); + _setResult(uri); + } + + void _setResult(String? result) { + if (result != null) { + setState(() { + snapshotResult = result.replaceAll("file:", ""); + }); + } + } + + Uint8List convertBase64Image(String base64String) { + return Base64Decoder().convert(base64String.split(',').last); + } + + @override + Widget build(BuildContext context) { + final height = MediaQuery.of(context).size.height; + return Column( + children: [ + Expanded( + child: MapboxMap( + key: mapKey, + accessToken: MapsDemo.ACCESS_TOKEN, + onMapCreated: _onMapCreated, + initialCameraPosition: + const CameraPosition(target: LatLng(0.0, 0.0)), + myLocationEnabled: true, + styleString: MapboxStyles.SATELLITE, + ), + ), + const SizedBox( + height: 5, + ), + Container( + height: height * 0.4, + child: Column( + children: [ + Wrap( + spacing: 10, + alignment: WrapAlignment.center, + children: [ + ElevatedButton( + onPressed: _onTakeSnapshot, + child: Text("Take Snap"), + ), + ElevatedButton( + onPressed: _onTakeSnapshotWithBounds, + child: Text("With Bounds"), + ), + ElevatedButton( + onPressed: _onTakeSnapshotWithCameraPosition, + child: Text("With Camera Position"), + ), + ElevatedButton( + onPressed: () => _onTakeSnapshot(false), + child: Text("With Base64"), + ), + ], + ), + const SizedBox( + height: 10, + ), + if (snapshotResult != null) + Container( + decoration: BoxDecoration(border: Border.all()), + child: snapshotResult!.contains("base64") + ? Image.memory( + convertBase64Image(snapshotResult!), + gaplessPlayback: true, + height: height * 0.20, + ) + : Image.file( + File(snapshotResult!), + height: height * 0.20, + ), + ), + ], + ), + ) + ], + ); + } +} diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index d53ef6437..8f3dd47c6 100644 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -3,7 +3,7 @@ import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { - override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true - } + override func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool { + return true + } } diff --git a/example/macos/Runner/MainFlutterWindow.swift b/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..decbd0eb1 100644 --- a/example/macos/Runner/MainFlutterWindow.swift +++ b/example/macos/Runner/MainFlutterWindow.swift @@ -2,14 +2,14 @@ import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { - override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = frame + contentViewController = flutterViewController + setFrame(windowFrame, display: true) - RegisterGeneratedPlugins(registry: flutterViewController) + RegisterGeneratedPlugins(registry: flutterViewController) - super.awakeFromNib() - } + super.awakeFromNib() + } } diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index 703b68c31..7fa523e0e 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -697,6 +697,120 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma guard let geojson = arguments["geojsonFeature"] as? String else { return } setFeature(sourceId: sourceId, geojsonFeature: geojson) result(nil) + case "snapshot#takeSnapshot": + guard let arguments = methodCall.arguments as? [String: Any] else { return } + let camera = MGLMapCamera() + + guard let pitch = arguments["pitch"] as? NSNumber else { + result(FlutterError(code: "invalidArgument", message: "pitch is not a number", + details: nil)) + return + } + camera.pitch = pitch.doubleValue + + guard let heading = arguments["heading"] as? NSNumber else { + result(FlutterError(code: "invalidArgument", message: "heading is not a number", + details: nil)) + return + } + camera.heading = heading.doubleValue + + camera.centerCoordinate = mapView.centerCoordinate + if arguments["centerCoordinate"] != nil { + guard let centerCoordinate = arguments["centerCoordinate"] as? [NSNumber] else { + result(FlutterError( + code: "invalidArgument", + message: "centerCoordinate is not a number list", + details: nil + )) + return + } + camera.centerCoordinate = CLLocationCoordinate2D( + latitude: centerCoordinate[0].doubleValue, + longitude: centerCoordinate[1].doubleValue + ) + } + + guard let width = arguments["width"] as? NSNumber else { + result(FlutterError(code: "invalidArgument", message: "width is not a number", + details: nil)) + return + } + guard let height = arguments["height"] as? NSNumber else { + result(FlutterError(code: "invalidArgument", message: "height is not a number", + details: nil)) + return + } + + let size = CGSize(width: width.doubleValue, height: height.doubleValue) + + var styleURL: URL = mapView.styleURL + if arguments["styleUri"] != nil { + guard let styleUri = arguments["styleUri"] as? String else { + result( + FlutterError(code: "invalidArgument", message: "styleUri is not a string", + details: nil) + ) + return + } + styleURL = URL(string: styleUri)! + } + + let snapshotOptions: MGLMapSnapshotOptions = .init( + styleURL: styleURL, + camera: camera, + size: size + ) + + snapshotOptions.zoomLevel = mapView.zoomLevel + if arguments["zoomLevel"] != nil { + guard let zoomLevel = arguments["zoomLevel"] as? NSNumber else { + result(FlutterError(code: "invalidArgument", + message: "zoomLevel is not a number", details: nil)) + return + } + snapshotOptions.zoomLevel = zoomLevel.doubleValue + } + + if arguments["bounds"] != nil { + guard let bounds = arguments["bounds"] as? [[NSNumber]] else { + result(FlutterError(code: "invalidArgument", + message: "bounds is not a number list", details: nil)) + return + } + let sw = bounds[0] + let ne = bounds[1] + snapshotOptions.coordinateBounds = MGLCoordinateBounds( + sw: CLLocationCoordinate2D(latitude: sw[0].doubleValue, + longitude: sw[1].doubleValue), + ne: CLLocationCoordinate2D( + latitude: ne[0].doubleValue, + longitude: ne[1].doubleValue + ) + ) + } + + let snapshotter: MGLMapSnapshotter? = MGLMapSnapshotter(options: snapshotOptions) + + snapshotter?.start { snapshot, error in + if error != nil { + result(FlutterError( + code: "canCreateSnapshot", + message: error?.localizedDescription, + details: error.debugDescription + )) + } else if let image = snapshot?.image { + guard let writeToDisk = arguments["writeToDisk"] as? NSNumber else { + result(FlutterError(code: "invalidArgument", + message: "writeToDisk is not a boolean", details: nil)) + return + } + + let value = writeToDisk.boolValue ? RNMBImageUtils + .createTempFile(image) : RNMBImageUtils.createBase64(image) + result(value.absoluteString) + } + } default: result(FlutterMethodNotImplemented) } diff --git a/ios/Classes/RNMBImageUtils.swift b/ios/Classes/RNMBImageUtils.swift new file mode 100644 index 000000000..1ecd74176 --- /dev/null +++ b/ios/Classes/RNMBImageUtils.swift @@ -0,0 +1,26 @@ +// +// RNMBImageUtils.swift +// mapbox_gl +// +// Created by mac on 30/05/2022. +// + +enum RNMBImageUtils { + static func createTempFile(_ image: UIImage) -> URL { + let fileID = UUID().uuidString + let pathComponent = "Documents/rctmgl-snapshot-\(fileID).jpeg" + + let filePath = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent(pathComponent) + + let data = image.jpegData(compressionQuality: 1.0) + try! data?.write(to: filePath, options: [.atomic]) + return filePath + } + + static func createBase64(_ image: UIImage) -> URL { + let data = image.jpegData(compressionQuality: 1.0) + let b64string: String = data!.base64EncodedString(options: [.endLineWithCarriageReturn]) + let result = "data:image/jpeg;base64,\(b64string)" + return URL(string: result)! + } +} diff --git a/lib/mapbox_gl.dart b/lib/mapbox_gl.dart index 0c836a3e5..ea45970d3 100644 --- a/lib/mapbox_gl.dart +++ b/lib/mapbox_gl.dart @@ -41,6 +41,7 @@ export 'package:mapbox_gl_platform_interface/mapbox_gl_platform_interface.dart' LineOptions, Fill, FillOptions, + SnapshotOptions, SourceProperties, RasterSourceProperties, VectorSourceProperties, diff --git a/lib/src/controller.dart b/lib/src/controller.dart index bb3d3e7b3..3a5257cd3 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -1243,6 +1243,16 @@ class MapboxMapController extends ChangeNotifier { } } + /// Generates static raster images of the map. Each snapshot image depicts a portion of a map defined by an [SnapshotOptions] object you provide + /// Android/iOS: Return snapshot uri in app specific cache storage or base64 string + /// Web: Return base64 string with current camera posision of [MapboxMap] + /// + /// Default will return snapshot uri in Android and iOS + /// If you want base64 value, you must set writeToDisk option to False + Future takeSnapshot(SnapshotOptions snapshotOptions) async { + return _mapboxGlPlatform.takeSnapshot(snapshotOptions); + } + @override void dispose() { super.dispose(); diff --git a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart index 62fa0b607..5281f1f0d 100644 --- a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart @@ -3,6 +3,7 @@ library mapbox_gl_platform_interface; import 'dart:async'; import 'dart:convert'; import 'dart:math'; +import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -20,5 +21,6 @@ part 'src/method_channel_mapbox_gl.dart'; part 'src/symbol.dart'; part 'src/fill.dart'; part 'src/ui.dart'; +part 'src/snapshot.dart'; part 'src/mapbox_gl_platform_interface.dart'; part 'src/source_properties.dart'; diff --git a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart index ca1f9252d..a86ae1c57 100644 --- a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart @@ -161,6 +161,8 @@ abstract class MapboxGlPlatform { Future addSource(String sourceId, SourceProperties properties); + Future takeSnapshot(SnapshotOptions snapshotOptions); + @mustCallSuper void dispose() { // clear all callbacks to avoid cyclic refs diff --git a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart index ea83b082a..63a080e46 100644 --- a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart +++ b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -685,4 +685,16 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { @override void resizeWebMap() {} + + @override + Future takeSnapshot(SnapshotOptions snapshotOptions) async { + try { + debugPrint("${snapshotOptions.toJson()}"); + var uri = await _channel.invokeMethod( + 'snapshot#takeSnapshot', snapshotOptions.toJson()); + return uri; + } on PlatformException catch (e) { + return new Future.error(e); + } + } } diff --git a/mapbox_gl_platform_interface/lib/src/snapshot.dart b/mapbox_gl_platform_interface/lib/src/snapshot.dart new file mode 100644 index 000000000..679770a57 --- /dev/null +++ b/mapbox_gl_platform_interface/lib/src/snapshot.dart @@ -0,0 +1,155 @@ +part of mapbox_gl_platform_interface; + +/// Set of options for taking map snapshot +class SnapshotOptions { + /// Dimensions of the snapshot + /// The width of the image + final double width; + + /// Dimensions of the snapshot + /// The height of the image + final double height; + + /// If you want to take snapshot with camera position option + /// + /// Current center coordinate of camera position + final LatLng? centerCoordinate; + + /// The coordinate rectangle that encompasses the bounds to capture. This is applied after the camera position + final LatLngBounds? bounds; + + /// If you want to take snapshot with camera position option + /// + /// Zoom level of camera position + final double? zoomLevel; + + /// If you want to take snapshot with camera position option + /// + /// Pitch toward the horizon measured in degrees, with 0 degrees resulting in a two-dimensional map + final double? pitch; + + /// If you want to take snapshot with camera position option + /// + /// Heading measured in degrees clockwise from true north + final double? heading; + + /// URL of the map style to snapshot. The URL may be a full HTTP or HTTPS URL, a Mapbox style URL + final String? styleUri; + + /// StyleJson of the map style to snapshot + final String? styleJson; + + /// Android Only: The flag indicating to show the Mapbox logo + final bool withLogo; + + /// True: Save snapshot in cache and return path + /// False: Return base64 value + final bool writeToDisk; + + ///The [width] and [height] arguments must not be null + SnapshotOptions( + {required this.width, + required this.height, + this.centerCoordinate, + this.bounds, + this.zoomLevel, + double? pitch, + double? heading, + this.styleUri, + this.styleJson, + bool? withLogo, + bool? writeToDisk}) + : this.withLogo = withLogo ?? false, + this.writeToDisk = writeToDisk ?? true, + this.pitch = pitch ?? 0, + this.heading = heading ?? 0; + + Map toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('width', Platform.isAndroid ? width.toInt() : width); + addIfPresent('height', Platform.isAndroid ? height.toInt() : height); + + if (bounds != null) { + if (Platform.isAndroid) { + final featureCollection = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + bounds!.northeast.longitude, + bounds!.northeast.latitude + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + bounds!.southwest.longitude, + bounds!.southwest.latitude + ] + } + } + ] + }; + addIfPresent("bounds", featureCollection.toString()); + } else { + final list = [ + [ + bounds!.southwest.latitude, + bounds!.southwest.longitude, + ], + [ + bounds!.northeast.latitude, + bounds!.northeast.longitude, + ] + ]; + addIfPresent("bounds", list); + } + } + if (centerCoordinate != null && zoomLevel != null) { + if (Platform.isAndroid) { + final feature = { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + centerCoordinate!.longitude, + centerCoordinate!.latitude + ] + } + }; + addIfPresent('centerCoordinate', feature.toString()); + } else { + final list = [ + centerCoordinate!.latitude, + centerCoordinate!.longitude, + ]; + addIfPresent('centerCoordinate', list); + } + + addIfPresent('zoomLevel', zoomLevel); + } + addIfPresent('pitch', pitch); + addIfPresent('heading', heading); + addIfPresent('styleUri', styleUri); + addIfPresent('styleJson', styleJson); + addIfPresent('withLogo', withLogo); + addIfPresent('writeToDisk', writeToDisk); + return json; + } +} diff --git a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart index ed2918556..1bd6e5244 100644 --- a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart +++ b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart @@ -75,6 +75,7 @@ class MapboxWebGlPlatform extends MapboxGlPlatform zoom: camera['zoom'], bearing: camera['bearing'], pitch: camera['tilt'], + preserveDrawingBuffer: true, ), ); _map.on('load', _onStyleLoaded); @@ -983,6 +984,24 @@ class MapboxWebGlPlatform extends MapboxGlPlatform } } + @override + Future takeSnapshot(SnapshotOptions snapshotOptions) async { + if (snapshotOptions.styleUri != null || snapshotOptions.styleJson != null) { + throw UnsupportedError("style option is not supported"); + } + if (snapshotOptions.bounds != null) { + throw UnsupportedError("bounds option is not supported"); + } + if (snapshotOptions.centerCoordinate != null || + snapshotOptions.zoomLevel != null || + snapshotOptions.pitch != 0 || + snapshotOptions.heading != 0) { + throw UnsupportedError("camera posision option is not supported"); + } + final base64String = await _map.getCanvas().toDataUrl('image/jpeg'); + return base64String; + } + @override void resizeWebMap() { _onMapResize(); diff --git a/pubspec.lock b/pubspec.lock index 23ff03728..194fe4780 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,7 +21,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" crypto: dependency: transitive description: @@ -52,7 +52,7 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3" + version: "0.6.4" mapbox_gl_dart: dependency: transitive description: @@ -80,7 +80,7 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "0.1.4" meta: dependency: transitive description: @@ -120,7 +120,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" xml: dependency: transitive description: @@ -129,5 +129,5 @@ packages: source: hosted version: "5.1.0" sdks: - dart: ">=2.14.0 <3.0.0" + dart: ">=2.17.0-0 <3.0.0" flutter: ">=2.0.0" From 2ebb7a53ccfc77a852becba1260146902b7fb835 Mon Sep 17 00:00:00 2001 From: Simon Irmancnik Date: Mon, 10 Oct 2022 12:09:34 +0200 Subject: [PATCH 08/22] Workaround for disposal crash with flutter3 (#1172) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Ugly workaround for disposal crash with flutter3 * Fixed linter errors * Fixed formatting issues * Parameters for hybrid composition and delayed disposal * Fixing bad merge * Added useDelayedDisposal note to README * Warning message on using useDelayedDisposal * Fixed linter errors for flutter 2.10 * Fixed formatting Co-authored-by: Simon Irmančnik --- README.md | 3 + example/lib/line.dart | 1 - example/lib/place_fill.dart | 1 - example/lib/place_source.dart | 1 - example/lib/place_symbol.dart | 1 - lib/src/mapbox_map.dart | 13 +- .../lib/mapbox_gl_platform_interface.dart | 1 + .../lib/src/method_channel_mapbox_gl.dart | 45 ++- .../lib/src/view_wrappers.dart | 299 ++++++++++++++++++ 9 files changed, 350 insertions(+), 15 deletions(-) create mode 100644 mapbox_gl_platform_interface/lib/src/view_wrappers.dart diff --git a/README.md b/README.md index 6ca9d0fc7..b92caa2d6 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,9 @@ xml ... [Recommended](https://docs.mapbox.com/help/tutorials/first-steps-ios-sdk/#display-the-users-location) explanation about "Shows your location on the map and helps improve the map". +## Flutter 3.x.x issues and experimental workarounds +Since Flutter 3.x.x was introduced, it exposed some race conditions resulting in occasional crashes upon map disposal. The parameter `useDelayedDisposal` was introduced as a workaround for this issue until Flutter and/or Mapbox fix this issue properly. Use with caution - this is not yet production ready since several users still report crashes after using this workaround. + ## Running the example code See the [documentation about this topic](doc/RUNNING_EXAMPLE_CODE.md) diff --git a/example/lib/line.dart b/example/lib/line.dart index 1dd281b90..cc8e541df 100644 --- a/example/lib/line.dart +++ b/example/lib/line.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -// ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/material.dart'; diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart index b323307d3..5b6420968 100644 --- a/example/lib/place_fill.dart +++ b/example/lib/place_fill.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -// ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/material.dart'; diff --git a/example/lib/place_source.dart b/example/lib/place_source.dart index 10a1b45c2..426a7760d 100644 --- a/example/lib/place_source.dart +++ b/example/lib/place_source.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -// ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/material.dart'; diff --git a/example/lib/place_symbol.dart b/example/lib/place_symbol.dart index 055d27c9e..2edc59051 100644 --- a/example/lib/place_symbol.dart +++ b/example/lib/place_symbol.dart @@ -5,7 +5,6 @@ import 'dart:async'; // ignore: unnecessary_import import 'dart:core'; import 'dart:math'; -// ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/material.dart'; diff --git a/lib/src/mapbox_map.dart b/lib/src/mapbox_map.dart index ffd86c02e..34b380acc 100644 --- a/lib/src/mapbox_map.dart +++ b/lib/src/mapbox_map.dart @@ -55,6 +55,8 @@ class MapboxMap extends StatefulWidget { AnnotationType.line, AnnotationType.circle, ], + this.useDelayedDisposal, + this.useHybridCompositionOverride, }) : assert(annotationOrder.length <= 4), assert(annotationConsumeTapEvents.length > 0), super(key: key); @@ -222,6 +224,13 @@ class MapboxMap extends StatefulWidget { /// * All fade/transition animations have completed final OnMapIdleCallback? onMapIdle; + /// Use delayed disposal of Android View Controller to avoid flutter 3.x.x crashes + /// Use with caution - this is not yet production ready since several users still report crashes after using this workaround + final bool? useDelayedDisposal; + + /// Override hybrid mode per map instance + final bool? useHybridCompositionOverride; + /// Set `MapboxMap.useHybridComposition` to `false` in order use Virtual-Display /// (better for Android 9 and below but may result in errors on Android 12) /// or leave it `true` (default) to use Hybrid composition (Slower on Android 9 and below). @@ -251,7 +260,9 @@ class _MapboxMapState extends State { 'options': _MapboxMapOptions.fromWidget(widget).toMap(), 'accessToken': widget.accessToken, 'onAttributionClickOverride': widget.onAttributionClick != null, - 'dragEnabled': widget.dragEnabled + 'dragEnabled': widget.dragEnabled, + 'useDelayedDisposal': widget.useDelayedDisposal, + 'useHybridCompositionOverride': widget.useHybridCompositionOverride, }; return _mapboxGlPlatform.buildView( creationParams, onPlatformViewCreated, widget.gestureRecognizers); diff --git a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart index 5281f1f0d..748773d4e 100644 --- a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart @@ -11,6 +11,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; +part 'src/view_wrappers.dart'; part 'src/annotation.dart'; part 'src/callbacks.dart'; part 'src/camera.dart'; diff --git a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart index 63a080e46..a04925003 100644 --- a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart +++ b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -141,7 +141,12 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { OnPlatformViewCreatedCallback onPlatformViewCreated, Set>? gestureRecognizers) { if (defaultTargetPlatform == TargetPlatform.android) { - if (useHybridComposition) { + final useDelayedDisposalParam = + (creationParams['useDelayedDisposal'] ?? false) as bool; + final useHybridCompositionParam = + (creationParams['useHybridCompositionOverride'] ?? + useHybridComposition) as bool; + if (useHybridCompositionParam) { return PlatformViewLink( viewType: 'plugins.flutter.io/mapbox_gl', surfaceFactory: ( @@ -156,15 +161,26 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { ); }, onCreatePlatformView: (PlatformViewCreationParams params) { - final SurfaceAndroidViewController controller = - PlatformViewsService.initSurfaceAndroidView( - id: params.id, - viewType: 'plugins.flutter.io/mapbox_gl', - layoutDirection: TextDirection.ltr, - creationParams: creationParams, - creationParamsCodec: const StandardMessageCodec(), - onFocus: () => params.onFocusChanged(true), - ); + late AndroidViewController controller; + if (useDelayedDisposalParam) { + controller = WrappedPlatformViewsService.initAndroidView( + id: params.id, + viewType: 'plugins.flutter.io/mapbox_gl', + layoutDirection: TextDirection.ltr, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + onFocus: () => params.onFocusChanged(true), + ); + } else { + controller = PlatformViewsService.initAndroidView( + id: params.id, + viewType: 'plugins.flutter.io/mapbox_gl', + layoutDirection: TextDirection.ltr, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + onFocus: () => params.onFocusChanged(true), + ); + } controller.addOnPlatformViewCreatedListener( params.onPlatformViewCreated, ); @@ -177,6 +193,15 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { }, ); } else { + if (useDelayedDisposalParam) { + return AndroidViewWithWrappedController( + viewType: 'plugins.flutter.io/mapbox_gl', + onPlatformViewCreated: onPlatformViewCreated, + gestureRecognizers: gestureRecognizers, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + ); + } return AndroidView( viewType: 'plugins.flutter.io/mapbox_gl', onPlatformViewCreated: onPlatformViewCreated, diff --git a/mapbox_gl_platform_interface/lib/src/view_wrappers.dart b/mapbox_gl_platform_interface/lib/src/view_wrappers.dart new file mode 100644 index 000000000..11efef517 --- /dev/null +++ b/mapbox_gl_platform_interface/lib/src/view_wrappers.dart @@ -0,0 +1,299 @@ +part of mapbox_gl_platform_interface; + +/// This file wrapps AndroidViewController classes in order to delay disposal process. +/// It is an workaround for flutter 3, where resourses get disposed quicker than before, while Mapbox behaves badly +/// and tries to access those resources after they had been disposed, resulting in a native crash. + +class WrappedPlatformViewsService { + static AndroidViewController initAndroidView({ + required int id, + required String viewType, + required TextDirection layoutDirection, + dynamic creationParams, + MessageCodec? creationParamsCodec, + VoidCallback? onFocus, + }) { + final view = PlatformViewsService.initAndroidView( + id: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: creationParams, + creationParamsCodec: creationParamsCodec, + onFocus: onFocus, + ); + return TextureAndroidViewControllerWrapper( + view as TextureAndroidViewController); + } +} + +class TextureAndroidViewControllerWrapper + implements TextureAndroidViewController { + TextureAndroidViewControllerWrapper(this._controller); + + final TextureAndroidViewController _controller; + + @override + PointTransformer get pointTransformer => _controller.pointTransformer; + set pointTransformer(PointTransformer transformer) => + _controller.pointTransformer = transformer; + + @override + void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) => + _controller.addOnPlatformViewCreatedListener(listener); + + @override + bool get awaitingCreation => _controller.awaitingCreation; + + @override + Future clearFocus() => _controller.clearFocus(); + + @override + Future create({Size? size}) => _controller.create(size: size); + + @override + // ignore: invalid_use_of_visible_for_testing_member + List get createdCallbacks => + _controller.createdCallbacks; + + @override + Future dispatchPointerEvent(PointerEvent event) => + _controller.dispatchPointerEvent(event); + + @override + //! workaround for flutter 3.0 + Future dispose() { + //? instead of this + // _controller.dispose(); + //? we do this + unawaited(Future.delayed(Duration(seconds: 5), _controller.dispose)); + return Future(() {}); + } + + @override + bool get isCreated => _controller.isCreated; + + @override + void removeOnPlatformViewCreatedListener( + PlatformViewCreatedCallback listener) => + _controller.removeOnPlatformViewCreatedListener(listener); + + @override + Future sendMotionEvent(AndroidMotionEvent event) => + _controller.sendMotionEvent(event); + + @override + Future setLayoutDirection(TextDirection layoutDirection) => + _controller.setLayoutDirection(layoutDirection); + + @override + Future setOffset(Offset off) => _controller.setOffset(off); + + @override + Future setSize(Size size) => _controller.setSize(size); + + @override + int? get textureId => _controller.textureId; + + @override + int get viewId => _controller.viewId; +} + +class AndroidViewWithWrappedController extends StatefulWidget { + const AndroidViewWithWrappedController({ + Key? key, + required this.viewType, + this.onPlatformViewCreated, + this.hitTestBehavior = PlatformViewHitTestBehavior.opaque, + this.layoutDirection, + this.gestureRecognizers, + this.creationParams, + this.creationParamsCodec, + this.clipBehavior = Clip.hardEdge, + }) : assert(viewType != null), + assert(hitTestBehavior != null), + assert(creationParams == null || creationParamsCodec != null), + assert(clipBehavior != null), + super(key: key); + + final String viewType; + final PlatformViewCreatedCallback? onPlatformViewCreated; + final PlatformViewHitTestBehavior hitTestBehavior; + final TextDirection? layoutDirection; + final Set>? gestureRecognizers; + final dynamic creationParams; + final MessageCodec? creationParamsCodec; + final Clip clipBehavior; + + @override + State createState() => + _AndroidViewWithWrappedControllerState(); +} + +class _AndroidViewWithWrappedControllerState + extends State { + int? _id; + late AndroidViewController _controller; + TextDirection? _layoutDirection; + bool _initialized = false; + FocusNode? _focusNode; + + static final Set> _emptyRecognizersSet = + >{}; + + @override + Widget build(BuildContext context) { + return Focus( + focusNode: _focusNode, + onFocusChange: _onFocusChange, + child: _CopyPastedAndroidPlatformView( + controller: _controller, + hitTestBehavior: widget.hitTestBehavior, + gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet, + clipBehavior: widget.clipBehavior, + ), + ); + } + + void _initializeOnce() { + if (_initialized) { + return; + } + _initialized = true; + _createNewAndroidView(); + _focusNode = FocusNode(debugLabel: 'AndroidView(id: $_id)'); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final TextDirection newLayoutDirection = _findLayoutDirection(); + final bool didChangeLayoutDirection = + _layoutDirection != newLayoutDirection; + _layoutDirection = newLayoutDirection; + + _initializeOnce(); + if (didChangeLayoutDirection) { + // The native view will update asynchronously, in the meantime we don't want + // to block the framework. (so this is intentionally not awaiting). + _controller.setLayoutDirection(_layoutDirection!); + } + } + + @override + void didUpdateWidget(AndroidViewWithWrappedController oldWidget) { + super.didUpdateWidget(oldWidget); + + final TextDirection newLayoutDirection = _findLayoutDirection(); + final bool didChangeLayoutDirection = + _layoutDirection != newLayoutDirection; + _layoutDirection = newLayoutDirection; + + if (widget.viewType != oldWidget.viewType) { + _controller.dispose(); + _createNewAndroidView(); + return; + } + + if (didChangeLayoutDirection) { + _controller.setLayoutDirection(_layoutDirection!); + } + } + + TextDirection _findLayoutDirection() { + assert( + widget.layoutDirection != null || debugCheckHasDirectionality(context)); + return widget.layoutDirection ?? Directionality.of(context); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void _createNewAndroidView() { + _id = platformViewsRegistry.getNextPlatformViewId(); + _controller = WrappedPlatformViewsService.initAndroidView( + id: _id!, + viewType: widget.viewType, + layoutDirection: _layoutDirection!, + creationParams: widget.creationParams, + creationParamsCodec: widget.creationParamsCodec, + onFocus: () { + _focusNode!.requestFocus(); + }, + ); + if (widget.onPlatformViewCreated != null) { + _controller + .addOnPlatformViewCreatedListener(widget.onPlatformViewCreated!); + } + } + + void _onFocusChange(bool isFocused) { + if (!_controller.isCreated) { + return; + } + if (!isFocused) { + _controller.clearFocus().catchError((dynamic e) { + if (e is MissingPluginException) { + // We land the framework part of Android platform views keyboard + // support before the engine part. There will be a commit range where + // clearFocus isn't implemented in the engine. When that happens we + // just swallow the error here. Once the engine part is rolled to the + // framework I'll remove this. + // TODO(amirh): remove this once the engine's clearFocus is rolled. + return; + } + }); + return; + } + SystemChannels.textInput.invokeMethod( + 'TextInput.setPlatformViewClient', + {'platformViewId': _id}, + ).catchError((dynamic e) { + if (e is MissingPluginException) { + // We land the framework part of Android platform views keyboard + // support before the engine part. There will be a commit range where + // setPlatformViewClient isn't implemented in the engine. When that + // happens we just swallow the error here. Once the engine part is + // rolled to the framework I'll remove this. + // TODO(amirh): remove this once the engine's clearFocus is rolled. + return; + } + }); + } +} + +class _CopyPastedAndroidPlatformView extends LeafRenderObjectWidget { + const _CopyPastedAndroidPlatformView({ + required this.controller, + required this.hitTestBehavior, + required this.gestureRecognizers, + this.clipBehavior = Clip.hardEdge, + }) : assert(controller != null), + assert(hitTestBehavior != null), + assert(gestureRecognizers != null), + assert(clipBehavior != null); + + final AndroidViewController controller; + final PlatformViewHitTestBehavior hitTestBehavior; + final Set> gestureRecognizers; + final Clip clipBehavior; + + @override + RenderObject createRenderObject(BuildContext context) => RenderAndroidView( + viewController: controller, + hitTestBehavior: hitTestBehavior, + gestureRecognizers: gestureRecognizers, + clipBehavior: clipBehavior, + ); + + @override + void updateRenderObject( + BuildContext context, RenderAndroidView renderObject) { + renderObject.controller = controller; + renderObject.hitTestBehavior = hitTestBehavior; + renderObject.updateGestureRecognizers(gestureRecognizers); + renderObject.clipBehavior = clipBehavior; + } +} From 83a5cda295ef4612144e861fcde61f5225ea1264 Mon Sep 17 00:00:00 2001 From: Hung Tran <32390731+hungtrn75@users.noreply.github.com> Date: Mon, 10 Oct 2022 18:10:52 +0700 Subject: [PATCH 09/22] Support heatmap layer & fill extrusion layer & update image source (#1187) * fix: can not find path when run script in macOS & nested dart types errors * fix: can not sync project * feat: support heatmap layer * feat: support heatmap layer * feat: support heatmap layer * feat: support heatmap layer * local pbxproj * fix: error when not await future setSymboAllowOverlap in example * fix: error when not await future setSymboAllowOverlap in example * add fill extrusion example * fix: error generate code swift template with snake case * migration: edit swift template & add ios rename props * feat: support fill extrusion layer * ci: run jobs * docs: support fill extrusion layer * docs: support fill extrusion layer * refactor codegen * add update image source example * feat: update image for image source * remove xcode teamid --- .gitignore | 1 + Makefile | 3 + README.md | 3 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../mapboxgl/LayerPropertyConverter.java | 79 ++ .../mapbox/mapboxgl/MapboxMapController.java | 143 +++- .../assets/fill-extrusion/indoor_3d_map.json | 685 ++++++++++++++++++ example/ios/Runner.xcodeproj/project.pbxproj | 2 +- example/lib/place_source.dart | 48 +- example/lib/place_symbol.dart | 7 +- example/lib/sources.dart | 99 +++ example/pubspec.yaml | 1 + ios/Classes/LayerPropertyConverter.swift | 64 ++ ios/Classes/MapboxMapController.swift | 156 ++++ lib/src/controller.dart | 90 +++ lib/src/layer_properties.dart | 308 ++++++++ .../lib/src/mapbox_gl_platform_interface.dart | 19 + .../lib/src/method_channel_mapbox_gl.dart | 57 ++ .../lib/src/source_properties.dart | 8 +- .../lib/src/mapbox_web_gl_platform.dart | 39 + scripts/lib/conversions.dart | 3 + scripts/lib/generate.dart | 14 +- .../LayerPropertyConverter.swift.template | 12 +- 23 files changed, 1809 insertions(+), 34 deletions(-) create mode 100644 example/assets/fill-extrusion/indoor_3d_map.json diff --git a/.gitignore b/.gitignore index 64487b8f3..19d24b075 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ .buildlog/ .history .svn/ +.fvm/ # IntelliJ related *.iml diff --git a/Makefile b/Makefile index 93faecdf7..71ae0f508 100644 --- a/Makefile +++ b/Makefile @@ -11,3 +11,6 @@ format: install_formatting: ./install_formatting_tools.sh + +codegen: + dart scripts/lib/generate.dart \ No newline at end of file diff --git a/README.md b/README.md index b92caa2d6..112b4c2fd 100644 --- a/README.md +++ b/README.md @@ -130,8 +130,9 @@ MapboxMap( | Circle Layer | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Line Layer | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Fill Layer | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Fill Extrusion Layer | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Hillshade Layer | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Heatmap Layer | :x: | :x: | :x: | +| Heatmap Layer | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Vector Source | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Raster Source | :white_check_mark: | :white_check_mark: | :white_check_mark: | | GeoJson Source | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index e7a6f4b2f..8428c0be2 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-all.zip \ No newline at end of file +distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-all.zip \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/LayerPropertyConverter.java b/android/src/main/java/com/mapbox/mapboxgl/LayerPropertyConverter.java index 4dcb9b034..b10599dbb 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/LayerPropertyConverter.java +++ b/android/src/main/java/com/mapbox/mapboxgl/LayerPropertyConverter.java @@ -372,6 +372,50 @@ static PropertyValue[] interpretFillLayerProperties(Object o) { return properties.toArray(new PropertyValue[properties.size()]); } + static PropertyValue[] interpretFillExtrusionLayerProperties(Object o) { + final Map data = (Map) toMap(o); + final List properties = new LinkedList(); + final JsonParser parser = new JsonParser(); + + for (Map.Entry entry : data.entrySet()) { + final JsonElement jsonElement = parser.parse(entry.getValue()); + Expression expression = Expression.Converter.convert(jsonElement); + switch (entry.getKey()) { + case "fill-extrusion-opacity": + properties.add(PropertyFactory.fillExtrusionOpacity(expression)); + break; + case "fill-extrusion-color": + properties.add(PropertyFactory.fillExtrusionColor(expression)); + break; + case "fill-extrusion-translate": + properties.add(PropertyFactory.fillExtrusionTranslate(expression)); + break; + case "fill-extrusion-translate-anchor": + properties.add(PropertyFactory.fillExtrusionTranslateAnchor(expression)); + break; + case "fill-extrusion-pattern": + properties.add(PropertyFactory.fillExtrusionPattern(expression)); + break; + case "fill-extrusion-height": + properties.add(PropertyFactory.fillExtrusionHeight(expression)); + break; + case "fill-extrusion-base": + properties.add(PropertyFactory.fillExtrusionBase(expression)); + break; + case "fill-extrusion-vertical-gradient": + properties.add(PropertyFactory.fillExtrusionVerticalGradient(expression)); + break; + case "visibility": + properties.add(PropertyFactory.visibility(entry.getValue())); + break; + default: + break; + } + } + + return properties.toArray(new PropertyValue[properties.size()]); + } + static PropertyValue[] interpretRasterLayerProperties(Object o) { final Map data = (Map) toMap(o); final List properties = new LinkedList(); @@ -453,4 +497,39 @@ static PropertyValue[] interpretHillshadeLayerProperties(Object o) { return properties.toArray(new PropertyValue[properties.size()]); } + + static PropertyValue[] interpretHeatmapLayerProperties(Object o) { + final Map data = (Map) toMap(o); + final List properties = new LinkedList(); + final JsonParser parser = new JsonParser(); + + for (Map.Entry entry : data.entrySet()) { + final JsonElement jsonElement = parser.parse(entry.getValue()); + Expression expression = Expression.Converter.convert(jsonElement); + switch (entry.getKey()) { + case "heatmap-radius": + properties.add(PropertyFactory.heatmapRadius(expression)); + break; + case "heatmap-weight": + properties.add(PropertyFactory.heatmapWeight(expression)); + break; + case "heatmap-intensity": + properties.add(PropertyFactory.heatmapIntensity(expression)); + break; + case "heatmap-color": + properties.add(PropertyFactory.heatmapColor(expression)); + break; + case "heatmap-opacity": + properties.add(PropertyFactory.heatmapOpacity(expression)); + break; + case "visibility": + properties.add(PropertyFactory.visibility(entry.getValue())); + break; + default: + break; + } + } + + return properties.toArray(new PropertyValue[properties.size()]); + } } diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index fe2a9f4db..22f9d28a8 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -105,6 +105,7 @@ final class MapboxMapController OnMapReadyCallback, OnCameraTrackingChangedListener, PlatformView { + private static final String TAG = "MapboxMapController"; private final int id; private final MethodChannel methodChannel; @@ -395,6 +396,7 @@ private void addSymbolLayer( PropertyValue[] properties, boolean enableInteraction, Expression filter) { + SymbolLayer symbolLayer = new SymbolLayer(layerName, sourceName); symbolLayer.setProperties(properties); if (sourceLayer != null) { @@ -487,6 +489,40 @@ private void addFillLayer( } } + private void addFillExtrusionLayer( + String layerName, + String sourceName, + String belowLayerId, + String sourceLayer, + Float minZoom, + Float maxZoom, + PropertyValue[] properties, + boolean enableInteraction, + Expression filter) { + FillExtrusionLayer fillLayer = new FillExtrusionLayer(layerName, sourceName); + fillLayer.setProperties(properties); + if (sourceLayer != null) { + fillLayer.setSourceLayer(sourceLayer); + } + if (minZoom != null) { + fillLayer.setMinZoom(minZoom); + } + if (maxZoom != null) { + fillLayer.setMaxZoom(maxZoom); + } + if (filter != null) { + fillLayer.setFilter(filter); + } + if (belowLayerId != null) { + style.addLayerBelow(fillLayer, belowLayerId); + } else { + style.addLayer(fillLayer); + } + if (enableInteraction) { + interactiveFeatureLayerIds.add(layerName); + } + } + private void addCircleLayer( String layerName, String sourceName, @@ -573,13 +609,38 @@ private void addHillshadeLayer( } } + private void addHeatmapLayer( + String layerName, + String sourceName, + Float minZoom, + Float maxZoom, + String belowLayerId, + PropertyValue[] properties, + Expression filter) { + HeatmapLayer layer = new HeatmapLayer(layerName, sourceName); + layer.setProperties(properties); + if (minZoom != null) { + layer.setMinZoom(minZoom); + } + if (maxZoom != null) { + layer.setMaxZoom(maxZoom); + } + if (belowLayerId != null) { + style.addLayerBelow(layer, belowLayerId); + } else { + style.addLayer(layer); + } + } + private Feature firstFeatureOnLayers(RectF in) { if (style != null) { final List layers = style.getLayers(); final List layersInOrder = new ArrayList(); for (Layer layer : layers) { String id = layer.getId(); - if (interactiveFeatureLayerIds.contains(id)) layersInOrder.add(id); + if (interactiveFeatureLayerIds.contains(id)) { + layersInOrder.add(id); + } } Collections.reverse(layersInOrder); @@ -923,6 +984,37 @@ public void onError(@NonNull String message) { filterExpression); updateLocationComponentLayer(); + result.success(null); + break; + } + case "fillExtrusionLayer#add": + { + final String sourceId = call.argument("sourceId"); + final String layerId = call.argument("layerId"); + final String belowLayerId = call.argument("belowLayerId"); + final String sourceLayer = call.argument("sourceLayer"); + final Double minzoom = call.argument("minzoom"); + final Double maxzoom = call.argument("maxzoom"); + final String filter = call.argument("filter"); + final boolean enableInteraction = call.argument("enableInteraction"); + final PropertyValue[] properties = + LayerPropertyConverter.interpretFillExtrusionLayerProperties( + call.argument("properties")); + + Expression filterExpression = parseFilter(filter); + + addFillExtrusionLayer( + layerId, + sourceId, + belowLayerId, + sourceLayer, + minzoom != null ? minzoom.floatValue() : null, + maxzoom != null ? maxzoom.floatValue() : null, + properties, + enableInteraction, + filterExpression); + updateLocationComponentLayer(); + result.success(null); break; } @@ -997,6 +1089,28 @@ public void onError(@NonNull String message) { null); updateLocationComponentLayer(); + result.success(null); + break; + } + case "heatmapLayer#add": + { + final String sourceId = call.argument("sourceId"); + final String layerId = call.argument("layerId"); + final String belowLayerId = call.argument("belowLayerId"); + final Double minzoom = call.argument("minzoom"); + final Double maxzoom = call.argument("maxzoom"); + final PropertyValue[] properties = + LayerPropertyConverter.interpretHeatmapLayerProperties(call.argument("properties")); + addHeatmapLayer( + layerId, + sourceId, + minzoom != null ? minzoom.floatValue() : null, + maxzoom != null ? maxzoom.floatValue() : null, + belowLayerId, + properties, + null); + updateLocationComponentLayer(); + result.success(null); break; } @@ -1065,6 +1179,32 @@ public void onFailure(@NonNull Exception exception) { result.success(null); break; } + case "style#updateImageSource": + { + if (style == null) { + result.error( + "STYLE IS NULL", + "The style is null. Has onStyleLoaded() already been invoked?", + null); + } + ImageSource imageSource = style.getSourceAs(call.argument("imageSourceId")); + List coordinates = Convert.toLatLngList(call.argument("coordinates"), false); + if (coordinates != null) { + // https://github.com/mapbox/mapbox-maps-android/issues/302 + imageSource.setCoordinates( + new LatLngQuad( + coordinates.get(0), + coordinates.get(1), + coordinates.get(2), + coordinates.get(3))); + } + byte[] bytes = call.argument("bytes"); + if (bytes != null) { + imageSource.setImage(BitmapFactory.decodeByteArray(bytes, 0, call.argument("length"))); + } + result.success(null); + break; + } case "style#addSource": { final String id = Convert.toString(call.argument("sourceId")); @@ -1844,6 +1984,7 @@ void stopDragging() { /** Simple Listener to listen for the status of camera movements. */ public class OnCameraMoveFinishedListener implements MapboxMap.CancelableCallback { + @Override public void onFinish() {} diff --git a/example/assets/fill-extrusion/indoor_3d_map.json b/example/assets/fill-extrusion/indoor_3d_map.json new file mode 100644 index 000000000..ac81fd8ca --- /dev/null +++ b/example/assets/fill-extrusion/indoor_3d_map.json @@ -0,0 +1,685 @@ +{ + "features": [ + { + "type": "Feature", + "properties": { + "level": 1, + "name": "Bird Exhibit", + "height": 0, + "base_height": 0, + "color": "orange" + }, + "geometry": { + "coordinates": [ + [ + [-87.618312, 41.866282], + [-87.61832, 41.866674], + [-87.618087, 41.866676], + [-87.618087, 41.866661], + [-87.617423, 41.86667], + [-87.617416, 41.8663], + [-87.618312, 41.866282] + ] + ], + "type": "Polygon" + }, + "id": "06e8fa0b3f851e3ae0e1da5fc17e111e" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "Bird Exhibit", + "height": 40, + "base_height": 0, + "color": "grey" + }, + "geometry": { + "coordinates": [ + [ + [-87.617808, 41.866266], + [-87.617806, 41.866293], + [-87.617415, 41.866298], + [-87.617424, 41.866671], + [-87.617382, 41.866669], + [-87.617371, 41.866274], + [-87.617808, 41.866266] + ] + ], + "type": "Polygon" + }, + "id": "08a10ab2bf15c4d14669b588062f7f08" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "East Hallway", + "base_height": 0, + "height": 0, + "color": "indigo" + }, + "geometry": { + "coordinates": [ + [ + [-87.616704, 41.866141], + [-87.616707, 41.866338], + [-87.61572, 41.866355], + [-87.61572, 41.866148], + [-87.616704, 41.866141] + ] + ], + "type": "Polygon" + }, + "id": "09ead44537d94ece576c7bc001c33e53" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "East Entrance", + "base_height": 0, + "height": 0, + "color": "violet" + }, + "geometry": { + "coordinates": [ + [ + [-87.61544, 41.866149], + [-87.615449, 41.866358], + [-87.615721, 41.866355], + [-87.61572, 41.866143], + [-87.61544, 41.866149] + ] + ], + "type": "Polygon" + }, + "id": "12251ebf764b36cf3b8c5ad42e2deb29" + }, + { + "type": "Feature", + "properties": { + "height": 0, + "base_height": 0, + "level": 1, + "name": "Under the Earth", + "color": "red" + }, + "geometry": { + "coordinates": [ + [ + [-87.616701, 41.865816], + [-87.616705, 41.866115], + [-87.615712, 41.866125], + [-87.615706, 41.865802], + [-87.615936, 41.865801], + [-87.615938, 41.865824], + [-87.616701, 41.865816] + ] + ], + "type": "Polygon" + }, + "id": "1ce4f8c186a40b9927006644e27bfd69" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "Atrium", + "base_height": 0, + "height": 0, + "color": "yellow" + }, + "geometry": { + "coordinates": [ + [ + [-87.617365, 41.865799], + [-87.6167, 41.865815], + [-87.616718, 41.866672], + [-87.617384, 41.86667], + [-87.617365, 41.865799] + ] + ], + "type": "Polygon" + }, + "id": "369f5d8865e677120b7576b1de6082eb" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "Ancient Egypt", + "height": 0, + "base_height": 0, + "color": "blue" + }, + "geometry": { + "coordinates": [ + [ + [-87.61807, 41.865761], + [-87.618299, 41.865758], + [-87.618307, 41.866139], + [-87.617407, 41.86615], + [-87.617399, 41.865799], + [-87.61807, 41.865789], + [-87.61807, 41.865761] + ] + ], + "type": "Polygon" + }, + "id": "3e9f198afe6d7dff01ac81c6eaa511fb" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "West Arch", + "height": 40, + "base_height": 30, + "color": "grey" + }, + "geometry": { + "coordinates": [ + [ + [-87.617424, 41.86667], + [-87.617384, 41.86667], + [-87.617365, 41.865799], + [-87.617405, 41.865799], + [-87.617424, 41.86667] + ] + ], + "type": "Polygon" + }, + "id": "402706f28b793d27c78d9615fff747f2" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "Bird Exhibit", + "height": 40, + "base_height": 0, + "color": "grey" + }, + "geometry": { + "coordinates": [ + [ + [-87.618312, 41.866283], + [-87.61797, 41.866288], + [-87.617972, 41.866265], + [-87.618312, 41.866259], + [-87.618312, 41.866283] + ] + ], + "type": "Polygon" + }, + "id": "43e1e2fc8399dee075ad59764f2a2878" + }, + { + "type": "Feature", + "properties": { + "name": "Arch", + "level": 1, + "color": "grey", + "base_height": 30, + "height": 40 + }, + "geometry": { + "coordinates": [ + [ + [-87.617971, 41.866291], + [-87.617973, 41.866265], + [-87.617805, 41.866267], + [-87.617806, 41.866294], + [-87.617971, 41.866291] + ] + ], + "type": "Polygon" + }, + "id": "4528ad9b9264cbec65bb2e55ac0012c1" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "color": "grey", + "base_height": 30, + "height": 40, + "name": "Arch" + }, + "geometry": { + "coordinates": [ + [ + [-87.617979, 41.866167], + [-87.617797, 41.866168], + [-87.617799, 41.866145], + [-87.617976, 41.866144], + [-87.617979, 41.866167] + ] + ], + "type": "Polygon" + }, + "id": "4be6817c3b595042c8d971eebd0c4fd3" + }, + { + "type": "Feature", + "properties": { + "name": "Kids Zone", + "level": 1, + "base_height": 0, + "height": 0, + "color": "green" + }, + "geometry": { + "coordinates": [ + [ + [-87.616718, 41.866675], + [-87.616709, 41.866371], + [-87.615725, 41.866381], + [-87.615732, 41.866711], + [-87.61596, 41.866711], + [-87.61596, 41.866688], + [-87.616718, 41.866675] + ] + ], + "type": "Polygon" + }, + "id": "5775eba03674ea1cb3550ffb38321432" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "color": "grey", + "name": "Arch", + "height": 40, + "base_height": 30 + }, + "geometry": { + "coordinates": [ + [ + [-87.616286, 41.866119], + [-87.616286, 41.866144], + [-87.616089, 41.866149], + [-87.616086, 41.86612], + [-87.616286, 41.866119] + ] + ], + "type": "Polygon" + }, + "id": "598832b1512dc9facc855c5367251531" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "Arch", + "color": "grey", + "height": 40, + "base_height": 30 + }, + "geometry": { + "coordinates": [ + [ + [-87.616287, 41.866343], + [-87.616288, 41.866374], + [-87.616076, 41.866378], + [-87.616077, 41.866347], + [-87.616287, 41.866343] + ] + ], + "type": "Polygon" + }, + "id": "59ed13d4411ff5f88714d9af539788d2" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "color": "grey", + "name": "center_arch", + "base_height": 30, + "height": 40 + }, + "geometry": { + "coordinates": [ + [ + [-87.61737, 41.866198], + [-87.617372, 41.86624], + [-87.616708, 41.866243], + [-87.616708, 41.866203], + [-87.61737, 41.866198] + ] + ], + "type": "Polygon" + }, + "id": "67f8952a18dfe21ee0744a3e86bc41d8" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "Kids Zone", + "height": 0, + "base_height": 40, + "color": "grey" + }, + "geometry": { + "coordinates": [ + [ + [-87.615719, 41.866354], + [-87.615718, 41.866381], + [-87.616077, 41.866378], + [-87.616077, 41.866347], + [-87.615719, 41.866354] + ] + ], + "type": "Polygon" + }, + "id": "6bb2c92386bcf4678113d6b3e400ae3b" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "Under the Earth", + "height": 40, + "base_height": 0, + "color": "grey" + }, + "geometry": { + "coordinates": [ + [ + [-87.616089, 41.866149], + [-87.616089, 41.866119], + [-87.615711, 41.866124], + [-87.615713, 41.866147], + [-87.616089, 41.866149] + ] + ], + "type": "Polygon" + }, + "id": "844c87987089df6b9db3923f6a00e4d6" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "Under the Earth", + "height": 40, + "base_height": 0, + "color": "grey" + }, + "geometry": { + "coordinates": [ + [ + [-87.616707, 41.866115], + [-87.616286, 41.866119], + [-87.616285, 41.866144], + [-87.616705, 41.866141], + [-87.616707, 41.866115] + ] + ], + "type": "Polygon" + }, + "id": "85547a1ecdbd2ca1fdc6324aea3c6b70" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "North Entrance", + "height": 0, + "base_height": 0, + "color": "grey" + }, + "geometry": { + "coordinates": [ + [ + [-87.617386, 41.86667], + [-87.617388, 41.866786], + [-87.617228, 41.866786], + [-87.617226, 41.866848], + [-87.616867, 41.866849], + [-87.616868, 41.866791], + [-87.616718, 41.866791], + [-87.616718, 41.866672], + [-87.617386, 41.86667] + ] + ], + "type": "Polygon" + }, + "id": "91ab1ee01729c33568c7b57eb258e699" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "Ancient Egypt", + "height": 40, + "base_height": 0, + "color": "grey" + }, + "geometry": { + "coordinates": [ + [ + [-87.617394, 41.865799], + [-87.617405, 41.86615], + [-87.617802, 41.866147], + [-87.6178, 41.866167], + [-87.617369, 41.866172], + [-87.617364, 41.865799], + [-87.617394, 41.865799] + ] + ], + "type": "Polygon" + }, + "id": "943171d55d207024791e15294def7e8f" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "Ancient Egypt", + "height": 40, + "base_height": 0, + "color": "grey" + }, + "geometry": { + "coordinates": [ + [ + [-87.617976, 41.866166], + [-87.617976, 41.866143], + [-87.618309, 41.866139], + [-87.618309, 41.866161], + [-87.617976, 41.866166] + ] + ], + "type": "Polygon" + }, + "id": "a37230898905988cab9b72927dc99258" + }, + { + "type": "Feature", + "properties": { + "name": "West Hallway", + "level": 1, + "base_height": 0, + "height": 0, + "color": "grey" + }, + "geometry": { + "coordinates": [ + [ + [-87.618309, 41.866161], + [-87.618312, 41.86626], + [-87.61737, 41.866272], + [-87.61737, 41.86617], + [-87.618309, 41.866161] + ] + ], + "type": "Polygon" + }, + "id": "c7cc79da8711d746985f23a9b22c1d5e" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "Kids Zone", + "height": 40, + "base_height": 0, + "color": "grey" + }, + "geometry": { + "coordinates": [ + [ + [-87.616286, 41.866343], + [-87.616286, 41.866374], + [-87.61671, 41.866371], + [-87.616708, 41.866338], + [-87.616286, 41.866343] + ] + ], + "type": "Polygon" + }, + "id": "cfbf2aee6a73a45c12e7fc7432d6009e" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "South Entrance", + "height": 0, + "base_height": 0, + "color": "teal" + }, + "geometry": { + "coordinates": [ + [ + [-87.617359, 41.865801], + [-87.617355, 41.865674], + [-87.617221, 41.865677], + [-87.617219, 41.86559], + [-87.616824, 41.865595], + [-87.616826, 41.86568], + [-87.616695, 41.865683], + [-87.6167, 41.865813], + [-87.617359, 41.865801] + ] + ], + "type": "Polygon" + }, + "id": "d71ab89467076ad023161c37f4ff0d5f" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "East Arch", + "height": 40, + "base_height": 30, + "color": "grey" + }, + "geometry": { + "coordinates": [ + [ + [-87.616688, 41.866675], + [-87.616716, 41.866675], + [-87.616699, 41.865814], + [-87.616665, 41.865814], + [-87.616688, 41.866675] + ] + ], + "type": "Polygon" + }, + "id": "dd2baec57e4079eb65dcae5be495da62" + }, + { + "type": "Feature", + "properties": { + "level": 1, + "name": "outer-walls", + "base_height": 0, + "height": 40, + "color": "grey" + }, + "geometry": { + "coordinates": [ + [ + [-87.618087, 41.86666], + [-87.618087, 41.866674], + [-87.618319, 41.866674], + [-87.618298, 41.865757], + [-87.618326, 41.865756], + [-87.618347, 41.866693], + [-87.618068, 41.866696], + [-87.618067, 41.866675], + [-87.61741, 41.866684], + [-87.617416, 41.866802], + [-87.617247, 41.866803], + [-87.617246, 41.866866], + [-87.616846, 41.866868], + [-87.616848, 41.866807], + [-87.61669, 41.866811], + [-87.616693, 41.866693], + [-87.615992, 41.866701], + [-87.615992, 41.866729], + [-87.615703, 41.866733], + [-87.615689, 41.866377], + [-87.615412, 41.866382], + [-87.615411, 41.866122], + [-87.615689, 41.866119], + [-87.615682, 41.865781], + [-87.615966, 41.865779], + [-87.615969, 41.865798], + [-87.616669, 41.865794], + [-87.616663, 41.865662], + [-87.6168, 41.865661], + [-87.616796, 41.865571], + [-87.61726, 41.865559], + [-87.617258, 41.865654], + [-87.617388, 41.865652], + [-87.617395, 41.865778], + [-87.618045, 41.865773], + [-87.618044, 41.865742], + [-87.618325, 41.865739], + [-87.618326, 41.865758], + [-87.61807, 41.865761], + [-87.61807, 41.865789], + [-87.617356, 41.8658], + [-87.617356, 41.865672], + [-87.617218, 41.865677], + [-87.617215, 41.86559], + [-87.616822, 41.865595], + [-87.616827, 41.86568], + [-87.616694, 41.865681], + [-87.616697, 41.865813], + [-87.615939, 41.865824], + [-87.615937, 41.8658], + [-87.615706, 41.865802], + [-87.615713, 41.866143], + [-87.615441, 41.86615], + [-87.61545, 41.866357], + [-87.615724, 41.866353], + [-87.615733, 41.866712], + [-87.615959, 41.86671], + [-87.615959, 41.866688], + [-87.616719, 41.866672], + [-87.61672, 41.866791], + [-87.616869, 41.86679], + [-87.616868, 41.86685], + [-87.617223, 41.866847], + [-87.617227, 41.866786], + [-87.617387, 41.866785], + [-87.617383, 41.86667], + [-87.618087, 41.86666] + ] + ], + "type": "Polygon" + }, + "id": "ef6512f46485e27963c248bcc945c3db" + } + ], + "type": "FeatureCollection" +} diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index bfc583f29..846740d33 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 46; objects = { /* Begin PBXBuildFile section */ diff --git a/example/lib/place_source.dart b/example/lib/place_source.dart index 426a7760d..428dc74aa 100644 --- a/example/lib/place_source.dart +++ b/example/lib/place_source.dart @@ -64,6 +64,23 @@ class PlaceSymbolBodyState extends State { ); } + /// Update an asset image as a source to the currently displayed style + Future updateImageSourceFromAsset( + String imageSourceId, String assetName) async { + final ByteData bytes = await rootBundle.load(assetName); + final Uint8List list = bytes.buffer.asUint8List(); + return controller.updateImageSource( + imageSourceId, + list, + const LatLngQuad( + bottomRight: LatLng(-33.89884564291081, 151.25229835510254), + bottomLeft: LatLng(-33.89884564291081, 151.20131492614746), + topLeft: LatLng(-33.934601369931634, 151.20131492614746), + topRight: LatLng(-33.934601369931634, 151.25229835510254), + ), + ); + } + Future removeImageSource(String imageSourceId) { return controller.removeSource(imageSourceId); } @@ -97,17 +114,14 @@ class PlaceSymbolBodyState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Center( - child: SizedBox( - width: 300.0, - height: 200.0, - child: MapboxMap( - accessToken: MapsDemo.ACCESS_TOKEN, - onMapCreated: _onMapCreated, - initialCameraPosition: const CameraPosition( - target: LatLng(-33.852, 151.211), - zoom: 11.0, - ), + SizedBox( + height: 300.0, + child: MapboxMap( + accessToken: MapsDemo.ACCESS_TOKEN, + onMapCreated: _onMapCreated, + initialCameraPosition: const CameraPosition( + target: LatLng(-33.852, 151.211), + zoom: 10.0, ), ), ), @@ -130,6 +144,18 @@ class PlaceSymbolBodyState extends State { }); }, ), + TextButton( + child: const Text('Update source (asset image)'), + onPressed: !sourceAdded + ? null + : () { + updateImageSourceFromAsset(SOURCE_ID, + 'assets/symbols/custom-icon.png') + .then((value) { + setState(() => sourceAdded = true); + }); + }, + ), TextButton( child: const Text('Remove source (asset image)'), onPressed: sourceAdded diff --git a/example/lib/place_symbol.dart b/example/lib/place_symbol.dart index 2edc59051..c4aa68eb9 100644 --- a/example/lib/place_symbol.dart +++ b/example/lib/place_symbol.dart @@ -275,8 +275,8 @@ class PlaceSymbolBodyState extends State { setState(() { _iconAllowOverlap = !_iconAllowOverlap; }); - controller!.setSymbolIconAllowOverlap(_iconAllowOverlap); - controller!.setSymbolTextAllowOverlap(_iconAllowOverlap); + await controller!.setSymbolIconAllowOverlap(_iconAllowOverlap); + await controller!.setSymbolTextAllowOverlap(_iconAllowOverlap); } @override @@ -287,8 +287,7 @@ class PlaceSymbolBodyState extends State { children: [ Center( child: SizedBox( - width: 300.0, - height: 200.0, + height: 300.0, child: MapboxMap( accessToken: MapsDemo.ACCESS_TOKEN, onMapCreated: _onMapCreated, diff --git a/example/lib/sources.dart b/example/lib/sources.dart index 86200c154..04e4c9e76 100644 --- a/example/lib/sources.dart +++ b/example/lib/sources.dart @@ -1,5 +1,8 @@ +import 'dart:convert'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:mapbox_gl/mapbox_gl.dart'; import 'main.dart'; @@ -99,6 +102,89 @@ class FullMapState extends State { )); } + static Future addGeojsonHeatmap(MapboxMapController controller) async { + await controller.addSource( + "earthquakes-heatmap-source", + GeojsonSourceProperties( + data: + 'https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson', + )); + await controller.addLayer( + "earthquakes-heatmap-source", + "earthquakes-heatmap-layer", + HeatmapLayerProperties( + heatmapColor: [ + Expressions.interpolate, + ["linear"], + ["heatmap-density"], + 0, + "rgba(33.0, 102.0, 172.0, 0.0)", + 0.2, + "rgb(103.0, 169.0, 207.0)", + 0.4, + "rgb(209.0, 229.0, 240.0)", + 0.6, + "rgb(253.0, 219.0, 240.0)", + 0.8, + "rgb(239.0, 138.0, 98.0)", + 1, + "rgb(178.0, 24.0, 43.0)", + ], + heatmapWeight: [ + Expressions.interpolate, + ["linear"], + [Expressions.get, "mag"], + 0, + 0, + 6, + 1, + ], + heatmapIntensity: [ + Expressions.interpolate, + ["linear"], + [Expressions.zoom], + 0, + 1, + 9, + 3, + ], + heatmapRadius: [ + Expressions.interpolate, + ["linear"], + [Expressions.zoom], + 0, + 2, + 9, + 20, + ], + heatmapOpacity: [ + Expressions.interpolate, + ["linear"], + [Expressions.zoom], + 7, + 1, + 9, + 0.5 + ], + )); + } + + static Future addIndoorBuilding(MapboxMapController controller) async { + final jsonStr = + await rootBundle.loadString("assets/fill-extrusion/indoor_3d_map.json"); + await controller.addGeoJsonSource( + "indoor-building-source", jsonDecode(jsonStr)); + await controller.addFillExtrusionLayer( + "indoor-building-source", + "indoor-building-layer", + FillExtrusionLayerProperties( + fillExtrusionOpacity: 0.5, + fillExtrusionHeight: [Expressions.get, "height"], + fillExtrusionBase: [Expressions.get, "base_height"], + fillExtrusionColor: [Expressions.get, "color"], + )); + } + static Future addVector(MapboxMapController controller) async { await controller.addSource( "terrain", @@ -191,6 +277,19 @@ class FullMapState extends State { addDetails: addGeojsonCluster, position: CameraPosition(target: LatLng(33.5, -118.1), zoom: 5), ), + StyleInfo( + name: "Geojson heatmap", + baseStyle: MapboxStyles.DARK, + addDetails: addGeojsonHeatmap, + position: CameraPosition(target: LatLng(33.5, -118.1), zoom: 5), + ), + StyleInfo( + name: "Indoor Building", + baseStyle: MapboxStyles.LIGHT, + addDetails: addIndoorBuilding, + position: CameraPosition( + target: LatLng(41.86625, -87.61694), zoom: 16, tilt: 20, bearing: 40), + ), StyleInfo( name: "Raster", baseStyle: MapboxStyles.EMPTY, diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9731f59c0..f27a043bc 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -53,6 +53,7 @@ flutter: assets: - assets/fill/cat_silhouette_pattern.png + - assets/fill-extrusion/indoor_3d_map.json - assets/symbols/custom-icon.png - assets/symbols/2.0x/custom-icon.png - assets/symbols/3.0x/custom-icon.png diff --git a/ios/Classes/LayerPropertyConverter.swift b/ios/Classes/LayerPropertyConverter.swift index e36fbba54..b711adcb9 100644 --- a/ios/Classes/LayerPropertyConverter.swift +++ b/ios/Classes/LayerPropertyConverter.swift @@ -251,6 +251,41 @@ class LayerPropertyConverter { } } + class func addFillExtrusionProperties( + fillExtrusionLayer: MGLFillExtrusionStyleLayer, + properties: [String: String] + ) { + for (propertyName, propertyValue) in properties { + let expression = interpretExpression( + propertyName: propertyName, + expression: propertyValue + ) + switch propertyName { + case "fill-extrusion-opacity": + fillExtrusionLayer.fillExtrusionOpacity = expression + case "fill-extrusion-color": + fillExtrusionLayer.fillExtrusionColor = expression + case "fill-extrusion-translate": + fillExtrusionLayer.fillExtrusionTranslation = expression + case "fill-extrusion-translate-anchor": + fillExtrusionLayer.fillExtrusionTranslationAnchor = expression + case "fill-extrusion-pattern": + fillExtrusionLayer.fillExtrusionPattern = expression + case "fill-extrusion-height": + fillExtrusionLayer.fillExtrusionHeight = expression + case "fill-extrusion-base": + fillExtrusionLayer.fillExtrusionBase = expression + case "fill-extrusion-vertical-gradient": + fillExtrusionLayer.fillExtrusionHasVerticalGradient = expression + case "visibility": + fillExtrusionLayer.isVisible = propertyValue == "visible" + + default: + break + } + } + } + class func addRasterProperties(rasterLayer: MGLRasterStyleLayer, properties: [String: String]) { for (propertyName, propertyValue) in properties { let expression = interpretExpression( @@ -314,6 +349,35 @@ class LayerPropertyConverter { } } + class func addHeatmapProperties( + heatmapLayer: MGLHeatmapStyleLayer, + properties: [String: String] + ) { + for (propertyName, propertyValue) in properties { + let expression = interpretExpression( + propertyName: propertyName, + expression: propertyValue + ) + switch propertyName { + case "heatmap-radius": + heatmapLayer.heatmapRadius = expression + case "heatmap-weight": + heatmapLayer.heatmapWeight = expression + case "heatmap-intensity": + heatmapLayer.heatmapIntensity = expression + case "heatmap-color": + heatmapLayer.heatmapColor = expression + case "heatmap-opacity": + heatmapLayer.heatmapOpacity = expression + case "visibility": + heatmapLayer.isVisible = propertyValue == "visible" + + default: + break + } + } + } + private class func interpretExpression(propertyName: String, expression: String) -> NSExpression? { diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index 7fa523e0e..f9d56dbae 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -426,6 +426,34 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma case let .failure(error): result(error.flutterError) } + case "fillExtrusionLayer#add": + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let sourceId = arguments["sourceId"] as? String else { return } + guard let layerId = arguments["layerId"] as? String else { return } + guard let properties = arguments["properties"] as? [String: String] else { return } + guard let enableInteraction = arguments["enableInteraction"] as? Bool else { return } + let belowLayerId = arguments["belowLayerId"] as? String + let sourceLayer = arguments["sourceLayer"] as? String + let minzoom = arguments["minzoom"] as? Double + let maxzoom = arguments["maxzoom"] as? Double + let filter = arguments["filter"] as? String + + let addResult = addFillExtrusionLayer( + sourceId: sourceId, + layerId: layerId, + belowLayerId: belowLayerId, + sourceLayerIdentifier: sourceLayer, + minimumZoomLevel: minzoom, + maximumZoomLevel: maxzoom, + filter: filter, + enableInteraction: enableInteraction, + properties: properties + ) + switch addResult { + case .success: result(nil) + case let .failure(error): result(error.flutterError) + } + case "circleLayer#add": guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let sourceId = arguments["sourceId"] as? String else { return } @@ -472,6 +500,24 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma ) result(nil) + case "heatmapLayer#add": + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let sourceId = arguments["sourceId"] as? String else { return } + guard let layerId = arguments["layerId"] as? String else { return } + guard let properties = arguments["properties"] as? [String: String] else { return } + let belowLayerId = arguments["belowLayerId"] as? String + let minzoom = arguments["minzoom"] as? Double + let maxzoom = arguments["maxzoom"] as? Double + addHeatmapLayer( + sourceId: sourceId, + layerId: layerId, + belowLayerId: belowLayerId, + minimumZoomLevel: minzoom, + maximumZoomLevel: maxzoom, + properties: properties + ) + result(nil) + case "rasterLayer#add": guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let sourceId = arguments["sourceId"] as? String else { return } @@ -550,6 +596,41 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma mapView.style?.addSource(source) result(nil) + case "style#updateImageSource": + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let imageSourceId = arguments["imageSourceId"] as? String else { return } + guard let imageSource = mapView.style? + .source(withIdentifier: imageSourceId) as? MGLImageSource else { return } + let bytes = arguments["bytes"] as? FlutterStandardTypedData + if bytes != nil { + guard let data = bytes!.data as? Data else { return } + guard let image = UIImage(data: data) else { return } + imageSource.image = image + } + let coordinates = arguments["coordinates"] as? [[Double]] + if coordinates != nil { + let quad = MGLCoordinateQuad( + topLeft: CLLocationCoordinate2D( + latitude: coordinates![0][0], + longitude: coordinates![0][1] + ), + bottomLeft: CLLocationCoordinate2D( + latitude: coordinates![3][0], + longitude: coordinates![3][1] + ), + bottomRight: CLLocationCoordinate2D( + latitude: coordinates![2][0], + longitude: coordinates![2][1] + ), + topRight: CLLocationCoordinate2D( + latitude: coordinates![1][0], + longitude: coordinates![1][1] + ) + ) + imageSource.coordinates = quad + } + result(nil) + case "style#removeSource": guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let sourceId = arguments["sourceId"] as? String else { return } @@ -1217,6 +1298,51 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma return .success(()) } + func addFillExtrusionLayer( + sourceId: String, + layerId: String, + belowLayerId: String?, + sourceLayerIdentifier: String?, + minimumZoomLevel: Double?, + maximumZoomLevel: Double?, + filter: String?, + enableInteraction: Bool, + properties: [String: String] + ) -> Result { + if let style = mapView.style { + if let source = style.source(withIdentifier: sourceId) { + let layer = MGLFillExtrusionStyleLayer(identifier: layerId, source: source) + LayerPropertyConverter.addFillExtrusionProperties( + fillExtrusionLayer: layer, + properties: properties + ) + if let sourceLayerIdentifier = sourceLayerIdentifier { + layer.sourceLayerIdentifier = sourceLayerIdentifier + } + if let minimumZoomLevel = minimumZoomLevel { + layer.minimumZoomLevel = Float(minimumZoomLevel) + } + if let maximumZoomLevel = maximumZoomLevel { + layer.maximumZoomLevel = Float(maximumZoomLevel) + } + if let filter = filter { + if case let .failure(error) = setFilter(layer, filter) { + return .failure(error) + } + } + if let id = belowLayerId, let belowLayer = style.layer(withIdentifier: id) { + style.insertLayer(layer, below: belowLayer) + } else { + style.addLayer(layer) + } + if enableInteraction { + interactiveFeatureLayerIds.insert(layerId) + } + } + } + return .success(()) + } + func addCircleLayer( sourceId: String, layerId: String, @@ -1315,6 +1441,36 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } } + func addHeatmapLayer( + sourceId: String, + layerId: String, + belowLayerId: String?, + minimumZoomLevel: Double?, + maximumZoomLevel: Double?, + properties: [String: String] + ) { + if let style = mapView.style { + if let source = style.source(withIdentifier: sourceId) { + let layer = MGLHeatmapStyleLayer(identifier: layerId, source: source) + LayerPropertyConverter.addHeatmapProperties( + heatmapLayer: layer, + properties: properties + ) + if let minimumZoomLevel = minimumZoomLevel { + layer.minimumZoomLevel = Float(minimumZoomLevel) + } + if let maximumZoomLevel = maximumZoomLevel { + layer.maximumZoomLevel = Float(maximumZoomLevel) + } + if let id = belowLayerId, let belowLayer = style.layer(withIdentifier: id) { + style.insertLayer(layer, below: belowLayer) + } else { + style.addLayer(layer) + } + } + } + } + func addRasterLayer( sourceId: String, layerId: String, diff --git a/lib/src/controller.dart b/lib/src/controller.dart index 3a5257cd3..f58a17bb9 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -467,6 +467,46 @@ class MapboxMapController extends ChangeNotifier { ); } + /// Add a fill extrusion layer to the map with the given properties + /// + /// Consider using [addLayer] for an unified layer api. + /// + /// The returned [Future] completes after the change has been made on the + /// platform side. + /// + /// Setting [belowLayerId] adds the new layer below the given id. + /// If [enableInteraction] is set the layer is considered for touch or drag + /// events. [sourceLayer] is used to selected a specific source layer from + /// Vector source. + /// [minzoom] is the minimum (inclusive) zoom level at which the layer is + /// visible. + /// [maxzoom] is the maximum (exclusive) zoom level at which the layer is + /// visible. + /// [filter] determines which features should be rendered in the layer. + /// Filters are written as [expressions]. + /// + /// [expressions]: https://docs.mapbox.com/mapbox-gl-js/style-spec/expressions + Future addFillExtrusionLayer( + String sourceId, String layerId, FillExtrusionLayerProperties properties, + {String? belowLayerId, + String? sourceLayer, + double? minzoom, + double? maxzoom, + dynamic filter, + bool enableInteraction = true}) async { + await _mapboxGlPlatform.addFillExtrusionLayer( + sourceId, + layerId, + properties.toJson(), + belowLayerId: belowLayerId, + sourceLayer: sourceLayer, + minzoom: minzoom, + maxzoom: maxzoom, + filter: filter, + enableInteraction: enableInteraction, + ); + } + /// Add a circle layer to the map with the given properties /// /// Consider using [addLayer] for an unified layer api. @@ -569,6 +609,37 @@ class MapboxMapController extends ChangeNotifier { ); } + /// Add a heatmap layer to the map with the given properties + /// + /// Consider using [addLayer] for an unified layer api. + /// + /// The returned [Future] completes after the change has been made on the + /// platform side. + /// + /// Setting [belowLayerId] adds the new layer below the given id. + /// [sourceLayer] is used to selected a specific source layer from + /// Raster source. + /// [minzoom] is the minimum (inclusive) zoom level at which the layer is + /// visible. + /// [maxzoom] is the maximum (exclusive) zoom level at which the layer is + /// visible. + Future addHeatmapLayer( + String sourceId, String layerId, HeatmapLayerProperties properties, + {String? belowLayerId, + String? sourceLayer, + double? minzoom, + double? maxzoom}) async { + await _mapboxGlPlatform.addHeatmapLayer( + sourceId, + layerId, + properties.toJson(), + belowLayerId: belowLayerId, + sourceLayer: sourceLayer, + minzoom: minzoom, + maxzoom: maxzoom, + ); + } + /// Updates user location tracking mode. /// /// The returned [Future] completes after the change has been made on the @@ -1087,6 +1158,13 @@ class MapboxMapController extends ChangeNotifier { return _mapboxGlPlatform.addImageSource(imageSourceId, bytes, coordinates); } + /// Update an image source to the style currently displayed in the map, so that it can later be referred to by the provided id. + Future updateImageSource( + String imageSourceId, Uint8List? bytes, LatLngQuad? coordinates) { + return _mapboxGlPlatform.updateImageSource( + imageSourceId, bytes, coordinates); + } + /// Removes previously added image source by id @Deprecated("This method was renamed to removeSource") Future removeImageSource(String imageSourceId) { @@ -1196,6 +1274,12 @@ class MapboxMapController extends ChangeNotifier { minzoom: minzoom, maxzoom: maxzoom, filter: filter); + } else if (properties is FillExtrusionLayerProperties) { + addFillExtrusionLayer(sourceId, layerId, properties, + belowLayerId: belowLayerId, + sourceLayer: sourceLayer, + minzoom: minzoom, + maxzoom: maxzoom); } else if (properties is LineLayerProperties) { addLineLayer(sourceId, layerId, properties, belowLayerId: belowLayerId, @@ -1238,6 +1322,12 @@ class MapboxMapController extends ChangeNotifier { sourceLayer: sourceLayer, minzoom: minzoom, maxzoom: maxzoom); + } else if (properties is HeatmapLayerProperties) { + addHeatmapLayer(sourceId, layerId, properties, + belowLayerId: belowLayerId, + sourceLayer: sourceLayer, + minzoom: minzoom, + maxzoom: maxzoom); } else { throw UnimplementedError("Unknown layer type $properties"); } diff --git a/lib/src/layer_properties.dart b/lib/src/layer_properties.dart index 4ada94868..8624c23d9 100644 --- a/lib/src/layer_properties.dart +++ b/lib/src/layer_properties.dart @@ -1751,6 +1751,188 @@ class FillLayerProperties implements LayerProperties { } } +class FillExtrusionLayerProperties implements LayerProperties { + // Paint Properties + /// The opacity of the entire fill extrusion layer. This is rendered on a + /// per-layer, not per-feature, basis, and data-driven styling is not + /// available. + /// + /// Type: number + /// default: 1 + /// minimum: 0 + /// maximum: 1 + /// + /// Sdk Support: + /// basic functionality with js, android, ios, macos + final dynamic fillExtrusionOpacity; + + /// The base color of the extruded fill. The extrusion's surfaces will be + /// shaded differently based on this color in combination with the root + /// `light` settings. If this color is specified as `rgba` with an alpha + /// component, the alpha component will be ignored; use + /// `fill-extrusion-opacity` to set layer opacity. + /// + /// Type: color + /// default: #000000 + /// + /// Sdk Support: + /// basic functionality with js, android, ios, macos + /// data-driven styling with js, android, ios, macos + final dynamic fillExtrusionColor; + + /// The geometry's offset. Values are [x, y] where negatives indicate left + /// and up (on the flat plane), respectively. + /// + /// Type: array + /// default: [0, 0] + /// + /// Sdk Support: + /// basic functionality with js, android, ios, macos + final dynamic fillExtrusionTranslate; + + /// Controls the frame of reference for `fill-extrusion-translate`. + /// + /// Type: enum + /// default: map + /// Options: + /// "map" + /// The fill extrusion is translated relative to the map. + /// "viewport" + /// The fill extrusion is translated relative to the viewport. + /// + /// Sdk Support: + /// basic functionality with js, android, ios, macos + final dynamic fillExtrusionTranslateAnchor; + + /// Name of image in sprite to use for drawing images on extruded fills. + /// For seamless patterns, image width and height must be a factor of two + /// (2, 4, 8, ..., 512). Note that zoom-dependent expressions will be + /// evaluated only at integer zoom levels. + /// + /// Type: resolvedImage + /// + /// Sdk Support: + /// basic functionality with js, android, ios, macos + /// data-driven styling with js, android, macos, ios + final dynamic fillExtrusionPattern; + + /// The height with which to extrude this layer. + /// + /// Type: number + /// default: 0 + /// minimum: 0 + /// + /// Sdk Support: + /// basic functionality with js, android, ios, macos + /// data-driven styling with js, android, ios, macos + final dynamic fillExtrusionHeight; + + /// The height with which to extrude the base of this layer. Must be less + /// than or equal to `fill-extrusion-height`. + /// + /// Type: number + /// default: 0 + /// minimum: 0 + /// + /// Sdk Support: + /// basic functionality with js, android, ios, macos + /// data-driven styling with js, android, ios, macos + final dynamic fillExtrusionBase; + + /// Whether to apply a vertical gradient to the sides of a fill-extrusion + /// layer. If true, sides will be shaded slightly darker farther down. + /// + /// Type: boolean + /// default: true + /// + /// Sdk Support: + /// basic functionality with js, ios, macos + final dynamic fillExtrusionVerticalGradient; + + // Layout Properties + /// Whether this layer is displayed. + /// + /// Type: enum + /// default: visible + /// Options: + /// "visible" + /// The layer is shown. + /// "none" + /// The layer is not shown. + /// + /// Sdk Support: + /// basic functionality with js, android, ios, macos + final dynamic visibility; + + const FillExtrusionLayerProperties({ + this.fillExtrusionOpacity, + this.fillExtrusionColor, + this.fillExtrusionTranslate, + this.fillExtrusionTranslateAnchor, + this.fillExtrusionPattern, + this.fillExtrusionHeight, + this.fillExtrusionBase, + this.fillExtrusionVerticalGradient, + this.visibility, + }); + + FillExtrusionLayerProperties copyWith(FillExtrusionLayerProperties changes) { + return FillExtrusionLayerProperties( + fillExtrusionOpacity: + changes.fillExtrusionOpacity ?? fillExtrusionOpacity, + fillExtrusionColor: changes.fillExtrusionColor ?? fillExtrusionColor, + fillExtrusionTranslate: + changes.fillExtrusionTranslate ?? fillExtrusionTranslate, + fillExtrusionTranslateAnchor: + changes.fillExtrusionTranslateAnchor ?? fillExtrusionTranslateAnchor, + fillExtrusionPattern: + changes.fillExtrusionPattern ?? fillExtrusionPattern, + fillExtrusionHeight: changes.fillExtrusionHeight ?? fillExtrusionHeight, + fillExtrusionBase: changes.fillExtrusionBase ?? fillExtrusionBase, + fillExtrusionVerticalGradient: changes.fillExtrusionVerticalGradient ?? + fillExtrusionVerticalGradient, + visibility: changes.visibility ?? visibility, + ); + } + + Map toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('fill-extrusion-opacity', fillExtrusionOpacity); + addIfPresent('fill-extrusion-color', fillExtrusionColor); + addIfPresent('fill-extrusion-translate', fillExtrusionTranslate); + addIfPresent( + 'fill-extrusion-translate-anchor', fillExtrusionTranslateAnchor); + addIfPresent('fill-extrusion-pattern', fillExtrusionPattern); + addIfPresent('fill-extrusion-height', fillExtrusionHeight); + addIfPresent('fill-extrusion-base', fillExtrusionBase); + addIfPresent( + 'fill-extrusion-vertical-gradient', fillExtrusionVerticalGradient); + addIfPresent('visibility', visibility); + return json; + } + + factory FillExtrusionLayerProperties.fromJson(Map json) { + return FillExtrusionLayerProperties( + fillExtrusionOpacity: json['fill-extrusion-opacity'], + fillExtrusionColor: json['fill-extrusion-color'], + fillExtrusionTranslate: json['fill-extrusion-translate'], + fillExtrusionTranslateAnchor: json['fill-extrusion-translate-anchor'], + fillExtrusionPattern: json['fill-extrusion-pattern'], + fillExtrusionHeight: json['fill-extrusion-height'], + fillExtrusionBase: json['fill-extrusion-base'], + fillExtrusionVerticalGradient: json['fill-extrusion-vertical-gradient'], + visibility: json['visibility'], + ); + } +} + class RasterLayerProperties implements LayerProperties { // Paint Properties /// The opacity at which the image will be drawn. @@ -2070,3 +2252,129 @@ class HillshadeLayerProperties implements LayerProperties { ); } } + +class HeatmapLayerProperties implements LayerProperties { + // Paint Properties + /// Radius of influence of one heatmap point in pixels. Increasing the + /// value makes the heatmap smoother, but less detailed. + /// + /// Type: number + /// default: 30 + /// minimum: 1 + /// + /// Sdk Support: + /// basic functionality with js, android, ios, macos + /// data-driven styling with js, android, ios, macos + final dynamic heatmapRadius; + + /// A measure of how much an individual point contributes to the heatmap. + /// A value of 10 would be equivalent to having 10 points of weight 1 in + /// the same spot. Especially useful when combined with clustering. + /// + /// Type: number + /// default: 1 + /// minimum: 0 + /// + /// Sdk Support: + /// basic functionality with js, android, ios, macos + /// data-driven styling with js, android, ios, macos + final dynamic heatmapWeight; + + /// Similar to `heatmap-weight` but controls the intensity of the heatmap + /// globally. Primarily used for adjusting the heatmap based on zoom + /// level. + /// + /// Type: number + /// default: 1 + /// minimum: 0 + /// + /// Sdk Support: + /// basic functionality with js, android, ios, macos + final dynamic heatmapIntensity; + + /// Defines the color of each pixel based on its density value in a + /// heatmap. Should be an expression that uses `["heatmap-density"]` as + /// input. + /// + /// Type: color + /// default: [interpolate, [linear], [heatmap-density], 0, rgba(0, 0, 255, 0), 0.1, royalblue, 0.3, cyan, 0.5, lime, 0.7, yellow, 1, red] + /// + /// Sdk Support: + /// basic functionality with js, android, ios, macos + final dynamic heatmapColor; + + /// The global opacity at which the heatmap layer will be drawn. + /// + /// Type: number + /// default: 1 + /// minimum: 0 + /// maximum: 1 + /// + /// Sdk Support: + /// basic functionality with js, android, ios, macos + final dynamic heatmapOpacity; + + // Layout Properties + /// Whether this layer is displayed. + /// + /// Type: enum + /// default: visible + /// Options: + /// "visible" + /// The layer is shown. + /// "none" + /// The layer is not shown. + /// + /// Sdk Support: + /// basic functionality with js, android, ios, macos + final dynamic visibility; + + const HeatmapLayerProperties({ + this.heatmapRadius, + this.heatmapWeight, + this.heatmapIntensity, + this.heatmapColor, + this.heatmapOpacity, + this.visibility, + }); + + HeatmapLayerProperties copyWith(HeatmapLayerProperties changes) { + return HeatmapLayerProperties( + heatmapRadius: changes.heatmapRadius ?? heatmapRadius, + heatmapWeight: changes.heatmapWeight ?? heatmapWeight, + heatmapIntensity: changes.heatmapIntensity ?? heatmapIntensity, + heatmapColor: changes.heatmapColor ?? heatmapColor, + heatmapOpacity: changes.heatmapOpacity ?? heatmapOpacity, + visibility: changes.visibility ?? visibility, + ); + } + + Map toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('heatmap-radius', heatmapRadius); + addIfPresent('heatmap-weight', heatmapWeight); + addIfPresent('heatmap-intensity', heatmapIntensity); + addIfPresent('heatmap-color', heatmapColor); + addIfPresent('heatmap-opacity', heatmapOpacity); + addIfPresent('visibility', visibility); + return json; + } + + factory HeatmapLayerProperties.fromJson(Map json) { + return HeatmapLayerProperties( + heatmapRadius: json['heatmap-radius'], + heatmapWeight: json['heatmap-weight'], + heatmapIntensity: json['heatmap-intensity'], + heatmapColor: json['heatmap-color'], + heatmapOpacity: json['heatmap-opacity'], + visibility: json['visibility'], + ); + } +} diff --git a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart index a86ae1c57..c542fdea8 100644 --- a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart @@ -81,6 +81,9 @@ abstract class MapboxGlPlatform { Future addImageSource( String imageSourceId, Uint8List bytes, LatLngQuad coordinates); + Future updateImageSource( + String imageSourceId, Uint8List? bytes, LatLngQuad? coordinates); + Future addLayer(String imageLayerId, String imageSourceId, double? minzoom, double? maxzoom); @@ -145,6 +148,15 @@ abstract class MapboxGlPlatform { dynamic filter, required bool enableInteraction}); + Future addFillExtrusionLayer( + String sourceId, String layerId, Map properties, + {String? belowLayerId, + String? sourceLayer, + double? minzoom, + double? maxzoom, + dynamic filter, + required bool enableInteraction}); + Future addRasterLayer( String sourceId, String layerId, Map properties, {String? belowLayerId, @@ -159,6 +171,13 @@ abstract class MapboxGlPlatform { double? minzoom, double? maxzoom}); + Future addHeatmapLayer( + String sourceId, String layerId, Map properties, + {String? belowLayerId, + String? sourceLayer, + double? minzoom, + double? maxzoom}); + Future addSource(String sourceId, SourceProperties properties); Future takeSnapshot(SnapshotOptions snapshotOptions); diff --git a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart index a04925003..ebfe7963b 100644 --- a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart +++ b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -411,6 +411,22 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { } } + @override + Future updateImageSource( + String imageSourceId, Uint8List? bytes, LatLngQuad? coordinates) async { + try { + return await _channel + .invokeMethod('style#updateImageSource', { + 'imageSourceId': imageSourceId, + 'bytes': bytes, + 'length': bytes?.length, + 'coordinates': coordinates?.toList() + }); + } on PlatformException catch (e) { + return new Future.error(e); + } + } + @override Future toScreenLocation(LatLng latLng) async { try { @@ -647,6 +663,29 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { }); } + @override + Future addFillExtrusionLayer( + String sourceId, String layerId, Map properties, + {String? belowLayerId, + String? sourceLayer, + double? minzoom, + double? maxzoom, + dynamic filter, + required bool enableInteraction}) async { + await _channel.invokeMethod('fillExtrusionLayer#add', { + 'sourceId': sourceId, + 'layerId': layerId, + 'belowLayerId': belowLayerId, + 'sourceLayer': sourceLayer, + 'minzoom': minzoom, + 'maxzoom': maxzoom, + 'filter': jsonEncode(filter), + 'enableInteraction': enableInteraction, + 'properties': properties + .map((key, value) => MapEntry(key, jsonEncode(value))) + }); + } + @override void dispose() { super.dispose(); @@ -697,6 +736,24 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { }); } + @override + Future addHeatmapLayer( + String sourceId, String layerId, Map properties, + {String? belowLayerId, + String? sourceLayer, + double? minzoom, + double? maxzoom}) async { + await _channel.invokeMethod('heatmapLayer#add', { + 'sourceId': sourceId, + 'layerId': layerId, + 'belowLayerId': belowLayerId, + 'minzoom': minzoom, + 'maxzoom': maxzoom, + 'properties': properties + .map((key, value) => MapEntry(key, jsonEncode(value))) + }); + } + Future setFeatureForGeoJsonSource( String sourceId, Map geojsonFeature) async { await _channel.invokeMethod('source#setFeature', { diff --git a/mapbox_gl_platform_interface/lib/src/source_properties.dart b/mapbox_gl_platform_interface/lib/src/source_properties.dart index 17351e9b0..c6b450c7c 100644 --- a/mapbox_gl_platform_interface/lib/src/source_properties.dart +++ b/mapbox_gl_platform_interface/lib/src/source_properties.dart @@ -601,7 +601,7 @@ class VideoSourceProperties implements SourceProperties { /// Corners of video specified in longitude, latitude pairs. /// /// Type: array - final List? coordinates; + final List? coordinates; const VideoSourceProperties({ this.urls, @@ -610,7 +610,7 @@ class VideoSourceProperties implements SourceProperties { VideoSourceProperties copyWith( List? urls, - List? coordinates, + List? coordinates, ) { return VideoSourceProperties( urls: urls ?? this.urls, @@ -650,7 +650,7 @@ class ImageSourceProperties implements SourceProperties { /// Corners of image specified in longitude, latitude pairs. /// /// Type: array - final List? coordinates; + final List? coordinates; const ImageSourceProperties({ this.url, @@ -659,7 +659,7 @@ class ImageSourceProperties implements SourceProperties { ImageSourceProperties copyWith( String? url, - List? coordinates, + List? coordinates, ) { return ImageSourceProperties( url: url ?? this.url, diff --git a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart index 1bd6e5244..2b4a69f83 100644 --- a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart +++ b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart @@ -766,6 +766,24 @@ class MapboxWebGlPlatform extends MapboxGlPlatform enableInteraction: enableInteraction); } + @override + Future addFillExtrusionLayer( + String sourceId, String layerId, Map properties, + {String? belowLayerId, + String? sourceLayer, + double? minzoom, + double? maxzoom, + dynamic filter, + required bool enableInteraction}) async { + return _addLayer(sourceId, layerId, properties, "fill-extrusion", + belowLayerId: belowLayerId, + sourceLayer: sourceLayer, + minzoom: minzoom, + maxzoom: maxzoom, + filter: filter, + enableInteraction: enableInteraction); + } + @override Future addLineLayer( String sourceId, String layerId, Map properties, @@ -817,6 +835,21 @@ class MapboxWebGlPlatform extends MapboxGlPlatform enableInteraction: false); } + @override + Future addHeatmapLayer( + String sourceId, String layerId, Map properties, + {String? belowLayerId, + String? sourceLayer, + double? minzoom, + double? maxzoom}) async { + return _addLayer(sourceId, layerId, properties, "heatmap", + belowLayerId: belowLayerId, + sourceLayer: sourceLayer, + minzoom: minzoom, + maxzoom: maxzoom, + enableInteraction: false); + } + @override Future addRasterLayer( String sourceId, String layerId, Map properties, @@ -944,6 +977,12 @@ class MapboxWebGlPlatform extends MapboxGlPlatform throw UnimplementedError(); } + Future updateImageSource( + String imageSourceId, Uint8List? bytes, LatLngQuad? coordinates) { + // TODO: implement addImageSource + throw UnimplementedError(); + } + @override Future addLayer(String imageLayerId, String imageSourceId, double? minzoom, double? maxzoom) { diff --git a/scripts/lib/conversions.dart b/scripts/lib/conversions.dart index 1cd5a411c..016f8f86a 100644 --- a/scripts/lib/conversions.dart +++ b/scripts/lib/conversions.dart @@ -35,6 +35,9 @@ const renamedIosProperties = { "visibility": "isVisible", "rasterBrightnessMin": "minimumRasterBrightness", "rasterBrightnessMax": "maximumRasterBrightness", + "fillExtrusionTranslate": "fillExtrusionTranslation", + "fillExtrusionTranslateAnchor": "fillExtrusionTranslationAnchor", + "fillExtrusionVerticalGradient": "fillExtrusionHasVerticalGradient", }; const dartTypeMappingTable = { diff --git a/scripts/lib/generate.dart b/scripts/lib/generate.dart index e797bb688..3ee9fed6b 100644 --- a/scripts/lib/generate.dart +++ b/scripts/lib/generate.dart @@ -15,8 +15,10 @@ main() async { "circle", "line", "fill", + "fill-extrusion", "raster", - "hillshade" + "hillshade", + "heatmap" ]; final sourceTypes = [ "vector", @@ -33,6 +35,7 @@ main() async { { "type": type, "typePascal": ReCase(type).pascalCase, + "typeCamel": ReCase(type).camelCase, "paint_properties": buildStyleProperties(styleJson, "paint_$type"), "layout_properties": buildStyleProperties(styleJson, "layout_$type"), }, @@ -42,6 +45,7 @@ main() async { { "type": type.replaceAll("_", "-"), "typePascal": ReCase(type).pascalCase, + "typeCamel": ReCase(type).camelCase, "properties": buildSourceProperties(styleJson, "source_$type"), }, ], @@ -54,7 +58,7 @@ main() async { ...type["layout_properties"].map((p) => p["value"]).toList() ].toSet().map((p) => {"property": p}).toList(); - const templates = [ + final templates = [ "android/src/main/java/com/mapbox/mapboxgl/LayerPropertyConverter.java", "ios/Classes/LayerPropertyConverter.swift", "lib/src/layer_expressions.dart", @@ -76,7 +80,7 @@ Future render( print("Rendering $filename"); var templateFile = - await File('./scripts/templates/$filename.template').readAsString(); + await File('scripts/templates/$filename.template').readAsString(); var template = Template(templateFile); var outputFile = File('$outputPath/$filename'); @@ -122,9 +126,9 @@ Map buildSourceProperty( final typeDart = dartTypeMappingTable[value["type"]]; final typeSwift = swiftTypeMappingTable[value["type"]]; final nestedTypeDart = dartTypeMappingTable[value["value"]] ?? - dartTypeMappingTable[value["value"]["type"]]; + dartTypeMappingTable[value["value"]?["type"]]; final nestedTypeSwift = swiftTypeMappingTable[value["value"]] ?? - swiftTypeMappingTable[value["value"]["type"]]; + swiftTypeMappingTable[value["value"]?["type"]]; var defaultValue = value["default"]; if (defaultValue is List) { diff --git a/scripts/templates/LayerPropertyConverter.swift.template b/scripts/templates/LayerPropertyConverter.swift.template index 4719a9cdb..a06903db4 100644 --- a/scripts/templates/LayerPropertyConverter.swift.template +++ b/scripts/templates/LayerPropertyConverter.swift.template @@ -6,17 +6,17 @@ import MapboxAnnotationExtension class LayerPropertyConverter { {{#layerTypes}} - class func add{{typePascal}}Properties({{type}}Layer: MGL{{typePascal}}StyleLayer, properties: [String: String]) { + class func add{{typePascal}}Properties({{typeCamel}}Layer: MGL{{typePascal}}StyleLayer, properties: [String: String]) { for (propertyName, propertyValue) in properties { let expression = interpretExpression(propertyName: propertyName, expression: propertyValue) switch propertyName { {{#paint_properties}} case "{{{value}}}": {{#isIosAsCamelCase}} - {{type}}Layer.{{iosAsCamelCase}} = expression; + {{typeCamel}}Layer.{{iosAsCamelCase}} = expression; {{/isIosAsCamelCase}} {{^isIosAsCamelCase}} - {{type}}Layer.{{valueAsCamelCase}} = expression; + {{typeCamel}}Layer.{{valueAsCamelCase}} = expression; {{/isIosAsCamelCase}} break; {{/paint_properties}} @@ -24,14 +24,14 @@ class LayerPropertyConverter { case "{{{value}}}": {{^isVisibilityProperty}} {{#isIosAsCamelCase}} - {{type}}Layer.{{iosAsCamelCase}} = expression; + {{typeCamel}}Layer.{{iosAsCamelCase}} = expression; {{/isIosAsCamelCase}} {{^isIosAsCamelCase}} - {{type}}Layer.{{valueAsCamelCase}} = expression; + {{typeCamel}}Layer.{{valueAsCamelCase}} = expression; {{/isIosAsCamelCase}} {{/isVisibilityProperty}} {{#isVisibilityProperty}} - {{type}}Layer.{{iosAsCamelCase}} = propertyValue == "visible"; + {{typeCamel}}Layer.{{iosAsCamelCase}} = propertyValue == "visible"; {{/isVisibilityProperty}} break; {{/layout_properties}} From d220d07ad66e0b76dbd68c8da8622628c987a4fd Mon Sep 17 00:00:00 2001 From: Simon Irmancnik Date: Wed, 26 Oct 2022 14:02:01 +0200 Subject: [PATCH 10/22] Backwards compatibility for delayed disposal in flutter 2.x (#1217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Backwards compatibility for delayed disposal in flutter 2.x * Backwards compatible TextureAndroidViewController with support for size in create method * Formatting cleanup Co-authored-by: Simon Irmančnik --- .../lib/src/view_wrappers.dart | 77 +++++++++++++------ 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/mapbox_gl_platform_interface/lib/src/view_wrappers.dart b/mapbox_gl_platform_interface/lib/src/view_wrappers.dart index 11efef517..ef11fe3fd 100644 --- a/mapbox_gl_platform_interface/lib/src/view_wrappers.dart +++ b/mapbox_gl_platform_interface/lib/src/view_wrappers.dart @@ -32,35 +32,54 @@ class TextureAndroidViewControllerWrapper final TextureAndroidViewController _controller; - @override + // @override PointTransformer get pointTransformer => _controller.pointTransformer; set pointTransformer(PointTransformer transformer) => _controller.pointTransformer = transformer; - @override + // @override void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) => _controller.addOnPlatformViewCreatedListener(listener); - @override - bool get awaitingCreation => _controller.awaitingCreation; + /// Beginning with flutter 3, [_controller.awaitingCreation] should be called. + /// + /// A false value is returned in order to consolidate usage with flutter 2. + /// A false return value does not necessarily indicate that the Future + /// returned by create has completed, only that creation has been started. + // @override + bool awaitingCreation = true; - @override + // @override Future clearFocus() => _controller.clearFocus(); - @override - Future create({Size? size}) => _controller.create(size: size); + /// Beginning with flutter 3, [_controller.create] with [size] should be called. + /// + /// size is the view's initial size in logical pixel. size can be omitted + /// if the concrete implementation doesn't require an initial size to create + /// the platform view. + Future create({Size? size}) async { + await _controller.create(); + awaitingCreation = false; + if (size != null) { + await _controller.setSize(size); + } + } - @override + /// Beginning with flutter 3, [_controller.createdCallbacks] should be called. + /// + /// This is for testing purposes only and is not relevant for production code. + // @override // ignore: invalid_use_of_visible_for_testing_member - List get createdCallbacks => - _controller.createdCallbacks; + List get createdCallbacks => []; - @override + // @override Future dispatchPointerEvent(PointerEvent event) => _controller.dispatchPointerEvent(event); - @override - //! workaround for flutter 3.0 + /// Beginning with flutter 3, disposal is called to soon, resulting in crashes. + /// + /// This is a workaround for users to be able to use this plugin with flutter 3. + // ignore: invalid_use_of_visible_for_testing_member Future dispose() { //? instead of this // _controller.dispose(); @@ -69,32 +88,40 @@ class TextureAndroidViewControllerWrapper return Future(() {}); } - @override + // @override bool get isCreated => _controller.isCreated; - @override + // @override void removeOnPlatformViewCreatedListener( PlatformViewCreatedCallback listener) => _controller.removeOnPlatformViewCreatedListener(listener); - @override + // @override Future sendMotionEvent(AndroidMotionEvent event) => _controller.sendMotionEvent(event); - @override + // @override Future setLayoutDirection(TextDirection layoutDirection) => _controller.setLayoutDirection(layoutDirection); - @override - Future setOffset(Offset off) => _controller.setOffset(off); - - @override - Future setSize(Size size) => _controller.setSize(size); + /// Beginning with flutter 3, [_controller.setOffset(off)] should be called. + /// + /// off is the view's new offset in logical pixel. + /// On Android, this allows the Android native view to draw the a11y highlights + /// in the same location on the screen as the platform view widget in the Flutter framework. + // @override + Future setOffset(Offset off) => Future(() => {}); + + // @override + Future setSize(Size size) async { + await _controller.setSize(size); + return size; + } - @override + // @override int? get textureId => _controller.textureId; - @override + // @override int get viewId => _controller.viewId; } @@ -291,7 +318,7 @@ class _CopyPastedAndroidPlatformView extends LeafRenderObjectWidget { @override void updateRenderObject( BuildContext context, RenderAndroidView renderObject) { - renderObject.controller = controller; + //renderObject.controller = controller; renderObject.hitTestBehavior = hitTestBehavior; renderObject.updateGestureRecognizers(gestureRecognizers); renderObject.clipBehavior = clipBehavior; From c6c4f5af3bb5efa78bcb9f163cf6d0f1ff30a66c Mon Sep 17 00:00:00 2001 From: Aaron Eri <36516690+arkeri@users.noreply.github.com> Date: Wed, 26 Oct 2022 06:22:38 -0600 Subject: [PATCH 11/22] LayerId to match zoo example for mapbox base map (#1186) Co-authored-by: Aaron S Kennedy <36516690+aaronsamkennedy@users.noreply.github.com> --- example/lib/map_ui.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/lib/map_ui.dart b/example/lib/map_ui.dart index b10a0334a..5be6b2bd8 100644 --- a/example/lib/map_ui.dart +++ b/example/lib/map_ui.dart @@ -351,7 +351,7 @@ class MapUiBodyState extends State { "Map click: ${point.x},${point.y} ${latLng.latitude}/${latLng.longitude}"); print("Filter $_featureQueryFilter"); List features = await mapController! - .queryRenderedFeatures(point, [], _featureQueryFilter); + .queryRenderedFeatures(point, ["landuse"], _featureQueryFilter); print('# features: ${features.length}'); _clearFill(); if (features.isEmpty && _featureQueryFilter != null) { From f505fcf5c79b75c165703028af77f3af62d382ae Mon Sep 17 00:00:00 2001 From: Thijs van der Burgt Date: Wed, 26 Oct 2022 15:52:58 +0200 Subject: [PATCH 12/22] enable texture mode to fix black screens (#1216) * enable texture mode to fix black screens * run make format Co-authored-by: Thijs van der Burgt --- .../src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java index a7a27e866..44121b20c 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java @@ -14,7 +14,8 @@ class MapboxMapBuilder implements MapboxMapOptionsSink { public final String TAG = getClass().getSimpleName(); - private final MapboxMapOptions options = new MapboxMapOptions().attributionEnabled(true); + private final MapboxMapOptions options = + new MapboxMapOptions().textureMode(true).attributionEnabled(true); private boolean trackCameraPosition = false; private boolean myLocationEnabled = false; private boolean dragEnabled = true; From dd20462f2db58a23f9a4bc0333f6a5398e57b479 Mon Sep 17 00:00:00 2001 From: Eduardo Folly Date: Thu, 30 Mar 2023 14:58:16 -0300 Subject: [PATCH 13/22] Fixing view_wrappers to Flutter 3.7. (#1273) --- mapbox_gl_platform_interface/lib/src/view_wrappers.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mapbox_gl_platform_interface/lib/src/view_wrappers.dart b/mapbox_gl_platform_interface/lib/src/view_wrappers.dart index ef11fe3fd..aee2c2a0b 100644 --- a/mapbox_gl_platform_interface/lib/src/view_wrappers.dart +++ b/mapbox_gl_platform_interface/lib/src/view_wrappers.dart @@ -34,6 +34,7 @@ class TextureAndroidViewControllerWrapper // @override PointTransformer get pointTransformer => _controller.pointTransformer; + set pointTransformer(PointTransformer transformer) => _controller.pointTransformer = transformer; @@ -57,11 +58,14 @@ class TextureAndroidViewControllerWrapper /// size is the view's initial size in logical pixel. size can be omitted /// if the concrete implementation doesn't require an initial size to create /// the platform view. - Future create({Size? size}) async { + Future create({Size? size, Offset? position}) async { await _controller.create(); awaitingCreation = false; if (size != null) { await _controller.setSize(size); + if (position != null) { + await _controller.setOffset(position); + } } } @@ -123,6 +127,9 @@ class TextureAndroidViewControllerWrapper // @override int get viewId => _controller.viewId; + + // @override + bool get requiresViewComposition => _controller.requiresViewComposition; } class AndroidViewWithWrappedController extends StatefulWidget { From 9eef30d649d4b62fc000a7ebc38bb79cdda78e14 Mon Sep 17 00:00:00 2001 From: Felix Horvat Date: Thu, 6 Apr 2023 19:31:02 +0200 Subject: [PATCH 14/22] Fix flutter 3.x crashes (#1293) * fix flutter crash * fix crash * fix format * refactoring --- .../mapbox/mapboxgl/MapboxMapController.java | 4 +- lib/src/mapbox_map.dart | 5 +- .../lib/mapbox_gl_platform_interface.dart | 1 - .../lib/src/method_channel_mapbox_gl.dart | 38 +- .../lib/src/view_wrappers.dart | 333 ------------------ pubspec.lock | 54 +-- 6 files changed, 47 insertions(+), 388 deletions(-) delete mode 100644 mapbox_gl_platform_interface/lib/src/view_wrappers.dart diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index 22f9d28a8..e8ca9d319 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -1554,12 +1554,14 @@ private void destroyMapViewIfNecessary() { return; } + mapView.onStop(); + mapView.onDestroy(); + if (locationComponent != null) { locationComponent.setLocationComponentEnabled(false); } stopListeningForLocationUpdates(); - mapView.onDestroy(); mapView = null; } diff --git a/lib/src/mapbox_map.dart b/lib/src/mapbox_map.dart index 34b380acc..6649c8c24 100644 --- a/lib/src/mapbox_map.dart +++ b/lib/src/mapbox_map.dart @@ -224,8 +224,8 @@ class MapboxMap extends StatefulWidget { /// * All fade/transition animations have completed final OnMapIdleCallback? onMapIdle; - /// Use delayed disposal of Android View Controller to avoid flutter 3.x.x crashes - /// Use with caution - this is not yet production ready since several users still report crashes after using this workaround + // This flag has no effect anymore and will be removed in the next major release. + @deprecated final bool? useDelayedDisposal; /// Override hybrid mode per map instance @@ -261,7 +261,6 @@ class _MapboxMapState extends State { 'accessToken': widget.accessToken, 'onAttributionClickOverride': widget.onAttributionClick != null, 'dragEnabled': widget.dragEnabled, - 'useDelayedDisposal': widget.useDelayedDisposal, 'useHybridCompositionOverride': widget.useHybridCompositionOverride, }; return _mapboxGlPlatform.buildView( diff --git a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart index 748773d4e..5281f1f0d 100644 --- a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart @@ -11,7 +11,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -part 'src/view_wrappers.dart'; part 'src/annotation.dart'; part 'src/callbacks.dart'; part 'src/camera.dart'; diff --git a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart index ebfe7963b..e54cf7cae 100644 --- a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart +++ b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -141,8 +141,6 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { OnPlatformViewCreatedCallback onPlatformViewCreated, Set>? gestureRecognizers) { if (defaultTargetPlatform == TargetPlatform.android) { - final useDelayedDisposalParam = - (creationParams['useDelayedDisposal'] ?? false) as bool; final useHybridCompositionParam = (creationParams['useHybridCompositionOverride'] ?? useHybridComposition) as bool; @@ -162,25 +160,14 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { }, onCreatePlatformView: (PlatformViewCreationParams params) { late AndroidViewController controller; - if (useDelayedDisposalParam) { - controller = WrappedPlatformViewsService.initAndroidView( - id: params.id, - viewType: 'plugins.flutter.io/mapbox_gl', - layoutDirection: TextDirection.ltr, - creationParams: creationParams, - creationParamsCodec: const StandardMessageCodec(), - onFocus: () => params.onFocusChanged(true), - ); - } else { - controller = PlatformViewsService.initAndroidView( - id: params.id, - viewType: 'plugins.flutter.io/mapbox_gl', - layoutDirection: TextDirection.ltr, - creationParams: creationParams, - creationParamsCodec: const StandardMessageCodec(), - onFocus: () => params.onFocusChanged(true), - ); - } + controller = PlatformViewsService.initAndroidView( + id: params.id, + viewType: 'plugins.flutter.io/mapbox_gl', + layoutDirection: TextDirection.ltr, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + onFocus: () => params.onFocusChanged(true), + ); controller.addOnPlatformViewCreatedListener( params.onPlatformViewCreated, ); @@ -193,15 +180,6 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { }, ); } else { - if (useDelayedDisposalParam) { - return AndroidViewWithWrappedController( - viewType: 'plugins.flutter.io/mapbox_gl', - onPlatformViewCreated: onPlatformViewCreated, - gestureRecognizers: gestureRecognizers, - creationParams: creationParams, - creationParamsCodec: const StandardMessageCodec(), - ); - } return AndroidView( viewType: 'plugins.flutter.io/mapbox_gl', onPlatformViewCreated: onPlatformViewCreated, diff --git a/mapbox_gl_platform_interface/lib/src/view_wrappers.dart b/mapbox_gl_platform_interface/lib/src/view_wrappers.dart deleted file mode 100644 index aee2c2a0b..000000000 --- a/mapbox_gl_platform_interface/lib/src/view_wrappers.dart +++ /dev/null @@ -1,333 +0,0 @@ -part of mapbox_gl_platform_interface; - -/// This file wrapps AndroidViewController classes in order to delay disposal process. -/// It is an workaround for flutter 3, where resourses get disposed quicker than before, while Mapbox behaves badly -/// and tries to access those resources after they had been disposed, resulting in a native crash. - -class WrappedPlatformViewsService { - static AndroidViewController initAndroidView({ - required int id, - required String viewType, - required TextDirection layoutDirection, - dynamic creationParams, - MessageCodec? creationParamsCodec, - VoidCallback? onFocus, - }) { - final view = PlatformViewsService.initAndroidView( - id: id, - viewType: viewType, - layoutDirection: layoutDirection, - creationParams: creationParams, - creationParamsCodec: creationParamsCodec, - onFocus: onFocus, - ); - return TextureAndroidViewControllerWrapper( - view as TextureAndroidViewController); - } -} - -class TextureAndroidViewControllerWrapper - implements TextureAndroidViewController { - TextureAndroidViewControllerWrapper(this._controller); - - final TextureAndroidViewController _controller; - - // @override - PointTransformer get pointTransformer => _controller.pointTransformer; - - set pointTransformer(PointTransformer transformer) => - _controller.pointTransformer = transformer; - - // @override - void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) => - _controller.addOnPlatformViewCreatedListener(listener); - - /// Beginning with flutter 3, [_controller.awaitingCreation] should be called. - /// - /// A false value is returned in order to consolidate usage with flutter 2. - /// A false return value does not necessarily indicate that the Future - /// returned by create has completed, only that creation has been started. - // @override - bool awaitingCreation = true; - - // @override - Future clearFocus() => _controller.clearFocus(); - - /// Beginning with flutter 3, [_controller.create] with [size] should be called. - /// - /// size is the view's initial size in logical pixel. size can be omitted - /// if the concrete implementation doesn't require an initial size to create - /// the platform view. - Future create({Size? size, Offset? position}) async { - await _controller.create(); - awaitingCreation = false; - if (size != null) { - await _controller.setSize(size); - if (position != null) { - await _controller.setOffset(position); - } - } - } - - /// Beginning with flutter 3, [_controller.createdCallbacks] should be called. - /// - /// This is for testing purposes only and is not relevant for production code. - // @override - // ignore: invalid_use_of_visible_for_testing_member - List get createdCallbacks => []; - - // @override - Future dispatchPointerEvent(PointerEvent event) => - _controller.dispatchPointerEvent(event); - - /// Beginning with flutter 3, disposal is called to soon, resulting in crashes. - /// - /// This is a workaround for users to be able to use this plugin with flutter 3. - // ignore: invalid_use_of_visible_for_testing_member - Future dispose() { - //? instead of this - // _controller.dispose(); - //? we do this - unawaited(Future.delayed(Duration(seconds: 5), _controller.dispose)); - return Future(() {}); - } - - // @override - bool get isCreated => _controller.isCreated; - - // @override - void removeOnPlatformViewCreatedListener( - PlatformViewCreatedCallback listener) => - _controller.removeOnPlatformViewCreatedListener(listener); - - // @override - Future sendMotionEvent(AndroidMotionEvent event) => - _controller.sendMotionEvent(event); - - // @override - Future setLayoutDirection(TextDirection layoutDirection) => - _controller.setLayoutDirection(layoutDirection); - - /// Beginning with flutter 3, [_controller.setOffset(off)] should be called. - /// - /// off is the view's new offset in logical pixel. - /// On Android, this allows the Android native view to draw the a11y highlights - /// in the same location on the screen as the platform view widget in the Flutter framework. - // @override - Future setOffset(Offset off) => Future(() => {}); - - // @override - Future setSize(Size size) async { - await _controller.setSize(size); - return size; - } - - // @override - int? get textureId => _controller.textureId; - - // @override - int get viewId => _controller.viewId; - - // @override - bool get requiresViewComposition => _controller.requiresViewComposition; -} - -class AndroidViewWithWrappedController extends StatefulWidget { - const AndroidViewWithWrappedController({ - Key? key, - required this.viewType, - this.onPlatformViewCreated, - this.hitTestBehavior = PlatformViewHitTestBehavior.opaque, - this.layoutDirection, - this.gestureRecognizers, - this.creationParams, - this.creationParamsCodec, - this.clipBehavior = Clip.hardEdge, - }) : assert(viewType != null), - assert(hitTestBehavior != null), - assert(creationParams == null || creationParamsCodec != null), - assert(clipBehavior != null), - super(key: key); - - final String viewType; - final PlatformViewCreatedCallback? onPlatformViewCreated; - final PlatformViewHitTestBehavior hitTestBehavior; - final TextDirection? layoutDirection; - final Set>? gestureRecognizers; - final dynamic creationParams; - final MessageCodec? creationParamsCodec; - final Clip clipBehavior; - - @override - State createState() => - _AndroidViewWithWrappedControllerState(); -} - -class _AndroidViewWithWrappedControllerState - extends State { - int? _id; - late AndroidViewController _controller; - TextDirection? _layoutDirection; - bool _initialized = false; - FocusNode? _focusNode; - - static final Set> _emptyRecognizersSet = - >{}; - - @override - Widget build(BuildContext context) { - return Focus( - focusNode: _focusNode, - onFocusChange: _onFocusChange, - child: _CopyPastedAndroidPlatformView( - controller: _controller, - hitTestBehavior: widget.hitTestBehavior, - gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet, - clipBehavior: widget.clipBehavior, - ), - ); - } - - void _initializeOnce() { - if (_initialized) { - return; - } - _initialized = true; - _createNewAndroidView(); - _focusNode = FocusNode(debugLabel: 'AndroidView(id: $_id)'); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final TextDirection newLayoutDirection = _findLayoutDirection(); - final bool didChangeLayoutDirection = - _layoutDirection != newLayoutDirection; - _layoutDirection = newLayoutDirection; - - _initializeOnce(); - if (didChangeLayoutDirection) { - // The native view will update asynchronously, in the meantime we don't want - // to block the framework. (so this is intentionally not awaiting). - _controller.setLayoutDirection(_layoutDirection!); - } - } - - @override - void didUpdateWidget(AndroidViewWithWrappedController oldWidget) { - super.didUpdateWidget(oldWidget); - - final TextDirection newLayoutDirection = _findLayoutDirection(); - final bool didChangeLayoutDirection = - _layoutDirection != newLayoutDirection; - _layoutDirection = newLayoutDirection; - - if (widget.viewType != oldWidget.viewType) { - _controller.dispose(); - _createNewAndroidView(); - return; - } - - if (didChangeLayoutDirection) { - _controller.setLayoutDirection(_layoutDirection!); - } - } - - TextDirection _findLayoutDirection() { - assert( - widget.layoutDirection != null || debugCheckHasDirectionality(context)); - return widget.layoutDirection ?? Directionality.of(context); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - void _createNewAndroidView() { - _id = platformViewsRegistry.getNextPlatformViewId(); - _controller = WrappedPlatformViewsService.initAndroidView( - id: _id!, - viewType: widget.viewType, - layoutDirection: _layoutDirection!, - creationParams: widget.creationParams, - creationParamsCodec: widget.creationParamsCodec, - onFocus: () { - _focusNode!.requestFocus(); - }, - ); - if (widget.onPlatformViewCreated != null) { - _controller - .addOnPlatformViewCreatedListener(widget.onPlatformViewCreated!); - } - } - - void _onFocusChange(bool isFocused) { - if (!_controller.isCreated) { - return; - } - if (!isFocused) { - _controller.clearFocus().catchError((dynamic e) { - if (e is MissingPluginException) { - // We land the framework part of Android platform views keyboard - // support before the engine part. There will be a commit range where - // clearFocus isn't implemented in the engine. When that happens we - // just swallow the error here. Once the engine part is rolled to the - // framework I'll remove this. - // TODO(amirh): remove this once the engine's clearFocus is rolled. - return; - } - }); - return; - } - SystemChannels.textInput.invokeMethod( - 'TextInput.setPlatformViewClient', - {'platformViewId': _id}, - ).catchError((dynamic e) { - if (e is MissingPluginException) { - // We land the framework part of Android platform views keyboard - // support before the engine part. There will be a commit range where - // setPlatformViewClient isn't implemented in the engine. When that - // happens we just swallow the error here. Once the engine part is - // rolled to the framework I'll remove this. - // TODO(amirh): remove this once the engine's clearFocus is rolled. - return; - } - }); - } -} - -class _CopyPastedAndroidPlatformView extends LeafRenderObjectWidget { - const _CopyPastedAndroidPlatformView({ - required this.controller, - required this.hitTestBehavior, - required this.gestureRecognizers, - this.clipBehavior = Clip.hardEdge, - }) : assert(controller != null), - assert(hitTestBehavior != null), - assert(gestureRecognizers != null), - assert(clipBehavior != null); - - final AndroidViewController controller; - final PlatformViewHitTestBehavior hitTestBehavior; - final Set> gestureRecognizers; - final Clip clipBehavior; - - @override - RenderObject createRenderObject(BuildContext context) => RenderAndroidView( - viewController: controller, - hitTestBehavior: hitTestBehavior, - gestureRecognizers: gestureRecognizers, - clipBehavior: clipBehavior, - ); - - @override - void updateRenderObject( - BuildContext context, RenderAndroidView renderObject) { - //renderObject.controller = controller; - renderObject.hitTestBehavior = hitTestBehavior; - renderObject.updateGestureRecognizers(gestureRecognizers); - renderObject.clipBehavior = clipBehavior; - } -} diff --git a/pubspec.lock b/pubspec.lock index 194fe4780..3064bfbed 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,28 +5,32 @@ packages: dependency: transitive description: name: archive - url: "https://pub.dartlang.org" + sha256: a92e39b291073bb840a72cf43d96d2a63c74e9a485d227833e8ea0054d16ad16 + url: "https://pub.dev" source: hosted version: "3.1.2" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" collection: dependency: "direct main" description: name: collection - url: "https://pub.dartlang.org" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" crypto: dependency: transitive description: name: crypto - url: "https://pub.dartlang.org" + sha256: cf75650c66c0316274e21d7c43d3dea246273af5955bd94e8184837cd577575c + url: "https://pub.dev" source: hosted version: "3.0.1" flutter: @@ -43,21 +47,24 @@ packages: dependency: transitive description: name: image - url: "https://pub.dartlang.org" + sha256: "3e5c9ef82c0af7823be4cb5294a829a6e0548a6f6b4e261e6386509a9e03bcab" + url: "https://pub.dev" source: hosted version: "3.0.2" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" mapbox_gl_dart: dependency: transitive description: name: mapbox_gl_dart - url: "https://pub.dartlang.org" + sha256: de6d03718e5eb05c9eb1ddaae7f0383b28acb5afa16405e1deed7ff04dd34f3d + url: "https://pub.dev" source: hosted version: "0.2.1" mapbox_gl_platform_interface: @@ -78,28 +85,32 @@ packages: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" source: hosted - version: "0.1.4" + version: "0.2.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "2ad4cddff7f5cc0e2d13069f2a3f7a73ca18f66abd6f5ecf215219cdb3638edb" + url: "https://pub.dev" source: hosted version: "1.8.0" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + sha256: "85e8f8b118afcccf948a9844d199e56260117400bd9b9982d87bf1d62ebc1690" + url: "https://pub.dev" source: hosted version: "4.1.0" sky_engine: @@ -111,21 +122,24 @@ packages: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: "53bdf7e979cfbf3e28987552fd72f637e63f3c8724c9e56d9246942dc2fa36ee" + url: "https://pub.dev" source: hosted version: "1.3.0" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + sha256: "88d26fad429944d29b7c418177d156d04bbef049f295cf48130eccc84f0b8b4b" + url: "https://pub.dev" source: hosted version: "5.1.0" sdks: From 622146f649bdf29410dca73ddcb4ec818916f7af Mon Sep 17 00:00:00 2001 From: Simon Irmancnik Date: Fri, 14 Apr 2023 15:09:59 +0200 Subject: [PATCH 15/22] Setting style as a json object directly for web (#1279) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Simon Irmančnik --- mapbox_gl_web/lib/mapbox_gl_web.dart | 2 ++ mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/mapbox_gl_web/lib/mapbox_gl_web.dart b/mapbox_gl_web/lib/mapbox_gl_web.dart index 70756d3be..0c154a4e6 100644 --- a/mapbox_gl_web/lib/mapbox_gl_web.dart +++ b/mapbox_gl_web/lib/mapbox_gl_web.dart @@ -1,6 +1,7 @@ library mapbox_gl_web; import 'dart:async'; +import 'dart:convert'; // FIXED HERE: https://github.com/dart-lang/linter/pull/1985 // ignore_for_file: avoid_web_libraries_in_flutter import 'dart:html'; @@ -21,6 +22,7 @@ import 'package:mapbox_gl_platform_interface/mapbox_gl_platform_interface.dart'; import 'package:mapbox_gl_dart/mapbox_gl_dart.dart' hide Point, Source; import 'package:mapbox_gl_dart/mapbox_gl_dart.dart' as mapbox show Point; import 'package:image/image.dart' hide Point; +import 'package:js/js_util.dart' as jsUtil; import 'package:mapbox_gl_web/src/layer_tools.dart'; part 'src/convert.dart'; diff --git a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart index 2b4a69f83..e447db391 100644 --- a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart +++ b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart @@ -641,7 +641,13 @@ class MapboxWebGlPlatform extends MapboxGlPlatform } _interactiveFeatureLayerIds.clear(); - _map.setStyle(styleString); + try { + final styleJson = jsonDecode(styleString ?? ''); + final styleJsObject = jsUtil.jsify(styleJson); + _map.setStyle(styleJsObject); + } catch(_) { + _map.setStyle(styleString); + } // catch style loaded for later style changes if (_mapReady) { _map.once("styledata", _onStyleLoaded); From 61b435e0143d8c41b5d1cee138cf25c00a013919 Mon Sep 17 00:00:00 2001 From: Felix Horvat Date: Mon, 17 Apr 2023 21:35:47 +0200 Subject: [PATCH 16/22] Fix annotation manager crashes (#1297) * fixed crash caused be race condition in layer deletion * better layer ids for annotations * added overlap button to example --- .../mapbox/mapboxgl/MapboxMapController.java | 17 ++++++- example/lib/click_annotations.dart | 44 +++++++++++++------ ios/Classes/MapboxMapController.swift | 23 +++++++--- lib/src/annotation_manager.dart | 21 ++++++--- lib/src/controller.dart | 2 +- .../lib/src/mapbox_web_gl_platform.dart | 10 +++-- 6 files changed, 87 insertions(+), 30 deletions(-) diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index e8ca9d319..cb9046a83 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -912,6 +912,7 @@ public void onError(@NonNull String message) { Expression filterExpression = parseFilter(filter); + removeLayer(layerId); addSymbolLayer( layerId, sourceId, @@ -942,6 +943,7 @@ public void onError(@NonNull String message) { Expression filterExpression = parseFilter(filter); + removeLayer(layerId); addLineLayer( layerId, sourceId, @@ -972,6 +974,7 @@ public void onError(@NonNull String message) { Expression filterExpression = parseFilter(filter); + removeLayer(layerId); addFillLayer( layerId, sourceId, @@ -1003,6 +1006,7 @@ public void onError(@NonNull String message) { Expression filterExpression = parseFilter(filter); + removeLayer(layerId); addFillExtrusionLayer( layerId, sourceId, @@ -1033,6 +1037,7 @@ public void onError(@NonNull String message) { Expression filterExpression = parseFilter(filter); + removeLayer(layerId); addCircleLayer( layerId, sourceId, @@ -1057,6 +1062,8 @@ public void onError(@NonNull String message) { final Double maxzoom = call.argument("maxzoom"); final PropertyValue[] properties = LayerPropertyConverter.interpretRasterLayerProperties(call.argument("properties")); + + removeLayer(layerId); addRasterLayer( layerId, sourceId, @@ -1281,8 +1288,7 @@ public void onFailure(@NonNull Exception exception) { null); } String layerId = call.argument("layerId"); - style.removeLayer(layerId); - interactiveFeatureLayerIds.remove(layerId); + removeLayer(layerId); result.success(null); break; @@ -1958,6 +1964,13 @@ boolean onMove(MoveGestureDetector detector) { return true; } + void removeLayer(String layerId) { + if (style != null && layerId != null) { + style.removeLayer(layerId); + interactiveFeatureLayerIds.remove(layerId); + } + } + void onMoveEnd(MoveGestureDetector detector) { PointF pointf = detector.getFocalPoint(); invokeFeatureDrag(pointf, "end"); diff --git a/example/lib/click_annotations.dart b/example/lib/click_annotations.dart index 1a43c3b84..77a1876ac 100644 --- a/example/lib/click_annotations.dart +++ b/example/lib/click_annotations.dart @@ -28,6 +28,7 @@ class ClickAnnotationBody extends StatefulWidget { class ClickAnnotationBodyState extends State { ClickAnnotationBodyState(); static const LatLng center = const LatLng(-33.88, 151.16); + bool overlapping = false; MapboxMapController? controller; @@ -131,20 +132,37 @@ class ClickAnnotationBodyState extends State { @override Widget build(BuildContext context) { - return MapboxMap( - accessToken: MapsDemo.ACCESS_TOKEN, - annotationOrder: [ - AnnotationType.fill, - AnnotationType.line, - AnnotationType.circle, - AnnotationType.symbol, - ], - onMapCreated: _onMapCreated, - onStyleLoadedCallback: _onStyleLoaded, - initialCameraPosition: const CameraPosition( - target: center, - zoom: 12.0, + return Scaffold( + body: MapboxMap( + accessToken: MapsDemo.ACCESS_TOKEN, + annotationOrder: [ + AnnotationType.fill, + AnnotationType.line, + AnnotationType.circle, + AnnotationType.symbol, + ], + onMapCreated: _onMapCreated, + onStyleLoadedCallback: _onStyleLoaded, + initialCameraPosition: const CameraPosition( + target: center, + zoom: 12.0, + ), ), + floatingActionButton: ElevatedButton( + onPressed: () { + setState(() { + overlapping = !overlapping; + }); + controller!.setSymbolIconAllowOverlap(overlapping); + controller!.setSymbolIconIgnorePlacement(overlapping); + + controller!.setSymbolTextAllowOverlap(overlapping); + controller!.setSymbolTextIgnorePlacement(overlapping); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text("Toggle overlapping"), + )), ); } } diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index f9d56dbae..a4f939095 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -354,6 +354,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma let maxzoom = arguments["maxzoom"] as? Double let filter = arguments["filter"] as? String + removeLayer(layerId: layerId) let addResult = addSymbolLayer( sourceId: sourceId, layerId: layerId, @@ -382,6 +383,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma let maxzoom = arguments["maxzoom"] as? Double let filter = arguments["filter"] as? String + removeLayer(layerId: layerId) let addResult = addLineLayer( sourceId: sourceId, layerId: layerId, @@ -410,6 +412,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma let maxzoom = arguments["maxzoom"] as? Double let filter = arguments["filter"] as? String + removeLayer(layerId: layerId) let addResult = addFillLayer( sourceId: sourceId, layerId: layerId, @@ -438,6 +441,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma let maxzoom = arguments["maxzoom"] as? Double let filter = arguments["filter"] as? String + removeLayer(layerId: layerId) let addResult = addFillExtrusionLayer( sourceId: sourceId, layerId: layerId, @@ -466,6 +470,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma let maxzoom = arguments["maxzoom"] as? Double let filter = arguments["filter"] as? String + removeLayer(layerId: layerId) let addResult = addCircleLayer( sourceId: sourceId, layerId: layerId, @@ -490,6 +495,8 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma let belowLayerId = arguments["belowLayerId"] as? String let minzoom = arguments["minzoom"] as? Double let maxzoom = arguments["maxzoom"] as? Double + + removeLayer(layerId: layerId) addHillshadeLayer( sourceId: sourceId, layerId: layerId, @@ -508,6 +515,8 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma let belowLayerId = arguments["belowLayerId"] as? String let minzoom = arguments["minzoom"] as? Double let maxzoom = arguments["maxzoom"] as? Double + + removeLayer(layerId: layerId) addHeatmapLayer( sourceId: sourceId, layerId: layerId, @@ -730,12 +739,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma case "style#removeLayer": guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let layerId = arguments["layerId"] as? String else { return } - guard let layer = mapView.style?.layer(withIdentifier: layerId) else { - result(nil) - return - } - interactiveFeatureLayerIds.remove(layerId) - mapView.style?.removeLayer(layer) + removeLayer(layerId: layerId) result(nil) case "style#setFilter": @@ -897,6 +901,13 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } } + private func removeLayer(layerId: String) { + if let layer = mapView.style?.layer(withIdentifier: layerId) { + mapView.style?.removeLayer(layer) + interactiveFeatureLayerIds.remove(layerId) + } + } + private func loadIconImage(name: String) -> UIImage? { // Build up the full path of the asset. // First find the last '/' ans split the image name in the asset directory and the image file name. diff --git a/lib/src/annotation_manager.dart b/lib/src/annotation_manager.dart index 2aa47c0c8..65bb22426 100644 --- a/lib/src/annotation_manager.dart +++ b/lib/src/annotation_manager.dart @@ -9,7 +9,11 @@ abstract class AnnotationManager { final void Function(T)? onTap; /// base id of the manager. User [layerdIds] to get the actual ids. - final String id; + String get id => "${managerType}_$randomPostFix"; + + final String managerType; + + final String randomPostFix; List get layerIds => [for (int i = 0; i < allLayerProperties.length; i++) _makeLayerId(i)]; @@ -29,9 +33,13 @@ abstract class AnnotationManager { Set get annotations => _idToAnnotation.values.toSet(); - AnnotationManager(this.controller, - {this.onTap, this.selectLayer, required this.enableInteraction}) - : id = getRandomString() { + AnnotationManager( + this.controller, { + required this.managerType, + this.onTap, + this.selectLayer, + required this.enableInteraction, + }) : randomPostFix = getRandomString() { for (var i = 0; i < allLayerProperties.length; i++) { final layerId = _makeLayerId(i); controller.addGeoJsonSource(layerId, buildFeatureCollection([]), @@ -50,7 +58,6 @@ abstract class AnnotationManager { Future _rebuildLayers() async { for (var i = 0; i < allLayerProperties.length; i++) { final layerId = _makeLayerId(i); - await controller.removeLayer(layerId); await controller.addLayer(layerId, layerId, allLayerProperties[i]); } } @@ -172,6 +179,7 @@ class LineManager extends AnnotationManager { {void Function(Line)? onTap, bool enableInteraction = true}) : super( controller, + managerType: "line", onTap: onTap, enableInteraction: enableInteraction, selectLayer: (Line line) => line.options.linePattern == null ? 0 : 1, @@ -201,6 +209,7 @@ class FillManager extends AnnotationManager { bool enableInteraction = true, }) : super( controller, + managerType: "fill", onTap: onTap, enableInteraction: enableInteraction, selectLayer: (Fill fill) => fill.options.fillPattern == null ? 0 : 1, @@ -228,6 +237,7 @@ class CircleManager extends AnnotationManager { bool enableInteraction = true, }) : super( controller, + managerType: "circle", enableInteraction: enableInteraction, onTap: onTap, ); @@ -260,6 +270,7 @@ class SymbolManager extends AnnotationManager { _textIgnorePlacement = textIgnorePlacement, super( controller, + managerType: "symbol", enableInteraction: enableInteraction, onTap: onTap, ); diff --git a/lib/src/controller.dart b/lib/src/controller.dart index f58a17bb9..eb707b185 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -1241,7 +1241,7 @@ class MapboxMapController extends ChangeNotifier { /// Add a layer to the map with the given properties /// /// The returned [Future] completes after the change has been made on the - /// platform side. + /// platform side. If the layer already exists, the layer is updated. /// /// Setting [belowLayerId] adds the new layer below the given id. /// If [enableInteraction] is set the layer is considered for touch or drag diff --git a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart index e447db391..807134991 100644 --- a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart +++ b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart @@ -645,7 +645,7 @@ class MapboxWebGlPlatform extends MapboxGlPlatform final styleJson = jsonDecode(styleString ?? ''); final styleJsObject = jsUtil.jsify(styleJson); _map.setStyle(styleJsObject); - } catch(_) { + } catch (_) { _map.setStyle(styleString); } // catch style loaded for later style changes @@ -692,8 +692,10 @@ class MapboxWebGlPlatform extends MapboxGlPlatform @override Future removeLayer(String layerId) async { - _interactiveFeatureLayerIds.remove(layerId); - _map.removeLayer(layerId); + if (_map.getLayer(layerId) != null) { + _interactiveFeatureLayerIds.remove(layerId); + _map.removeLayer(layerId); + } } @override @@ -884,6 +886,8 @@ class MapboxWebGlPlatform extends MapboxGlPlatform final paint = Map.fromEntries( properties.entries.where((entry) => !isLayoutProperty(entry.key))); + removeLayer(layerId); + _map.addLayer({ 'id': layerId, 'type': layerType, From 318078a937858e34452555f375020d4a0fe4327b Mon Sep 17 00:00:00 2001 From: Simon Irmancnik Date: Fri, 5 May 2023 22:51:17 +0200 Subject: [PATCH 17/22] Implementation of toggling layer visibility (#1197) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implementation of toggling layer visibility * Added documentation to setVisibility, added check if layerId exists * Fixed linter errors Co-authored-by: Simon Irmančnik --- .../mapbox/mapboxgl/MapboxMapController.java | 21 +++++++++++++++++++ example/lib/layer.dart | 8 +++++++ ios/Classes/MapboxMapController.swift | 11 ++++++++++ lib/src/controller.dart | 7 +++++++ .../lib/src/mapbox_gl_platform_interface.dart | 2 ++ .../lib/src/method_channel_mapbox_gl.dart | 10 +++++++++ .../lib/src/mapbox_web_gl_platform.dart | 9 ++++++++ 7 files changed, 68 insertions(+) diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index cb9046a83..3499d4ef5 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -4,6 +4,9 @@ package com.mapbox.mapboxgl; +import static com.mapbox.mapboxsdk.style.layers.Property.*; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.visibility; + import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; @@ -1330,6 +1333,24 @@ public void onFailure(@NonNull Exception exception) { break; } + result.success(null); + break; + } + case "style#setVisibility": + { + if (style == null) { + result.error( + "STYLE IS NULL", + "The style is null. Has onStyleLoaded() already been invoked?", + null); + } + String layerId = call.argument("layerId"); + boolean isVisible = call.argument("isVisible"); + Layer layer = style.getLayer(layerId); + if (layer != null) { + layer.setProperties(isVisible ? visibility(VISIBLE) : visibility(NONE)); + } + result.success(null); break; } diff --git a/example/lib/layer.dart b/example/lib/layer.dart index 240383629..bdcd8828e 100644 --- a/example/lib/layer.dart +++ b/example/lib/layer.dart @@ -24,7 +24,9 @@ class LayerState extends State { late MapboxMapController controller; Timer? bikeTimer; Timer? filterTimer; + Timer? visibilityTimer; int filteredId = 0; + bool isVisible = true; @override Widget build(BuildContext context) { @@ -148,12 +150,18 @@ class LayerState extends State { filteredId = filteredId == 0 ? 1 : 0; controller.setFilter('fills', ['==', 'id', filteredId]); }); + + visibilityTimer = Timer.periodic(Duration(seconds: 5), (t) { + isVisible = !isVisible; + controller.setVisibility('water', isVisible); + }); } @override void dispose() { bikeTimer?.cancel(); filterTimer?.cancel(); + visibilityTimer?.cancel(); super.dispose(); } } diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index a4f939095..e9b992653 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -755,6 +755,17 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma case let .failure(error): result(error.flutterError) } + case "map#setVisibility": + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let layerId = arguments["layerId"] as? String else { return } + guard let isVisible = arguments["isVisible"] as? Bool else { return } + guard let layer = mapView.style?.layer(withIdentifier: layerId) else { + result(nil) + return + } + layer.isVisible = isVisible + result(nil) + case "source#addGeoJson": guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let sourceId = arguments["sourceId"] as? String else { return } diff --git a/lib/src/controller.dart b/lib/src/controller.dart index eb707b185..4c97274fa 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -1208,6 +1208,13 @@ class MapboxMapController extends ChangeNotifier { return _mapboxGlPlatform.setFilter(layerId, filter); } + /// Sets the visibility by specifying [isVisible] of the layer with + /// the specified id [layerId]. + /// Returns silently if [layerId] does not exist. + Future setVisibility(String layerId, bool isVisible) { + return _mapboxGlPlatform.setVisibility(layerId, isVisible); + } + /// Returns the point on the screen that corresponds to a geographical coordinate ([latLng]). The screen location is in screen pixels (not display pixels) relative to the top left of the map (not of the whole screen) /// /// Note: The resulting x and y coordinates are rounded to [int] on web, on other platforms they may differ very slightly (in the range of about 10^-10) from the actual nearest screen coordinate. diff --git a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart index c542fdea8..346998bd0 100644 --- a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart @@ -94,6 +94,8 @@ abstract class MapboxGlPlatform { Future setFilter(String layerId, dynamic filter); + Future setVisibility(String layerId, bool isVisible); + Future toScreenLocation(LatLng latLng); Future> toScreenLocationBatch(Iterable latLngs); diff --git a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart index e54cf7cae..28a8e268c 100644 --- a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart +++ b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -504,6 +504,16 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { } } + @override + Future setVisibility(String layerId, bool isVisible) async { + try { + return await _channel.invokeMethod('style#setVisibility', + {'layerId': layerId, 'isVisible': isVisible}); + } on PlatformException catch (e) { + return new Future.error(e); + } + } + @override Future toLatLng(Point screenLocation) async { try { diff --git a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart index 807134991..c3872cf50 100644 --- a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart +++ b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart @@ -703,6 +703,15 @@ class MapboxWebGlPlatform extends MapboxGlPlatform _map.setFilter(layerId, filter); } + @override + Future setVisibility(String layerId, bool isVisible) async { + final layer = _map.getLayer(layerId); + if (layer != null) { + _map.setLayoutProperty( + layerId, 'visibility', isVisible ? 'visible' : 'none'); + } + } + @override Future addGeoJsonSource(String sourceId, Map geojson, {String? promoteId}) async { From fa824ce167da3554e580ea92b92c5aa992b1fd07 Mon Sep 17 00:00:00 2001 From: Keith Gulbro Date: Fri, 5 May 2023 14:53:55 -0600 Subject: [PATCH 18/22] Update pubspec.yaml (#1309) --- mapbox_gl_web/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapbox_gl_web/pubspec.yaml b/mapbox_gl_web/pubspec.yaml index 769890992..d14051e77 100644 --- a/mapbox_gl_web/pubspec.yaml +++ b/mapbox_gl_web/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: url: https://github.com/tobrun/flutter-mapbox-gl.git path: mapbox_gl_platform_interface mapbox_gl_dart: ^0.2.1 - image: ^3.0.0 + image: '>=3.0.0 <5.0.0' dependency_overrides: mapbox_gl_platform_interface: From ddad6562d0b3c21785b71e26dfde0d108690c1c6 Mon Sep 17 00:00:00 2001 From: Aaron Eri <36516690+arkeri@users.noreply.github.com> Date: Thu, 1 Jun 2023 10:55:37 -0600 Subject: [PATCH 19/22] Swift CGRect constructor for QueryRenderedFeaturesInRect (#1208) * fix swift CGRect constructor for query in rect * make formating changes --------- Co-authored-by: Aaron S Kennedy <36516690+aaronsamkennedy@users.noreply.github.com> --- ios/Classes/MapboxMapController.swift | 7 ++++--- .../lib/src/method_channel_mapbox_gl.dart | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index e9b992653..4af7ff3b4 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -227,12 +227,13 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma ) } if let top = arguments["top"] as? Double, - let bottom = arguments["bottom"] as? Double, let left = arguments["left"] as? Double, - let right = arguments["right"] as? Double + let width = arguments["width"] as? Double, + let height = arguments["height"] as? Double { + dump(arguments) features = mapView.visibleFeatures( - in: CGRect(x: left, y: top, width: right, height: bottom), + in: CGRect(x: left, y: top, width: width, height: height), styleLayerIdentifiers: styleLayerIdentifiers, predicate: filterExpression ) diff --git a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart index 28a8e268c..2187f21c8 100644 --- a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart +++ b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -302,8 +302,13 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { { 'left': rect.left, 'top': rect.top, + //specific arguments needed for android rect function 'right': rect.right, 'bottom': rect.bottom, + //specific arguments needed for iOS rect function + 'width': rect.width, + 'height': rect.height, + //arguments for mapbox 'layerIds': layerIds, 'filter': filter, }, From b2bfef669e42397704b6f43d55b5df67b18c0fa8 Mon Sep 17 00:00:00 2001 From: Felix Horvat Date: Tue, 12 Sep 2023 13:29:33 +0200 Subject: [PATCH 20/22] add dispose guard (#1323) * add dispose guard * fix ci --- .github/workflows/flutter_ci.yml | 2 +- lib/src/controller.dart | 54 ++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/.github/workflows/flutter_ci.yml b/.github/workflows/flutter_ci.yml index 4f16f71fd..cf1a18f55 100644 --- a/.github/workflows/flutter_ci.yml +++ b/.github/workflows/flutter_ci.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v1 - uses: subosito/flutter-action@v1 - name: Check Dart formatting - run: flutter format --set-exit-if-changed . + run: dart format --set-exit-if-changed . check-swift-formatting: name: "Check Swift formatting" diff --git a/lib/src/controller.dart b/lib/src/controller.dart index 4c97274fa..e70e06a27 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -173,6 +173,7 @@ class MapboxMapController extends ChangeNotifier { onUserLocationUpdated?.call(location); }); } + bool _disposed = false; FillManager? fillManager; LineManager? lineManager; @@ -253,6 +254,7 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes after listeners have been notified. Future _updateMapOptions(Map optionsUpdate) async { + _disposeGuard(); _cameraPosition = await _mapboxGlPlatform.updateMapOptions(optionsUpdate); notifyListeners(); } @@ -264,14 +266,24 @@ class MapboxMapController extends ChangeNotifier { /// /// To force resize map (without any checks) have a look at forceResizeWebMap() void resizeWebMap() { + _disposeGuard(); _mapboxGlPlatform.resizeWebMap(); } /// Triggers a hard map resize event on web and does not check if it is required or not. void forceResizeWebMap() { + _disposeGuard(); _mapboxGlPlatform.forceResizeWebMap(); } + void _disposeGuard() { + if (_disposed) { + throw StateError( + 'This MapboxMapController has already been disposed. This happens if flutter disposes a MapboxMap and you try to use its Controller afterwards.', + ); + } + } + /// Starts an animated change of the map camera position. /// /// [duration] is the amount of time, that the transition animation should take. @@ -282,6 +294,7 @@ class MapboxMapController extends ChangeNotifier { /// Note: this currently always returns immediately with a value of null on iOS Future animateCamera(CameraUpdate cameraUpdate, {Duration? duration}) async { + _disposeGuard(); return _mapboxGlPlatform.animateCamera(cameraUpdate, duration: duration); } @@ -293,6 +306,7 @@ class MapboxMapController extends ChangeNotifier { /// It returns true if the camera was successfully moved and false if the movement was canceled. /// Note: this currently always returns immediately with a value of null on iOS Future moveCamera(CameraUpdate cameraUpdate) async { + _disposeGuard(); return _mapboxGlPlatform.moveCamera(cameraUpdate); } @@ -310,6 +324,7 @@ class MapboxMapController extends ChangeNotifier { /// Future addGeoJsonSource(String sourceId, Map geojson, {String? promoteId}) async { + _disposeGuard(); await _mapboxGlPlatform.addGeoJsonSource(sourceId, geojson, promoteId: promoteId); } @@ -327,6 +342,7 @@ class MapboxMapController extends ChangeNotifier { /// platform side. Future setGeoJsonSource( String sourceId, Map geojson) async { + _disposeGuard(); await _mapboxGlPlatform.setGeoJsonSource(sourceId, geojson); } @@ -343,6 +359,7 @@ class MapboxMapController extends ChangeNotifier { /// platform side. Future setGeoJsonFeature( String sourceId, Map geojsonFeature) async { + _disposeGuard(); await _mapboxGlPlatform.setFeatureForGeoJsonSource( sourceId, geojsonFeature); } @@ -374,6 +391,7 @@ class MapboxMapController extends ChangeNotifier { double? maxzoom, dynamic filter, bool enableInteraction = true}) async { + _disposeGuard(); await _mapboxGlPlatform.addSymbolLayer( sourceId, layerId, @@ -414,6 +432,7 @@ class MapboxMapController extends ChangeNotifier { double? maxzoom, dynamic filter, bool enableInteraction = true}) async { + _disposeGuard(); await _mapboxGlPlatform.addLineLayer( sourceId, layerId, @@ -454,6 +473,7 @@ class MapboxMapController extends ChangeNotifier { double? maxzoom, dynamic filter, bool enableInteraction = true}) async { + _disposeGuard(); await _mapboxGlPlatform.addFillLayer( sourceId, layerId, @@ -494,6 +514,7 @@ class MapboxMapController extends ChangeNotifier { double? maxzoom, dynamic filter, bool enableInteraction = true}) async { + _disposeGuard(); await _mapboxGlPlatform.addFillExtrusionLayer( sourceId, layerId, @@ -534,6 +555,7 @@ class MapboxMapController extends ChangeNotifier { double? maxzoom, dynamic filter, bool enableInteraction = true}) async { + _disposeGuard(); await _mapboxGlPlatform.addCircleLayer( sourceId, layerId, @@ -567,6 +589,7 @@ class MapboxMapController extends ChangeNotifier { String? sourceLayer, double? minzoom, double? maxzoom}) async { + _disposeGuard(); await _mapboxGlPlatform.addRasterLayer( sourceId, layerId, @@ -598,6 +621,7 @@ class MapboxMapController extends ChangeNotifier { String? sourceLayer, double? minzoom, double? maxzoom}) async { + _disposeGuard(); await _mapboxGlPlatform.addHillshadeLayer( sourceId, layerId, @@ -629,6 +653,7 @@ class MapboxMapController extends ChangeNotifier { String? sourceLayer, double? minzoom, double? maxzoom}) async { + _disposeGuard(); await _mapboxGlPlatform.addHeatmapLayer( sourceId, layerId, @@ -646,6 +671,7 @@ class MapboxMapController extends ChangeNotifier { /// platform side. Future updateMyLocationTrackingMode( MyLocationTrackingMode myLocationTrackingMode) async { + _disposeGuard(); return _mapboxGlPlatform .updateMyLocationTrackingMode(myLocationTrackingMode); } @@ -655,6 +681,7 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes after the change has been made on the /// platform side. Future matchMapLanguageWithDeviceDefault() async { + _disposeGuard(); return _mapboxGlPlatform.matchMapLanguageWithDeviceDefault(); } @@ -671,6 +698,7 @@ class MapboxMapController extends ChangeNotifier { /// platform side. Future updateContentInsets(EdgeInsets insets, [bool animated = false]) async { + _disposeGuard(); return _mapboxGlPlatform.updateContentInsets(insets, animated); } @@ -681,6 +709,7 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes after the change has been made on the /// platform side. Future setMapLanguage(String language) async { + _disposeGuard(); return _mapboxGlPlatform.setMapLanguage(language); } @@ -689,6 +718,7 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes after the change has been made on the /// platform side. Future setTelemetryEnabled(bool enabled) async { + _disposeGuard(); return _mapboxGlPlatform.setTelemetryEnabled(enabled); } @@ -697,6 +727,7 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes after the query has been made on the /// platform side. Future getTelemetryEnabled() async { + _disposeGuard(); return _mapboxGlPlatform.getTelemetryEnabled(); } @@ -1067,17 +1098,20 @@ class MapboxMapController extends ChangeNotifier { /// Query rendered features at a point in screen cooridnates Future queryRenderedFeatures( Point point, List layerIds, List? filter) async { + _disposeGuard(); return _mapboxGlPlatform.queryRenderedFeatures(point, layerIds, filter); } /// Query rendered features in a Rect in screen coordinates Future queryRenderedFeaturesInRect( Rect rect, List layerIds, String? filter) async { + _disposeGuard(); return _mapboxGlPlatform.queryRenderedFeaturesInRect( rect, layerIds, filter); } Future invalidateAmbientCache() async { + _disposeGuard(); return _mapboxGlPlatform.invalidateAmbientCache(); } @@ -1085,11 +1119,13 @@ class MapboxMapController extends ChangeNotifier { /// /// Return last latlng, nullable Future requestMyLocationLatLng() async { + _disposeGuard(); return _mapboxGlPlatform.requestMyLocationLatLng(); } /// This method returns the boundaries of the region currently displayed in the map. Future getVisibleRegion() async { + _disposeGuard(); return _mapboxGlPlatform.getVisibleRegion(); } @@ -1129,6 +1165,7 @@ class MapboxMapController extends ChangeNotifier { /// } /// ``` Future addImage(String name, Uint8List bytes, [bool sdf = false]) { + _disposeGuard(); return _mapboxGlPlatform.addImage(name, bytes, sdf); } @@ -1155,12 +1192,14 @@ class MapboxMapController extends ChangeNotifier { /// Adds an image source to the style currently displayed in the map, so that it can later be referred to by the provided id. Future addImageSource( String imageSourceId, Uint8List bytes, LatLngQuad coordinates) { + _disposeGuard(); return _mapboxGlPlatform.addImageSource(imageSourceId, bytes, coordinates); } /// Update an image source to the style currently displayed in the map, so that it can later be referred to by the provided id. Future updateImageSource( String imageSourceId, Uint8List? bytes, LatLngQuad? coordinates) { + _disposeGuard(); return _mapboxGlPlatform.updateImageSource( imageSourceId, bytes, coordinates); } @@ -1168,17 +1207,20 @@ class MapboxMapController extends ChangeNotifier { /// Removes previously added image source by id @Deprecated("This method was renamed to removeSource") Future removeImageSource(String imageSourceId) { + _disposeGuard(); return _mapboxGlPlatform.removeSource(imageSourceId); } /// Removes previously added source by id Future removeSource(String sourceId) { + _disposeGuard(); return _mapboxGlPlatform.removeSource(sourceId); } /// Adds a Mapbox image layer to the map's style at render time. Future addImageLayer(String layerId, String imageSourceId, {double? minzoom, double? maxzoom}) { + _disposeGuard(); return _mapboxGlPlatform.addLayer(layerId, imageSourceId, minzoom, maxzoom); } @@ -1186,6 +1228,7 @@ class MapboxMapController extends ChangeNotifier { Future addImageLayerBelow( String layerId, String sourceId, String imageSourceId, {double? minzoom, double? maxzoom}) { + _disposeGuard(); return _mapboxGlPlatform.addLayerBelow( layerId, sourceId, imageSourceId, minzoom, maxzoom); } @@ -1195,16 +1238,19 @@ class MapboxMapController extends ChangeNotifier { Future addLayerBelow( String layerId, String sourceId, String imageSourceId, {double? minzoom, double? maxzoom}) { + _disposeGuard(); return _mapboxGlPlatform.addLayerBelow( layerId, sourceId, imageSourceId, minzoom, maxzoom); } /// Removes a Mapbox style layer Future removeLayer(String layerId) { + _disposeGuard(); return _mapboxGlPlatform.removeLayer(layerId); } Future setFilter(String layerId, dynamic filter) { + _disposeGuard(); return _mapboxGlPlatform.setFilter(layerId, filter); } @@ -1222,26 +1268,31 @@ class MapboxMapController extends ChangeNotifier { /// /// Returns null if [latLng] is not currently visible on the map. Future toScreenLocation(LatLng latLng) async { + _disposeGuard(); return _mapboxGlPlatform.toScreenLocation(latLng); } Future> toScreenLocationBatch(Iterable latLngs) async { + _disposeGuard(); return _mapboxGlPlatform.toScreenLocationBatch(latLngs); } /// Returns the geographic location (as [LatLng]) that corresponds to a point on the screen. The screen location is specified in screen pixels (not display pixels) relative to the top left of the map (not the top left of the whole screen). Future toLatLng(Point screenLocation) async { + _disposeGuard(); return _mapboxGlPlatform.toLatLng(screenLocation); } /// Returns the distance spanned by one pixel at the specified [latitude] and current zoom level. /// The distance between pixels decreases as the latitude approaches the poles. This relationship parallels the relationship between longitudinal coordinates at different latitudes. Future getMetersPerPixelAtLatitude(double latitude) async { + _disposeGuard(); return _mapboxGlPlatform.getMetersPerPixelAtLatitude(latitude); } /// Add a new source to the map Future addSource(String sourceid, SourceProperties properties) async { + _disposeGuard(); return _mapboxGlPlatform.addSource(sourceid, properties); } @@ -1285,6 +1336,7 @@ class MapboxMapController extends ChangeNotifier { addFillExtrusionLayer(sourceId, layerId, properties, belowLayerId: belowLayerId, sourceLayer: sourceLayer, + enableInteraction: enableInteraction, minzoom: minzoom, maxzoom: maxzoom); } else if (properties is LineLayerProperties) { @@ -1347,11 +1399,13 @@ class MapboxMapController extends ChangeNotifier { /// Default will return snapshot uri in Android and iOS /// If you want base64 value, you must set writeToDisk option to False Future takeSnapshot(SnapshotOptions snapshotOptions) async { + _disposeGuard(); return _mapboxGlPlatform.takeSnapshot(snapshotOptions); } @override void dispose() { + _disposed = true; super.dispose(); _mapboxGlPlatform.dispose(); } From d4aee363c31ea92b6c79ac569d3e0984686411e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kelven=20Galv=C3=A3o?= <32758755+irvine5k@users.noreply.github.com> Date: Fri, 10 Jan 2025 11:21:30 -0300 Subject: [PATCH 21/22] Use Object.hash instead of hashValues (#1431) * Use Object.all instead of hashValues * Use Object.hash instead of .hashAll * dart format --- mapbox_gl_platform_interface/lib/src/camera.dart | 2 +- mapbox_gl_platform_interface/lib/src/location.dart | 6 +++--- mapbox_gl_platform_interface/lib/src/ui.dart | 2 +- mapbox_gl_platform_interface/pubspec.yaml | 2 +- pubspec.yaml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mapbox_gl_platform_interface/lib/src/camera.dart b/mapbox_gl_platform_interface/lib/src/camera.dart index 3094923aa..0c49e3371 100644 --- a/mapbox_gl_platform_interface/lib/src/camera.dart +++ b/mapbox_gl_platform_interface/lib/src/camera.dart @@ -80,7 +80,7 @@ class CameraPosition { } @override - int get hashCode => hashValues(bearing, target, tilt, zoom); + int get hashCode => Object.hash(bearing, target, tilt, zoom); @override String toString() => diff --git a/mapbox_gl_platform_interface/lib/src/location.dart b/mapbox_gl_platform_interface/lib/src/location.dart index 7c058dda9..879cd74cf 100644 --- a/mapbox_gl_platform_interface/lib/src/location.dart +++ b/mapbox_gl_platform_interface/lib/src/location.dart @@ -53,7 +53,7 @@ class LatLng { } @override - int get hashCode => hashValues(latitude, longitude); + int get hashCode => Object.hash(latitude, longitude); } /// A latitude/longitude aligned rectangle. @@ -106,7 +106,7 @@ class LatLngBounds { } @override - int get hashCode => hashValues(southwest, northeast); + int get hashCode => Object.hash(southwest, northeast); } /// A geographical area representing a non-aligned quadrilateral @@ -164,7 +164,7 @@ class LatLngQuad { } @override - int get hashCode => hashValues(topLeft, topRight, bottomRight, bottomLeft); + int get hashCode => Object.hash(topLeft, topRight, bottomRight, bottomLeft); } /// User's observed location diff --git a/mapbox_gl_platform_interface/lib/src/ui.dart b/mapbox_gl_platform_interface/lib/src/ui.dart index 5bacf8a08..6a766d8fa 100644 --- a/mapbox_gl_platform_interface/lib/src/ui.dart +++ b/mapbox_gl_platform_interface/lib/src/ui.dart @@ -143,7 +143,7 @@ class MinMaxZoomPreference { } @override - int get hashCode => hashValues(minZoom, maxZoom); + int get hashCode => Object.hash(minZoom, maxZoom); @override String toString() { diff --git a/mapbox_gl_platform_interface/pubspec.yaml b/mapbox_gl_platform_interface/pubspec.yaml index c3eae26be..cd1269fc2 100644 --- a/mapbox_gl_platform_interface/pubspec.yaml +++ b/mapbox_gl_platform_interface/pubspec.yaml @@ -9,5 +9,5 @@ dependencies: meta: ^1.0.5 environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.14.0 <3.0.0' flutter: ">=2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index ea086f30b..d14c36b8e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,6 @@ flutter: default_package: mapbox_gl_web environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.14.0 <3.0.0' # Flutter versions prior to 1.10 did not support the flutter.plugin.platforms map. flutter: ">=2.0.0" From 4a09cc4401bb77e900f005cc1e2f712193b997af Mon Sep 17 00:00:00 2001 From: Cohen Adair Date: Thu, 13 Mar 2025 14:02:58 -0400 Subject: [PATCH 22/22] #1434: Add namespace to gradle; #1437: Remove deprecated code now causing build errors (#1438) * #1434: Add namespace to gradle file required by new AGP * #1437: Remove deprecated code causing build errors; updated example project AGP --- .gitignore | 1 + android/build.gradle | 1 + .../mapbox/mapboxgl/GlobalMethodHandler.java | 11 +----- .../com/mapbox/mapboxgl/MapboxMapsPlugin.java | 36 ------------------- example/android/app/build.gradle | 22 ++++++------ example/android/build.gradle | 15 ++------ .../gradle/wrapper/gradle-wrapper.properties | 2 +- example/android/settings.gradle | 29 ++++++++++----- example/lib/generated_plugin_registrant.dart | 2 -- example/pubspec.yaml | 4 +-- 10 files changed, 39 insertions(+), 84 deletions(-) diff --git a/.gitignore b/.gitignore index 19d24b075..e8639a336 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.log *.pyc *.swp +*.cxx .DS_Store .atom/ .buildlog/ diff --git a/android/build.gradle b/android/build.gradle index 88f4d5bd1..39bacb48e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -36,6 +36,7 @@ rootProject.allprojects { apply plugin: 'com.android.library' android { + namespace "com.mapbox.mapboxgl" compileSdkVersion 31 ndkVersion "20.1.5948944" diff --git a/android/src/main/java/com/mapbox/mapboxgl/GlobalMethodHandler.java b/android/src/main/java/com/mapbox/mapboxgl/GlobalMethodHandler.java index 567f1edaf..771fd8a28 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/GlobalMethodHandler.java +++ b/android/src/main/java/com/mapbox/mapboxgl/GlobalMethodHandler.java @@ -26,15 +26,8 @@ class GlobalMethodHandler implements MethodChannel.MethodCallHandler { private static final int BUFFER_SIZE = 1024 * 2; @NonNull private final Context context; @NonNull private final BinaryMessenger messenger; - @Nullable private PluginRegistry.Registrar registrar; @Nullable private FlutterPlugin.FlutterAssets flutterAssets; - GlobalMethodHandler(@NonNull PluginRegistry.Registrar registrar) { - this.registrar = registrar; - this.context = registrar.activeContext(); - this.messenger = registrar.messenger(); - } - GlobalMethodHandler(@NonNull FlutterPlugin.FlutterPluginBinding binding) { this.context = binding.getApplicationContext(); this.flutterAssets = binding.getFlutterAssets(); @@ -142,9 +135,7 @@ private InputStream openTilesDbFile(String tilesDb) throws IOException { return new FileInputStream(new File(tilesDb)); } else { String assetKey; - if (registrar != null) { - assetKey = registrar.lookupKeyForAsset(tilesDb); - } else if (flutterAssets != null) { + if (flutterAssets != null) { assetKey = flutterAssets.getAssetFilePathByName(tilesDb); } else { throw new IllegalStateException(); diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapsPlugin.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapsPlugin.java index 3f25d26a6..8eac1fdfe 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapsPlugin.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapsPlugin.java @@ -17,7 +17,6 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference; import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.PluginRegistry.Registrar; /** * Plugin for controlling a set of MapboxMap views to be shown as overlays on top of the Flutter @@ -86,41 +85,6 @@ public void onDetachedFromActivity() { lifecycle = null; } - // Old Plugin APIs - - public static void registerWith(Registrar registrar) { - final Activity activity = registrar.activity(); - if (activity == null) { - // When a background flutter view tries to register the plugin, the registrar has no activity. - // We stop the registration process as this plugin is foreground only. - return; - } - if (activity instanceof LifecycleOwner) { - registrar - .platformViewRegistry() - .registerViewFactory( - VIEW_TYPE, - new MapboxMapFactory( - registrar.messenger(), - new LifecycleProvider() { - @Override - public Lifecycle getLifecycle() { - return ((LifecycleOwner) activity).getLifecycle(); - } - })); - } else { - registrar - .platformViewRegistry() - .registerViewFactory( - VIEW_TYPE, - new MapboxMapFactory(registrar.messenger(), new ProxyLifecycleProvider(activity))); - } - - MethodChannel methodChannel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/mapbox_gl"); - methodChannel.setMethodCallHandler(new GlobalMethodHandler(registrar)); - } - private static final class ProxyLifecycleProvider implements Application.ActivityLifecycleCallbacks, LifecycleOwner, LifecycleProvider { diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 94ab59de9..c5a736cc4 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,3 +1,8 @@ +plugins { + id "com.android.application" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +11,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -21,12 +21,10 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion 31 - ndkVersion "20.1.5948944" + namespace "com.mapbox.mapboxglexample" + compileSdkVersion 35 + ndkVersion "25.1.8937393" lintOptions { disable 'InvalidPackage' @@ -35,8 +33,8 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.mapbox.mapboxglexample" - minSdkVersion 20 - targetSdkVersion 31 + minSdkVersion flutter.minSdkVersion + targetSdkVersion 35 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/example/android/build.gradle b/example/android/build.gradle index 8ae8ba30a..ad89b89e3 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,14 +1,3 @@ -buildscript { - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.2.0' - } -} - allprojects { repositories { google() @@ -24,6 +13,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { - delete rootProject.buildDir +tasks.register("clean", Delete) { + delete rootProject.layout.buildDirectory } diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 8428c0be2..15de90249 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-all.zip \ No newline at end of file diff --git a/example/android/settings.gradle b/example/android/settings.gradle index f860b7645..4f97b7cc6 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,11 +1,24 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" \ No newline at end of file +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version '8.2.2' apply false +} + +include ":app" \ No newline at end of file diff --git a/example/lib/generated_plugin_registrant.dart b/example/lib/generated_plugin_registrant.dart index 91d1beadb..48574c614 100644 --- a/example/lib/generated_plugin_registrant.dart +++ b/example/lib/generated_plugin_registrant.dart @@ -6,7 +6,6 @@ // ignore_for_file: lines_longer_than_80_chars // ignore_for_file: depend_on_referenced_packages -import 'package:device_info_plus_web/device_info_plus_web.dart'; import 'package:location_web/location_web.dart'; import 'package:mapbox_gl_web/mapbox_gl_web.dart'; @@ -14,7 +13,6 @@ import 'package:flutter_web_plugins/flutter_web_plugins.dart'; // ignore: public_member_api_docs void registerPlugins(Registrar registrar) { - DeviceInfoPlusPlugin.registerWith(registrar); LocationWebPlugin.registerWith(registrar); MapboxMapPlugin.registerWith(registrar); registrar.registerMessageHandler(); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index f27a043bc..bb0d73dcd 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -13,11 +13,11 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.0 - location: ^4.0.0 + location: ^8.0.0 path_provider: ^2.0.0 http: ^0.13.0 collection: ^1.0.0 - device_info_plus: ^3.2.2 + device_info_plus: ^9.0.0 dependency_overrides: mapbox_gl_platform_interface: