diff --git a/.github/workflows/flutter_ci.yml b/.github/workflows/flutter_ci.yml
index b6707163e..cf1a18f55 100644
--- a/.github/workflows/flutter_ci.yml
+++ b/.github/workflows/flutter_ci.yml
@@ -8,67 +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: dart 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@v1
- - 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
@@ -79,8 +85,10 @@ jobs:
- uses: actions/checkout@v1
- uses: actions/setup-java@v1
with:
- java-version: '12.x'
- - uses: subosito/flutter-action@v1
+ java-version: "12.x"
+ - uses: subosito/flutter-action@v2
+ with:
+ flutter-version: "2.10.5"
- run: flutter pub get
- name: build iOS package
run: |
@@ -98,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..e8639a336 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,11 +4,13 @@
*.log
*.pyc
*.swp
+*.cxx
.DS_Store
.atom/
.buildlog/
.history
.svn/
+.fvm/
# IntelliJ related
*.iml
@@ -98,10 +100,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 +119,4 @@ app.*.symbols
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock
+
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/Makefile b/Makefile
new file mode 100644
index 000000000..71ae0f508
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,16 @@
+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
+
+codegen:
+ dart scripts/lib/generate.dart
\ No newline at end of file
diff --git a/README.md b/README.md
index ad6887d49..112b4c2fd 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)
@@ -70,8 +71,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/).*
@@ -121,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: |
@@ -198,7 +208,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
@@ -224,6 +252,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/android/build.gradle b/android/build.gradle
index 1430207bc..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"
@@ -57,6 +58,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/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/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/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/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/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;
diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java
index f45556c9f..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;
@@ -37,6 +40,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 +62,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 +91,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")
@@ -101,6 +108,7 @@ final class MapboxMapController
OnMapReadyCallback,
OnCameraTrackingChangedListener,
PlatformView {
+
private static final String TAG = "MapboxMapController";
private final int id;
private final MethodChannel methodChannel;
@@ -108,6 +116,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 +135,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 +180,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);
}
@@ -393,6 +399,7 @@ private void addSymbolLayer(
PropertyValue[] properties,
boolean enableInteraction,
Expression filter) {
+
SymbolLayer symbolLayer = new SymbolLayer(layerName, sourceName);
symbolLayer.setProperties(properties);
if (sourceLayer != null) {
@@ -485,6 +492,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,
@@ -571,13 +612,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);
@@ -849,6 +915,7 @@ public void onError(@NonNull String message) {
Expression filterExpression = parseFilter(filter);
+ removeLayer(layerId);
addSymbolLayer(
layerId,
sourceId,
@@ -879,6 +946,7 @@ public void onError(@NonNull String message) {
Expression filterExpression = parseFilter(filter);
+ removeLayer(layerId);
addLineLayer(
layerId,
sourceId,
@@ -909,6 +977,7 @@ public void onError(@NonNull String message) {
Expression filterExpression = parseFilter(filter);
+ removeLayer(layerId);
addFillLayer(
layerId,
sourceId,
@@ -921,6 +990,38 @@ 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);
+
+ removeLayer(layerId);
+ addFillExtrusionLayer(
+ layerId,
+ sourceId,
+ belowLayerId,
+ sourceLayer,
+ minzoom != null ? minzoom.floatValue() : null,
+ maxzoom != null ? maxzoom.floatValue() : null,
+ properties,
+ enableInteraction,
+ filterExpression);
+ updateLocationComponentLayer();
+
result.success(null);
break;
}
@@ -939,6 +1040,7 @@ public void onError(@NonNull String message) {
Expression filterExpression = parseFilter(filter);
+ removeLayer(layerId);
addCircleLayer(
layerId,
sourceId,
@@ -963,6 +1065,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,
@@ -995,6 +1099,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;
}
@@ -1063,6 +1189,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"));
@@ -1139,8 +1291,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;
@@ -1185,6 +1336,99 @@ public void onFailure(@NonNull Exception exception) {
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;
+ }
+ 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();
}
@@ -1337,12 +1581,14 @@ private void destroyMapViewIfNecessary() {
return;
}
+ mapView.onStop();
+ mapView.onDestroy();
+
if (locationComponent != null) {
locationComponent.setLocationComponentEnabled(false);
}
stopListeningForLocationUpdates();
- mapView.onDestroy();
mapView = null;
}
@@ -1739,6 +1985,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");
@@ -1767,6 +2020,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/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/.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/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/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/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/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 c40f8995b..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 */
@@ -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/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/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/example/lib/generated_plugin_registrant.dart b/example/lib/generated_plugin_registrant.dart
index ae1e051c7..48574c614 100644
--- a/example/lib/generated_plugin_registrant.dart
+++ b/example/lib/generated_plugin_registrant.dart
@@ -4,8 +4,8 @@
// 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';
import 'package:mapbox_gl_web/mapbox_gl_web.dart';
@@ -13,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/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/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/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) {
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/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/example/pubspec.yaml b/example/pubspec.yaml
index 9731f59c0..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:
@@ -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/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/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/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 e1d55b8ab..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
)
@@ -336,8 +337,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)
@@ -353,6 +355,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,
@@ -381,6 +384,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,
@@ -409,6 +413,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,
@@ -425,6 +430,35 @@ 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
+
+ removeLayer(layerId: layerId)
+ 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 }
@@ -437,6 +471,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,
@@ -461,6 +496,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,
@@ -471,6 +508,26 @@ 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
+
+ removeLayer(layerId: layerId)
+ 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 }
@@ -549,6 +606,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 }
@@ -648,12 +740,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":
@@ -669,6 +756,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 }
@@ -696,11 +794,132 @@ 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)
}
}
+ 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.
@@ -1102,6 +1321,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,
@@ -1200,6 +1464,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/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/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 1d4583f13..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,18 +254,48 @@ 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();
}
+ /// 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() {
+ _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.
+ ///
/// 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 {
+ _disposeGuard();
+ return _mapboxGlPlatform.animateCamera(cameraUpdate, duration: duration);
}
/// Instantaneously re-position the camera.
@@ -275,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);
}
@@ -292,6 +324,7 @@ class MapboxMapController extends ChangeNotifier {
///
Future addGeoJsonSource(String sourceId, Map geojson,
{String? promoteId}) async {
+ _disposeGuard();
await _mapboxGlPlatform.addGeoJsonSource(sourceId, geojson,
promoteId: promoteId);
}
@@ -309,6 +342,7 @@ class MapboxMapController extends ChangeNotifier {
/// platform side.
Future setGeoJsonSource(
String sourceId, Map geojson) async {
+ _disposeGuard();
await _mapboxGlPlatform.setGeoJsonSource(sourceId, geojson);
}
@@ -325,6 +359,7 @@ class MapboxMapController extends ChangeNotifier {
/// platform side.
Future setGeoJsonFeature(
String sourceId, Map geojsonFeature) async {
+ _disposeGuard();
await _mapboxGlPlatform.setFeatureForGeoJsonSource(
sourceId, geojsonFeature);
}
@@ -340,6 +375,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].
///
@@ -352,6 +391,7 @@ class MapboxMapController extends ChangeNotifier {
double? maxzoom,
dynamic filter,
bool enableInteraction = true}) async {
+ _disposeGuard();
await _mapboxGlPlatform.addSymbolLayer(
sourceId,
layerId,
@@ -376,6 +416,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].
///
@@ -388,6 +432,7 @@ class MapboxMapController extends ChangeNotifier {
double? maxzoom,
dynamic filter,
bool enableInteraction = true}) async {
+ _disposeGuard();
await _mapboxGlPlatform.addLineLayer(
sourceId,
layerId,
@@ -412,6 +457,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].
///
@@ -424,6 +473,7 @@ class MapboxMapController extends ChangeNotifier {
double? maxzoom,
dynamic filter,
bool enableInteraction = true}) async {
+ _disposeGuard();
await _mapboxGlPlatform.addFillLayer(
sourceId,
layerId,
@@ -437,6 +487,47 @@ 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 {
+ _disposeGuard();
+ 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.
@@ -448,6 +539,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].
///
@@ -460,6 +555,7 @@ class MapboxMapController extends ChangeNotifier {
double? maxzoom,
dynamic filter,
bool enableInteraction = true}) async {
+ _disposeGuard();
await _mapboxGlPlatform.addCircleLayer(
sourceId,
layerId,
@@ -482,13 +578,18 @@ 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,
String? sourceLayer,
double? minzoom,
double? maxzoom}) async {
+ _disposeGuard();
await _mapboxGlPlatform.addRasterLayer(
sourceId,
layerId,
@@ -509,13 +610,18 @@ 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,
String? sourceLayer,
double? minzoom,
double? maxzoom}) async {
+ _disposeGuard();
await _mapboxGlPlatform.addHillshadeLayer(
sourceId,
layerId,
@@ -527,12 +633,45 @@ 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 {
+ _disposeGuard();
+ 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
/// platform side.
Future updateMyLocationTrackingMode(
MyLocationTrackingMode myLocationTrackingMode) async {
+ _disposeGuard();
return _mapboxGlPlatform
.updateMyLocationTrackingMode(myLocationTrackingMode);
}
@@ -542,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();
}
@@ -558,6 +698,7 @@ class MapboxMapController extends ChangeNotifier {
/// platform side.
Future updateContentInsets(EdgeInsets insets,
[bool animated = false]) async {
+ _disposeGuard();
return _mapboxGlPlatform.updateContentInsets(insets, animated);
}
@@ -568,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);
}
@@ -576,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);
}
@@ -584,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();
}
@@ -954,17 +1098,20 @@ class MapboxMapController extends ChangeNotifier {
/// Query rendered features at a point in screen cooridnates
Future queryRenderedFeatures(
Point point, List layerIds, List