From 3aaa7051c8c7f1bc31bd504229aad7f7eddb9de3 Mon Sep 17 00:00:00 2001 From: Felix Horvat Date: Tue, 18 Jan 2022 16:40:27 +0100 Subject: [PATCH] Annotation manager moved to dart (#779) * line annotations in dart * working drag * add ios drag support * added other annotation managers * support old api with new managers * ios remove annotations * web cleanup * working android version * fixed symbol text issues * fixed fill pattern issue * added support for symbol allow overlap * working android drag * fixed ios issues * added source#setFeature to iOS * added setGeoJsonFeature to android * add support for dragEnabled * added option to toggle interaction for layers * fixed web issue with textFont * removed unused imports * better documentation added missing options to line manager * revert line example changes * added a fix for the android performance issues * improved code for detecting drag starts * code review changes * post rebase fixes * fix build issue with location --- .../com/mapbox/mapboxgl/CircleBuilder.java | 76 -- .../com/mapbox/mapboxgl/CircleController.java | 96 -- .../mapbox/mapboxgl/CircleOptionsSink.java | 31 - .../java/com/mapbox/mapboxgl/Convert.java | 272 ------ .../java/com/mapbox/mapboxgl/FillBuilder.java | 62 -- .../com/mapbox/mapboxgl/FillController.java | 78 -- .../com/mapbox/mapboxgl/FillOptionsSink.java | 27 - .../java/com/mapbox/mapboxgl/LineBuilder.java | 82 -- .../com/mapbox/mapboxgl/LineController.java | 112 --- .../com/mapbox/mapboxgl/LineOptionsSink.java | 36 - .../com/mapbox/mapboxgl/MapboxMapBuilder.java | 14 +- .../mapbox/mapboxgl/MapboxMapController.java | 848 ++++-------------- .../com/mapbox/mapboxgl/MapboxMapFactory.java | 11 +- .../mapboxgl/OnCircleTappedListener.java | 11 - .../mapbox/mapboxgl/OnFillTappedListener.java | 7 - .../mapbox/mapboxgl/OnLineTappedListener.java | 11 - .../mapboxgl/OnSymbolTappedListener.java | 11 - .../com/mapbox/mapboxgl/SymbolBuilder.java | 168 ---- .../com/mapbox/mapboxgl/SymbolController.java | 191 ---- .../mapbox/mapboxgl/SymbolOptionsSink.java | 72 -- example/android/app/build.gradle | 8 + example/lib/layer.dart | 56 +- example/lib/place_symbol.dart | 7 +- ios/Classes/Convert.swift | 227 ----- ios/Classes/MapboxMapController.swift | 745 +++++---------- ios/Classes/Symbol.swift | 75 -- ios/Classes/SymbolOptionsSink.swift | 31 - lib/mapbox_gl.dart | 4 +- lib/src/annotation_manager.dart | 334 +++++++ lib/src/controller.dart | 386 ++++---- lib/src/mapbox_map.dart | 20 +- lib/src/util.dart | 14 + .../lib/mapbox_gl_platform_interface.dart | 1 + .../lib/src/annotation.dart | 8 + .../lib/src/circle.dart | 34 +- .../lib/src/fill.dart | 49 +- .../lib/src/line.dart | 35 +- .../lib/src/location.dart | 4 + .../lib/src/mapbox_gl_platform_interface.dart | 285 +----- .../lib/src/method_channel_mapbox_gl.dart | 352 +------- .../lib/src/symbol.dart | 36 +- mapbox_gl_web/lib/mapbox_gl_web.dart | 8 +- .../src/feature_manager/circle_manager.dart | 43 - .../src/feature_manager/feature_manager.dart | 115 --- .../lib/src/feature_manager/fill_manager.dart | 46 - .../lib/src/feature_manager/line_manager.dart | 47 - .../src/feature_manager/symbol_manager.dart | 93 -- mapbox_gl_web/lib/src/mapbox_map_plugin.dart | 2 +- ...oller.dart => mapbox_web_gl_platform.dart} | 393 ++++---- mapbox_gl_web/pubspec.yaml | 2 +- 50 files changed, 1455 insertions(+), 4221 deletions(-) delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/CircleBuilder.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/CircleController.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/CircleOptionsSink.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/FillController.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/LineBuilder.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/LineController.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/LineOptionsSink.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/OnCircleTappedListener.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/OnLineTappedListener.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/OnSymbolTappedListener.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/SymbolBuilder.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/SymbolController.java delete mode 100644 android/src/main/java/com/mapbox/mapboxgl/SymbolOptionsSink.java delete mode 100644 ios/Classes/Symbol.swift delete mode 100644 ios/Classes/SymbolOptionsSink.swift create mode 100644 lib/src/annotation_manager.dart create mode 100644 lib/src/util.dart create mode 100644 mapbox_gl_platform_interface/lib/src/annotation.dart delete mode 100644 mapbox_gl_web/lib/src/feature_manager/circle_manager.dart delete mode 100644 mapbox_gl_web/lib/src/feature_manager/feature_manager.dart delete mode 100644 mapbox_gl_web/lib/src/feature_manager/fill_manager.dart delete mode 100644 mapbox_gl_web/lib/src/feature_manager/line_manager.dart delete mode 100644 mapbox_gl_web/lib/src/feature_manager/symbol_manager.dart rename mapbox_gl_web/lib/src/{mapbox_map_controller.dart => mapbox_web_gl_platform.dart} (73%) diff --git a/android/src/main/java/com/mapbox/mapboxgl/CircleBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/CircleBuilder.java deleted file mode 100644 index db7accf56..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/CircleBuilder.java +++ /dev/null @@ -1,76 +0,0 @@ -// This file is generated. - -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import com.mapbox.geojson.Point; -import com.mapbox.mapboxsdk.geometry.LatLng; -import com.mapbox.mapboxsdk.plugins.annotation.Circle; -import com.mapbox.mapboxsdk.plugins.annotation.CircleManager; -import com.mapbox.mapboxsdk.plugins.annotation.CircleOptions; - -class CircleBuilder implements CircleOptionsSink { - private final CircleManager circleManager; - private final CircleOptions circleOptions; - - CircleBuilder(CircleManager circleManager) { - this.circleManager = circleManager; - this.circleOptions = new CircleOptions(); - } - - public CircleOptions getCircleOptions(){ - return this.circleOptions; - } - - Circle build() { - return circleManager.create(circleOptions); - } - - @Override - public void setCircleRadius(float circleRadius) { - circleOptions.withCircleRadius(circleRadius); - } - - @Override - public void setCircleColor(String circleColor) { - circleOptions.withCircleColor(circleColor); - } - - @Override - public void setCircleBlur(float circleBlur) { - circleOptions.withCircleBlur(circleBlur); - } - - @Override - public void setCircleOpacity(float circleOpacity) { - circleOptions.withCircleOpacity(circleOpacity); - } - - @Override - public void setCircleStrokeWidth(float circleStrokeWidth) { - circleOptions.withCircleStrokeWidth(circleStrokeWidth); - } - - @Override - public void setCircleStrokeColor(String circleStrokeColor) { - circleOptions.withCircleStrokeColor(circleStrokeColor); - } - - @Override - public void setCircleStrokeOpacity(float circleStrokeOpacity) { - circleOptions.withCircleStrokeOpacity(circleStrokeOpacity); - } - - @Override - public void setGeometry(LatLng geometry) { - circleOptions.withGeometry(Point.fromLngLat(geometry.getLongitude(), geometry.getLatitude())); - } - - @Override - public void setDraggable(boolean draggable) { - circleOptions.withDraggable(draggable); - } -} \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/CircleController.java b/android/src/main/java/com/mapbox/mapboxgl/CircleController.java deleted file mode 100644 index 76860ea6f..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/CircleController.java +++ /dev/null @@ -1,96 +0,0 @@ -// This file is generated. - -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import android.graphics.Color; -import com.mapbox.geojson.Point; -import com.mapbox.mapboxsdk.geometry.LatLng; -import com.mapbox.mapboxsdk.plugins.annotation.Circle; -import com.mapbox.mapboxsdk.plugins.annotation.CircleManager; - -/** Controller of a single Circle on the map. */ -class CircleController implements CircleOptionsSink { - private final Circle circle; - private final OnCircleTappedListener onTappedListener; - private boolean consumeTapEvents; - - CircleController(Circle circle, boolean consumeTapEvents, OnCircleTappedListener onTappedListener) { - this.circle = circle; - this.consumeTapEvents = consumeTapEvents; - this.onTappedListener = onTappedListener; - } - - public Circle getCircle(){ - return this.circle; - } - - boolean onTap() { - if (onTappedListener != null) { - onTappedListener.onCircleTapped(circle); - } - return consumeTapEvents; - } - - void remove(CircleManager circleManager) { - circleManager.delete(circle); - } - - @Override - public void setCircleRadius(float circleRadius) { - circle.setCircleRadius(circleRadius); - } - - @Override - public void setCircleColor(String circleColor) { - circle.setCircleColor(Color.parseColor(circleColor)); - } - - @Override - public void setCircleBlur(float circleBlur) { - circle.setCircleBlur(circleBlur); - } - - @Override - public void setCircleOpacity(float circleOpacity) { - circle.setCircleOpacity(circleOpacity); - } - - @Override - public void setCircleStrokeWidth(float circleStrokeWidth) { - circle.setCircleStrokeWidth(circleStrokeWidth); - } - - @Override - public void setCircleStrokeColor(String circleStrokeColor) { - circle.setCircleStrokeColor(Color.parseColor(circleStrokeColor)); - } - - @Override - public void setCircleStrokeOpacity(float circleStrokeOpacity) { - circle.setCircleStrokeOpacity(circleStrokeOpacity); - } - - @Override - public void setGeometry(LatLng geometry) { - circle.setGeometry(Point.fromLngLat(geometry.getLongitude(), geometry.getLatitude())); - } - - public LatLng getGeometry() { - Point point = circle.getGeometry(); - return new LatLng(point.latitude(), point.longitude()); - } - - @Override - public void setDraggable(boolean draggable) { - circle.setDraggable(draggable); - } - - public void update(CircleManager circleManager) { - circleManager.update(circle); - } - -} diff --git a/android/src/main/java/com/mapbox/mapboxgl/CircleOptionsSink.java b/android/src/main/java/com/mapbox/mapboxgl/CircleOptionsSink.java deleted file mode 100644 index 80bd8ea04..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/CircleOptionsSink.java +++ /dev/null @@ -1,31 +0,0 @@ -// This file is generated. - -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import com.mapbox.mapboxsdk.geometry.LatLng; - -/** Receiver of Circle configuration options. */ -interface CircleOptionsSink { - - void setCircleRadius(float circleRadius); - - void setCircleColor(String circleColor); - - void setCircleBlur(float circleBlur); - - void setCircleOpacity(float circleOpacity); - - void setCircleStrokeWidth(float circleStrokeWidth); - - void setCircleStrokeColor(String circleStrokeColor); - - void setCircleStrokeOpacity(float circleStrokeOpacity); - - void setGeometry(LatLng geometry); - - void setDraggable(boolean draggable); -} diff --git a/android/src/main/java/com/mapbox/mapboxgl/Convert.java b/android/src/main/java/com/mapbox/mapboxgl/Convert.java index f4ba4a389..745833380 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/Convert.java +++ b/android/src/main/java/com/mapbox/mapboxgl/Convert.java @@ -31,28 +31,6 @@ class Convert { private final static String TAG = "Convert"; -// private static BitmapDescriptor toBitmapDescriptor(Object o) { -// final List data = toList(o); -// switch (toString(data.get(0))) { -// case "defaultMarker": -// if (data.size() == 1) { -// return BitmapDescriptorFactory.defaultMarker(); -// } else { -// return BitmapDescriptorFactory.defaultMarker(toFloat(data.get(1))); -// } -// case "fromAsset": -// if (data.size() == 2) { -// return BitmapDescriptorFactory.fromAsset( -// FlutterMain.getLookupKeyForAsset(toString(data.get(1)))); -// } else { -// return BitmapDescriptorFactory.fromAsset( -// FlutterMain.getLookupKeyForAsset(toString(data.get(1)), toString(data.get(2)))); -// } -// default: -// throw new IllegalArgumentException("Cannot interpret " + o + " as BitmapDescriptor"); -// } -// } - static boolean toBoolean(Object o) { return (Boolean) o; } @@ -67,19 +45,6 @@ static CameraPosition toCameraPosition(Object o) { return builder.build(); } - static List toAnnotationOrder(Object o) { - final List data = toList(o); - List annotations = new ArrayList(); - for (int index = 0; index < data.size(); index++) { - annotations.add(toString(data.get(index))); - } - return annotations; - } - - static List toAnnotationConsumeTapEvents(Object o) { - return toAnnotationOrder(o); - } - static boolean isScrollByCameraUpdate(Object o) { return toString(toList(o).get(0)).equals("scrollBy"); } @@ -326,241 +291,4 @@ static void interpretMapboxMapOptions(Object o, MapboxMapOptionsSink sink, Conte sink.setAttributionButtonMargins(point.x, point.y); } } - - static void interpretSymbolOptions(Object o, SymbolOptionsSink sink) { - final Map data = toMap(o); - final Object iconSize = data.get("iconSize"); - if (iconSize != null) { - sink.setIconSize(toFloat(iconSize)); - } - final Object iconImage = data.get("iconImage"); - if (iconImage != null) { - sink.setIconImage(toString(iconImage)); - } - final Object iconRotate = data.get("iconRotate"); - if (iconRotate != null) { - sink.setIconRotate(toFloat(iconRotate)); - } - final Object iconOffset = data.get("iconOffset"); - if (iconOffset != null) { - sink.setIconOffset(new float[] {toFloat(toList(iconOffset).get(0)), toFloat(toList(iconOffset).get(1))}); - } - final Object iconAnchor = data.get("iconAnchor"); - if (iconAnchor != null) { - sink.setIconAnchor(toString(iconAnchor)); - } - final ArrayList fontNames = (ArrayList) data.get("fontNames"); - if (fontNames != null) { - sink.setFontNames((String[]) fontNames.toArray(new String[0])); - } - final Object textField = data.get("textField"); - if (textField != null) { - sink.setTextField(toString(textField)); - } - final Object textSize = data.get("textSize"); - if (textSize != null) { - sink.setTextSize(toFloat(textSize)); - } - final Object textMaxWidth = data.get("textMaxWidth"); - if (textMaxWidth != null) { - sink.setTextMaxWidth(toFloat(textMaxWidth)); - } - final Object textLetterSpacing = data.get("textLetterSpacing"); - if (textLetterSpacing != null) { - sink.setTextLetterSpacing(toFloat(textLetterSpacing)); - } - final Object textJustify = data.get("textJustify"); - if (textJustify != null) { - sink.setTextJustify(toString(textJustify)); - } - final Object textAnchor = data.get("textAnchor"); - if (textAnchor != null) { - sink.setTextAnchor(toString(textAnchor)); - } - final Object textRotate = data.get("textRotate"); - if (textRotate != null) { - sink.setTextRotate(toFloat(textRotate)); - } - final Object textTransform = data.get("textTransform"); - if (textTransform != null) { - sink.setTextTransform(toString(textTransform)); - } - final Object textOffset = data.get("textOffset"); - if (textOffset != null) { - sink.setTextOffset(new float[] {toFloat(toList(textOffset).get(0)), toFloat(toList(textOffset).get(1))}); - } - final Object iconOpacity = data.get("iconOpacity"); - if (iconOpacity != null) { - sink.setIconOpacity(toFloat(iconOpacity)); - } - final Object iconColor = data.get("iconColor"); - if (iconColor != null) { - sink.setIconColor(toString(iconColor)); - } - final Object iconHaloColor = data.get("iconHaloColor"); - if (iconHaloColor != null) { - sink.setIconHaloColor(toString(iconHaloColor)); - } - final Object iconHaloWidth = data.get("iconHaloWidth"); - if (iconHaloWidth != null) { - sink.setIconHaloWidth(toFloat(iconHaloWidth)); - } - final Object iconHaloBlur = data.get("iconHaloBlur"); - if (iconHaloBlur != null) { - sink.setIconHaloBlur(toFloat(iconHaloBlur)); - } - final Object textOpacity = data.get("textOpacity"); - if (textOpacity != null) { - sink.setTextOpacity(toFloat(textOpacity)); - } - final Object textColor = data.get("textColor"); - if (textColor != null) { - sink.setTextColor(toString(textColor)); - } - final Object textHaloColor = data.get("textHaloColor"); - if (textHaloColor != null) { - sink.setTextHaloColor(toString(textHaloColor)); - } - final Object textHaloWidth = data.get("textHaloWidth"); - if (textHaloWidth != null) { - sink.setTextHaloWidth(toFloat(textHaloWidth)); - } - final Object textHaloBlur = data.get("textHaloBlur"); - if (textHaloBlur != null) { - sink.setTextHaloBlur(toFloat(textHaloBlur)); - } - final Object geometry = data.get("geometry"); - if (geometry != null) { - sink.setGeometry(toLatLng(geometry)); - } - final Object symbolSortKey = data.get("zIndex"); - if (symbolSortKey != null) { - sink.setSymbolSortKey(toFloat(symbolSortKey)); - } - final Object draggable = data.get("draggable"); - if (draggable != null) { - sink.setDraggable(toBoolean(draggable)); - } - } - - static void interpretCircleOptions(Object o, CircleOptionsSink sink) { - final Map data = toMap(o); - final Object circleRadius = data.get("circleRadius"); - if (circleRadius != null) { - sink.setCircleRadius(toFloat(circleRadius)); - } - final Object circleColor = data.get("circleColor"); - if (circleColor != null) { - sink.setCircleColor(toString(circleColor)); - } - final Object circleBlur = data.get("circleBlur"); - if (circleBlur != null) { - sink.setCircleBlur(toFloat(circleBlur)); - } - final Object circleOpacity = data.get("circleOpacity"); - if (circleOpacity != null) { - sink.setCircleOpacity(toFloat(circleOpacity)); - } - final Object circleStrokeWidth = data.get("circleStrokeWidth"); - if (circleStrokeWidth != null) { - sink.setCircleStrokeWidth(toFloat(circleStrokeWidth)); - } - final Object circleStrokeColor = data.get("circleStrokeColor"); - if (circleStrokeColor != null) { - sink.setCircleStrokeColor(toString(circleStrokeColor)); - } - final Object circleStrokeOpacity = data.get("circleStrokeOpacity"); - if (circleStrokeOpacity != null) { - sink.setCircleStrokeOpacity(toFloat(circleStrokeOpacity)); - } - final Object geometry = data.get("geometry"); - if (geometry != null) { - sink.setGeometry(toLatLng(geometry)); - } - final Object draggable = data.get("draggable"); - if (draggable != null) { - sink.setDraggable(toBoolean(draggable)); - } - } - static void interpretLineOptions(Object o, LineOptionsSink sink) { - final Map data = toMap(o); - final Object lineJoin = data.get("lineJoin"); - if (lineJoin != null) { - Logger.e(TAG, "setLineJoin" + lineJoin); - sink.setLineJoin(toString(lineJoin)); - } - final Object lineOpacity = data.get("lineOpacity"); - if (lineOpacity != null) { - Logger.e(TAG, "setLineOpacity" + lineOpacity); - sink.setLineOpacity(toFloat(lineOpacity)); - } - final Object lineColor = data.get("lineColor"); - if (lineColor != null) { - Logger.e(TAG, "setLineColor" + lineColor); - sink.setLineColor(toString(lineColor)); - } - final Object lineWidth = data.get("lineWidth"); - if (lineWidth != null) { - Logger.e(TAG, "setLineWidth" + lineWidth); - sink.setLineWidth(toFloat(lineWidth)); - } - final Object lineGapWidth = data.get("lineGapWidth"); - if (lineGapWidth != null) { - Logger.e(TAG, "setLineGapWidth" + lineGapWidth); - sink.setLineGapWidth(toFloat(lineGapWidth)); - } - final Object lineOffset = data.get("lineOffset"); - if (lineOffset != null) { - Logger.e(TAG, "setLineOffset" + lineOffset); - sink.setLineOffset(toFloat(lineOffset)); - } - final Object lineBlur = data.get("lineBlur"); - if (lineBlur != null) { - Logger.e(TAG, "setLineBlur" + lineBlur); - sink.setLineBlur(toFloat(lineBlur)); - } - final Object linePattern = data.get("linePattern"); - if (linePattern != null) { - Logger.e(TAG, "setLinePattern" + linePattern); - sink.setLinePattern(toString(linePattern)); - } - final Object geometry = data.get("geometry"); - if (geometry != null) { - Logger.e(TAG, "SetGeometry"); - sink.setGeometry(toLatLngList(geometry, false)); - } - final Object draggable = data.get("draggable"); - if (draggable != null) { - Logger.e(TAG, "SetDraggable"); - sink.setDraggable(toBoolean(draggable)); - } - } - - static void interpretFillOptions(Object o, FillOptionsSink sink) { - final Map data = toMap(o); - final Object fillOpacity = data.get("fillOpacity"); - if (fillOpacity != null) { - sink.setFillOpacity(toFloat(fillOpacity)); - } - final Object fillColor = data.get("fillColor"); - if (fillColor != null) { - sink.setFillColor(toString(fillColor)); - } - final Object fillOutlineColor = data.get("fillOutlineColor"); - if (fillOutlineColor != null) { - sink.setFillOutlineColor(toString(fillOutlineColor)); - } - final Object fillPattern = data.get("fillPattern"); - if (fillPattern != null) { - sink.setFillPattern(toString(fillPattern)); - } - final Object geometry = data.get("geometry"); - if (geometry != null) { - sink.setGeometry(toLatLngListList(geometry)); - } - final Object draggable = data.get("draggable"); - if (draggable != null) { - sink.setDraggable(toBoolean(draggable)); - } - } } \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java deleted file mode 100644 index b70589cba..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java +++ /dev/null @@ -1,62 +0,0 @@ -// This file is generated. - -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import com.mapbox.mapboxsdk.geometry.LatLng; -import com.mapbox.mapboxsdk.plugins.annotation.Fill; -import com.mapbox.mapboxsdk.plugins.annotation.FillManager; -import com.mapbox.mapboxsdk.plugins.annotation.FillOptions; - -import java.util.List; - -class FillBuilder implements FillOptionsSink { - private final FillManager fillManager; - private final FillOptions fillOptions; - - FillBuilder(FillManager fillManager) { - this.fillManager = fillManager; - this.fillOptions = new FillOptions(); - } - - public FillOptions getFillOptions(){ - return this.fillOptions; - } - - Fill build() { - return fillManager.create(fillOptions); - } - - @Override - public void setFillOpacity(float fillOpacity) { - fillOptions.withFillOpacity(fillOpacity); - } - - @Override - public void setFillColor(String fillColor) { - fillOptions.withFillColor(fillColor); - } - - @Override - public void setFillOutlineColor(String fillOutlineColor) { - fillOptions.withFillOutlineColor(fillOutlineColor); - } - - @Override - public void setFillPattern(String fillPattern) { - fillOptions.withFillPattern(fillPattern); - } - - @Override - public void setGeometry(List> geometry) { - fillOptions.withGeometry(Convert.interpretListLatLng(geometry)); - } - - @Override - public void setDraggable(boolean draggable) { - fillOptions.withDraggable(draggable); - } -} \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillController.java b/android/src/main/java/com/mapbox/mapboxgl/FillController.java deleted file mode 100644 index 86bc01137..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/FillController.java +++ /dev/null @@ -1,78 +0,0 @@ -// This file is generated. - -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import android.graphics.Color; -import com.mapbox.mapboxsdk.geometry.LatLng; -import com.mapbox.mapboxsdk.plugins.annotation.Fill; -import com.mapbox.mapboxsdk.plugins.annotation.FillManager; - -import java.util.List; - -/** - * Controller of a single Fill on the map. - */ -class FillController implements FillOptionsSink { - private final Fill fill; - private final OnFillTappedListener onTappedListener; - private boolean consumeTapEvents; - - FillController(Fill fill, boolean consumeTapEvents, OnFillTappedListener onTappedListener) { - this.fill = fill; - this.consumeTapEvents = consumeTapEvents; - this.onTappedListener = onTappedListener; - } - - public Fill getFill(){ - return this.fill; - } - - boolean onTap() { - if (onTappedListener != null) { - onTappedListener.onFillTapped(fill); - } - return consumeTapEvents; - } - - void remove(FillManager fillManager) { - fillManager.delete(fill); - } - - @Override - public void setFillOpacity(float fillOpacity) { - fill.setFillOpacity(fillOpacity); - } - - @Override - public void setFillColor(String fillColor) { - fill.setFillColor(Color.parseColor(fillColor)); - } - - @Override - public void setFillOutlineColor(String fillOutlineColor) { - fill.setFillOutlineColor(Color.parseColor(fillOutlineColor)); - } - - @Override - public void setFillPattern(String fillPattern) { - fill.setFillPattern(fillPattern); - } - - @Override - public void setGeometry(List> geometry) { - fill.setGeometry(Convert.interpretListLatLng(geometry)); - } - - @Override - public void setDraggable(boolean draggable) { - fill.setDraggable(draggable); - } - - public void update(FillManager fillManager) { - fillManager.update(fill); - } -} diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java b/android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java deleted file mode 100644 index 849788103..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java +++ /dev/null @@ -1,27 +0,0 @@ -// This file is generated. - -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import com.mapbox.mapboxsdk.geometry.LatLng; - -import java.util.List; - -/** Receiver of Fill configuration options. */ -interface FillOptionsSink { - - void setFillOpacity(float fillOpacity); - - void setFillColor(String fillColor); - - void setFillOutlineColor(String fillOutlineColor); - - void setFillPattern(String fillPattern); - - void setGeometry(List> geometry); - - void setDraggable(boolean draggable); -} diff --git a/android/src/main/java/com/mapbox/mapboxgl/LineBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/LineBuilder.java deleted file mode 100644 index 5d7276143..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/LineBuilder.java +++ /dev/null @@ -1,82 +0,0 @@ -// This file is generated. - -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import java.util.List; -import com.mapbox.geojson.Point; -import com.mapbox.mapboxsdk.geometry.LatLng; -import com.mapbox.mapboxsdk.plugins.annotation.Line; -import com.mapbox.mapboxsdk.plugins.annotation.LineManager; -import com.mapbox.mapboxsdk.plugins.annotation.LineOptions; - -class LineBuilder implements LineOptionsSink { - private final LineManager lineManager; - private final LineOptions lineOptions; - - LineBuilder(LineManager lineManager) { - this.lineManager = lineManager; - this.lineOptions = new LineOptions(); - } - - public LineOptions getLineOptions(){ - return this.lineOptions; - } - - Line build() { - return lineManager.create(lineOptions); - } - - @Override - public void setLineJoin(String lineJoin) { - lineOptions.withLineJoin(lineJoin); - } - - @Override - public void setLineOpacity(float lineOpacity) { - lineOptions.withLineOpacity(lineOpacity); - } - - @Override - public void setLineColor(String lineColor) { - lineOptions.withLineColor(lineColor); - } - - @Override - public void setLineWidth(float lineWidth) { - lineOptions.withLineWidth(lineWidth); - } - - @Override - public void setLineGapWidth(float lineGapWidth) { - lineOptions.withLineGapWidth(lineGapWidth); - } - - @Override - public void setLineOffset(float lineOffset) { - lineOptions.withLineOffset(lineOffset); - } - - @Override - public void setLineBlur(float lineBlur) { - lineOptions.withLineBlur(lineBlur); - } - - @Override - public void setLinePattern(String linePattern) { - lineOptions.withLinePattern(linePattern); - } - - @Override - public void setGeometry(List geometry) { - lineOptions.withLatLngs(geometry); - } - - @Override - public void setDraggable(boolean draggable) { - lineOptions.withDraggable(draggable); - } -} \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/LineController.java b/android/src/main/java/com/mapbox/mapboxgl/LineController.java deleted file mode 100644 index 27ae7e06e..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/LineController.java +++ /dev/null @@ -1,112 +0,0 @@ -// This file is generated. - -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import java.util.ArrayList; -import java.util.List; - -import android.graphics.PointF; -import android.util.Log; - -import com.mapbox.geojson.Point; -import com.mapbox.mapboxsdk.geometry.LatLng; -import com.mapbox.mapboxsdk.plugins.annotation.Line; -import com.mapbox.mapboxsdk.plugins.annotation.LineManager; -import android.graphics.Color; - -/** - * Controller of a single Line on the map. - */ -class LineController implements LineOptionsSink { - private final Line line; - private final OnLineTappedListener onTappedListener; - private boolean consumeTapEvents; - - LineController(Line line, boolean consumeTapEvents, OnLineTappedListener onTappedListener) { - this.line = line; - this.consumeTapEvents = consumeTapEvents; - this.onTappedListener = onTappedListener; - } - - public Line getLine(){ - return this.line; - } - - boolean onTap() { - if (onTappedListener != null) { - onTappedListener.onLineTapped(line); - } - return consumeTapEvents; - } - - void remove(LineManager lineManager) { - lineManager.delete(line); - } - - @Override - public void setLineJoin(String lineJoin) { - line.setLineJoin(lineJoin); - } - - @Override - public void setLineOpacity(float lineOpacity) { - line.setLineOpacity(lineOpacity); - } - - @Override - public void setLineColor(String lineColor) { - line.setLineColor(Color.parseColor(lineColor)); - } - - @Override - public void setLineWidth(float lineWidth) { - line.setLineWidth(lineWidth); - } - - @Override - public void setLineGapWidth(float lineGapWidth) { - line.setLineGapWidth(lineGapWidth); - } - - @Override - public void setLineOffset(float lineOffset) { - line.setLineOffset(lineOffset); - } - - @Override - public void setLineBlur(float lineBlur) { - line.setLineBlur(lineBlur); - } - - @Override - public void setLinePattern(String linePattern) { - line.setLinePattern(linePattern); - } - - @Override - public void setGeometry(List geometry) { - line.setLatLngs(geometry); - } - - public List getGeometry() { - List points = line.getGeometry().coordinates(); - List latLngs = new ArrayList<>(); - for (Point point : points) { - latLngs.add(new LatLng(point.latitude(), point.longitude())); - } - return latLngs; - } - - @Override - public void setDraggable(boolean draggable) { - line.setDraggable(draggable); - } - - public void update(LineManager lineManager) { - lineManager.update(line); - } -} diff --git a/android/src/main/java/com/mapbox/mapboxgl/LineOptionsSink.java b/android/src/main/java/com/mapbox/mapboxgl/LineOptionsSink.java deleted file mode 100644 index ddd2646b7..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/LineOptionsSink.java +++ /dev/null @@ -1,36 +0,0 @@ -// This file is generated. - -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import java.util.List; -import com.mapbox.mapboxsdk.geometry.LatLng; - -/** - * Receiver of Line configuration options. - */ -interface LineOptionsSink { - - void setLineJoin(String lineJoin); - - void setLineOpacity(float lineOpacity); - - void setLineColor(String lineColor); - - void setLineWidth(float lineWidth); - - void setLineGapWidth(float lineGapWidth); - - void setLineOffset(float lineOffset); - - void setLineBlur(float lineBlur); - - void setLinePattern(String linePattern); - - void setGeometry(List geometry); - - void setDraggable(boolean draggable); -} diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java index a9e93cd82..0d5786287 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java @@ -27,17 +27,16 @@ class MapboxMapBuilder implements MapboxMapOptionsSink { .attributionEnabled(true); private boolean trackCameraPosition = false; private boolean myLocationEnabled = false; + private boolean dragEnabled = true; private int myLocationTrackingMode = 0; private int myLocationRenderMode = 0; private String styleString = Style.MAPBOX_STREETS; - private List annotationOrder = new ArrayList(); - private List annotationConsumeTapEvents = new ArrayList(); private LatLngBounds bounds = null; MapboxMapController build( int id, Context context, BinaryMessenger messenger, MapboxMapsPlugin.LifecycleProvider lifecycleProvider, String accessToken) { final MapboxMapController controller = - new MapboxMapController(id, context, messenger, lifecycleProvider, options, accessToken, styleString, annotationOrder, annotationConsumeTapEvents); + new MapboxMapController(id, context, messenger, lifecycleProvider, options, accessToken, styleString, dragEnabled); controller.init(); controller.setMyLocationEnabled(myLocationEnabled); controller.setMyLocationTrackingMode(myLocationTrackingMode); @@ -210,12 +209,7 @@ public void setAttributionButtonMargins(int x, int y) { } } - public void setAnnotationOrder(List annotations) { - this.annotationOrder = annotations; + public void setDragEnabled(boolean enabled){ + this.dragEnabled = enabled; } - - public void setAnnotationConsumeTapEvents(List annotations) { - this.annotationConsumeTapEvents = annotations; - } - } diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index f5a273bf1..eac17f712 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -19,6 +19,8 @@ import android.util.Log; import android.view.Gravity; import android.view.View; +import android.view.MotionEvent; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -33,6 +35,8 @@ import com.mapbox.android.core.location.LocationEngineCallback; import com.mapbox.android.core.location.LocationEngineProvider; import com.mapbox.android.core.location.LocationEngineResult; +import com.mapbox.android.gestures.MoveGestureDetector; +import com.mapbox.android.gestures.AndroidGesturesManager; import com.mapbox.android.telemetry.TelemetryEnabler; import com.mapbox.geojson.Feature; import com.mapbox.geojson.FeatureCollection; @@ -55,20 +59,6 @@ import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.maps.Style; import com.mapbox.mapboxsdk.offline.OfflineManager; -import com.mapbox.mapboxsdk.plugins.annotation.Annotation; -import com.mapbox.mapboxsdk.plugins.annotation.Circle; -import com.mapbox.mapboxsdk.plugins.annotation.CircleManager; -import com.mapbox.mapboxsdk.plugins.annotation.CircleOptions; -import com.mapbox.mapboxsdk.plugins.annotation.Fill; -import com.mapbox.mapboxsdk.plugins.annotation.FillManager; -import com.mapbox.mapboxsdk.plugins.annotation.FillOptions; -import com.mapbox.mapboxsdk.plugins.annotation.Line; -import com.mapbox.mapboxsdk.plugins.annotation.LineManager; -import com.mapbox.mapboxsdk.plugins.annotation.LineOptions; -import com.mapbox.mapboxsdk.plugins.annotation.OnAnnotationClickListener; -import com.mapbox.mapboxsdk.plugins.annotation.Symbol; -import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager; -import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions; import com.mapbox.mapboxsdk.plugins.localization.LocalizationPlugin; import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.Layer; @@ -86,6 +76,7 @@ import com.mapbox.mapboxsdk.style.layers.PropertyValue; import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; + import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry; @@ -117,17 +108,12 @@ final class MapboxMapController MapboxMap.OnCameraMoveListener, MapboxMap.OnCameraMoveStartedListener, MapView.OnDidBecomeIdleListener, - OnAnnotationClickListener, MapboxMap.OnMapClickListener, MapboxMap.OnMapLongClickListener, MapboxMapOptionsSink, MethodChannel.MethodCallHandler, OnMapReadyCallback, OnCameraTrackingChangedListener, - OnSymbolTappedListener, - OnLineTappedListener, - OnCircleTappedListener, - OnFillTappedListener, PlatformView { private static final String TAG = "MapboxMapController"; private final int id; @@ -135,19 +121,12 @@ final class MapboxMapController private final MapboxMapsPlugin.LifecycleProvider lifecycleProvider; private MapView mapView; private MapboxMap mapboxMap; - private final Map symbols; - private final Map lines; - private final Map circles; - private final Map fills; - private SymbolManager symbolManager; - private LineManager lineManager; - private CircleManager circleManager; - private FillManager fillManager; private boolean trackCameraPosition = false; private boolean myLocationEnabled = false; private int myLocationTrackingMode = 0; private int myLocationRenderMode = 0; private boolean disposed = false; + private boolean dragEnabled = true; private final float density; private MethodChannel.Result mapReadyResult; private final Context context; @@ -157,12 +136,16 @@ final class MapboxMapController private LocationEngineCallback locationEngineCallback = null; private LocalizationPlugin localizationPlugin; private Style style; - private List annotationOrder; - private List annotationConsumeTapEvents; - private Set featureLayerIdentifiers; + private Feature draggedFeature; + private AndroidGesturesManager androidGesturesManager; + + private LatLng dragOrigin; + private LatLng dragPrevious; + + private Set interactiveFeatureLayerIds; + private Map addedFeaturesByLayer; + private LatLngBounds bounds = null; - private static final String annotationManagerNotCreatedErrorCode = "NO ANNOTATION MANAGER"; - private static final String annotationManagerNotCreatedErrorMessage = "To use %ss please add it to the annotation list"; MapboxMapController( int id, @@ -172,24 +155,23 @@ final class MapboxMapController MapboxMapOptions options, String accessToken, String styleStringInitial, - List annotationOrder, - List annotationConsumeTapEvents) { + boolean dragEnabled) { MapBoxUtils.getMapbox(context, accessToken); this.id = id; this.context = context; + this.dragEnabled = dragEnabled; this.styleStringInitial = styleStringInitial; this.mapView = new MapView(context, options); - this.featureLayerIdentifiers = new HashSet<>(); - this.symbols = new HashMap<>(); - this.lines = new HashMap<>(); - this.circles = new HashMap<>(); - this.fills = new HashMap<>(); + this.interactiveFeatureLayerIds = new HashSet<>(); + this.addedFeaturesByLayer = new HashMap(); this.density = context.getResources().getDisplayMetrics().density; this.lifecycleProvider = lifecycleProvider; + if(dragEnabled){ + this.androidGesturesManager = new AndroidGesturesManager(this.mapView.getContext(), false); + } + methodChannel = new MethodChannel(messenger, "plugins.flutter.io/mapbox_maps_" + id); methodChannel.setMethodCallHandler(this); - this.annotationOrder = annotationOrder; - this.annotationConsumeTapEvents = annotationConsumeTapEvents; } @Override @@ -214,71 +196,6 @@ private CameraPosition getCameraPosition() { return trackCameraPosition ? mapboxMap.getCameraPosition() : null; } - private SymbolController symbol(String symbolId) { - final SymbolController symbol = symbols.get(symbolId); - if (symbol == null) { - throw new IllegalArgumentException("Unknown symbol: " + symbolId); - } - return symbol; - } - - private LineBuilder newLineBuilder() { - return new LineBuilder(lineManager); - } - - private void removeLine(String lineId) { - final LineController lineController = lines.remove(lineId); - if (lineController != null) { - lineController.remove(lineManager); - } - } - - private LineController line(String lineId) { - final LineController line = lines.get(lineId); - if (line == null) { - throw new IllegalArgumentException("Unknown line: " + lineId); - } - return line; - } - - private CircleBuilder newCircleBuilder() { - return new CircleBuilder(circleManager); - } - - private void removeCircle(String circleId) { - final CircleController circleController = circles.remove(circleId); - if (circleController != null) { - circleController.remove(circleManager); - } - } - - private CircleController circle(String circleId) { - final CircleController circle = circles.get(circleId); - if (circle == null) { - throw new IllegalArgumentException("Unknown circle: " + circleId); - } - return circle; - } - - private FillBuilder newFillBuilder() { - return new FillBuilder(fillManager); - } - - private void removeFill(String fillId) { - final FillController fillController = fills.remove(fillId); - if (fillController != null) { - fillController.remove(fillManager); - } - } - - private FillController fill(String fillId) { - final FillController fill = fills.get(fillId); - if (fill == null) { - throw new IllegalArgumentException("Unknown fill: " + fillId); - } - return fill; - } - @Override public void onMapReady(MapboxMap mapboxMap) { this.mapboxMap = mapboxMap; @@ -290,6 +207,18 @@ public void onMapReady(MapboxMap mapboxMap) { mapboxMap.addOnCameraMoveListener(this); mapboxMap.addOnCameraIdleListener(this); + if(androidGesturesManager != null){ + androidGesturesManager.setMoveGestureListener(new MoveGestureListener()); + mapView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + androidGesturesManager.onTouchEvent(event); + + return draggedFeature != null; + } + }); + } + mapView.addOnStyleImageMissingListener((id) -> { DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); final Bitmap bitmap = getScaledImage(id, displayMetrics.density); @@ -331,32 +260,6 @@ public void setStyleString(String styleString) { public void onStyleLoaded(@NonNull Style style) { MapboxMapController.this.style = style; - // only add managers once to avoid issues with getLayerId after a style switch - if(symbolManager == null && circleManager == null && lineManager == null && fillManager == null) - { - final List orderReversed = new ArrayList(annotationOrder); - Collections.reverse(orderReversed); - String belowLayer = null; - for(String annotationType : orderReversed) { - switch (annotationType) { - case "AnnotationType.fill": - belowLayer = enableFillManager(style, belowLayer); - break; - case "AnnotationType.line": - belowLayer = enableLineManager(style, belowLayer); - break; - case "AnnotationType.circle": - belowLayer = enableCircleManager(style, belowLayer); - break; - case "AnnotationType.symbol": - belowLayer = enableSymbolManager(style, belowLayer); - break; - default: - throw new IllegalArgumentException("Unknown annotation type: " + annotationType + ", must be either 'fill', 'line', 'circle' or 'symbol'"); - } - } - } - if (myLocationEnabled) { enableLocationComponent(style); } @@ -365,8 +268,6 @@ public void onStyleLoaded(@NonNull Style style) { mapboxMap.setLatLngBoundsForCameraTarget(bounds); } - // needs to be placed after SymbolManager#addClickListener, - // is fixed with 0.6.0 of annotations plugin mapboxMap.addOnMapClickListener(MapboxMapController.this); mapboxMap.addOnMapLongClickListener(MapboxMapController.this); localizationPlugin = new LocalizationPlugin(mapView, mapboxMap, style); @@ -422,22 +323,43 @@ private void onUserLocationUpdate(Location location){ private void addGeoJsonSource(String sourceName, String source) { FeatureCollection featureCollection = FeatureCollection.fromJson(source); GeoJsonSource geoJsonSource = new GeoJsonSource(sourceName, featureCollection); + addedFeaturesByLayer.put(sourceName, featureCollection); style.addSource(geoJsonSource); } - private void setGeoJsonSource(String sourceName, String source) { - FeatureCollection featureCollection = FeatureCollection.fromJson(source); + private void setGeoJsonSource(String sourceName, String geojson) { + FeatureCollection featureCollection = FeatureCollection.fromJson(geojson); GeoJsonSource geoJsonSource = style.getSourceAs(sourceName); + addedFeaturesByLayer.put(sourceName, featureCollection); geoJsonSource.setGeoJson(featureCollection); } + private void setGeoJsonFeature(String sourceName, String geojsonFeature) { + Feature feature = Feature.fromJson(geojsonFeature); + FeatureCollection featureCollection = addedFeaturesByLayer.get(sourceName); + GeoJsonSource geoJsonSource = style.getSourceAs(sourceName); + if(featureCollection != null && geoJsonSource != null){ + final List features = featureCollection.features(); + for (int i = 0; i < features.size(); i++) { + final String id = features.get(i).id(); + if(id.equals(feature.id())){ + features.set(i, feature); + break; + } + } + + geoJsonSource.setGeoJson(featureCollection); + } + } + private void addSymbolLayer(String layerName, String sourceName, String belowLayerId, String sourceLayer, PropertyValue[] properties, + boolean enableInteraction, Expression filter) { SymbolLayer symbolLayer = new SymbolLayer(layerName, sourceName); symbolLayer.setProperties(properties); @@ -452,7 +374,9 @@ private void addSymbolLayer(String layerName, { style.addLayer(symbolLayer); } - featureLayerIdentifiers.add(layerName); + if(enableInteraction){ + interactiveFeatureLayerIds.add(layerName); + } } private void addLineLayer(String layerName, @@ -460,6 +384,7 @@ private void addLineLayer(String layerName, String belowLayerId, String sourceLayer, PropertyValue[] properties, + boolean enableInteraction, Expression filter) { LineLayer lineLayer = new LineLayer(layerName, sourceName); lineLayer.setProperties(properties); @@ -474,7 +399,9 @@ private void addLineLayer(String layerName, { style.addLayer(lineLayer); } - featureLayerIdentifiers.add(layerName); + if(enableInteraction){ + interactiveFeatureLayerIds.add(layerName); + } } private void addFillLayer(String layerName, @@ -482,6 +409,7 @@ private void addFillLayer(String layerName, String belowLayerId, String sourceLayer, PropertyValue[] properties, + boolean enableInteraction, Expression filter) { FillLayer fillLayer = new FillLayer(layerName, sourceName); fillLayer.setProperties(properties); @@ -496,7 +424,9 @@ private void addFillLayer(String layerName, { style.addLayer(fillLayer); } - featureLayerIdentifiers.add(layerName); + if(enableInteraction){ + interactiveFeatureLayerIds.add(layerName); + } } private void addCircleLayer(String layerName, @@ -504,6 +434,7 @@ private void addCircleLayer(String layerName, String belowLayerId, String sourceLayer, PropertyValue[] properties, + boolean enableInteraction, Expression filter) { CircleLayer circleLayer = new CircleLayer(layerName, sourceName); circleLayer.setProperties(properties); @@ -511,7 +442,6 @@ private void addCircleLayer(String layerName, circleLayer.setSourceLayer(sourceLayer); } - featureLayerIdentifiers.add(layerName); if(belowLayerId != null){ style.addLayerBelow(circleLayer, belowLayerId); } @@ -519,8 +449,11 @@ private void addCircleLayer(String layerName, { style.addLayer(circleLayer); } + if(enableInteraction){ + interactiveFeatureLayerIds.add(layerName); + }; } - + private void addRasterLayer(String layerName, String sourceName, String belowLayerId, @@ -538,7 +471,7 @@ private void addRasterLayer(String layerName, } } - private void addHillshadeLayer(String layerName, + private void addHillshadeLayer(String layerName, String sourceName, String belowLayerId, PropertyValue[] properties, @@ -555,49 +488,13 @@ private void addHillshadeLayer(String layerName, } } - private String enableSymbolManager(@NonNull Style style, @Nullable String belowLayer) { - if (symbolManager == null) { - symbolManager = new SymbolManager(mapView, mapboxMap, style, belowLayer); - symbolManager.setIconAllowOverlap(true); - symbolManager.setIconIgnorePlacement(true); - symbolManager.setTextAllowOverlap(true); - symbolManager.setTextIgnorePlacement(true); - symbolManager.addClickListener(MapboxMapController.this::onAnnotationClick); - } - return symbolManager.getLayerId(); - } - - private String enableLineManager(@NonNull Style style, @Nullable String belowLayer) { - if (lineManager == null) { - lineManager = new LineManager(mapView, mapboxMap, style, belowLayer); - lineManager.addClickListener(MapboxMapController.this::onAnnotationClick); - } - return lineManager.getLayerId(); - } - - private String enableCircleManager(@NonNull Style style, @Nullable String belowLayer) { - if (circleManager == null) { - circleManager = new CircleManager(mapView, mapboxMap, style, belowLayer); - circleManager.addClickListener(MapboxMapController.this::onAnnotationClick); - } - return circleManager.getLayerId(); - } - - private String enableFillManager(@NonNull Style style, @Nullable String belowLayer) { - if (fillManager == null) { - fillManager = new FillManager(mapView, mapboxMap, style, belowLayer); - fillManager.addClickListener(MapboxMapController.this::onAnnotationClick); - } - return fillManager.getLayerId(); - } - 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(featureLayerIdentifiers.contains(id)) + if(interactiveFeatureLayerIds.contains(id)) layersInOrder.add(id); } Collections.reverse(layersInOrder); @@ -812,406 +709,6 @@ public void onError(@NonNull String message) { }); break; } - case "symbols#addAll": { - if(symbolManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "symbol"), null); - return; - } - List newSymbolIds = new ArrayList(); - final List options = call.argument("options"); - List symbolOptionsList = new ArrayList(); - if (options != null) { - SymbolBuilder symbolBuilder; - for (Object o : options) { - symbolBuilder = new SymbolBuilder(); - Convert.interpretSymbolOptions(o, symbolBuilder); - symbolOptionsList.add(symbolBuilder.getSymbolOptions()); - } - if (!symbolOptionsList.isEmpty()) { - List newSymbols = symbolManager.create(symbolOptionsList); - String symbolId; - for (Symbol symbol : newSymbols) { - symbolId = String.valueOf(symbol.getId()); - newSymbolIds.add(symbolId); - symbols.put(symbolId, new SymbolController(symbol, annotationConsumeTapEvents.contains("AnnotationType.symbol"), this)); - } - } - } - result.success(newSymbolIds); - break; - } - case "symbols#removeAll": { - if(symbolManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "symbol"), null); - return; - } - final ArrayList symbolIds = call.argument("ids"); - SymbolController symbolController; - - List symbolList = new ArrayList(); - for(String symbolId : symbolIds){ - symbolController = symbols.remove(symbolId); - if (symbolController != null) { - symbolList.add(symbolController.getSymbol()); - } - } - if(!symbolList.isEmpty()) { - symbolManager.delete(symbolList); - } - result.success(null); - break; - } - case "symbol#update": { - if(symbolManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "symbol"), null); - return; - } - final String symbolId = call.argument("symbol"); - final SymbolController symbol = symbol(symbolId); - Convert.interpretSymbolOptions(call.argument("options"), symbol); - symbol.update(symbolManager); - result.success(null); - break; - } - case "symbol#getGeometry": { - if(symbolManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "symbol"), null); - return; - } - final String symbolId = call.argument("symbol"); - final SymbolController symbol = symbol(symbolId); - final LatLng symbolLatLng = symbol.getGeometry(); - Map hashMapLatLng = new HashMap<>(); - hashMapLatLng.put("latitude", symbolLatLng.getLatitude()); - hashMapLatLng.put("longitude", symbolLatLng.getLongitude()); - result.success(hashMapLatLng); - } - case "symbolManager#iconAllowOverlap": { - if(symbolManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "symbol"), null); - return; - } - final Boolean value = call.argument("iconAllowOverlap"); - symbolManager.setIconAllowOverlap(value); - result.success(null); - break; - } - case "symbolManager#iconIgnorePlacement": { - if(symbolManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "symbol"), null); - return; - } - final Boolean value = call.argument("iconIgnorePlacement"); - symbolManager.setIconIgnorePlacement(value); - result.success(null); - break; - } - case "symbolManager#textAllowOverlap": { - if(symbolManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "symbol"), null); - return; - } - final Boolean value = call.argument("textAllowOverlap"); - symbolManager.setTextAllowOverlap(value); - result.success(null); - break; - } - case "symbolManager#textIgnorePlacement": { - if(symbolManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "symbol"), null); - return; - } - final Boolean iconAllowOverlap = call.argument("textIgnorePlacement"); - symbolManager.setTextIgnorePlacement(iconAllowOverlap); - result.success(null); - break; - } - case "line#add": { - if(lineManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "line"), null); - return; - } - final LineBuilder lineBuilder = newLineBuilder(); - Convert.interpretLineOptions(call.argument("options"), lineBuilder); - final Line line = lineBuilder.build(); - final String lineId = String.valueOf(line.getId()); - lines.put(lineId, new LineController(line, annotationConsumeTapEvents.contains("AnnotationType.line"), this)); - result.success(lineId); - break; - } - case "line#remove": { - if(lineManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "line"), null); - return; - } - final String lineId = call.argument("line"); - removeLine(lineId); - result.success(null); - break; - } - case "line#addAll": { - if(lineManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "line"), null); - return; - } - List newIds = new ArrayList(); - final List options = call.argument("options"); - List optionList = new ArrayList(); - if (options != null) { - LineBuilder builder; - for (Object o : options) { - builder = newLineBuilder(); - Convert.interpretLineOptions(o, builder); - optionList.add(builder.getLineOptions()); - } - if (!optionList.isEmpty()) { - List newLines = lineManager.create(optionList); - String id; - for (Line line : newLines) { - id = String.valueOf(line.getId()); - newIds.add(id); - lines.put(id, new LineController(line, true, this)); - } - } - } - result.success(newIds); - break; - } - case "line#removeAll": { - if(lineManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "line"), null); - return; - } - final ArrayList ids = call.argument("ids"); - LineController lineController; - - List toBeRemoved = new ArrayList(); - for(String id : ids){ - lineController = lines.remove(id); - if (lineController != null) { - toBeRemoved.add(lineController.getLine()); - } - } - if(!toBeRemoved.isEmpty()) { - lineManager.delete(toBeRemoved); - } - result.success(null); - break; - } - case "line#update": { - if(lineManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "line"), null); - return; - } - final String lineId = call.argument("line"); - final LineController line = line(lineId); - Convert.interpretLineOptions(call.argument("options"), line); - line.update(lineManager); - result.success(null); - break; - } - case "line#getGeometry": { - if(lineManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "line"), null); - return; - } - final String lineId = call.argument("line"); - final LineController line = line(lineId); - final List lineLatLngs = line.getGeometry(); - final List resultList = new ArrayList<>(); - for (LatLng latLng: lineLatLngs){ - Map hashMapLatLng = new HashMap<>(); - hashMapLatLng.put("latitude", latLng.getLatitude()); - hashMapLatLng.put("longitude", latLng.getLongitude()); - resultList.add(hashMapLatLng); - } - result.success(resultList); - break; - } - case "circle#add": { - if(circleManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "circle"), null); - return; - } - final CircleBuilder circleBuilder = newCircleBuilder(); - Convert.interpretCircleOptions(call.argument("options"), circleBuilder); - final Circle circle = circleBuilder.build(); - final String circleId = String.valueOf(circle.getId()); - circles.put(circleId, new CircleController(circle, annotationConsumeTapEvents.contains("AnnotationType.circle"), this)); - result.success(circleId); - break; - } - case "circle#addAll": { - if(circleManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "circle"), null); - return; - } - List newIds = new ArrayList(); - final List options = call.argument("options"); - List optionList = new ArrayList(); - if (options != null) { - CircleBuilder builder; - for (Object o : options) { - builder = newCircleBuilder(); - Convert.interpretCircleOptions(o, builder); - optionList.add(builder.getCircleOptions()); - } - if (!optionList.isEmpty()) { - List newCircles = circleManager.create(optionList); - String id; - for (Circle circle : newCircles) { - id = String.valueOf(circle.getId()); - newIds.add(id); - circles.put(id, new CircleController(circle, true, this)); - } - } - } - result.success(newIds); - break; - } - case "circle#removeAll": { - if(circleManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "circle"), null); - return; - } - final ArrayList ids = call.argument("ids"); - CircleController circleController; - - List toBeRemoved = new ArrayList(); - for(String id : ids){ - circleController = circles.remove(id); - if (circleController != null) { - toBeRemoved.add(circleController.getCircle()); - } - } - if(!toBeRemoved.isEmpty()) { - circleManager.delete(toBeRemoved); - } - result.success(null); - break; - } - case "circle#remove": { - if(circleManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "circle"), null); - return; - } - final String circleId = call.argument("circle"); - removeCircle(circleId); - result.success(null); - break; - } - case "circle#update": { - if(circleManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "circle"), null); - return; - } - Log.e(TAG, "update circle"); - final String circleId = call.argument("circle"); - final CircleController circle = circle(circleId); - Convert.interpretCircleOptions(call.argument("options"), circle); - circle.update(circleManager); - result.success(null); - break; - } - case "circle#getGeometry": { - if(circleManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "circle"), null); - return; - } - final String circleId = call.argument("circle"); - final CircleController circle = circle(circleId); - final LatLng circleLatLng = circle.getGeometry(); - Map hashMapLatLng = new HashMap<>(); - hashMapLatLng.put("latitude", circleLatLng.getLatitude()); - hashMapLatLng.put("longitude", circleLatLng.getLongitude()); - result.success(hashMapLatLng); - break; - } - case "fill#add": { - if(fillManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "fill"), null); - return; - } - final FillBuilder fillBuilder = newFillBuilder(); - Convert.interpretFillOptions(call.argument("options"), fillBuilder); - final Fill fill = fillBuilder.build(); - final String fillId = String.valueOf(fill.getId()); - fills.put(fillId, new FillController(fill, annotationConsumeTapEvents.contains("AnnotationType.fill"), this)); - result.success(fillId); - break; - } - - case "fill#addAll": { - if(fillManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "fill"), null); - return; - } - List newIds = new ArrayList(); - final List options = call.argument("options"); - List optionList = new ArrayList(); - if (options != null) { - FillBuilder builder; - for (Object o : options) { - builder = newFillBuilder(); - Convert.interpretFillOptions(o, builder); - optionList.add(builder.getFillOptions()); - } - if (!optionList.isEmpty()) { - List newFills = fillManager.create(optionList); - String id; - for (Fill fill : newFills) { - id = String.valueOf(fill.getId()); - newIds.add(id); - fills.put(id, new FillController(fill, true, this)); - } - } - } - result.success(newIds); - break; - } - case "fill#removeAll": { - if(fillManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "fill"), null); - return; - } - final ArrayList ids = call.argument("ids"); - FillController fillController; - - List toBeRemoved = new ArrayList(); - for(String id : ids){ - fillController = fills.remove(id); - if (fillController != null) { - toBeRemoved.add(fillController.getFill()); - } - } - if(!toBeRemoved.isEmpty()) { - fillManager.delete(toBeRemoved); - } - result.success(null); - break; - } - case "fill#remove": { - if(fillManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "fill"), null); - return; - } - final String fillId = call.argument("fill"); - removeFill(fillId); - result.success(null); - break; - } - case "fill#update": { - if(fillManager == null){ - result.error(annotationManagerNotCreatedErrorCode, String.format(annotationManagerNotCreatedErrorCode, "fill"), null); - return; - } - final String fillId = call.argument("fill"); - final FillController fill = fill(fillId); - Convert.interpretFillOptions(call.argument("options"), fill); - fill.update(fillManager); - result.success(null); - break; - } case "source#addGeoJson": { final String sourceId = call.argument("sourceId"); final String geojson = call.argument("geojson"); @@ -1226,13 +723,21 @@ public void onError(@NonNull String message) { result.success(null); break; } + case "source#setFeature":{ + final String sourceId = call.argument("sourceId"); + final String geojsonFeature = call.argument("geojsonFeature"); + setGeoJsonFeature(sourceId, geojsonFeature); + result.success(null); + break; + } case "symbolLayer#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 boolean enableInteraction = call.argument("enableInteraction"); final PropertyValue[] properties = LayerPropertyConverter.interpretSymbolLayerProperties(call.argument("properties")); - addSymbolLayer(layerId, sourceId, belowLayerId, sourceLayer, properties, null); + addSymbolLayer(layerId, sourceId, belowLayerId, sourceLayer, properties, enableInteraction, null); result.success(null); break; } @@ -1241,8 +746,9 @@ public void onError(@NonNull String message) { final String layerId = call.argument("layerId"); final String belowLayerId = call.argument("belowLayerId"); final String sourceLayer = call.argument("sourceLayer"); + final boolean enableInteraction = call.argument("enableInteraction"); final PropertyValue[] properties = LayerPropertyConverter.interpretLineLayerProperties(call.argument("properties")); - addLineLayer(layerId, sourceId, belowLayerId, sourceLayer, properties, null); + addLineLayer(layerId, sourceId, belowLayerId, sourceLayer, properties, enableInteraction, null); result.success(null); break; } @@ -1251,8 +757,9 @@ public void onError(@NonNull String message) { final String layerId = call.argument("layerId"); final String belowLayerId = call.argument("belowLayerId"); final String sourceLayer = call.argument("sourceLayer"); + final boolean enableInteraction = call.argument("enableInteraction"); final PropertyValue[] properties = LayerPropertyConverter.interpretFillLayerProperties(call.argument("properties")); - addFillLayer(layerId, sourceId, belowLayerId, sourceLayer, properties, null); + addFillLayer(layerId, sourceId, belowLayerId, sourceLayer, properties, enableInteraction, null); result.success(null); break; } @@ -1261,8 +768,9 @@ public void onError(@NonNull String message) { final String layerId = call.argument("layerId"); final String belowLayerId = call.argument("belowLayerId"); final String sourceLayer = call.argument("sourceLayer"); + final boolean enableInteraction = call.argument("enableInteraction"); final PropertyValue[] properties = LayerPropertyConverter.interpretCircleLayerProperties(call.argument("properties")); - addCircleLayer(layerId, sourceId, belowLayerId, sourceLayer, properties, null); + addCircleLayer(layerId, sourceId, belowLayerId, sourceLayer, properties, enableInteraction, null); result.success(null); break; } @@ -1343,14 +851,6 @@ public void onFailure(@NonNull Exception exception) { result.success(null); break; } - case "style#setSource": { - if (style == null) { - result.error("STYLE IS NULL", "The style is null. Has onStyleLoaded() already been invoked?", null); - } - style.removeSource((String) call.argument("sourceId")); - result.success(null); - break; - } case "style#addLayer": { if (style == null) { result.error("STYLE IS NULL", "The style is null. Has onStyleLoaded() already been invoked?", null); @@ -1373,7 +873,7 @@ public void onFailure(@NonNull Exception exception) { } String layerId = call.argument("layerId"); style.removeLayer(layerId); - featureLayerIdentifiers.remove(layerId); + interactiveFeatureLayerIds.remove(layerId); result.success(null); break; @@ -1428,65 +928,6 @@ public void onDidBecomeIdle() { methodChannel.invokeMethod("map#onIdle", new HashMap<>()); } - @Override - public boolean onAnnotationClick(Annotation annotation) { - if (annotation instanceof Symbol) { - final SymbolController symbolController = symbols.get(String.valueOf(annotation.getId())); - if (symbolController != null) { - return symbolController.onTap(); - } - } - - if (annotation instanceof Line) { - final LineController lineController = lines.get(String.valueOf(annotation.getId())); - if (lineController != null) { - return lineController.onTap(); - } - } - - if (annotation instanceof Circle) { - final CircleController circleController = circles.get(String.valueOf(annotation.getId())); - if (circleController != null) { - return circleController.onTap(); - } - } - if (annotation instanceof Fill) { - final FillController fillController = fills.get(String.valueOf(annotation.getId())); - if (fillController != null) { - return fillController.onTap(); - } - } - return false; - } - - @Override - public void onSymbolTapped(Symbol symbol) { - final Map arguments = new HashMap<>(2); - arguments.put("symbol", String.valueOf(symbol.getId())); - methodChannel.invokeMethod("symbol#onTap", arguments); - } - - @Override - public void onLineTapped(Line line) { - final Map arguments = new HashMap<>(2); - arguments.put("line", String.valueOf(line.getId())); - methodChannel.invokeMethod("line#onTap", arguments); - } - - @Override - public void onCircleTapped(Circle circle) { - final Map arguments = new HashMap<>(2); - arguments.put("circle", String.valueOf(circle.getId())); - methodChannel.invokeMethod("circle#onTap", arguments); - } - - @Override - public void onFillTapped(Fill fill) { - final Map arguments = new HashMap<>(2); - arguments.put("fill", String.valueOf(fill.getId())); - methodChannel.invokeMethod("fill#onTap", arguments); - } - @Override public boolean onMapClick(@NonNull LatLng point) { PointF pointf = mapboxMap.getProjection().toScreenLocation(point); @@ -1545,18 +986,6 @@ private void destroyMapViewIfNecessary() { if (locationComponent != null) { locationComponent.setLocationComponentEnabled(false); } - if (symbolManager != null) { - symbolManager.onDestroy(); - } - if (lineManager != null) { - lineManager.onDestroy(); - } - if (circleManager != null) { - circleManager.onDestroy(); - } - if (fillManager != null) { - fillManager.onDestroy(); - } stopListeningForLocationUpdates(); mapView.onDestroy(); @@ -1903,4 +1332,97 @@ public void onFinish() { public void onCancel() { } } + + boolean onMoveBegin(MoveGestureDetector detector) { + // onMoveBegin gets called even during a move - move end is also not called unless this function returns + // true at least once. To avoid redundant queries only check for feature if the previous event was ACTION_DOWN + if (detector.getPreviousEvent().getActionMasked() == MotionEvent.ACTION_DOWN && detector.getPointersCount() == 1) { + PointF pointf = detector.getFocalPoint(); + LatLng origin = mapboxMap.getProjection().fromScreenLocation(pointf); + RectF rectF = new RectF( + pointf.x - 10, + pointf.y - 10, + pointf.x + 10, + pointf.y + 10 + ); + Feature feature = firstFeatureOnLayers(rectF); + if (feature != null && startDragging(feature, origin)) { + return true; + } + } + return false; + } + + + boolean onMove(MoveGestureDetector detector) { + if (draggedFeature != null) { + if (detector.getPointersCount() > 1) { + stopDragging(); + return true; + } + + PointF pointf = detector.getFocalPoint(); + LatLng current = mapboxMap.getProjection().fromScreenLocation(pointf); + + final Map arguments = new HashMap<>(9); + arguments.put("id", draggedFeature.id()); + arguments.put("x", pointf.x); + arguments.put("y", pointf.y); + + arguments.put("originLng", dragOrigin.getLongitude()); + arguments.put("originLat", dragOrigin.getLatitude()); + arguments.put("currentLng", current.getLongitude()); + arguments.put("currentLat", current.getLatitude()); + arguments.put("deltaLng", current.getLongitude() - dragPrevious.getLongitude()); + arguments.put("deltaLat", current.getLatitude() - dragPrevious.getLatitude()); + + methodChannel.invokeMethod("feature#onDrag", arguments); + dragPrevious = current; + return false; + } + return true; + } + + void onMoveEnd() { + stopDragging(); + } + + boolean startDragging(@NonNull Feature feature, @NonNull LatLng origin) { + final boolean draggable = feature.hasNonNullValueForProperty("draggable") ? + feature.getBooleanProperty("draggable") : false; + if (draggable) { + draggedFeature = feature; + dragPrevious = origin; + dragOrigin = origin; + return true; + } + return false; + } + + + void stopDragging() { + draggedFeature = null; + dragOrigin = null; + dragPrevious = null; + } + + + + private class MoveGestureListener implements MoveGestureDetector.OnMoveGestureListener { + + @Override + public boolean onMoveBegin(MoveGestureDetector detector) { + return MapboxMapController.this.onMoveBegin(detector); + } + + @Override + public boolean onMove(MoveGestureDetector detector, float distanceX, float distanceY) { + return MapboxMapController.this.onMove(detector); + } + + @Override + public void onMoveEnd(MoveGestureDetector detector, float velocityX, float velocityY) { + MapboxMapController.this.onMoveEnd(); + } + } } diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapFactory.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapFactory.java index 1b2c4c9c8..6e322098c 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapFactory.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapFactory.java @@ -35,14 +35,11 @@ public PlatformView create(Context context, int id, Object args) { CameraPosition position = Convert.toCameraPosition(params.get("initialCameraPosition")); builder.setInitialCameraPosition(position); } - if (params.containsKey("annotationOrder")) { - List annotations = Convert.toAnnotationOrder(params.get("annotationOrder")); - builder.setAnnotationOrder(annotations); - } - if (params.containsKey("annotationConsumeTapEvents")) { - List annotations = Convert.toAnnotationConsumeTapEvents(params.get("annotationConsumeTapEvents")); - builder.setAnnotationConsumeTapEvents(annotations); + if (params.containsKey("dragEnabled")) { + boolean dragEnabled = Convert.toBoolean(params.get("dragEnabled")); + builder.setDragEnabled(dragEnabled); } + return builder.build(id, context, messenger, lifecycleProvider, (String) params.get("accessToken")); } } diff --git a/android/src/main/java/com/mapbox/mapboxgl/OnCircleTappedListener.java b/android/src/main/java/com/mapbox/mapboxgl/OnCircleTappedListener.java deleted file mode 100644 index c2988d596..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/OnCircleTappedListener.java +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import com.mapbox.mapboxsdk.plugins.annotation.Circle; - -interface OnCircleTappedListener { - void onCircleTapped(Circle circle); -} diff --git a/android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java b/android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java deleted file mode 100644 index 27eb86425..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.mapbox.mapboxgl; - -import com.mapbox.mapboxsdk.plugins.annotation.Fill; - -public interface OnFillTappedListener { - void onFillTapped(Fill fill); -} diff --git a/android/src/main/java/com/mapbox/mapboxgl/OnLineTappedListener.java b/android/src/main/java/com/mapbox/mapboxgl/OnLineTappedListener.java deleted file mode 100644 index 325f5277c..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/OnLineTappedListener.java +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import com.mapbox.mapboxsdk.plugins.annotation.Line; - -interface OnLineTappedListener { - void onLineTapped(Line line); -} diff --git a/android/src/main/java/com/mapbox/mapboxgl/OnSymbolTappedListener.java b/android/src/main/java/com/mapbox/mapboxgl/OnSymbolTappedListener.java deleted file mode 100644 index 9e567e74a..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/OnSymbolTappedListener.java +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import com.mapbox.mapboxsdk.plugins.annotation.Symbol; - -interface OnSymbolTappedListener { - void onSymbolTapped(Symbol symbol); -} diff --git a/android/src/main/java/com/mapbox/mapboxgl/SymbolBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/SymbolBuilder.java deleted file mode 100644 index 93e2718af..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/SymbolBuilder.java +++ /dev/null @@ -1,168 +0,0 @@ -// This file is generated. - -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import com.mapbox.geojson.Point; -import com.mapbox.mapboxsdk.geometry.LatLng; -import com.mapbox.mapboxsdk.plugins.annotation.Symbol; -import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager; -import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions; - -class SymbolBuilder implements SymbolOptionsSink { - private final SymbolOptions symbolOptions; - private static boolean customImage; - - SymbolBuilder() { - this.symbolOptions = new SymbolOptions(); - } - - public SymbolOptions getSymbolOptions(){ - return this.symbolOptions; - } - - @Override - public void setIconSize(float iconSize) { - symbolOptions.withIconSize(iconSize); - } - - @Override - public void setIconImage(String iconImage) { - symbolOptions.withIconImage(iconImage); - } - - @Override - public void setIconRotate(float iconRotate) { - symbolOptions.withIconRotate(iconRotate); - } - - @Override - public void setIconOffset(float[] iconOffset) { - symbolOptions.withIconOffset(new Float[] {iconOffset[0], iconOffset[1]}); - } - - @Override - public void setIconAnchor(String iconAnchor) { - symbolOptions.withIconAnchor(iconAnchor); - } - - @Override - public void setFontNames(String[] fontNames) { symbolOptions.withTextFont(fontNames); } - - @Override - public void setTextField(String textField) { - symbolOptions.withTextField(textField); - } - - @Override - public void setTextSize(float textSize) { - symbolOptions.withTextSize(textSize); - } - - @Override - public void setTextMaxWidth(float textMaxWidth) { - symbolOptions.withTextMaxWidth(textMaxWidth); - } - - @Override - public void setTextLetterSpacing(float textLetterSpacing) { - symbolOptions.withTextLetterSpacing(textLetterSpacing); - } - - @Override - public void setTextJustify(String textJustify) { - symbolOptions.withTextJustify(textJustify); - } - - @Override - public void setTextAnchor(String textAnchor) { - symbolOptions.withTextAnchor(textAnchor); - } - - @Override - public void setTextRotate(float textRotate) { - symbolOptions.withTextRotate(textRotate); - } - - @Override - public void setTextTransform(String textTransform) { - symbolOptions.withTextTransform(textTransform); - } - - @Override - public void setTextOffset(float[] textOffset) { - symbolOptions.withTextOffset(new Float[] {textOffset[0], textOffset[1]}); - } - - @Override - public void setIconOpacity(float iconOpacity) { - symbolOptions.withIconOpacity(iconOpacity); - } - - @Override - public void setIconColor(String iconColor) { - symbolOptions.withIconColor(iconColor); - } - - @Override - public void setIconHaloColor(String iconHaloColor) { - symbolOptions.withIconHaloColor(iconHaloColor); - } - - @Override - public void setIconHaloWidth(float iconHaloWidth) { - symbolOptions.withIconHaloWidth(iconHaloWidth); - } - - @Override - public void setIconHaloBlur(float iconHaloBlur) { - symbolOptions.withIconHaloBlur(iconHaloBlur); - } - - @Override - public void setTextOpacity(float textOpacity) { - symbolOptions.withTextOpacity(textOpacity); - } - - @Override - public void setTextColor(String textColor) { - symbolOptions.withTextColor(textColor); - } - - @Override - public void setTextHaloColor(String textHaloColor) { - symbolOptions.withTextHaloColor(textHaloColor); - } - - @Override - public void setTextHaloWidth(float textHaloWidth) { - symbolOptions.withTextHaloWidth(textHaloWidth); - } - - @Override - public void setTextHaloBlur(float textHaloBlur) { - symbolOptions.withTextHaloBlur(textHaloBlur); - } - - @Override - public void setGeometry(LatLng geometry) { - symbolOptions.withGeometry(Point.fromLngLat(geometry.getLongitude(), geometry.getLatitude())); - } - - @Override - public void setSymbolSortKey(float symbolSortKey) { - symbolOptions.withSymbolSortKey(symbolSortKey); - } - - @Override - public void setDraggable(boolean draggable) { - symbolOptions.withDraggable(draggable); - } - - public boolean getCustomImage() { - return this.customImage; - } -} \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/SymbolController.java b/android/src/main/java/com/mapbox/mapboxgl/SymbolController.java deleted file mode 100644 index 348779527..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/SymbolController.java +++ /dev/null @@ -1,191 +0,0 @@ -// This file is generated. - -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import android.graphics.Color; -import android.graphics.PointF; -import com.mapbox.geojson.Point; -import com.mapbox.mapboxsdk.geometry.LatLng; -import com.mapbox.mapboxsdk.plugins.annotation.Symbol; -import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager; - -/** - * Controller of a single Symbol on the map. - */ -class SymbolController implements SymbolOptionsSink { - private final Symbol symbol; - private final OnSymbolTappedListener onTappedListener; - private boolean consumeTapEvents; - - SymbolController(Symbol symbol, boolean consumeTapEvents, OnSymbolTappedListener onTappedListener) { - this.symbol = symbol; - this.consumeTapEvents = consumeTapEvents; - this.onTappedListener = onTappedListener; - } - - boolean onTap() { - if (onTappedListener != null) { - onTappedListener.onSymbolTapped(symbol); - } - return consumeTapEvents; - } - - public Symbol getSymbol(){ - return this.symbol; - } - - void remove(SymbolManager symbolManager) { - symbolManager.delete(symbol); - } - - @Override - public void setIconSize(float iconSize) { - symbol.setIconSize(iconSize); - } - - @Override - public void setIconImage(String iconImage) { - symbol.setIconImage(iconImage); - } - - @Override - public void setIconRotate(float iconRotate) { - symbol.setIconRotate(iconRotate); - } - - @Override - public void setIconOffset(float[] iconOffset) { - symbol.setIconOffset(new PointF(iconOffset[0], iconOffset[1])); - } - - @Override - public void setIconAnchor(String iconAnchor) { - symbol.setIconAnchor(iconAnchor); - } - - @Override - public void setFontNames(String[] fontNames) { symbol.setTextFont(fontNames); } - - @Override - public void setTextField(String textField) { - symbol.setTextField(textField); - } - - @Override - public void setTextSize(float textSize) { - symbol.setTextSize(textSize); - } - - @Override - public void setTextMaxWidth(float textMaxWidth) { - symbol.setTextMaxWidth(textMaxWidth); - } - - @Override - public void setTextLetterSpacing(float textLetterSpacing) { - symbol.setTextLetterSpacing(textLetterSpacing); - } - - @Override - public void setTextJustify(String textJustify) { - symbol.setTextJustify(textJustify); - } - - @Override - public void setTextAnchor(String textAnchor) { - symbol.setTextAnchor(textAnchor); - } - - @Override - public void setTextRotate(float textRotate) { - symbol.setTextRotate(textRotate); - } - - @Override - public void setTextTransform(String textTransform) { - symbol.setTextTransform(textTransform); - } - - @Override - public void setTextOffset(float[] textOffset) { - symbol.setTextOffset(new PointF(textOffset[0], textOffset[1])); - } - - @Override - public void setIconOpacity(float iconOpacity) { - symbol.setIconOpacity(iconOpacity); - } - - @Override - public void setIconColor(String iconColor) { - symbol.setIconColor(Color.parseColor(iconColor)); - } - - @Override - public void setIconHaloColor(String iconHaloColor) { - symbol.setIconHaloColor(Color.parseColor(iconHaloColor)); - } - - @Override - public void setIconHaloWidth(float iconHaloWidth) { - symbol.setIconHaloWidth(iconHaloWidth); - } - - @Override - public void setIconHaloBlur(float iconHaloBlur) { - symbol.setIconHaloBlur(iconHaloBlur); - } - - @Override - public void setTextOpacity(float textOpacity) { - symbol.setTextOpacity(textOpacity); - } - - @Override - public void setTextColor(String textColor) { - symbol.setTextColor(Color.parseColor(textColor)); - } - - @Override - public void setTextHaloColor(String textHaloColor) { - symbol.setTextHaloColor(Color.parseColor(textHaloColor)); - } - - @Override - public void setTextHaloWidth(float textHaloWidth) { - symbol.setTextHaloWidth(textHaloWidth); - } - - @Override - public void setTextHaloBlur(float textHaloBlur) { - symbol.setTextHaloBlur(textHaloBlur); - } - - @Override - public void setSymbolSortKey(float symbolSortKey) { - symbol.setSymbolSortKey(symbolSortKey); - } - - @Override - public void setGeometry(LatLng geometry) { - symbol.setGeometry(Point.fromLngLat(geometry.getLongitude(), geometry.getLatitude())); - } - - public LatLng getGeometry() { - Point point = symbol.getGeometry(); - return new LatLng(point.latitude(), point.longitude()); - } - - @Override - public void setDraggable(boolean draggable) { - symbol.setDraggable(draggable); - } - - public void update(SymbolManager symbolManager) { - symbolManager.update(symbol); - } -} diff --git a/android/src/main/java/com/mapbox/mapboxgl/SymbolOptionsSink.java b/android/src/main/java/com/mapbox/mapboxgl/SymbolOptionsSink.java deleted file mode 100644 index ee52e8672..000000000 --- a/android/src/main/java/com/mapbox/mapboxgl/SymbolOptionsSink.java +++ /dev/null @@ -1,72 +0,0 @@ -// This file is generated. - -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.mapbox.mapboxgl; - -import com.mapbox.mapboxsdk.geometry.LatLng; - -/** - * Receiver of Symbol configuration options. - */ -interface SymbolOptionsSink { - - void setIconSize(float iconSize); - - void setIconImage(String iconImage); - - void setIconRotate(float iconRotate); - - void setIconOffset(float[] iconOffset); - - void setIconAnchor(String iconAnchor); - - void setFontNames(String[] fontNames); - - void setTextField(String textField); - - void setTextSize(float textSize); - - void setTextMaxWidth(float textMaxWidth); - - void setTextLetterSpacing(float textLetterSpacing); - - void setTextJustify(String textJustify); - - void setTextAnchor(String textAnchor); - - void setTextRotate(float textRotate); - - void setTextTransform(String textTransform); - - void setTextOffset(float[] textOffset); - - void setIconOpacity(float iconOpacity); - - void setIconColor(String iconColor); - - void setIconHaloColor(String iconHaloColor); - - void setIconHaloWidth(float iconHaloWidth); - - void setIconHaloBlur(float iconHaloBlur); - - void setTextOpacity(float textOpacity); - - void setTextColor(String textColor); - - void setTextHaloColor(String textHaloColor); - - void setTextHaloWidth(float textHaloWidth); - - void setTextHaloBlur(float textHaloBlur); - - void setGeometry(LatLng geometry); - - void setSymbolSortKey(float symbolSortKey); - - void setDraggable(boolean draggable); - -} diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index cfe29c302..364c91ab1 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -69,4 +69,12 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.0-alpha4' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4' + constraints { + implementation('com.google.android.gms:play-services-location') { + version { + strictly "16.0.0" + } + because 'location: 4.2.3 does not specify version play-services-location' + } + } } diff --git a/example/lib/layer.dart b/example/lib/layer.dart index 561fa53de..80b4e7c2b 100644 --- a/example/lib/layer.dart +++ b/example/lib/layer.dart @@ -26,19 +26,18 @@ class LayerState extends State { @override Widget build(BuildContext context) { - return Container( - child: MapboxMap( - accessToken: MapsDemo.ACCESS_TOKEN, - onMapCreated: _onMapCreated, - onMapClick: (point, latLong) => - print(point.toString() + latLong.toString()), - onStyleLoadedCallback: _onStyleLoadedCallback, - initialCameraPosition: CameraPosition( - target: center, - zoom: 11.0, - ), - annotationOrder: const [], + return MapboxMap( + accessToken: MapsDemo.ACCESS_TOKEN, + dragEnabled: false, + onMapCreated: _onMapCreated, + onMapClick: (point, latLong) => + print(point.toString() + latLong.toString()), + onStyleLoadedCallback: _onStyleLoadedCallback, + initialCameraPosition: CameraPosition( + target: center, + zoom: 11.0, ), + annotationOrder: const [], ); } @@ -118,22 +117,23 @@ class LayerState extends State { ); await controller.addSymbolLayer( - "moving", - "moving", - SymbolLayerProperties( - textField: [Expressions.get, "name"], - textHaloWidth: 1, - textSize: 10, - textHaloColor: Colors.white.toHexStringRGB(), - textOffset: [ - Expressions.literal, - [0, 2] - ], - iconImage: "bicycle-15", - iconSize: 2, - iconAllowOverlap: true, - textAllowOverlap: true, - )); + "moving", + "moving", + SymbolLayerProperties( + textField: [Expressions.get, "name"], + textHaloWidth: 1, + textSize: 10, + textHaloColor: Colors.white.toHexStringRGB(), + textOffset: [ + Expressions.literal, + [0, 2] + ], + iconImage: "bicycle-15", + iconSize: 2, + iconAllowOverlap: true, + textAllowOverlap: true, + ), + ); timer = Timer.periodic( Duration(milliseconds: 10), (t) => controller.setGeoJsonSource( diff --git a/example/lib/place_symbol.dart b/example/lib/place_symbol.dart index 1e618d327..2edc59051 100644 --- a/example/lib/place_symbol.dart +++ b/example/lib/place_symbol.dart @@ -87,8 +87,8 @@ class PlaceSymbolBodyState extends State { ); } - void _updateSelectedSymbol(SymbolOptions changes) { - controller!.updateSymbol(_selectedSymbol!, changes); + void _updateSelectedSymbol(SymbolOptions changes) async { + await controller!.updateSymbol(_selectedSymbol!, changes); } void _add(String iconImage) { @@ -126,6 +126,8 @@ class PlaceSymbolBodyState extends State { ) : SymbolOptions( geometry: geometry, + textField: 'Airport', + textOffset: Offset(0, 0.8), iconImage: iconImage, ); } @@ -274,6 +276,7 @@ class PlaceSymbolBodyState extends State { _iconAllowOverlap = !_iconAllowOverlap; }); controller!.setSymbolIconAllowOverlap(_iconAllowOverlap); + controller!.setSymbolTextAllowOverlap(_iconAllowOverlap); } @override diff --git a/ios/Classes/Convert.swift b/ios/Classes/Convert.swift index b6113fa48..fb1a521c5 100644 --- a/ios/Classes/Convert.swift +++ b/ios/Classes/Convert.swift @@ -185,190 +185,6 @@ class Convert { ) } - class func interpretSymbolOptions(options: Any?, delegate: MGLSymbolStyleAnnotation) { - guard let options = options as? [String: Any] else { return } - if let iconSize = options["iconSize"] as? CGFloat { - delegate.iconScale = iconSize - } - if let iconImage = options["iconImage"] as? String { - delegate.iconImageName = iconImage - } - if let iconRotate = options["iconRotate"] as? CGFloat { - delegate.iconRotation = iconRotate - } - if let iconOffset = options["iconOffset"] as? [Double] { - delegate.iconOffset = CGVector(dx: iconOffset[0], dy: iconOffset[1]) - } - if let iconAnchorStr = options["iconAnchor"] as? String { - if let iconAnchor = Constants.symbolIconAnchorMapping[iconAnchorStr] { - delegate.iconAnchor = iconAnchor - } else { - delegate.iconAnchor = MGLIconAnchor.center - } - } - if let iconOpacity = options["iconOpacity"] as? CGFloat { - delegate.iconOpacity = iconOpacity - } - if let iconColor = options["iconColor"] as? String { - delegate.iconColor = UIColor(hexString: iconColor) ?? UIColor.black - } - if let iconHaloColor = options["iconHaloColor"] as? String { - delegate.iconHaloColor = UIColor(hexString: iconHaloColor) ?? UIColor.white - } - if let iconHaloWidth = options["iconHaloWidth"] as? CGFloat { - delegate.iconHaloWidth = iconHaloWidth - } - if let iconHaloBlur = options["iconHaloBlur"] as? CGFloat { - delegate.iconHaloBlur = iconHaloBlur - } - if let fontNames = options["fontNames"] as? [String] { - delegate.fontNames = fontNames - } - if let textField = options["textField"] as? String { - delegate.text = textField - } - if let textSize = options["textSize"] as? CGFloat { - delegate.textFontSize = textSize - } - if let textMaxWidth = options["textMaxWidth"] as? CGFloat { - delegate.maximumTextWidth = textMaxWidth - } - if let textLetterSpacing = options["textLetterSpacing"] as? CGFloat { - delegate.textLetterSpacing = textLetterSpacing - } - if let textJustify = options["textJustify"] as? String { - if let textJustifaction = Constants.symbolTextJustificationMapping[textJustify] { - delegate.textJustification = textJustifaction - } else { - delegate.textJustification = MGLTextJustification.center - } - } - if let textRadialOffset = options["textRadialOffset"] as? CGFloat { - delegate.textRadialOffset = textRadialOffset - } - if let textAnchorStr = options["textAnchor"] as? String { - if let textAnchor = Constants.symbolTextAnchorMapping[textAnchorStr] { - delegate.textAnchor = textAnchor - } else { - delegate.textAnchor = MGLTextAnchor.center - } - } - if let textRotate = options["textRotate"] as? CGFloat { - delegate.textRotation = textRotate - } - if let textTransform = options["textTransform"] as? String { - if let textTransformation = Constants.symbolTextTransformationMapping[textTransform] { - delegate.textTransform = textTransformation - } else { - delegate.textTransform = MGLTextTransform.none - } - } - if let textTranslate = options["textTranslate"] as? [Double] { - delegate.textTranslation = CGVector(dx: textTranslate[0], dy: textTranslate[1]) - } - if let textOffset = options["textOffset"] as? [Double] { - delegate.textOffset = CGVector(dx: textOffset[0], dy: textOffset[1]) - } - if let textOpacity = options["textOpacity"] as? CGFloat { - delegate.textOpacity = textOpacity - } - if let textColor = options["textColor"] as? String { - delegate.textColor = UIColor(hexString: textColor) ?? UIColor.black - } - if let textHaloColor = options["textHaloColor"] as? String { - delegate.textHaloColor = UIColor(hexString: textHaloColor) ?? UIColor.white - } - if let textHaloWidth = options["textHaloWidth"] as? CGFloat { - delegate.textHaloWidth = textHaloWidth - } - if let textHaloBlur = options["textHaloBlur"] as? CGFloat { - delegate.textHaloBlur = textHaloBlur - } - if let geometry = options["geometry"] as? [Double] { - // We cannot set the geometry directy on the annotation so calculate - // the difference and update the coordinate using the delta. - let currCoord = delegate.feature.coordinate - let newCoord = CLLocationCoordinate2DMake(geometry[0], geometry[1]) - let delta = CGVector( - dx: newCoord.longitude - currCoord.longitude, - dy: newCoord.latitude - currCoord.latitude - ) - delegate.updateGeometryCoordinates(withDelta: delta) - } - if let zIndex = options["zIndex"] as? Int { - delegate.symbolSortKey = zIndex - } - if let draggable = options["draggable"] as? Bool { - delegate.isDraggable = draggable - } - } - - class func interpretCircleOptions(options: Any?, delegate: MGLCircleStyleAnnotation) { - guard let options = options as? [String: Any] else { return } - if let circleRadius = options["circleRadius"] as? CGFloat { - delegate.circleRadius = circleRadius - } - if let circleColor = options["circleColor"] as? String { - delegate.circleColor = UIColor(hexString: circleColor) ?? UIColor.black - } - if let circleBlur = options["circleBlur"] as? CGFloat { - delegate.circleBlur = circleBlur - } - if let circleOpacity = options["circleOpacity"] as? CGFloat { - delegate.circleOpacity = circleOpacity - } - if let circleStrokeWidth = options["circleStrokeWidth"] as? CGFloat { - delegate.circleStrokeWidth = circleStrokeWidth - } - if let circleStrokeColor = options["circleStrokeColor"] as? String { - delegate.circleStrokeColor = UIColor(hexString: circleStrokeColor) ?? UIColor.black - } - if let circleStrokeOpacity = options["circleStrokeOpacity"] as? CGFloat { - delegate.circleStrokeOpacity = circleStrokeOpacity - } - if let geometry = options["geometry"] as? [Double] { - delegate.center = CLLocationCoordinate2DMake(geometry[0], geometry[1]) - } - if let draggable = options["draggable"] as? Bool { - delegate.isDraggable = draggable - } - } - - class func interpretLineOptions(options: Any?, delegate: MGLLineStyleAnnotation) { - guard let options = options as? [String: Any] else { return } - if let lineJoinStr = options["lineJoin"] as? String { - if let lineJoin = Constants.lineJoinMapping[lineJoinStr] { - delegate.lineJoin = lineJoin - } else { - delegate.lineJoin = MGLLineJoin.miter - } - } - if let lineOpacity = options["lineOpacity"] as? CGFloat { - delegate.lineOpacity = lineOpacity - } - if let lineColor = options["lineColor"] as? String { - delegate.lineColor = UIColor(hexString: lineColor) ?? UIColor.black - } - if let lineWidth = options["lineWidth"] as? CGFloat { - delegate.lineWidth = lineWidth - } - if let lineGapWidth = options["lineGapWidth"] as? CGFloat { - delegate.lineGapWidth = lineGapWidth - } - if let lineOffset = options["lineOffset"] as? CGFloat { - delegate.lineOffset = lineOffset - } - if let lineBlur = options["lineBlur"] as? CGFloat { - delegate.lineBlur = lineBlur - } - if let linePattern = options["linePattern"] as? String { - delegate.linePattern = linePattern - } - if let draggable = options["draggable"] as? Bool { - delegate.isDraggable = draggable - } - } - class func getCoordinates(options: Any?) -> [CLLocationCoordinate2D] { var coordinates: [CLLocationCoordinate2D] = [] @@ -381,47 +197,4 @@ class Convert { } return coordinates } - - class func interpretGeometryUpdate(options: Any?, delegate: MGLLineStyleAnnotation) { - if let options = options as? [String: Any], - let geometry = options["geometry"] as? [[Double]], geometry.count > 0 - { - if let feature = delegate.feature as? MGLPolylineFeature { - var coordinates = Convert.getCoordinates(options: options) - feature.setCoordinates(&coordinates, count: UInt(coordinates.count)) - } - } - } - - class func interpretFillOptions(options: Any?, delegate: MGLPolygonStyleAnnotation) { - guard let options = options as? [String: Any] else { return } - if let fillOpacity = options["fillOpacity"] as? CGFloat { - delegate.fillOpacity = fillOpacity - } - if let fillColor = options["fillColor"] as? String { - delegate.fillColor = UIColor(hexString: fillColor) ?? UIColor.black - } - if let fillOutlineColor = options["fillOutlineColor"] as? String { - delegate.fillOutlineColor = UIColor(hexString: fillOutlineColor) ?? UIColor.black - } - if let fillPattern = options["fillPattern"] as? String { - delegate.fillPattern = fillPattern - } - if let draggable = options["draggable"] as? Bool { - delegate.isDraggable = draggable - } - } - - class func toPolygons(geometry: [[[Double]]]) -> [MGLPolygonFeature] { - var polygons: [MGLPolygonFeature] = [] - for lineString in geometry { - var linearRing: [CLLocationCoordinate2D] = [] - for coordinate in lineString { - linearRing.append(CLLocationCoordinate2DMake(coordinate[0], coordinate[1])) - } - let polygon = MGLPolygonFeature(coordinates: linearRing, count: UInt(linearRing.count)) - polygons.append(polygon) - } - return polygons - } } diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index 4e750900e..127d24d0b 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -4,31 +4,29 @@ import MapboxAnnotationExtension import UIKit class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, MapboxMapOptionsSink, - MGLAnnotationControllerDelegate + UIGestureRecognizerDelegate { private var registrar: FlutterPluginRegistrar private var channel: FlutterMethodChannel? private var mapView: MGLMapView private var isMapReady = false + private var dragEnabled = true private var isFirstStyleLoad = true private var onStyleLoadedCalled = false private var mapReadyResult: FlutterResult? + private var previousDragCoordinate: CLLocationCoordinate2D? + private var originDragCoordinate: CLLocationCoordinate2D? + private var dragFeature: MGLFeature? private var initialTilt: CGFloat? private var cameraTargetBounds: MGLCoordinateBounds? private var trackCameraPosition = false private var myLocationEnabled = false + private var scrollingEnabled = true - private var symbolAnnotationController: MGLSymbolAnnotationController? - private var circleAnnotationController: MGLCircleAnnotationController? - private var lineAnnotationController: MGLLineAnnotationController? - private var fillAnnotationController: MGLPolygonAnnotationController? - - private var annotationOrder = [String]() - private var annotationConsumeTapEvents = [String]() - - private var featureLayerIdentifiers = Set() + private var interactiveFeatureLayerIds = Set() + private var addedShapesByLayer = [String: MGLShape]() func view() -> UIView { return mapView @@ -94,17 +92,23 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma ) initialTilt = camera.pitch } - if let annotationOrderArg = args["annotationOrder"] as? [String] { - annotationOrder = annotationOrderArg - } - if let annotationConsumeTapEventsArg = args["annotationConsumeTapEvents"] as? [String] { - annotationConsumeTapEvents = annotationConsumeTapEventsArg - } if let onAttributionClickOverride = args["onAttributionClickOverride"] as? Bool { if onAttributionClickOverride { setupAttribution(mapView) } } + + if let enabled = args["dragEnabled"] as? Bool { + dragEnabled = enabled + } + } + if dragEnabled { + let pan = UIPanGestureRecognizer( + target: self, + action: #selector(handleMapPan(sender:)) + ) + pan.delegate = self + mapView.addGestureRecognizer(pan) } } @@ -114,6 +118,13 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma controller.removeStyleAnnotations(annotations.filter { idSet.contains($0.identifier) }) } + func gestureRecognizer( + _: UIGestureRecognizer, + shouldRecognizeSimultaneouslyWith _: UIGestureRecognizer + ) -> Bool { + return true + } + func onMethodCall(methodCall: FlutterMethodCall, result: @escaping FlutterResult) { switch methodCall.method { case "map#waitForMap": @@ -317,272 +328,13 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma mapView.setCamera(camera, animated: true) } result(nil) - case "symbols#addAll": - guard let symbolAnnotationController = symbolAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - - if let options = arguments["options"] as? [[String: Any]] { - var symbols: [MGLSymbolStyleAnnotation] = [] - for o in options { - if let symbol = getSymbolForOptions(options: o) { - symbols.append(symbol) - } - } - if !symbols.isEmpty { - symbolAnnotationController.addStyleAnnotations(symbols) - symbolAnnotationController - .annotationsInteractionEnabled = annotationConsumeTapEvents - .contains("AnnotationType.symbol") - } - - result(symbols.map { $0.identifier }) - } else { - result(nil) - } - case "symbol#update": - guard let symbolAnnotationController = symbolAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let symbolId = arguments["symbol"] as? String else { return } - - for symbol in symbolAnnotationController.styleAnnotations() { - if symbol.identifier == symbolId { - Convert.interpretSymbolOptions( - options: arguments["options"], - delegate: symbol as! MGLSymbolStyleAnnotation - ) - // Load (updated) icon image from asset if an icon name is supplied. - if let options = arguments["options"] as? [String: Any], - let iconImage = options["iconImage"] as? String - { - addIconImageToMap(iconImageName: iconImage) - } - symbolAnnotationController.updateStyleAnnotation(symbol) - break - } - } - result(nil) - case "symbols#removeAll": - guard let symbolAnnotationController = symbolAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let symbolIds = arguments["ids"] as? [String] else { return } - - removeAllForController(controller: symbolAnnotationController, ids: symbolIds) - result(nil) - - case "symbol#getGeometry": - guard let symbolAnnotationController = symbolAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let symbolId = arguments["symbol"] as? String else { return } - - var reply: [String: Double]? - for symbol in symbolAnnotationController.styleAnnotations() { - if symbol.identifier == symbolId { - if let geometry = symbol.geoJSONDictionary["geometry"] as? [String: Any], - let coordinates = geometry["coordinates"] as? [Double] - { - reply = ["latitude": coordinates[1], "longitude": coordinates[0]] - } - break - } - } - result(reply) - case "symbolManager#iconAllowOverlap": - guard let symbolAnnotationController = symbolAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let iconAllowOverlap = arguments["iconAllowOverlap"] as? Bool else { return } - - symbolAnnotationController.iconAllowsOverlap = iconAllowOverlap - result(nil) - case "symbolManager#iconIgnorePlacement": - guard let symbolAnnotationController = symbolAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let iconIgnorePlacement = arguments["iconIgnorePlacement"] as? Bool - else { return } - - symbolAnnotationController.iconIgnoresPlacement = iconIgnorePlacement - result(nil) - case "symbolManager#textAllowOverlap": - guard let symbolAnnotationController = symbolAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let textAllowOverlap = arguments["textAllowOverlap"] as? Bool else { return } - - symbolAnnotationController.textAllowsOverlap = textAllowOverlap - result(nil) - case "symbolManager#textIgnorePlacement": - result(FlutterMethodNotImplemented) - case "circle#add": - guard let circleAnnotationController = circleAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - // Parse geometry - if let options = arguments["options"] as? [String: Any], - let geometry = options["geometry"] as? [Double] - { - // Convert geometry to coordinate and create circle. - let coordinate = CLLocationCoordinate2DMake(geometry[0], geometry[1]) - let circle = MGLCircleStyleAnnotation(center: coordinate) - Convert.interpretCircleOptions(options: arguments["options"], delegate: circle) - circleAnnotationController.addStyleAnnotation(circle) - circleAnnotationController - .annotationsInteractionEnabled = annotationConsumeTapEvents - .contains("AnnotationType.circle") - result(circle.identifier) - } else { - result(nil) - } - - case "circle#addAll": - guard let circleAnnotationController = circleAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - // Parse geometry - var identifier: String? - if let allOptions = arguments["options"] as? [[String: Any]] { - var circles: [MGLCircleStyleAnnotation] = [] - - for options in allOptions { - if let geometry = options["geometry"] as? [Double] { - guard geometry.count > 0 else { break } - - let coordinate = CLLocationCoordinate2DMake(geometry[0], geometry[1]) - let circle = MGLCircleStyleAnnotation(center: coordinate) - Convert.interpretCircleOptions(options: options, delegate: circle) - circles.append(circle) - } - } - if !circles.isEmpty { - circleAnnotationController.addStyleAnnotations(circles) - } - result(circles.map { $0.identifier }) - } else { - result(nil) - } - - case "circle#update": - guard let circleAnnotationController = circleAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let circleId = arguments["circle"] as? String else { return } - - for circle in circleAnnotationController.styleAnnotations() { - if circle.identifier == circleId { - Convert.interpretCircleOptions( - options: arguments["options"], - delegate: circle as! MGLCircleStyleAnnotation - ) - circleAnnotationController.updateStyleAnnotation(circle) - break - } - } - result(nil) - case "circle#remove": - guard let circleAnnotationController = circleAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let circleId = arguments["circle"] as? String else { return } - - for circle in circleAnnotationController.styleAnnotations() { - if circle.identifier == circleId { - circleAnnotationController.removeStyleAnnotation(circle) - break - } - } - result(nil) - - case "circle#removeAll": - guard let circleAnnotationController = circleAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let ids = arguments["ids"] as? [String] else { return } - - removeAllForController(controller: circleAnnotationController, ids: ids) - result(nil) - - case "line#add": - guard let lineAnnotationController = lineAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - - if let options = arguments["options"] as? [String: Any] { - var coordinates = Convert.getCoordinates(options: options) - let line = MGLLineStyleAnnotation( - coordinates: &coordinates, - count: UInt(coordinates.count) - ) - Convert.interpretLineOptions(options: options, delegate: line) - lineAnnotationController.addStyleAnnotation(line) - lineAnnotationController.annotationsInteractionEnabled = annotationConsumeTapEvents - .contains("AnnotationType.line") - result(line.identifier) - } else { - result(nil) - } - - case "line#addAll": - guard let lineAnnotationController = lineAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - - var identifier: String? - if let allOptions = arguments["options"] as? [[String: Any]] { - var lines: [MGLLineStyleAnnotation] = [] - - for options in allOptions { - var coordinates = Convert.getCoordinates(options: options) - let line = MGLLineStyleAnnotation( - coordinates: &coordinates, - count: UInt(coordinates.count) - ) - Convert.interpretLineOptions(options: options, delegate: line) - lines.append(line) - } - if !lines.isEmpty { - lineAnnotationController.addStyleAnnotations(lines) - } - result(lines.map { $0.identifier }) - } else { - result(nil) - } - - case "line#update": - guard let lineAnnotationController = lineAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let lineId = arguments["line"] as? String else { return } - - for line in lineAnnotationController.styleAnnotations() { - if line.identifier == lineId { - Convert.interpretGeometryUpdate( - options: arguments["options"], - delegate: line as! MGLLineStyleAnnotation - ) - Convert.interpretLineOptions( - options: arguments["options"], - delegate: line as! MGLLineStyleAnnotation - ) - lineAnnotationController.updateStyleAnnotation(line) - break - } - } - result(nil) - case "line#remove": - guard let lineAnnotationController = lineAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let lineId = arguments["line"] as? String else { return } - - for line in lineAnnotationController.styleAnnotations() { - if line.identifier == lineId { - lineAnnotationController.removeStyleAnnotation(line) - break - } - } - result(nil) - - case "line#removeAll": - guard let lineAnnotationController = lineAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let ids = arguments["ids"] as? [String] else { return } - - removeAllForController(controller: lineAnnotationController, ids: ids) - result(nil) case "symbolLayer#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 addSymbolLayer( @@ -590,6 +342,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma layerId: layerId, belowLayerId: belowLayerId, sourceLayerIdentifier: sourceLayer, + enableInteraction: enableInteraction, properties: properties ) result(nil) @@ -599,6 +352,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma 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 addLineLayer( @@ -606,6 +360,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma layerId: layerId, belowLayerId: belowLayerId, sourceLayerIdentifier: sourceLayer, + enableInteraction: enableInteraction, properties: properties ) result(nil) @@ -615,6 +370,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma 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 addFillLayer( @@ -622,6 +378,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma layerId: layerId, belowLayerId: belowLayerId, sourceLayerIdentifier: sourceLayer, + enableInteraction: enableInteraction, properties: properties ) result(nil) @@ -631,6 +388,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma 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 addCircleLayer( @@ -638,6 +396,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma layerId: layerId, belowLayerId: belowLayerId, sourceLayerIdentifier: sourceLayer, + enableInteraction: enableInteraction, properties: properties ) result(nil) @@ -670,125 +429,6 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma ) result(nil) - case "line#getGeometry": - guard let lineAnnotationController = lineAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let lineId = arguments["line"] as? String else { return } - - var reply: [Any]? - for line in lineAnnotationController.styleAnnotations() { - if line.identifier == lineId { - if let geometry = line.geoJSONDictionary["geometry"] as? [String: Any], - let coordinates = geometry["coordinates"] as? [[Double]] - { - reply = coordinates.map { ["latitude": $0[1], "longitude": $0[0]] } - } - break - } - } - result(reply) - case "fill#add": - guard let fillAnnotationController = fillAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - // Parse geometry - var identifier: String? - if let options = arguments["options"] as? [String: Any], - let geometry = options["geometry"] as? [[[Double]]] - { - guard geometry.count > 0 else { break } - // Convert geometry to coordinate and interior polygonc. - var fillCoordinates: [CLLocationCoordinate2D] = [] - for coordinate in geometry[0] { - fillCoordinates.append(CLLocationCoordinate2DMake(coordinate[0], coordinate[1])) - } - let polygons = Convert.toPolygons(geometry: geometry.tail) - let fill = MGLPolygonStyleAnnotation( - coordinates: fillCoordinates, - count: UInt(fillCoordinates.count), - interiorPolygons: polygons - ) - Convert.interpretFillOptions(options: arguments["options"], delegate: fill) - fillAnnotationController.addStyleAnnotation(fill) - fillAnnotationController.annotationsInteractionEnabled = annotationConsumeTapEvents - .contains("AnnotationType.fill") - identifier = fill.identifier - } - - result(identifier) - - case "fill#addAll": - guard let fillAnnotationController = fillAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - // Parse geometry - var identifier: String? - if let allOptions = arguments["options"] as? [[String: Any]] { - var fills: [MGLPolygonStyleAnnotation] = [] - - for options in allOptions { - if let geometry = options["geometry"] as? [[[Double]]] { - guard geometry.count > 0 else { break } - // Convert geometry to coordinate and interior polygonc. - var fillCoordinates: [CLLocationCoordinate2D] = [] - for coordinate in geometry[0] { - fillCoordinates - .append(CLLocationCoordinate2DMake(coordinate[0], coordinate[1])) - } - let polygons = Convert.toPolygons(geometry: geometry.tail) - let fill = MGLPolygonStyleAnnotation( - coordinates: fillCoordinates, - count: UInt(fillCoordinates.count), - interiorPolygons: polygons - ) - Convert.interpretFillOptions(options: options, delegate: fill) - fills.append(fill) - } - } - if !fills.isEmpty { - fillAnnotationController.addStyleAnnotations(fills) - } - result(fills.map { $0.identifier }) - } else { - result(nil) - } - - case "fill#update": - guard let fillAnnotationController = fillAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let fillId = arguments["fill"] as? String else { return } - - for fill in fillAnnotationController.styleAnnotations() { - if fill.identifier == fillId { - Convert.interpretFillOptions( - options: arguments["options"], - delegate: fill as! MGLPolygonStyleAnnotation - ) - fillAnnotationController.updateStyleAnnotation(fill) - break - } - } - - result(nil) - case "fill#remove": - guard let fillAnnotationController = fillAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let fillId = arguments["fill"] as? String else { return } - - for fill in fillAnnotationController.styleAnnotations() { - if fill.identifier == fillId { - fillAnnotationController.removeStyleAnnotation(fill) - break - } - } - result(nil) - - case "fill#removeAll": - guard let fillAnnotationController = fillAnnotationController else { return } - guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let ids = arguments["ids"] as? [String] else { return } - - removeAllForController(controller: fillAnnotationController, ids: ids) - result(nil) - case "style#addImage": guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let name = arguments["name"] as? String else { return } @@ -919,11 +559,83 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma mapView.style?.insertLayer(layer, below: belowLayer) result(nil) + case "symbolLayer#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 enableInteraction = arguments["enableInteraction"] as? Bool else { return } + guard let properties = arguments["properties"] as? [String: String] else { return } + let belowLayerId = arguments["belowLayerId"] as? String + let sourceLayer = arguments["sourceLayer"] as? String + addSymbolLayer( + sourceId: sourceId, + layerId: layerId, + belowLayerId: belowLayerId, + sourceLayerIdentifier: sourceLayer, + enableInteraction: enableInteraction, + properties: properties + ) + result(nil) + + case "lineLayer#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 enableInteraction = arguments["enableInteraction"] as? Bool else { return } + guard let properties = arguments["properties"] as? [String: String] else { return } + let belowLayerId = arguments["belowLayerId"] as? String + let sourceLayer = arguments["sourceLayer"] as? String + addLineLayer( + sourceId: sourceId, + layerId: layerId, + belowLayerId: belowLayerId, + sourceLayerIdentifier: sourceLayer, + enableInteraction: enableInteraction, + properties: properties + ) + result(nil) + + case "fillLayer#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 enableInteraction = arguments["enableInteraction"] as? Bool else { return } + guard let properties = arguments["properties"] as? [String: String] else { return } + let belowLayerId = arguments["belowLayerId"] as? String + let sourceLayer = arguments["sourceLayer"] as? String + addFillLayer( + sourceId: sourceId, + layerId: layerId, + belowLayerId: belowLayerId, + sourceLayerIdentifier: sourceLayer, + enableInteraction: enableInteraction, + properties: properties + ) + result(nil) + + case "circleLayer#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 enableInteraction = arguments["enableInteraction"] as? Bool else { return } + guard let properties = arguments["properties"] as? [String: String] else { return } + let belowLayerId = arguments["belowLayerId"] as? String + let sourceLayer = arguments["sourceLayer"] as? String + addCircleLayer( + sourceId: sourceId, + layerId: layerId, + belowLayerId: belowLayerId, + sourceLayerIdentifier: sourceLayer, + enableInteraction: enableInteraction, + properties: properties + ) + result(nil) + 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 { return } - featureLayerIdentifiers.remove(layerId) + interactiveFeatureLayerIds.remove(layerId) mapView.style?.removeLayer(layer) result(nil) @@ -948,27 +660,17 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma setSource(sourceId: sourceId, geojson: geojson) result(nil) + case "source#setFeature": + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let sourceId = arguments["sourceId"] as? String else { return } + guard let geojson = arguments["geojsonFeature"] as? String else { return } + setFeature(sourceId: sourceId, geojsonFeature: geojson) + result(nil) default: result(FlutterMethodNotImplemented) } } - private func getSymbolForOptions(options: [String: Any]) -> MGLSymbolStyleAnnotation? { - // Parse geometry - if let geometry = options["geometry"] as? [Double] { - // Convert geometry to coordinate and create symbol. - let coordinate = CLLocationCoordinate2DMake(geometry[0], geometry[1]) - let symbol = MGLSymbolStyleAnnotation(coordinate: coordinate) - Convert.interpretSymbolOptions(options: options, delegate: symbol) - // Load icon image from asset if an icon name is supplied. - if let iconImage = options["iconImage"] as? String { - addIconImageToMap(iconImageName: iconImage) - } - return symbol - } - return nil - } - private func addIconImageToMap(iconImageName: String) { // Check if the image has already been added to the map. if mapView.style?.image(forName: iconImageName) == nil { @@ -1003,9 +705,9 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma private func firstFeatureOnLayers(at: CGPoint) -> MGLFeature? { guard let style = mapView.style else { return nil } - // get layers in order (featureLayerIdentifiers is unordered) + // get layers in order (interactiveFeatureLayerIds is unordered) let clickableLayers = style.layers.filter { layer in - featureLayerIdentifiers.contains(layer.identifier) + interactiveFeatureLayerIds.contains(layer.identifier) } for layer in clickableLayers.reversed() { @@ -1047,6 +749,57 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } } + /* + * UITapGestureRecognizer + * On pan might invoke the feature#onDrag callback. + */ + @IBAction func handleMapPan(sender: UIPanGestureRecognizer) { + let point = sender.location(in: mapView) + let coordinate = mapView.convert(point, toCoordinateFrom: mapView) + + if sender.state == UIGestureRecognizer.State.began, + sender.numberOfTouches == 1, + let feature = firstFeatureOnLayers(at: point), + let draggable = feature.attribute(forKey: "draggable") as? Bool, + draggable + { + dragFeature = feature + originDragCoordinate = coordinate + previousDragCoordinate = coordinate + mapView.allowsScrolling = false + for gestureRecognizer in mapView.gestureRecognizers! { + if let _ = gestureRecognizer as? UIPanGestureRecognizer { + gestureRecognizer.addTarget(self, action: #selector(handleMapPan)) + break + } + } + } else if sender.state == UIGestureRecognizer.State.ended || sender.numberOfTouches != 1 { + sender.state = UIGestureRecognizer.State.ended + mapView.allowsScrolling = scrollingEnabled + dragFeature = nil + originDragCoordinate = nil + previousDragCoordinate = nil + } else if let feature = dragFeature, + let id = feature.identifier, + let previous = previousDragCoordinate, + let origin = originDragCoordinate + { + print("in drag") + channel?.invokeMethod("feature#onDrag", arguments: [ + "id": id, + "x": point.x, + "y": point.y, + "originLng": origin.longitude, + "originLat": origin.latitude, + "currentLng": coordinate.longitude, + "currentLat": coordinate.latitude, + "deltaLng": coordinate.longitude - previous.longitude, + "deltaLat": coordinate.latitude - previous.latitude, + ]) + previousDragCoordinate = coordinate + } + } + /* * UILongPressGestureRecognizer * After a long press invoke the map#onMapLongClick callback. @@ -1066,43 +819,6 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } } - /* - * MGLAnnotationControllerDelegate - */ - func annotationController( - _ annotationController: MGLAnnotationController, - didSelect styleAnnotation: MGLStyleAnnotation - ) { - DispatchQueue.main.async { - // Remove tint color overlay from selected annotation by - // deselecting. This is not handled correctly if requested - // synchronously from the callback. - annotationController.deselectStyleAnnotation(styleAnnotation) - } - - guard let channel = channel else { - return - } - - if let symbol = styleAnnotation as? MGLSymbolStyleAnnotation { - channel.invokeMethod("symbol#onTap", arguments: ["symbol": "\(symbol.identifier)"]) - } else if let circle = styleAnnotation as? MGLCircleStyleAnnotation { - channel.invokeMethod("circle#onTap", arguments: ["circle": "\(circle.identifier)"]) - } else if let line = styleAnnotation as? MGLLineStyleAnnotation { - channel.invokeMethod("line#onTap", arguments: ["line": "\(line.identifier)"]) - } else if let fill = styleAnnotation as? MGLPolygonStyleAnnotation { - channel.invokeMethod("fill#onTap", arguments: ["fill": "\(fill.identifier)"]) - } - } - - // This is required in order to hide the default Maps SDK pin - func mapView(_: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? { - if annotation is MGLUserLocation { - return nil - } - return MGLAnnotationView(frame: CGRect(x: 0, y: 0, width: 10, height: 10)) - } - /* * Override the attribution button's click target to handle the event locally. * Called if the application supplies an onAttributionClick handler. @@ -1141,37 +857,8 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma mapView.setCamera(camera, animated: false) } - for annotationType in annotationOrder { - switch annotationType { - case "AnnotationType.fill": - fillAnnotationController = MGLPolygonAnnotationController(mapView: self.mapView) - fillAnnotationController!.annotationsInteractionEnabled = annotationConsumeTapEvents - .contains("AnnotationType.fill") - fillAnnotationController?.delegate = self - case "AnnotationType.line": - lineAnnotationController = MGLLineAnnotationController(mapView: self.mapView) - lineAnnotationController!.annotationsInteractionEnabled = annotationConsumeTapEvents - .contains("AnnotationType.line") - - lineAnnotationController?.delegate = self - case "AnnotationType.circle": - circleAnnotationController = MGLCircleAnnotationController(mapView: self.mapView) - circleAnnotationController! - .annotationsInteractionEnabled = annotationConsumeTapEvents - .contains("AnnotationType.circle") - circleAnnotationController?.delegate = self - case "AnnotationType.symbol": - symbolAnnotationController = MGLSymbolAnnotationController(mapView: self.mapView) - symbolAnnotationController! - .annotationsInteractionEnabled = annotationConsumeTapEvents - .contains("AnnotationType.symbol") - symbolAnnotationController?.delegate = self - default: - print( - "Unknown annotation type: \(annotationType), must be either 'fill', 'line', 'circle' or 'symbol'" - ) - } - } + addedShapesByLayer.removeAll() + interactiveFeatureLayerIds.removeAll() mapReadyResult?(nil) @@ -1211,46 +898,6 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma return inside && intersects } - func mapView(_ mapView: MGLMapView, imageFor annotation: MGLAnnotation) -> MGLAnnotationImage? { - // Only for Symbols images should loaded. - guard let symbol = annotation as? Symbol, - let iconImageFullPath = symbol.iconImage - else { - return nil - } - // Reuse existing annotations for better performance. - var annotationImage = mapView - .dequeueReusableAnnotationImage(withIdentifier: iconImageFullPath) - if annotationImage == nil { - // Initialize the annotation image (from predefined assets symbol folder). - if let range = iconImageFullPath.range(of: "/", options: [.backwards]) { - let directory = String(iconImageFullPath[.. Bool { - return true - } - func mapView(_: MGLMapView, didUpdate userLocation: MGLUserLocation?) { if let channel = channel, let userLocation = userLocation, let location = userLocation.location @@ -1276,6 +923,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma layerId: String, belowLayerId: String?, sourceLayerIdentifier: String?, + enableInteraction: Bool, properties: [String: String] ) { if let style = mapView.style { @@ -1293,7 +941,9 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } else { style.addLayer(layer) } - featureLayerIdentifiers.insert(layerId) + if enableInteraction { + interactiveFeatureLayerIds.insert(layerId) + } } } } @@ -1303,6 +953,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma layerId: String, belowLayerId: String?, sourceLayerIdentifier: String?, + enableInteraction: Bool, properties: [String: String] ) { if let style = mapView.style { @@ -1317,7 +968,9 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } else { style.addLayer(layer) } - featureLayerIdentifiers.insert(layerId) + if enableInteraction { + interactiveFeatureLayerIds.insert(layerId) + } } } } @@ -1327,6 +980,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma layerId: String, belowLayerId: String?, sourceLayerIdentifier: String?, + enableInteraction: Bool, properties: [String: String] ) { if let style = mapView.style { @@ -1341,7 +995,9 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } else { style.addLayer(layer) } - featureLayerIdentifiers.insert(layerId) + if enableInteraction { + interactiveFeatureLayerIds.insert(layerId) + } } } } @@ -1351,6 +1007,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma layerId: String, belowLayerId: String?, sourceLayerIdentifier: String?, + enableInteraction: Bool, properties: [String: String] ) { if let style = mapView.style { @@ -1368,7 +1025,9 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } else { style.addLayer(layer) } - featureLayerIdentifiers.insert(layerId) + if enableInteraction { + interactiveFeatureLayerIds.insert(layerId) + } } } } @@ -1391,7 +1050,6 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } else { style.addLayer(layer) } - featureLayerIdentifiers.insert(layerId) } } } @@ -1414,7 +1072,6 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } else { style.addLayer(layer) } - featureLayerIdentifiers.insert(layerId) } } } @@ -1496,7 +1153,9 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma encoding: String.Encoding.utf8.rawValue ) let source = MGLShapeSource(identifier: sourceId, shape: parsed, options: [:]) + addedShapesByLayer[sourceId] = parsed mapView.style?.addSource(source) + print(source) } catch {} } @@ -1507,11 +1166,40 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma encoding: String.Encoding.utf8.rawValue ) if let source = mapView.style?.source(withIdentifier: sourceId) as? MGLShapeSource { + addedShapesByLayer[sourceId] = parsed source.shape = parsed } } catch {} } + func setFeature(sourceId: String, geojsonFeature: String) { + do { + let newShape = try MGLShape( + data: geojsonFeature.data(using: .utf8)!, + encoding: String.Encoding.utf8.rawValue + ) + if let source = mapView.style?.source(withIdentifier: sourceId) as? MGLShapeSource, + let shape = addedShapesByLayer[sourceId] as? MGLShapeCollectionFeature, + let feature = newShape as? MGLShape & MGLFeature + { + if let index = shape.shapes + .firstIndex(where: { + if let id = $0.identifier as? String, + let featureId = feature.identifier as? String + { return id == featureId } + return false + }) + { + var shapes = shape.shapes + shapes[index] = feature + + source.shape = MGLShapeCollectionFeature(shapes: shapes) + } + } + + } catch {} + } + /* * MapboxMapOptionsSink */ @@ -1559,6 +1247,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma func setScrollGesturesEnabled(scrollGesturesEnabled: Bool) { mapView.allowsScrolling = scrollGesturesEnabled + scrollingEnabled = scrollGesturesEnabled } func setTiltGesturesEnabled(tiltGesturesEnabled: Bool) { diff --git a/ios/Classes/Symbol.swift b/ios/Classes/Symbol.swift deleted file mode 100644 index dae118ba1..000000000 --- a/ios/Classes/Symbol.swift +++ /dev/null @@ -1,75 +0,0 @@ -import Mapbox - -class Symbol: MGLPointAnnotation, SymbolOptionsSink { - private var _id = UUID().uuidString - var id: String { return _id } - - private var _iconImage: String? - var iconImage: String? { return _iconImage } - - var textField: String? { return title } - - var geometry: CLLocationCoordinate2D { return coordinate } - - // MARK: Setters - - func setIconSize(iconSize _: Double) {} - - func setIconImage(iconImage: String) { - _iconImage = iconImage - } - - func setIconRotate(iconRotate _: Double) {} - - func setIconAnchor(iconAnchor _: String) {} - - func setTextField(textField: String) { - title = textField - } - - func setTextSize(textSize _: Double) {} - - func setTextMaxWidth(textMaxWidth _: Double) {} - - func setTextLetterSpacing(textLetterSpacing _: Double) {} - - func setTextJustify(textJustify _: String) {} - - func setTextAnchor(textAnchor _: String) {} - - func setTextRotate(textRotate _: Double) {} - - func setTextTransform(textTransform _: String) {} - - func setIconOpacity(iconOpacity _: Double) {} - - func setIconColor(iconColor _: String) {} - - func setIconHaloColor(iconHaloColor _: String) {} - - func setIconHaloWidth(iconHaloWidth _: Double) {} - - func setIconHaloBlur(iconHaloBlur _: Double) {} - - func setTextOpacity(textOpacity _: Double) {} - - func setTextColor(textColor _: String) {} - - func setTextHaloColor(textHaloColor _: String) {} - - func setTextHaloWidth(textHaloWidth _: Double) {} - - func setTextHaloBlur(textHaloBlur _: Double) {} - - func setGeometry(geometry: [Double]) { - if geometry.count == 2, -90 ... 90 ~= geometry[0], -180 ... 180 ~= geometry[1] { - coordinate = CLLocationCoordinate2D(latitude: geometry[0], longitude: geometry[1]) - } else { - NSLog("Invalid geometry") - } - } - - func setZIndex(zIndex _: Int) {} - - func setDraggable(draggable _: Bool) {} -} diff --git a/ios/Classes/SymbolOptionsSink.swift b/ios/Classes/SymbolOptionsSink.swift deleted file mode 100644 index db658fce5..000000000 --- a/ios/Classes/SymbolOptionsSink.swift +++ /dev/null @@ -1,31 +0,0 @@ - -protocol SymbolOptionsSink { - func setIconSize(iconSize: Double) - func setIconImage(iconImage: String) - func setIconRotate(iconRotate: Double) -// final Offset iconOffset; - func setIconAnchor(iconAnchor: String) - func setTextField(textField: String) - func setTextSize(textSize: Double) - func setTextMaxWidth(textMaxWidth: Double) - func setTextLetterSpacing(textLetterSpacing: Double) - func setTextJustify(textJustify: String) - func setTextAnchor(textAnchor: String) - func setTextRotate(textRotate: Double) - func setTextTransform(textTransform: String) -// final Offset textOffset; - func setIconOpacity(iconOpacity: Double) - func setIconColor(iconColor: String) - func setIconHaloColor(iconHaloColor: String) - func setIconHaloWidth(iconHaloWidth: Double) - func setIconHaloBlur(iconHaloBlur: Double) - func setTextOpacity(textOpacity: Double) - func setTextColor(textColor: String) - func setTextHaloColor(textHaloColor: String) - func setTextHaloWidth(textHaloWidth: Double) - func setTextHaloBlur(textHaloBlur: Double) - - func setGeometry(geometry: [Double]) - func setZIndex(zIndex: Int) - func setDraggable(draggable: Bool) -} diff --git a/lib/mapbox_gl.dart b/lib/mapbox_gl.dart index 6d388c930..0c836a3e5 100644 --- a/lib/mapbox_gl.dart +++ b/lib/mapbox_gl.dart @@ -13,7 +13,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:collection/collection.dart'; import 'package:mapbox_gl_platform_interface/mapbox_gl_platform_interface.dart'; export 'package:mapbox_gl_platform_interface/mapbox_gl_platform_interface.dart' @@ -35,6 +34,7 @@ export 'package:mapbox_gl_platform_interface/mapbox_gl_platform_interface.dart' MyLocationRenderMode, CompassViewPosition, AttributionButtonPosition, + Annotation, Circle, CircleOptions, Line, @@ -57,3 +57,5 @@ part 'src/download_region_status.dart'; part 'src/layer_expressions.dart'; part 'src/layer_properties.dart'; part 'src/color_tools.dart'; +part 'src/annotation_manager.dart'; +part 'src/util.dart'; diff --git a/lib/src/annotation_manager.dart b/lib/src/annotation_manager.dart new file mode 100644 index 000000000..446ee4b76 --- /dev/null +++ b/lib/src/annotation_manager.dart @@ -0,0 +1,334 @@ +part of mapbox_gl; + +abstract class AnnotationManager { + final MapboxMapController controller; + final _idToAnnotation = {}; + final _idToLayerIndex = {}; + + /// Called if a annotation is tapped + final void Function(T)? onTap; + + /// base id of the manager. User [layerdIds] to get the actual ids. + final String id; + + List get layerIds => + [for (int i = 0; i < allLayerProperties.length; i++) _makeLayerId(i)]; + + /// If disabled the manager offers no interaction for the created symbols + final bool enableInteraction; + + /// implemented to define the layer properties + List get allLayerProperties; + + /// used to spedicy the layer and annotation will life on + /// This can be replaced by layer filters a soon as they are implemented + final int Function(T)? selectLayer; + + /// get the an annotation by its id + T? byId(String id) => _idToAnnotation[id]; + + Set get annotations => _idToAnnotation.values.toSet(); + + AnnotationManager(this.controller, + {this.onTap, this.selectLayer, required this.enableInteraction}) + : id = getRandomString() { + for (var i = 0; i < allLayerProperties.length; i++) { + final layerId = _makeLayerId(i); + controller.addGeoJsonSource(layerId, buildFeatureCollection([]), + promoteId: "id"); + controller.addLayer(layerId, layerId, allLayerProperties[i]); + } + + if (onTap != null) { + controller.onFeatureTapped.add(_onFeatureTapped); + } + controller.onFeatureDrag.add(_onDrag); + } + + /// This function can be used to rebuild all layers after their properties + /// changed + 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]); + } + } + + _onFeatureTapped(dynamic id, Point point, LatLng coordinates) { + final annotation = _idToAnnotation[id]; + if (annotation != null) { + onTap!(annotation); + } + } + + String _makeLayerId(int layerIndex) => "${id}_$layerIndex"; + + Future _setAll() async { + if (selectLayer != null) { + final featureBuckets = [for (final _ in allLayerProperties) []]; + + for (final annotation in _idToAnnotation.values) { + final layerIndex = selectLayer!(annotation); + _idToLayerIndex[annotation.id] = layerIndex; + featureBuckets[layerIndex].add(annotation); + } + + for (var i = 0; i < featureBuckets.length; i++) { + await controller.setGeoJsonSource( + _makeLayerId(i), + buildFeatureCollection( + [for (final l in featureBuckets[i]) l.toGeoJson()])); + } + } else { + await controller.setGeoJsonSource( + _makeLayerId(0), + buildFeatureCollection( + [for (final l in _idToAnnotation.values) l.toGeoJson()])); + } + } + + /// Adds a multiple annotations to the map. This much faster than calling add + /// multiple times + Future addAll(Iterable annotations) async { + for (var a in annotations) { + _idToAnnotation[a.id] = a; + } + await _setAll(); + } + + /// add a single annotation to the map + Future add(T annotation) async { + _idToAnnotation[annotation.id] = annotation; + await _setAll(); + } + + /// Remove a single annotation form the map + Future remove(T annotation) async { + _idToAnnotation.remove(annotation.id); + await _setAll(); + } + + /// Removes all annotations from the map + Future clear() async { + _idToAnnotation.clear(); + + await _setAll(); + } + + /// Fully dipose of all the the resouces managed by the annotation manager. + /// The manager cannot be used after this has been called + Future dispose() async { + _idToAnnotation.clear(); + await _setAll(); + for (var i = 0; i < allLayerProperties.length; i++) { + await controller.removeLayer(_makeLayerId(i)); + await controller.removeSource(_makeLayerId(i)); + } + } + + _onDrag(dynamic id, + {required Point point, + required LatLng origin, + required LatLng current, + required LatLng delta}) { + final annotation = byId(id); + if (annotation != null) { + annotation.translate(delta); + set(annotation); + } + } + + /// Set an existing anntotation to the map. Use this to do a fast update for a + /// single annotation + Future set(T anntotation) async { + assert(_idToAnnotation.containsKey(anntotation.id), + "you can only set existing annotations"); + _idToAnnotation[anntotation.id] = anntotation; + final oldLayerIndex = _idToLayerIndex[anntotation.id]; + final layerIndex = selectLayer != null ? selectLayer!(anntotation) : 0; + if (oldLayerIndex != layerIndex) { + // if the annotation has to be moved to another layer/source we have to + // set all + await _setAll(); + } else { + await controller.setGeoJsonFeature( + _makeLayerId(layerIndex), anntotation.toGeoJson()); + } + } +} + +class LineManager extends AnnotationManager { + LineManager(MapboxMapController controller, + {void Function(Line)? onTap, bool enableInteraction = true}) + : super( + controller, + onTap: onTap, + enableInteraction: enableInteraction, + selectLayer: (Line line) => line.options.linePattern == null ? 0 : 1, + ); + + static const _baseProperties = LineLayerProperties( + lineJoin: [Expressions.get, 'lineJoin'], + lineOpacity: [Expressions.get, 'lineOpacity'], + lineColor: [Expressions.get, 'lineColor'], + lineWidth: [Expressions.get, 'lineWidth'], + lineGapWidth: [Expressions.get, 'lineGapWidth'], + lineOffset: [Expressions.get, 'lineOffset'], + lineBlur: [Expressions.get, 'lineBlur'], + ); + @override + List get allLayerProperties => [ + _baseProperties, + _baseProperties.copyWith( + LineLayerProperties(linePattern: [Expressions.get, 'linePattern'])), + ]; +} + +class FillManager extends AnnotationManager { + FillManager( + MapboxMapController controller, { + void Function(Fill)? onTap, + bool enableInteraction = true, + }) : super( + controller, + onTap: onTap, + enableInteraction: enableInteraction, + selectLayer: (Fill fill) => fill.options.fillPattern == null ? 0 : 1, + ); + @override + List get allLayerProperties => const [ + FillLayerProperties( + fillOpacity: [Expressions.get, 'fillOpacity'], + fillColor: [Expressions.get, 'fillColor'], + fillOutlineColor: [Expressions.get, 'fillOutlineColor'], + ), + FillLayerProperties( + fillOpacity: [Expressions.get, 'fillOpacity'], + fillColor: [Expressions.get, 'fillColor'], + fillOutlineColor: [Expressions.get, 'fillOutlineColor'], + fillPattern: [Expressions.get, 'fillPattern'], + ) + ]; +} + +class CircleManager extends AnnotationManager { + CircleManager( + MapboxMapController controller, { + void Function(Circle)? onTap, + bool enableInteraction = true, + }) : super( + controller, + enableInteraction: enableInteraction, + onTap: onTap, + ); + @override + List get allLayerProperties => const [ + CircleLayerProperties( + circleRadius: [Expressions.get, 'circleRadius'], + circleColor: [Expressions.get, 'circleColor'], + circleBlur: [Expressions.get, 'circleBlur'], + circleOpacity: [Expressions.get, 'circleOpacity'], + circleStrokeWidth: [Expressions.get, 'circleStrokeWidth'], + circleStrokeColor: [Expressions.get, 'circleStrokeColor'], + circleStrokeOpacity: [Expressions.get, 'circleStrokeOpacity'], + ) + ]; +} + +class SymbolManager extends AnnotationManager { + SymbolManager( + MapboxMapController controller, { + void Function(Symbol)? onTap, + bool iconAllowOverlap = false, + bool textAllowOverlap = false, + bool iconIgnorePlacement = false, + bool textIgnorePlacement = false, + bool enableInteraction = true, + }) : _iconAllowOverlap = iconAllowOverlap, + _textAllowOverlap = textAllowOverlap, + _iconIgnorePlacement = iconIgnorePlacement, + _textIgnorePlacement = textIgnorePlacement, + super( + controller, + enableInteraction: enableInteraction, + onTap: onTap, + ); + + bool _iconAllowOverlap; + bool _textAllowOverlap; + bool _iconIgnorePlacement; + bool _textIgnorePlacement; + + /// For more information on what this does, see https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/#label-collision + Future setIconAllowOverlap(bool value) async { + _iconAllowOverlap = value; + await _rebuildLayers(); + } + + /// For more information on what this does, see https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/#label-collision + Future setTextAllowOverlap(bool value) async { + _textAllowOverlap = value; + await _rebuildLayers(); + } + + /// For more information on what this does, see https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/#label-collision + Future setIconIgnorePlacement(bool value) async { + _iconIgnorePlacement = value; + await _rebuildLayers(); + } + + /// For more information on what this does, see https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/#label-collision + Future setTextIgnorePlacement(bool value) async { + _textIgnorePlacement = value; + await _rebuildLayers(); + } + + @override + List get allLayerProperties => [ + SymbolLayerProperties( + iconSize: [Expressions.get, 'iconSize'], + iconImage: [Expressions.get, 'iconImage'], + iconRotate: [Expressions.get, 'iconRotate'], + iconOffset: [Expressions.get, 'iconOffset'], + iconAnchor: [Expressions.get, 'iconAnchor'], + iconOpacity: [Expressions.get, 'iconOpacity'], + iconColor: [Expressions.get, 'iconColor'], + iconHaloColor: [Expressions.get, 'iconHaloColor'], + iconHaloWidth: [Expressions.get, 'iconHaloWidth'], + iconHaloBlur: [Expressions.get, 'iconHaloBlur'], + // note that web does not support setting this in a fully data driven + // way this is a upstream issue + textFont: kIsWeb + ? null + : [ + Expressions.caseExpression, + [Expressions.has, 'fontNames'], + [Expressions.get, 'fontNames'], + [ + Expressions.literal, + ["Open Sans Regular", "Arial Unicode MS Regular"] + ], + ], + textField: [Expressions.get, 'textField'], + textSize: [Expressions.get, 'textSize'], + textMaxWidth: [Expressions.get, 'textMaxWidth'], + textLetterSpacing: [Expressions.get, 'textLetterSpacing'], + textJustify: [Expressions.get, 'textJustify'], + textAnchor: [Expressions.get, 'textAnchor'], + textRotate: [Expressions.get, 'textRotate'], + textTransform: [Expressions.get, 'textTransform'], + textOffset: [Expressions.get, 'textOffset'], + textOpacity: [Expressions.get, 'textOpacity'], + textColor: [Expressions.get, 'textColor'], + textHaloColor: [Expressions.get, 'textHaloColor'], + textHaloWidth: [Expressions.get, 'textHaloWidth'], + textHaloBlur: [Expressions.get, 'textHaloBlur'], + symbolSortKey: [Expressions.get, 'zIndex'], + iconAllowOverlap: _iconAllowOverlap, + iconIgnorePlacement: _iconIgnorePlacement, + textAllowOverlap: _textAllowOverlap, + textIgnorePlacement: _textIgnorePlacement, + ) + ]; +} diff --git a/lib/src/controller.dart b/lib/src/controller.dart index 1909f34a6..39e0eecea 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -6,9 +6,15 @@ part of mapbox_gl; typedef void OnMapClickCallback(Point point, LatLng coordinates); -typedef void OnFeatureTappedCallback( +typedef void OnFeatureInteractionCallback( dynamic id, Point point, LatLng coordinates); +typedef void OnFeatureDragnCallback(dynamic id, + {required Point point, + required LatLng origin, + required LatLng current, + required LatLng delta}); + typedef void OnMapLongClickCallback(Point point, LatLng coordinates); typedef void OnAttributionClickCallback(); @@ -43,6 +49,8 @@ class MapboxMapController extends ChangeNotifier { MapboxMapController({ required MapboxGlPlatform mapboxGlPlatform, required CameraPosition initialCameraPosition, + required Iterable annotationOrder, + required Iterable annotationConsumeTapEvents, this.onStyleLoadedCallback, this.onMapClick, this.onMapLongClick, @@ -55,44 +63,20 @@ class MapboxMapController extends ChangeNotifier { }) : _mapboxGlPlatform = mapboxGlPlatform { _cameraPosition = initialCameraPosition; - _mapboxGlPlatform.onInfoWindowTappedPlatform.add((symbolId) { - final symbol = _symbols[symbolId]; - if (symbol != null) { - onInfoWindowTapped(symbol); - } - }); - - _mapboxGlPlatform.onSymbolTappedPlatform.add((symbolId) { - final symbol = _symbols[symbolId]; - if (symbol != null) { - onSymbolTapped(symbol); - } - }); - - _mapboxGlPlatform.onLineTappedPlatform.add((lineId) { - final line = _lines[lineId]; - if (line != null) { - onLineTapped(line); - } - }); - - _mapboxGlPlatform.onCircleTappedPlatform.add((circleId) { - final circle = _circles[circleId]; - if (circle != null) { - onCircleTapped(circle); - } - }); - - _mapboxGlPlatform.onFillTappedPlatform.add((fillId) { - final fill = _fills[fillId]; - if (fill != null) { - onFillTapped(fill); + _mapboxGlPlatform.onFeatureTappedPlatform.add((payload) { + for (final fun + in List.from(onFeatureTapped)) { + fun(payload["id"], payload["point"], payload["latLng"]); } }); - _mapboxGlPlatform.onFeatureTappedPlatform.add((payload) { - for (final fun in List.from(onFeatureTapped)) { - fun(payload["id"], payload["point"], payload["latLng"]); + _mapboxGlPlatform.onFeatureDraggedPlatform.add((payload) { + for (final fun in List.from(onFeatureDrag)) { + fun(payload["id"], + point: payload["point"], + origin: payload["origin"], + current: payload["current"], + delta: payload["delta"]); } }); @@ -118,6 +102,29 @@ class MapboxMapController extends ChangeNotifier { }); _mapboxGlPlatform.onMapStyleLoadedPlatform.add((_) { + final interactionEnabled = annotationConsumeTapEvents.toSet(); + for (var type in annotationOrder.toSet()) { + final enableInteraction = interactionEnabled.contains(type); + switch (type) { + case AnnotationType.fill: + fillManager = FillManager(this, + onTap: onFillTapped, enableInteraction: enableInteraction); + break; + case AnnotationType.line: + lineManager = LineManager(this, + onTap: onLineTapped, enableInteraction: enableInteraction); + break; + case AnnotationType.circle: + circleManager = CircleManager(this, + onTap: onCircleTapped, enableInteraction: enableInteraction); + break; + case AnnotationType.symbol: + symbolManager = SymbolManager(this, + onTap: onSymbolTapped, enableInteraction: enableInteraction); + break; + default: + } + } if (onStyleLoadedCallback != null) { onStyleLoadedCallback!(); } @@ -163,6 +170,11 @@ class MapboxMapController extends ChangeNotifier { }); } + FillManager? fillManager; + LineManager? lineManager; + CircleManager? circleManager; + SymbolManager? symbolManager; + final OnStyleLoadedCallback? onStyleLoadedCallback; final OnMapClickCallback? onMapClick; final OnMapLongClickCallback? onMapLongClick; @@ -187,17 +199,19 @@ class MapboxMapController extends ChangeNotifier { final ArgumentCallbacks onFillTapped = ArgumentCallbacks(); /// Callbacks to receive tap events for features (geojson layer) placed on this map. - final onFeatureTapped = []; + final onFeatureTapped = []; + + final onFeatureDrag = []; /// Callbacks to receive tap events for info windows on symbols + @Deprecated("InfoWindow tapped is no longer supported") final ArgumentCallbacks onInfoWindowTapped = ArgumentCallbacks(); /// The current set of symbols on this map. /// /// The returned set will be a detached snapshot of the symbols collection. - Set get symbols => Set.from(_symbols.values); - final Map _symbols = {}; + Set get symbols => symbolManager!.annotations; /// Callbacks to receive tap events for lines placed on this map. final ArgumentCallbacks onLineTapped = ArgumentCallbacks(); @@ -205,20 +219,17 @@ class MapboxMapController extends ChangeNotifier { /// The current set of lines on this map. /// /// The returned set will be a detached snapshot of the lines collection. - Set get lines => Set.from(_lines.values); - final Map _lines = {}; + Set get lines => lineManager!.annotations; /// The current set of circles on this map. /// /// The returned set will be a detached snapshot of the circles collection. - Set get circles => Set.from(_circles.values); - final Map _circles = {}; + Set get circles => circleManager!.annotations; /// The current set of fills on this map. /// /// The returned set will be a detached snapshot of the fills collection. - Set get fills => Set.from(_fills.values); - final Map _fills = {}; + Set get fills => fillManager!.annotations; /// True if the map camera is currently moving. bool get isCameraMoving => _isCameraMoving; @@ -282,6 +293,7 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes after the change has been made on the /// platform side. + /// Future addGeoJsonSource(String sourceId, Map geojson, {String? promoteId}) async { await _mapboxGlPlatform.addGeoJsonSource(sourceId, geojson, @@ -304,84 +316,137 @@ class MapboxMapController extends ChangeNotifier { await _mapboxGlPlatform.setGeoJsonSource(sourceId, geojson); } + /// Sets new geojson data to and existing source + /// + /// This only works as exected if the source has been created with + /// [addGeoJsonSource] before. This is very useful if you want to update and + /// existing source with modified data. + /// + /// The json in [geojson] has to comply with the schema for FeatureCollection + /// as specified in https://datatracker.ietf.org/doc/html/rfc7946#section-3.3 + /// + /// The returned [Future] completes after the change has been made on the + /// platform side. + Future setGeoJsonFeature( + String sourceId, Map geojsonFeature) async { + await _mapboxGlPlatform.setFeatureForGeoJsonSource( + sourceId, geojsonFeature); + } + /// Add a symbol 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. /// - /// Note: [belowLayerId] is currently ignored on the web + /// 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 Future addSymbolLayer( String sourceId, String layerId, SymbolLayerProperties properties, - {String? belowLayerId, String? sourceLayer}) async { + {String? belowLayerId, + String? sourceLayer, + bool enableInteraction = true}) async { await _mapboxGlPlatform.addSymbolLayer( sourceId, layerId, properties.toJson(), belowLayerId: belowLayerId, sourceLayer: sourceLayer, + enableInteraction: enableInteraction, ); } /// Add a line 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. /// - /// Note: [belowLayerId] is currently ignored on the web + /// 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 Future addLineLayer( String sourceId, String layerId, LineLayerProperties properties, - {String? belowLayerId, String? sourceLayer}) async { + {String? belowLayerId, + String? sourceLayer, + bool enableInteraction = true}) async { await _mapboxGlPlatform.addLineLayer( sourceId, layerId, properties.toJson(), belowLayerId: belowLayerId, sourceLayer: sourceLayer, + enableInteraction: enableInteraction, ); } /// Add a fill 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. /// - /// Note: [belowLayerId] is currently ignored on the web + /// 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 Future addFillLayer( String sourceId, String layerId, FillLayerProperties properties, - {String? belowLayerId, String? sourceLayer}) async { + {String? belowLayerId, + String? sourceLayer, + bool enableInteraction = true}) async { await _mapboxGlPlatform.addFillLayer( sourceId, layerId, properties.toJson(), belowLayerId: belowLayerId, sourceLayer: sourceLayer, + enableInteraction: enableInteraction, ); } /// Add a circle 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. /// - /// Note: [belowLayerId] is currently ignored on the web + /// 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 Future addCircleLayer( String sourceId, String layerId, CircleLayerProperties properties, - {String? belowLayerId, String? sourceLayer}) async { + {String? belowLayerId, + String? sourceLayer, + bool enableInteraction = true}) async { await _mapboxGlPlatform.addCircleLayer( sourceId, layerId, properties.toJson(), belowLayerId: belowLayerId, sourceLayer: sourceLayer, + enableInteraction: enableInteraction, ); } - /// Add a circle layer to the map with the given properties + /// Add a raster 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. /// - /// Note: [belowLayerId] is currently ignored on the web + /// Setting [belowLayerId] adds the new layer below the given id. + /// [sourceLayer] is used to selected a specific source layer from + /// Raster source Future addRasterLayer( String sourceId, String layerId, RasterLayerProperties properties, {String? belowLayerId, String? sourceLayer}) async { @@ -394,6 +459,16 @@ class MapboxMapController extends ChangeNotifier { ); } + /// Add a hillshade 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 Future addHillshadeLayer( String sourceId, String layerId, HillshadeLayerProperties properties, {String? belowLayerId, String? sourceLayer}) async { @@ -474,10 +549,11 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes with the added symbol once listeners have /// been notified. Future addSymbol(SymbolOptions options, [Map? data]) async { - List result = - await addSymbols([options], data != null ? [data] : []); - - return result.first; + final effectiveOptions = SymbolOptions.defaultOptions.copyWith(options); + final symbol = Symbol(getRandomString(), effectiveOptions, data); + await symbolManager!.add(symbol); + notifyListeners(); + return symbol; } /// Adds multiple symbols to the map, configured using the specified custom @@ -490,11 +566,13 @@ class MapboxMapController extends ChangeNotifier { /// been notified. Future> addSymbols(List options, [List? data]) async { - final List effectiveOptions = - options.map((o) => SymbolOptions.defaultOptions.copyWith(o)).toList(); + final symbols = [ + for (var i = 0; i < options.length; i++) + Symbol(getRandomString(), + SymbolOptions.defaultOptions.copyWith(options[i]), data?[i]) + ]; + await symbolManager!.addAll(symbols); - final symbols = await _mapboxGlPlatform.addSymbols(effectiveOptions, data); - symbols.forEach((s) => _symbols[s.id] = s); notifyListeners(); return symbols; } @@ -507,10 +585,9 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future updateSymbol(Symbol symbol, SymbolOptions changes) async { - assert(_symbols[symbol.id] == symbol); + await symbolManager! + .set(symbol..options = symbol.options.copyWith(changes)); - await _mapboxGlPlatform.updateSymbol(symbol, changes); - symbol.options = symbol.options.copyWith(changes); notifyListeners(); } @@ -518,10 +595,7 @@ class MapboxMapController extends ChangeNotifier { /// This may be different from the value of `symbol.options.geometry` if the symbol is draggable. /// In that case this method provides the symbol's actual position, and `symbol.options.geometry` the last programmatically set position. Future getSymbolLatLng(Symbol symbol) async { - assert(_symbols[symbol.id] == symbol); - final symbolLatLng = await _mapboxGlPlatform.getSymbolLatLng(symbol); - notifyListeners(); - return symbolLatLng; + return symbol.options.geometry!; } /// Removes the specified [symbol] from the map. The symbol must be a current @@ -532,8 +606,7 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future removeSymbol(Symbol symbol) async { - assert(_symbols[symbol.id] == symbol); - await _removeSymbols([symbol.id]); + await symbolManager!.remove(symbol); notifyListeners(); } @@ -545,10 +618,9 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future removeSymbols(Iterable symbols) async { - final ids = symbols.where((s) => _symbols[s.id] == s).map((s) => s.id); - assert(symbols.length == ids.length); - - await _removeSymbols(ids); + for (var symbol in symbols) { + await symbolManager!.remove(symbol); + } notifyListeners(); } @@ -559,21 +631,10 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future clearSymbols() async { - await _mapboxGlPlatform.removeSymbols(_symbols.keys); - _symbols.clear(); + symbolManager!.clear(); notifyListeners(); } - /// Helper method to remove a single symbol from the map. Consumed by - /// [removeSymbol] and [clearSymbols]. - /// - /// The returned [Future] completes once the symbol has been removed from - /// [_symbols]. - Future _removeSymbols(Iterable ids) async { - await _mapboxGlPlatform.removeSymbols(ids); - _symbols.removeWhere((k, s) => ids.contains(k)); - } - /// Adds a line to the map, configured using the specified custom [options]. /// /// Change listeners are notified once the line has been added on the @@ -582,10 +643,9 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes with the added line once listeners have /// been notified. Future addLine(LineOptions options, [Map? data]) async { - final LineOptions effectiveOptions = - LineOptions.defaultOptions.copyWith(options); - final line = await _mapboxGlPlatform.addLine(effectiveOptions, data); - _lines[line.id] = line; + final effectiveOptions = LineOptions.defaultOptions.copyWith(options); + final line = Line(getRandomString(), effectiveOptions, data); + await lineManager!.add(line); notifyListeners(); return line; } @@ -599,8 +659,13 @@ class MapboxMapController extends ChangeNotifier { /// been notified. Future> addLines(List options, [List? data]) async { - final lines = await _mapboxGlPlatform.addLines(options, data); - lines.forEach((l) => _lines[l.id] = l); + final lines = [ + for (var i = 0; i < options.length; i++) + Line(getRandomString(), LineOptions.defaultOptions.copyWith(options[i]), + data?[i]) + ]; + await lineManager!.addAll(lines); + notifyListeners(); return lines; } @@ -613,9 +678,8 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future updateLine(Line line, LineOptions changes) async { - assert(_lines[line.id] == line); - await _mapboxGlPlatform.updateLine(line, changes); line.options = line.options.copyWith(changes); + await lineManager!.set(line); notifyListeners(); } @@ -623,10 +687,7 @@ class MapboxMapController extends ChangeNotifier { /// This may be different from the value of `line.options.geometry` if the line is draggable. /// In that case this method provides the line's actual position, and `line.options.geometry` the last programmatically set position. Future> getLineLatLngs(Line line) async { - assert(_lines[line.id] == line); - final lineLatLngs = await _mapboxGlPlatform.getLineLatLngs(line); - notifyListeners(); - return lineLatLngs; + return line.options.geometry!; } /// Removes the specified [line] from the map. The line must be a current @@ -637,10 +698,7 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future removeLine(Line line) async { - assert(_lines[line.id] == line); - - await _mapboxGlPlatform.removeLine(line.id); - _lines.remove(line.id); + await lineManager!.remove(line); notifyListeners(); } @@ -652,11 +710,10 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future removeLines(Iterable lines) async { - final ids = lines.where((l) => _lines[l.id] == l).map((l) => l.id); - assert(lines.length == ids.length); + for (var line in lines) { + await lineManager!.remove(line); + } - await _mapboxGlPlatform.removeLines(ids); - ids.forEach((id) => _lines.remove(id)); notifyListeners(); } @@ -667,9 +724,7 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future clearLines() async { - final List lineIds = List.from(_lines.keys); - await _mapboxGlPlatform.removeLines(lineIds); - _lines.clear(); + await lineManager!.clear(); notifyListeners(); } @@ -683,8 +738,8 @@ class MapboxMapController extends ChangeNotifier { Future addCircle(CircleOptions options, [Map? data]) async { final CircleOptions effectiveOptions = CircleOptions.defaultOptions.copyWith(options); - final circle = await _mapboxGlPlatform.addCircle(effectiveOptions, data); - _circles[circle.id] = circle; + final circle = Circle(getRandomString(), effectiveOptions, data); + await circleManager!.add(circle); notifyListeners(); return circle; } @@ -699,10 +754,15 @@ class MapboxMapController extends ChangeNotifier { /// been notified. Future> addCircles(List options, [List? data]) async { - final circles = await _mapboxGlPlatform.addCircles(options, data); - circles.forEach((c) => _circles[c.id] = c); + final cricles = [ + for (var i = 0; i < options.length; i++) + Circle(getRandomString(), + CircleOptions.defaultOptions.copyWith(options[i]), data?[i]) + ]; + await circleManager!.addAll(cricles); + notifyListeners(); - return circles; + return cricles; } /// Updates the specified [circle] with the given [changes]. The circle must @@ -713,9 +773,9 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future updateCircle(Circle circle, CircleOptions changes) async { - assert(_circles[circle.id] == circle); - await _mapboxGlPlatform.updateCircle(circle, changes); circle.options = circle.options.copyWith(changes); + await circleManager!.set(circle); + notifyListeners(); } @@ -723,10 +783,7 @@ class MapboxMapController extends ChangeNotifier { /// This may be different from the value of `circle.options.geometry` if the circle is draggable. /// In that case this method provides the circle's actual position, and `circle.options.geometry` the last programmatically set position. Future getCircleLatLng(Circle circle) async { - assert(_circles[circle.id] == circle); - final circleLatLng = await _mapboxGlPlatform.getCircleLatLng(circle); - notifyListeners(); - return circleLatLng; + return circle.options.geometry!; } /// Removes the specified [circle] from the map. The circle must be a current @@ -737,10 +794,7 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future removeCircle(Circle circle) async { - assert(_circles[circle.id] == circle); - - await _mapboxGlPlatform.removeCircle(circle.id); - _circles.remove(circle.id); + circleManager!.remove(circle); notifyListeners(); } @@ -753,11 +807,9 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future removeCircles(Iterable circles) async { - final ids = circles.where((c) => _circles[c.id] == c).map((c) => c.id); - assert(circles.length == ids.length); - - await _mapboxGlPlatform.removeCircles(ids); - ids.forEach((id) => _circles.remove(id)); + for (var circle in circles) { + await circleManager!.remove(circle); + } notifyListeners(); } @@ -768,8 +820,7 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future clearCircles() async { - await _mapboxGlPlatform.removeCircles(_circles.keys); - _circles.clear(); + circleManager!.clear(); notifyListeners(); } @@ -784,8 +835,8 @@ class MapboxMapController extends ChangeNotifier { Future addFill(FillOptions options, [Map? data]) async { final FillOptions effectiveOptions = FillOptions.defaultOptions.copyWith(options); - final fill = await _mapboxGlPlatform.addFill(effectiveOptions, data); - _fills[fill.id] = fill; + final fill = Fill(getRandomString(), effectiveOptions, data); + await fillManager!.add(fill); notifyListeners(); return fill; } @@ -800,10 +851,15 @@ class MapboxMapController extends ChangeNotifier { /// been notified. Future> addFills(List options, [List? data]) async { - final circles = await _mapboxGlPlatform.addFills(options, data); - circles.forEach((f) => _fills[f.id] = f); + final fills = [ + for (var i = 0; i < options.length; i++) + Fill(getRandomString(), FillOptions.defaultOptions.copyWith(options[i]), + data?[i]) + ]; + await fillManager!.addAll(fills); + notifyListeners(); - return circles; + return fills; } /// Updates the specified [fill] with the given [changes]. The fill must @@ -814,9 +870,9 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future updateFill(Fill fill, FillOptions changes) async { - assert(_fills[fill.id] == fill); - await _mapboxGlPlatform.updateFill(fill, changes); fill.options = fill.options.copyWith(changes); + await fillManager!.set(fill); + notifyListeners(); } @@ -827,8 +883,7 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future clearFills() async { - await _mapboxGlPlatform.removeFills(_fills.keys); - _fills.clear(); + await fillManager!.clear(); notifyListeners(); } @@ -841,10 +896,7 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future removeFill(Fill fill) async { - assert(_fills[fill.id] == fill); - await _mapboxGlPlatform.removeFill(fill.id); - _fills.remove(fill.id); - + await fillManager!.remove(fill); notifyListeners(); } @@ -856,11 +908,10 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once listeners have been notified. Future removeFills(Iterable fills) async { - final ids = fills.where((f) => _fills[f.id] == f).map((f) => f.id); - assert(fills.length == ids.length); + for (var fill in fills) { + await fillManager!.remove(fill); + } - await _mapboxGlPlatform.removeFills(ids); - ids.forEach((id) => _fills.remove(id)); notifyListeners(); } @@ -934,22 +985,22 @@ class MapboxMapController extends ChangeNotifier { /// For more information on what this does, see https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/#label-collision Future setSymbolIconAllowOverlap(bool enable) async { - await _mapboxGlPlatform.setSymbolIconAllowOverlap(enable); + await symbolManager?.setIconAllowOverlap(enable); } /// For more information on what this does, see https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/#label-collision Future setSymbolIconIgnorePlacement(bool enable) async { - await _mapboxGlPlatform.setSymbolIconIgnorePlacement(enable); + await symbolManager?.setIconIgnorePlacement(enable); } /// For more information on what this does, see https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/#label-collision Future setSymbolTextAllowOverlap(bool enable) async { - await _mapboxGlPlatform.setSymbolTextAllowOverlap(enable); + await symbolManager?.setTextAllowOverlap(enable); } /// For more information on what this does, see https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/#label-collision Future setSymbolTextIgnorePlacement(bool enable) async { - await _mapboxGlPlatform.setSymbolTextIgnorePlacement(enable); + await symbolManager?.setTextIgnorePlacement(enable); } /// Adds an image source to the style currently displayed in the map, so that it can later be referred to by the provided id. @@ -1022,6 +1073,17 @@ class MapboxMapController extends ChangeNotifier { return _mapboxGlPlatform.addSource(sourceid, properties); } + /// Add a layer to the map with the given properties + /// + /// 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 this has no effect for [RasterLayerProperties] and + /// [HillshadeLayerProperties]. + /// [sourceLayer] is used to selected a specific source layer from Vector + /// source. Future addLayer( String sourceId, String layerId, LayerProperties properties, {String? belowLayerId, @@ -1029,16 +1091,24 @@ class MapboxMapController extends ChangeNotifier { String? sourceLayer}) async { if (properties is FillLayerProperties) { addFillLayer(sourceId, layerId, properties, - belowLayerId: belowLayerId, sourceLayer: sourceLayer); + belowLayerId: belowLayerId, + enableInteraction: enableInteraction, + sourceLayer: sourceLayer); } else if (properties is LineLayerProperties) { addLineLayer(sourceId, layerId, properties, - belowLayerId: belowLayerId, sourceLayer: sourceLayer); + belowLayerId: belowLayerId, + enableInteraction: enableInteraction, + sourceLayer: sourceLayer); } else if (properties is SymbolLayerProperties) { addSymbolLayer(sourceId, layerId, properties, - belowLayerId: belowLayerId, sourceLayer: sourceLayer); + belowLayerId: belowLayerId, + enableInteraction: enableInteraction, + sourceLayer: sourceLayer); } else if (properties is CircleLayerProperties) { addCircleLayer(sourceId, layerId, properties, - belowLayerId: belowLayerId, sourceLayer: sourceLayer); + belowLayerId: belowLayerId, + enableInteraction: enableInteraction, + sourceLayer: sourceLayer); } else if (properties is RasterLayerProperties) { addRasterLayer(sourceId, layerId, properties, belowLayerId: belowLayerId, sourceLayer: sourceLayer); diff --git a/lib/src/mapbox_map.dart b/lib/src/mapbox_map.dart index ce11c2a1e..3e2c92856 100644 --- a/lib/src/mapbox_map.dart +++ b/lib/src/mapbox_map.dart @@ -25,6 +25,7 @@ class MapboxMap extends StatefulWidget { this.zoomGesturesEnabled = true, this.tiltGesturesEnabled = true, this.doubleClickZoomEnabled, + this.dragEnabled = true, this.trackCameraPosition = false, this.myLocationEnabled = false, this.myLocationTrackingMode = MyLocationTrackingMode.None, @@ -91,6 +92,12 @@ class MapboxMap extends StatefulWidget { /// True if the map should show a compass when rotated. final bool compassEnabled; + /// True if drag functionality should be enabled. + /// + /// Disable to avoid performance issues that from the drag event listeners. + /// Biggest impact in android + final bool dragEnabled; + /// Geographical bounding box for the camera target. final CameraTargetBounds cameraTargetBounds; @@ -228,20 +235,15 @@ class _MapboxMapState extends State { @override Widget build(BuildContext context) { - final List annotationOrder = - widget.annotationOrder.map((e) => e.toString()).toList(); - assert(annotationOrder.toSet().length == annotationOrder.length, + assert( + widget.annotationOrder.toSet().length == widget.annotationOrder.length, "annotationOrder must not have duplicate types"); - final List annotationConsumeTapEvents = - widget.annotationConsumeTapEvents.map((e) => e.toString()).toList(); - final Map creationParams = { 'initialCameraPosition': widget.initialCameraPosition.toMap(), 'options': _MapboxMapOptions.fromWidget(widget).toMap(), 'accessToken': widget.accessToken, - 'annotationOrder': annotationOrder, - 'annotationConsumeTapEvents': annotationConsumeTapEvents, 'onAttributionClickOverride': widget.onAttributionClick != null, + 'dragEnabled': widget.dragEnabled }; return _mapboxGlPlatform.buildView( creationParams, onPlatformViewCreated, widget.gestureRecognizers); @@ -299,6 +301,8 @@ class _MapboxMapState extends State { onCameraTrackingChanged: widget.onCameraTrackingChanged, onCameraIdle: widget.onCameraIdle, onMapIdle: widget.onMapIdle, + annotationOrder: widget.annotationOrder, + annotationConsumeTapEvents: widget.annotationConsumeTapEvents, ); await _mapboxGlPlatform.initPlatform(id); _controller.complete(controller); diff --git a/lib/src/util.dart b/lib/src/util.dart new file mode 100644 index 000000000..5ba0b789e --- /dev/null +++ b/lib/src/util.dart @@ -0,0 +1,14 @@ +part of mapbox_gl; + +Map buildFeatureCollection( + List> features) { + return {"type": "FeatureCollection", "features": features}; +} + +final _random = Random(); +String getRandomString([int length = 10]) { + const charSet = + 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890'; + return String.fromCharCodes(Iterable.generate( + length, (_) => charSet.codeUnitAt(_random.nextInt(charSet.length)))); +} diff --git a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart index d9afebae7..3585cd037 100644 --- a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart @@ -10,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:meta/meta.dart' show visibleForTesting; +part 'src/annotation.dart'; part 'src/callbacks.dart'; part 'src/camera.dart'; part 'src/circle.dart'; diff --git a/mapbox_gl_platform_interface/lib/src/annotation.dart b/mapbox_gl_platform_interface/lib/src/annotation.dart new file mode 100644 index 000000000..8478d34c3 --- /dev/null +++ b/mapbox_gl_platform_interface/lib/src/annotation.dart @@ -0,0 +1,8 @@ +part of mapbox_gl_platform_interface; + +abstract class Annotation { + String get id; + Map toGeoJson(); + + void translate(LatLng delta); +} diff --git a/mapbox_gl_platform_interface/lib/src/circle.dart b/mapbox_gl_platform_interface/lib/src/circle.dart index a75a0217e..5e0000805 100644 --- a/mapbox_gl_platform_interface/lib/src/circle.dart +++ b/mapbox_gl_platform_interface/lib/src/circle.dart @@ -6,7 +6,7 @@ part of mapbox_gl_platform_interface; -class Circle { +class Circle implements Annotation { Circle(this._id, this.options, [this._data]); /// A unique identifier for this circle. @@ -24,6 +24,21 @@ class Circle { /// The returned value does not reflect any changes made to the circle through /// touch events. Add listeners to the owning map controller to track those. CircleOptions options; + + @override + Map toGeoJson() { + final geojson = options.toGeoJson(); + geojson["id"] = id; + geojson["properties"]["id"] = id; + + return geojson; + } + + @override + void translate(LatLng delta) { + options = options + .copyWith(CircleOptions(geometry: this.options.geometry! + delta)); + } } /// Configuration options for [Circle] instances. @@ -73,7 +88,7 @@ class CircleOptions { ); } - dynamic toJson() { + dynamic toJson([bool addGeometry = true]) { final Map json = {}; void addIfPresent(String fieldName, dynamic value) { @@ -89,8 +104,21 @@ class CircleOptions { addIfPresent('circleStrokeWidth', circleStrokeWidth); addIfPresent('circleStrokeColor', circleStrokeColor); addIfPresent('circleStrokeOpacity', circleStrokeOpacity); - addIfPresent('geometry', geometry?.toJson()); + if (addGeometry) { + addIfPresent('geometry', geometry?.toJson()); + } addIfPresent('draggable', draggable); return json; } + + Map toGeoJson() { + return { + "type": "Feature", + "properties": toJson(false), + "geometry": { + "type": "Point", + "coordinates": geometry!.toGeoJsonCoordinates() + } + }; + } } diff --git a/mapbox_gl_platform_interface/lib/src/fill.dart b/mapbox_gl_platform_interface/lib/src/fill.dart index 15c966551..d645de9a7 100644 --- a/mapbox_gl_platform_interface/lib/src/fill.dart +++ b/mapbox_gl_platform_interface/lib/src/fill.dart @@ -16,12 +16,12 @@ FillOptions translateFillOptions(FillOptions options, LatLng delta) { } newGeometry.add(newRing); } - return FillOptions(geometry: newGeometry); + return options.copyWith(FillOptions(geometry: newGeometry)); } return options; } -class Fill { +class Fill implements Annotation { Fill(this._id, this.options, [this._data]); /// A unique identifier for this fill. @@ -39,6 +39,20 @@ class Fill { /// The returned value does not reflect any changes made to the fill through /// touch events. Add listeners to the owning map controller to track those. FillOptions options; + + @override + Map toGeoJson() { + final geojson = options.toGeoJson(); + geojson["id"] = id; + geojson["properties"]["id"] = id; + + return geojson; + } + + @override + void translate(LatLng delta) { + options = translateFillOptions(options, delta); + } } /// Configuration options for [Fill] instances. @@ -78,7 +92,7 @@ class FillOptions { ); } - dynamic toJson() { + dynamic toJson([bool addGeometry = true]) { final Map json = {}; void addIfPresent(String fieldName, dynamic value) { @@ -91,13 +105,30 @@ class FillOptions { addIfPresent('fillColor', fillColor); addIfPresent('fillOutlineColor', fillOutlineColor); addIfPresent('fillPattern', fillPattern); - addIfPresent( - 'geometry', - geometry - ?.map((List latLngList) => - latLngList.map((LatLng latLng) => latLng.toJson()).toList()) - .toList()); + if (addGeometry) { + addIfPresent( + 'geometry', + geometry + ?.map((List latLngList) => + latLngList.map((LatLng latLng) => latLng.toJson()).toList()) + .toList()); + } addIfPresent('draggable', draggable); return json; } + + Map toGeoJson() { + return { + "type": "Feature", + "properties": toJson(false), + "geometry": { + "type": "Polygon", + "coordinates": geometry! + .map((List latLngList) => latLngList + .map((LatLng latLng) => latLng.toGeoJsonCoordinates()) + .toList()) + .toList() + } + }; + } } diff --git a/mapbox_gl_platform_interface/lib/src/line.dart b/mapbox_gl_platform_interface/lib/src/line.dart index 0447bd26d..092a69ab8 100644 --- a/mapbox_gl_platform_interface/lib/src/line.dart +++ b/mapbox_gl_platform_interface/lib/src/line.dart @@ -6,7 +6,7 @@ part of mapbox_gl_platform_interface; -class Line { +class Line implements Annotation { Line(this._id, this.options, [this._data]); /// A unique identifier for this line. @@ -26,6 +26,20 @@ class Line { /// The returned value does not reflect any changes made to the line through /// touch events. Add listeners to the owning map controller to track those. LineOptions options; + + Map toGeoJson() { + final geojson = options.toGeoJson(); + geojson["id"] = id; + geojson["properties"]["id"] = id; + + return geojson; + } + + @override + void translate(LatLng delta) { + options = options.copyWith(LineOptions( + geometry: this.options.geometry?.map((e) => e + delta).toList())); + } } /// Configuration options for [Line] instances. @@ -78,7 +92,7 @@ class LineOptions { ); } - dynamic toJson() { + dynamic toJson([bool addGeometry = true]) { final Map json = {}; void addIfPresent(String fieldName, dynamic value) { @@ -95,9 +109,22 @@ class LineOptions { addIfPresent('lineOffset', lineOffset); addIfPresent('lineBlur', lineBlur); addIfPresent('linePattern', linePattern); - addIfPresent( - 'geometry', geometry?.map((LatLng latLng) => latLng.toJson()).toList()); + if (addGeometry) { + addIfPresent('geometry', + geometry?.map((LatLng latLng) => latLng.toJson()).toList()); + } addIfPresent('draggable', draggable); return json; } + + Map toGeoJson() { + return { + "type": "Feature", + "properties": toJson(false), + "geometry": { + "type": "LineString", + "coordinates": geometry!.map((c) => c.toGeoJsonCoordinates()).toList() + } + }; + } } diff --git a/mapbox_gl_platform_interface/lib/src/location.dart b/mapbox_gl_platform_interface/lib/src/location.dart index c64e05e2c..7c058dda9 100644 --- a/mapbox_gl_platform_interface/lib/src/location.dart +++ b/mapbox_gl_platform_interface/lib/src/location.dart @@ -36,6 +36,10 @@ class LatLng { return [latitude, longitude]; } + dynamic toGeoJsonCoordinates() { + return [longitude, latitude]; + } + static LatLng _fromJson(List json) { return LatLng(json[0], json[1]); } diff --git a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart index ec618d067..25e0c4d85 100644 --- a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart @@ -16,16 +16,10 @@ abstract class MapboxGlPlatform { final onInfoWindowTappedPlatform = ArgumentCallbacks(); - final onSymbolTappedPlatform = ArgumentCallbacks(); - - final onLineTappedPlatform = ArgumentCallbacks(); - - final onCircleTappedPlatform = ArgumentCallbacks(); - - final onFillTappedPlatform = ArgumentCallbacks(); - final onFeatureTappedPlatform = ArgumentCallbacks>(); + final onFeatureDraggedPlatform = ArgumentCallbacks>(); + final onCameraMoveStartedPlatform = ArgumentCallbacks(); final onCameraMovePlatform = ArgumentCallbacks(); @@ -51,269 +45,86 @@ abstract class MapboxGlPlatform { final onUserLocationUpdatedPlatform = ArgumentCallbacks(); - Future initPlatform(int id) async { - throw UnimplementedError('initPlatform() has not been implemented.'); - } - + Future initPlatform(int id); Widget buildView( Map creationParams, OnPlatformViewCreatedCallback onPlatformViewCreated, - Set>? gestureRecognizers) { - throw UnimplementedError('buildView() has not been implemented.'); - } - - Future updateMapOptions( - Map optionsUpdate) async { - throw UnimplementedError('updateMapOptions() has not been implemented.'); - } - - Future animateCamera(CameraUpdate cameraUpdate) async { - throw UnimplementedError('animateCamera() has not been implemented.'); - } - - Future moveCamera(CameraUpdate cameraUpdate) async { - throw UnimplementedError('moveCamera() has not been implemented.'); - } - + Set>? gestureRecognizers); + Future updateMapOptions(Map optionsUpdate); + Future animateCamera(CameraUpdate cameraUpdate); + Future moveCamera(CameraUpdate cameraUpdate); Future updateMyLocationTrackingMode( - MyLocationTrackingMode myLocationTrackingMode) async { - throw UnimplementedError( - 'updateMyLocationTrackingMode() has not been implemented.'); - } - - Future matchMapLanguageWithDeviceDefault() async { - throw UnimplementedError( - 'matchMapLanguageWithDeviceDefault() has not been implemented.'); - } - - Future updateContentInsets(EdgeInsets insets, bool animated) async { - throw UnimplementedError('updateContentInsets() has not been implemented.'); - } - - Future setMapLanguage(String language) async { - throw UnimplementedError('setMapLanguage() has not been implemented.'); - } - - Future setTelemetryEnabled(bool enabled) async { - throw UnimplementedError('setTelemetryEnabled() has not been implemented.'); - } - - Future getTelemetryEnabled() async { - throw UnimplementedError('getTelemetryEnabled() has not been implemented.'); - } - - Future> addSymbols(List options, - [List? data]) async { - throw UnimplementedError('addSymbols() has not been implemented.'); - } - - Future updateSymbol(Symbol symbol, SymbolOptions changes) async { - throw UnimplementedError('updateSymbol() has not been implemented.'); - } - - Future removeSymbols(Iterable symbolsIds) async { - throw UnimplementedError('removeSymbol() has not been implemented.'); - } - - Future addLine(LineOptions options, [Map? data]) async { - throw UnimplementedError('addLine() has not been implemented.'); - } - - Future> addLines(List options, - [List? data]) async { - throw UnimplementedError('addLines() has not been implemented.'); - } - - Future updateLine(Line line, LineOptions changes) async { - throw UnimplementedError('updateLine() has not been implemented.'); - } - - Future removeLine(String lineId) async { - throw UnimplementedError('removeLine() has not been implemented.'); - } - - Future removeLines(Iterable ids) async { - throw UnimplementedError('removeLines() has not been implemented.'); - } - - Future addCircle(CircleOptions options, [Map? data]) async { - throw UnimplementedError('addCircle() has not been implemented.'); - } - - Future> addCircles(List options, - [List? data]) async { - throw UnimplementedError('addCircles() has not been implemented.'); - } - - Future updateCircle(Circle circle, CircleOptions changes) async { - throw UnimplementedError('updateCircle() has not been implemented.'); - } - - Future getCircleLatLng(Circle circle) async { - throw UnimplementedError('getCircleLatLng() has not been implemented.'); - } - - Future getSymbolLatLng(Symbol symbol) async { - throw UnimplementedError('getSymbolLatLng() has not been implemented.'); - } - - Future> getLineLatLngs(Line line) async { - throw UnimplementedError('getLineLatLngs() has not been implemented.'); - } - - Future removeCircle(String circleId) async { - throw UnimplementedError('removeCircle() has not been implemented.'); - } - - Future removeCircles(Iterable ids) async { - throw UnimplementedError('removeCircles() has not been implemented.'); - } - - Future addFill(FillOptions options, [Map? data]) async { - throw UnimplementedError('addFill() has not been implemented.'); - } - - Future> addFills(List options, - [List? data]) async { - throw UnimplementedError('addFills() has not been implemented.'); - } - - Future updateFill(Fill fill, FillOptions changes) async { - throw UnimplementedError('updateFill() has not been implemented.'); - } - - Future removeFill(String fillId) async { - throw UnimplementedError('removeFill() has not been implemented.'); - } + MyLocationTrackingMode myLocationTrackingMode); - Future removeFills(Iterable fillIds) async { - throw UnimplementedError('removeFills() has not been implemented.'); - } + Future matchMapLanguageWithDeviceDefault(); + Future updateContentInsets(EdgeInsets insets, bool animated); + Future setMapLanguage(String language); + Future setTelemetryEnabled(bool enabled); + + Future getTelemetryEnabled(); Future queryRenderedFeatures( - Point point, List layerIds, List? filter) async { - throw UnimplementedError( - 'queryRenderedFeatures() has not been implemented.'); - } + Point point, List layerIds, List? filter); Future queryRenderedFeaturesInRect( - Rect rect, List layerIds, String? filter) async { - throw UnimplementedError( - 'queryRenderedFeaturesInRect() has not been implemented.'); - } - - Future invalidateAmbientCache() async { - throw UnimplementedError( - 'invalidateAmbientCache() has not been implemented.'); - } - - Future requestMyLocationLatLng() async { - throw UnimplementedError( - 'requestMyLocationLatLng() has not been implemented.'); - } - - Future getVisibleRegion() async { - throw UnimplementedError('getVisibleRegion() has not been implemented.'); - } - - Future addImage(String name, Uint8List bytes, - [bool sdf = false]) async { - throw UnimplementedError('addImage() has not been implemented.'); - } - - Future setSymbolIconAllowOverlap(bool enable) async { - throw UnimplementedError( - 'setSymbolIconAllowOverlap() has not been implemented.'); - } - - Future setSymbolIconIgnorePlacement(bool enable) async { - throw UnimplementedError( - 'setSymbolIconIgnorePlacement() has not been implemented.'); - } - - Future setSymbolTextAllowOverlap(bool enable) async { - throw UnimplementedError( - 'setSymbolTextAllowOverlap() has not been implemented.'); - } - - Future setSymbolTextIgnorePlacement(bool enable) async { - throw UnimplementedError( - 'setSymbolTextIgnorePlacement() has not been implemented.'); - } + Rect rect, List layerIds, String? filter); + Future invalidateAmbientCache(); + Future requestMyLocationLatLng(); - Future addImageSource( - String imageSourceId, Uint8List bytes, LatLngQuad coordinates) async { - throw UnimplementedError('addImageSource() has not been implemented.'); - } + Future getVisibleRegion(); - Future addLayer(String imageLayerId, String imageSourceId) async { - throw UnimplementedError('addLayer() has not been implemented.'); - } + Future addImage(String name, Uint8List bytes, [bool sdf = false]); + Future addImageSource( + String imageSourceId, Uint8List bytes, LatLngQuad coordinates); + + Future addLayer(String imageLayerId, String imageSourceId); Future addLayerBelow( - String imageLayerId, String imageSourceId, String belowLayerId) async { - throw UnimplementedError('addLayerBelow() has not been implemented.'); - } + String imageLayerId, String imageSourceId, String belowLayerId); - Future removeLayer(String imageLayerId) async { - throw UnimplementedError('removeLayer() has not been implemented.'); - } + Future removeLayer(String imageLayerId); - Future toScreenLocation(LatLng latLng) async { - throw UnimplementedError('toScreenLocation() has not been implemented.'); - } + Future toScreenLocation(LatLng latLng); - Future> toScreenLocationBatch(Iterable latLngs) async { - throw UnimplementedError( - 'toScreenLocationList() has not been implemented.'); - } + Future> toScreenLocationBatch(Iterable latLngs); - Future toLatLng(Point screenLocation) async { - throw UnimplementedError('toLatLng() has not been implemented.'); - } + Future toLatLng(Point screenLocation); - Future getMetersPerPixelAtLatitude(double latitude) async { - throw UnimplementedError( - 'getMetersPerPixelAtLatitude() has not been implemented.'); - } + Future getMetersPerPixelAtLatitude(double latitude); Future addGeoJsonSource(String sourceId, Map geojson, - {String? promoteId}) async { - throw UnimplementedError('addGeoJsonSource() has not been implemented.'); - } + {String? promoteId}); + + Future setGeoJsonSource(String sourceId, Map geojson); - Future setGeoJsonSource( - String sourceId, Map geojson) async { - throw UnimplementedError('setGeoJsonSource() has not been implemented.'); - } + Future setFeatureForGeoJsonSource( + String sourceId, Map geojsonFeature); - Future removeSource(String sourceId) async { - throw UnimplementedError('removeSource() has not been implemented.'); - } + Future removeSource(String sourceId); Future addSymbolLayer( String sourceId, String layerId, Map properties, - {String? belowLayerId, String? sourceLayer}) async { - throw UnimplementedError('addSymbolLayer() has not been implemented.'); - } + {String? belowLayerId, + String? sourceLayer, + required bool enableInteraction}); Future addLineLayer( String sourceId, String layerId, Map properties, - {String? belowLayerId, String? sourceLayer}) async { - throw UnimplementedError('addLineLayer() has not been implemented.'); - } + {String? belowLayerId, + String? sourceLayer, + required bool enableInteraction}); Future addCircleLayer( String sourceId, String layerId, Map properties, - {String? belowLayerId, String? sourceLayer}) async { - throw UnimplementedError('addCircleLayer() has not been implemented.'); - } + {String? belowLayerId, + String? sourceLayer, + required bool enableInteraction}); Future addFillLayer( String sourceId, String layerId, Map properties, - {String? belowLayerId, String? sourceLayer}) async { - throw UnimplementedError('addFillLayer() has not been implemented.'); - } + {String? belowLayerId, + String? sourceLayer, + required bool enableInteraction}); Future addRasterLayer( String sourceId, String layerId, Map properties, diff --git a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart index 7bd89eaff..b53ef9100 100644 --- a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart +++ b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -11,30 +11,7 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { onInfoWindowTappedPlatform(symbolId); } break; - case 'symbol#onTap': - final String? symbolId = call.arguments['symbol']; - if (symbolId != null) { - onSymbolTappedPlatform(symbolId); - } - break; - case 'line#onTap': - final String? lineId = call.arguments['line']; - if (lineId != null) { - onLineTappedPlatform(lineId); - } - break; - case 'circle#onTap': - final String? circleId = call.arguments['circle']; - if (circleId != null) { - onCircleTappedPlatform(circleId); - } - break; - case 'fill#onTap': - final String? fillId = call.arguments['fill']; - if (fillId != null) { - onFillTappedPlatform(fillId); - } - break; + case 'feature#onTap': final id = call.arguments['id']; final double x = call.arguments['x']; @@ -47,6 +24,28 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { 'latLng': LatLng(lat, lng) }); break; + case 'feature#onDrag': + final id = call.arguments['id']; + final double x = call.arguments['x']; + final double y = call.arguments['y']; + final double originLat = call.arguments['originLat']; + final double originLng = call.arguments['originLng']; + + final double currentLat = call.arguments['currentLat']; + final double currentLng = call.arguments['currentLng']; + + final double deltaLat = call.arguments['deltaLat']; + final double deltaLng = call.arguments['deltaLng']; + + onFeatureDraggedPlatform({ + 'id': id, + 'point': Point(x, y), + 'origin': LatLng(originLat, originLng), + 'current': LatLng(currentLat, currentLng), + 'delta': LatLng(deltaLat, deltaLng), + }); + break; + case 'camera#onMoveStarted': onCameraMoveStartedPlatform(null); break; @@ -231,235 +230,6 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { return await _channel.invokeMethod('map#getTelemetryEnabled'); } - @override - Future> addSymbols(List options, - [List? data]) async { - final List symbolIds = await _channel.invokeMethod( - 'symbols#addAll', - { - 'options': options.map((o) => o.toJson()).toList(), - }, - ); - final List symbols = symbolIds - .asMap() - .map((i, id) => MapEntry( - i, - Symbol(id, options.elementAt(i), - data != null && data.length > i ? data.elementAt(i) : null))) - .values - .toList(); - - return symbols; - } - - @override - Future updateSymbol(Symbol symbol, SymbolOptions changes) async { - await _channel.invokeMethod('symbol#update', { - 'symbol': symbol.id, - 'options': changes.toJson(), - }); - } - - @override - Future getSymbolLatLng(Symbol symbol) async { - Map mapLatLng = - await _channel.invokeMethod('symbol#getGeometry', { - 'symbol': symbol._id, - }); - LatLng symbolLatLng = - new LatLng(mapLatLng['latitude'], mapLatLng['longitude']); - return symbolLatLng; - } - - @override - Future removeSymbols(Iterable ids) async { - await _channel.invokeMethod('symbols#removeAll', { - 'ids': ids.toList(), - }); - } - - @override - Future addLine(LineOptions options, [Map? data]) async { - final String lineId = await _channel.invokeMethod( - 'line#add', - { - 'options': options.toJson(), - }, - ); - return Line(lineId, options, data); - } - - @override - Future> addLines(List options, - [List? data]) async { - final List ids = await _channel.invokeMethod( - 'line#addAll', - { - 'options': options.map((o) => o.toJson()).toList(), - }, - ); - final List lines = ids - .asMap() - .map((i, id) => MapEntry( - i, - Line(id, options.elementAt(i), - data != null && data.length > i ? data.elementAt(i) : null))) - .values - .toList(); - - return lines; - } - - @override - Future updateLine(Line line, LineOptions changes) async { - await _channel.invokeMethod('line#update', { - 'line': line.id, - 'options': changes.toJson(), - }); - } - - @override - Future> getLineLatLngs(Line line) async { - List latLngList = - await _channel.invokeMethod('line#getGeometry', { - 'line': line._id, - }); - List resultList = []; - for (var latLng in latLngList) { - resultList.add(LatLng(latLng['latitude'], latLng['longitude'])); - } - return resultList; - } - - @override - Future removeLine(String lineId) async { - await _channel.invokeMethod('line#remove', { - 'line': lineId, - }); - } - - @override - Future removeLines(Iterable ids) async { - await _channel.invokeMethod('line#removeAll', { - 'ids': ids.toList(), - }); - } - - @override - Future addCircle(CircleOptions options, [Map? data]) async { - final String circleId = await _channel.invokeMethod( - 'circle#add', - { - 'options': options.toJson(), - }, - ); - return Circle(circleId, options, data); - } - - @override - Future> addCircles(List options, - [List? data]) async { - final List ids = await _channel.invokeMethod( - 'circle#addAll', - { - 'options': options.map((o) => o.toJson()).toList(), - }, - ); - return ids - .asMap() - .map((i, id) => MapEntry( - i, - Circle(id, options.elementAt(i), - data != null && data.length > i ? data.elementAt(i) : null))) - .values - .toList(); - } - - @override - Future updateCircle(Circle circle, CircleOptions changes) async { - await _channel.invokeMethod('circle#update', { - 'circle': circle.id, - 'options': changes.toJson(), - }); - } - - @override - Future getCircleLatLng(Circle circle) async { - Map mapLatLng = - await _channel.invokeMethod('circle#getGeometry', { - 'circle': circle.id, - }); - return LatLng(mapLatLng['latitude'], mapLatLng['longitude']); - } - - @override - Future removeCircle(String circleId) async { - await _channel.invokeMethod('circle#remove', { - 'circle': circleId, - }); - } - - @override - Future removeCircles(Iterable ids) async { - await _channel.invokeMethod('circle#removeAll', { - 'ids': ids.toList(), - }); - } - - @override - Future addFill(FillOptions options, [Map? data]) async { - final String fillId = await _channel.invokeMethod( - 'fill#add', - { - 'options': options.toJson(), - }, - ); - return Fill(fillId, options, data); - } - - @override - Future> addFills(List options, - [List? data]) async { - final List ids = await _channel.invokeMethod( - 'fill#addAll', - { - 'options': options.map((o) => o.toJson()).toList(), - }, - ); - final List fills = ids - .asMap() - .map((i, id) => MapEntry( - i, - Fill(id, options.elementAt(i), - data != null && data.length > i ? data.elementAt(i) : null))) - .values - .toList(); - - return fills; - } - - @override - Future updateFill(Fill fill, FillOptions changes) async { - await _channel.invokeMethod('fill#update', { - 'fill': fill.id, - 'options': changes.toJson(), - }); - } - - @override - Future removeFill(String fillId) async { - await _channel.invokeMethod('fill#remove', { - 'fill': fillId, - }); - } - - @override - Future removeFills(Iterable ids) async { - await _channel.invokeMethod('fill#removeAll', { - 'ids': ids.toList(), - }); - } - @override Future queryRenderedFeatures( Point point, List layerIds, List? filter) async { @@ -559,54 +329,6 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { } } - @override - Future setSymbolIconAllowOverlap(bool enable) async { - try { - await _channel - .invokeMethod('symbolManager#iconAllowOverlap', { - 'iconAllowOverlap': enable, - }); - } on PlatformException catch (e) { - return new Future.error(e); - } - } - - @override - Future setSymbolIconIgnorePlacement(bool enable) async { - try { - await _channel - .invokeMethod('symbolManager#iconIgnorePlacement', { - 'iconIgnorePlacement': enable, - }); - } on PlatformException catch (e) { - return new Future.error(e); - } - } - - @override - Future setSymbolTextAllowOverlap(bool enable) async { - try { - await _channel - .invokeMethod('symbolManager#textAllowOverlap', { - 'textAllowOverlap': enable, - }); - } on PlatformException catch (e) { - return new Future.error(e); - } - } - - @override - Future setSymbolTextIgnorePlacement(bool enable) async { - try { - await _channel - .invokeMethod('symbolManager#textIgnorePlacement', { - 'textIgnorePlacement': enable, - }); - } on PlatformException catch (e) { - return new Future.error(e); - } - } - @override Future addImageSource( String imageSourceId, Uint8List bytes, LatLngQuad coordinates) async { @@ -755,12 +477,15 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { @override Future addSymbolLayer( String sourceId, String layerId, Map properties, - {String? belowLayerId, String? sourceLayer}) async { + {String? belowLayerId, + String? sourceLayer, + required bool enableInteraction}) async { await _channel.invokeMethod('symbolLayer#add', { 'sourceId': sourceId, 'layerId': layerId, 'belowLayerId': belowLayerId, 'sourceLayer': sourceLayer, + 'enableInteraction': enableInteraction, 'properties': properties .map((key, value) => MapEntry(key, jsonEncode(value))) }); @@ -769,12 +494,15 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { @override Future addLineLayer( String sourceId, String layerId, Map properties, - {String? belowLayerId, String? sourceLayer}) async { + {String? belowLayerId, + String? sourceLayer, + required bool enableInteraction}) async { await _channel.invokeMethod('lineLayer#add', { 'sourceId': sourceId, 'layerId': layerId, 'belowLayerId': belowLayerId, 'sourceLayer': sourceLayer, + 'enableInteraction': enableInteraction, 'properties': properties .map((key, value) => MapEntry(key, jsonEncode(value))) }); @@ -783,12 +511,15 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { @override Future addCircleLayer( String sourceId, String layerId, Map properties, - {String? belowLayerId, String? sourceLayer}) async { + {String? belowLayerId, + String? sourceLayer, + required bool enableInteraction}) async { await _channel.invokeMethod('circleLayer#add', { 'sourceId': sourceId, 'layerId': layerId, 'belowLayerId': belowLayerId, 'sourceLayer': sourceLayer, + 'enableInteraction': enableInteraction, 'properties': properties .map((key, value) => MapEntry(key, jsonEncode(value))) }); @@ -797,12 +528,15 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { @override Future addFillLayer( String sourceId, String layerId, Map properties, - {String? belowLayerId, String? sourceLayer}) async { + {String? belowLayerId, + String? sourceLayer, + required bool enableInteraction}) async { await _channel.invokeMethod('fillLayer#add', { 'sourceId': sourceId, 'layerId': layerId, 'belowLayerId': belowLayerId, 'sourceLayer': sourceLayer, + 'enableInteraction': enableInteraction, 'properties': properties .map((key, value) => MapEntry(key, jsonEncode(value))) }); @@ -847,4 +581,12 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { .map((key, value) => MapEntry(key, jsonEncode(value))) }); } + + Future setFeatureForGeoJsonSource( + String sourceId, Map geojsonFeature) async { + await _channel.invokeMethod('source#setFeature', { + 'sourceId': sourceId, + 'geojsonFeature': jsonEncode(geojsonFeature) + }); + } } diff --git a/mapbox_gl_platform_interface/lib/src/symbol.dart b/mapbox_gl_platform_interface/lib/src/symbol.dart index 87eaa79b5..5e088d538 100644 --- a/mapbox_gl_platform_interface/lib/src/symbol.dart +++ b/mapbox_gl_platform_interface/lib/src/symbol.dart @@ -6,7 +6,7 @@ part of mapbox_gl_platform_interface; -class Symbol { +class Symbol implements Annotation { Symbol(this._id, this.options, [this._data]); /// A unique identifier for this symbol. @@ -25,6 +25,21 @@ class Symbol { /// The returned value does not reflect any changes made to the symbol through /// touch events. Add listeners to the owning map controller to track those. SymbolOptions options; + + @override + Map toGeoJson() { + final geojson = options.toGeoJson(); + geojson["id"] = id; + geojson["properties"]["id"] = id; + + return geojson; + } + + @override + void translate(LatLng delta) { + options = options + .copyWith(SymbolOptions(geometry: this.options.geometry! + delta)); + } } dynamic _offsetToJson(Offset? offset) { @@ -79,6 +94,8 @@ class SymbolOptions { final double? iconRotate; final Offset? iconOffset; final String? iconAnchor; + + /// Not supported on web final List? fontNames; final String? textField; final double? textSize; @@ -138,7 +155,7 @@ class SymbolOptions { ); } - dynamic toJson() { + dynamic toJson([bool addGeometry = true]) { final Map json = {}; void addIfPresent(String fieldName, dynamic value) { @@ -172,9 +189,22 @@ class SymbolOptions { addIfPresent('textHaloColor', textHaloColor); addIfPresent('textHaloWidth', textHaloWidth); addIfPresent('textHaloBlur', textHaloBlur); - addIfPresent('geometry', geometry?.toJson()); + if (addGeometry) { + addIfPresent('geometry', geometry?.toJson()); + } addIfPresent('zIndex', zIndex); addIfPresent('draggable', draggable); return json; } + + Map toGeoJson() { + return { + "type": "Feature", + "properties": toJson(false), + "geometry": { + "type": "Point", + "coordinates": geometry!.toGeoJsonCoordinates() + } + }; + } } diff --git a/mapbox_gl_web/lib/mapbox_gl_web.dart b/mapbox_gl_web/lib/mapbox_gl_web.dart index 92a219118..1a6702d54 100644 --- a/mapbox_gl_web/lib/mapbox_gl_web.dart +++ b/mapbox_gl_web/lib/mapbox_gl_web.dart @@ -4,6 +4,7 @@ import 'dart:async'; // FIXED HERE: https://github.com/dart-lang/linter/pull/1985 // ignore_for_file: avoid_web_libraries_in_flutter import 'dart:html'; +// ignore: unused_import import 'dart:js'; import 'dart:math'; import 'dart:typed_data'; @@ -23,9 +24,4 @@ import 'package:mapbox_gl_web/src/layer_tools.dart'; part 'src/convert.dart'; part 'src/mapbox_map_plugin.dart'; part 'src/options_sink.dart'; -part 'src/feature_manager/feature_manager.dart'; -part 'src/feature_manager/symbol_manager.dart'; -part 'src/feature_manager/line_manager.dart'; -part 'src/feature_manager/circle_manager.dart'; -part 'src/feature_manager/fill_manager.dart'; -part 'src/mapbox_map_controller.dart'; +part 'src/mapbox_web_gl_platform.dart'; diff --git a/mapbox_gl_web/lib/src/feature_manager/circle_manager.dart b/mapbox_gl_web/lib/src/feature_manager/circle_manager.dart deleted file mode 100644 index 94269242f..000000000 --- a/mapbox_gl_web/lib/src/feature_manager/circle_manager.dart +++ /dev/null @@ -1,43 +0,0 @@ -part of mapbox_gl_web; - -class CircleManager extends FeatureManager { - CircleManager({ - required MapboxMap map, - ArgumentCallbacks? onTap, - }) : super( - sourceId: 'circle_source', - layerId: 'circle_layer', - map: map, - onTap: onTap, - ); - - @override - void initLayer() { - map.addLayer({ - 'id': layerId, - 'type': 'circle', - 'source': sourceId, - 'paint': { - 'circle-radius': ['get', 'circleRadius'], - 'circle-color': ['get', 'circleColor'], - 'circle-blur': ['get', 'circleBlur'], - 'circle-opacity': ['get', 'circleOpacity'], - 'circle-stroke-width': ['get', 'circleStrokeWidth'], - 'circle-stroke-color': ['get', 'circleStrokeColor'], - 'circle-stroke-opacity': ['get', 'circleStrokeOpacity'], - } - }); - } - - @override - void onDrag(String featureId, LatLng latLng) { - update(featureId, CircleOptions(geometry: latLng)); - } - - @override - void update(String lineId, CircleOptions changes) { - Feature olfFeature = getFeature(lineId)!; - Feature newFeature = Convert.interpretCircleOptions(changes, olfFeature); - updateFeature(newFeature); - } -} diff --git a/mapbox_gl_web/lib/src/feature_manager/feature_manager.dart b/mapbox_gl_web/lib/src/feature_manager/feature_manager.dart deleted file mode 100644 index a4bcfbfb0..000000000 --- a/mapbox_gl_web/lib/src/feature_manager/feature_manager.dart +++ /dev/null @@ -1,115 +0,0 @@ -part of mapbox_gl_web; - -abstract class FeatureManager { - final String sourceId; - final String layerId; - final MapboxMap map; - final ArgumentCallbacks? onTap; - @protected - late LatLng dragOrigin; - - final Map _features = {}; - num featureCounter = 1; - String? _draggableFeatureId; - - FeatureManager({ - required this.sourceId, - required this.layerId, - required this.map, - this.onTap, - }) { - var featureSource = GeoJsonSource(data: FeatureCollection(features: [])); - map.addSource(sourceId, featureSource); - initLayer(); - _initClickHandler(); - _initDragHandler(); - } - - void initLayer(); - - void update(String featureId, T changes); - - void onDrag(String featureId, LatLng latLng); - - String add(Feature feature) { - feature.id = featureCounter++; - _features['${feature.id}'] = feature; - _updateSource(); - return '${feature.id}'; - } - - void updateFeature(Feature feature) { - updateFeatures([feature]); - } - - void updateFeatures(Iterable features) { - features.forEach((feature) => _features['${feature.id}'] = feature); - _updateSource(); - } - - void remove(String featureId) { - removeAll([featureId]); - } - - void removeAll(Iterable featuresIds) { - featuresIds.forEach((featureId) => _features.remove(featureId)); - _updateSource(); - } - - Feature? getFeature(String featureId) { - return _features[featureId]; - } - - void _initClickHandler() { - map.on('click', (e) { - if (e is Event) { - final features = map.queryRenderedFeatures([e.point.x, e.point.y]); - if (features.isNotEmpty && features[0].source == sourceId) { - if (onTap != null) { - onTap!('${features[0].id}'); - } - } - } - }); - - map.on('mouseenter', layerId, (_) { - map.getCanvas().style.cursor = 'pointer'; - }); - - map.on('mouseleave', layerId, (_) { - map.getCanvas().style.cursor = ''; - }); - } - - void _initDragHandler() { - map.on('mousedown', layerId, (e) { - var isDraggable = e.features[0].properties['draggable']; - if (isDraggable != null && isDraggable) { - // Prevent the default map drag behavior. - e.preventDefault(); - _draggableFeatureId = '${e.features[0].id}'; - map.getCanvas().style.cursor = 'grabbing'; - var coords = e.lngLat; - dragOrigin = LatLng(coords.lat as double, coords.lng as double); - } - }); - - map.on('mousemove', (e) { - if (_draggableFeatureId != null) { - var coords = e.lngLat; - onDrag(_draggableFeatureId!, LatLng(coords.lat, coords.lng)); - } - }); - - map.on('mouseup', (_) { - _draggableFeatureId = null; - map.getCanvas().style.cursor = ''; - }); - } - - void _updateSource() { - GeoJsonSource featureSource = map.getSource(sourceId); - featureSource - .setData(FeatureCollection(features: _features.values.toList())); - } -} diff --git a/mapbox_gl_web/lib/src/feature_manager/fill_manager.dart b/mapbox_gl_web/lib/src/feature_manager/fill_manager.dart deleted file mode 100644 index 14def9505..000000000 --- a/mapbox_gl_web/lib/src/feature_manager/fill_manager.dart +++ /dev/null @@ -1,46 +0,0 @@ -part of mapbox_gl_web; - -class FillManager extends FeatureManager { - FillManager({ - required MapboxMap map, - ArgumentCallbacks? onTap, - }) : super( - sourceId: 'fill_source', - layerId: 'fill_layer', - map: map, - onTap: onTap, - ); - - @override - void initLayer() { - map.addLayer({ - 'id': layerId, - 'type': 'fill', - 'source': sourceId, - 'paint': { - 'fill-color': ['get', 'fillColor'], - 'fill-opacity': ['get', 'fillOpacity'], - 'fill-outline-color': ['get', 'fillOutlineColor'], - } - }); - } - - @override - void onDrag(String featureId, LatLng latLng) { - Feature oldFeature = getFeature(featureId)!; - final geometry = - Convert.featureGeometryToFillGeometry(oldFeature.geometry.coordinates); - update( - featureId, - translateFillOptions( - FillOptions(geometry: geometry), latLng - dragOrigin)); - dragOrigin = latLng; - } - - @override - void update(String featureId, FillOptions changes) { - Feature oldFeature = getFeature(featureId)!; - Feature newFeature = Convert.intepretFillOptions(changes, oldFeature); - updateFeature(newFeature); - } -} diff --git a/mapbox_gl_web/lib/src/feature_manager/line_manager.dart b/mapbox_gl_web/lib/src/feature_manager/line_manager.dart deleted file mode 100644 index 59c3a71ed..000000000 --- a/mapbox_gl_web/lib/src/feature_manager/line_manager.dart +++ /dev/null @@ -1,47 +0,0 @@ -part of mapbox_gl_web; - -class LineManager extends FeatureManager { - LineManager({ - required MapboxMap map, - ArgumentCallbacks? onTap, - }) : super( - sourceId: 'line_source', - layerId: 'line_layer', - map: map, - onTap: onTap, - ); - - @override - void initLayer() { - // NOTE: line-pattern disable line-color - map.addLayer({ - 'id': layerId, - 'type': 'line', - 'source': sourceId, - 'layout': { - 'line-join': ['get', 'lineJoin'], - }, - 'paint': { - 'line-opacity': ['get', 'lineOpacity'], - 'line-color': ['get', 'lineColor'], - 'line-width': ['get', 'lineWidth'], - 'line-gap-width': ['get', 'lineGapWidth'], - 'line-offset': ['get', 'lineOffset'], - 'line-blur': ['get', 'lineBlur'], - //'line-pattern': ['get', 'linePattern'], - } - }); - } - - void update(String lineId, LineOptions changes) { - Feature olfFeature = getFeature(lineId)!; - Feature newFeature = Convert.interpretLineOptions(changes, olfFeature); - updateFeature(newFeature); - } - - @override - void onDrag(String featureId, LatLng latLng) { - // TODO: implement onDrag - print('onDrag is not already implemented'); - } -} diff --git a/mapbox_gl_web/lib/src/feature_manager/symbol_manager.dart b/mapbox_gl_web/lib/src/feature_manager/symbol_manager.dart deleted file mode 100644 index a3d2e7567..000000000 --- a/mapbox_gl_web/lib/src/feature_manager/symbol_manager.dart +++ /dev/null @@ -1,93 +0,0 @@ -part of mapbox_gl_web; - -class SymbolManager extends FeatureManager { - SymbolManager({ - required MapboxMap map, - ArgumentCallbacks? onTap, - }) : super( - sourceId: 'symbol_source', - layerId: 'symbol_layer', - map: map, - onTap: onTap, - ); - - @override - void initLayer() { - map.addLayer({ - 'id': layerId, - 'type': 'symbol', - 'source': sourceId, - 'layout': { - 'icon-image': '{iconImage}', - 'icon-size': ['get', 'iconSize'], - 'icon-rotate': ['get', 'iconRotate'], - 'icon-offset': ['get', 'iconOffset'], - 'icon-anchor': ['get', 'iconAnchor'], - 'text-field': ['get', 'textField'], - 'text-size': ['get', 'textSize'], - 'text-max-width': ['get', 'textMaxWidth'], - 'text-letter-spacing': ['get', 'textLetterSpacing'], - 'text-justify': ['get', 'textJustify'], - 'text-anchor': ['get', 'textAnchor'], - 'text-rotate': ['get', 'textRotate'], - 'text-transform': ['get', 'textTransform'], - 'text-offset': ['get', 'textOffset'], - 'symbol-sort-key': ['get', 'symbolSortKey'], - 'icon-allow-overlap': true, - 'icon-ignore-placement': true, - 'text-allow-overlap': true, - 'text-ignore-placement': true, - }, - 'paint': { - 'icon-opacity': ['get', 'iconOpacity'], - 'icon-color': ['get', 'iconColor'], - 'icon-halo-color': ['get', 'iconHaloColor'], - 'icon-halo-width': ['get', 'iconHaloWidth'], - 'icon-halo-blur': ['get', 'iconHaloBlur'], - 'text-opacity': ['get', 'textOpacity'], - 'text-color': ['get', 'textColor'], - 'text-halo-color': ['get', 'textHaloColor'], - 'text-halo-width': ['get', 'textHaloWidth'], - 'text-halo-blur': ['get', 'textHaloBlur'], - } - }); - - map.on('styleimagemissing', (event) { - if (event.id == '') { - return; - } - var density = context['window'].devicePixelRatio ?? 1; - var imagePath = density == 1 - ? '/assets/assets/symbols/custom-icon.png' - : '/assets/assets/symbols/$density.0x/custom-icon.png'; - map.loadImage(imagePath, (error, image) { - if (error != null) throw error; - if (!map.hasImage(event.id)) - map.addImage(event.id, image, {'pixelRatio': density}); - }); - }); - } - - @override - void update(String lineId, SymbolOptions changes) { - updateAll({lineId: changes}); - } - - void updateAll(Map changesById) { - List featuresWithUpdatedOptions = []; - changesById.forEach( - (id, options) => featuresWithUpdatedOptions.add( - Convert.interpretSymbolOptions( - options, - getFeature(id)!, - ), - ), - ); - updateFeatures(featuresWithUpdatedOptions); - } - - @override - void onDrag(String featureId, LatLng latLng) { - update(featureId, SymbolOptions(geometry: latLng)); - } -} diff --git a/mapbox_gl_web/lib/src/mapbox_map_plugin.dart b/mapbox_gl_web/lib/src/mapbox_map_plugin.dart index dcc779d70..68f7975f5 100644 --- a/mapbox_gl_web/lib/src/mapbox_map_plugin.dart +++ b/mapbox_gl_web/lib/src/mapbox_map_plugin.dart @@ -3,6 +3,6 @@ part of mapbox_gl_web; class MapboxMapPlugin { /// Registers this class as the default instance of [MapboxGlPlatform]. static void registerWith(Registrar registrar) { - MapboxGlPlatform.createInstance = () => MapboxMapController(); + MapboxGlPlatform.createInstance = () => MapboxWebGlPlatform(); } } diff --git a/mapbox_gl_web/lib/src/mapbox_map_controller.dart b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart similarity index 73% rename from mapbox_gl_web/lib/src/mapbox_map_controller.dart rename to mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart index 48eed54dd..2f839d036 100644 --- a/mapbox_gl_web/lib/src/mapbox_map_controller.dart +++ b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart @@ -3,20 +3,20 @@ part of mapbox_gl_web; const _mapboxGlCssUrl = 'https://api.mapbox.com/mapbox-gl-js/v2.6.1/mapbox-gl.css'; -class MapboxMapController extends MapboxGlPlatform +class MapboxWebGlPlatform extends MapboxGlPlatform implements MapboxMapOptionsSink { late DivElement _mapElement; late Map _creationParams; late MapboxMap _map; bool _mapReady = false; + dynamic _draggedFeatureId; + LatLng? _dragOrigin; + LatLng? _dragPrevious; + bool _dragEnabled = true; + final _addedFeaturesByLayer = {}; - List annotationOrder = []; - final _featureLayerIdentifiers = Set(); - late SymbolManager symbolManager; - late LineManager lineManager; - late CircleManager circleManager; - late FillManager fillManager; + final _interactiveFeatureLayerIds = Set(); bool _trackCameraPosition = false; GeolocateControl? _geolocateControl; @@ -53,6 +53,9 @@ class MapboxMapController extends MapboxGlPlatform await _addStylesheetToShadowRoot(_mapElement); if (_creationParams.containsKey('initialCameraPosition')) { var camera = _creationParams['initialCameraPosition']; + + _dragEnabled = _creationParams['dragEnabled'] ?? true; + if (_creationParams.containsKey('accessToken')) { Mapbox.accessToken = _creationParams['accessToken']; } @@ -74,11 +77,45 @@ class MapboxMapController extends MapboxGlPlatform _map.on('move', _onCameraMove); _map.on('moveend', _onCameraIdle); _map.on('resize', _onMapResize); + if (_dragEnabled) { + _map.on('mouseup', _onMouseUp); + _map.on('mousemove', _onMouseMove); + } } Convert.interpretMapboxMapOptions(_creationParams['options'], this); + } + + _onMouseDown(Event e) { + var isDraggable = e.features[0].properties['draggable']; + if (isDraggable != null && isDraggable) { + // Prevent the default map drag behavior. + e.preventDefault(); + _draggedFeatureId = e.features[0].id; + _map.getCanvas().style.cursor = 'grabbing'; + var coords = e.lngLat; + _dragOrigin = LatLng(coords.lat as double, coords.lng as double); + } + } + + _onMouseUp(Event e) { + _draggedFeatureId = null; + _dragPrevious = null; + _dragOrigin = null; + _map.getCanvas().style.cursor = ''; + } - if (_creationParams.containsKey('annotationOrder')) { - annotationOrder = _creationParams['annotationOrder']; + _onMouseMove(Event e) { + if (_draggedFeatureId != null) { + final current = LatLng(e.lngLat.lat.toDouble(), e.lngLat.lng.toDouble()); + final payload = { + 'id': _draggedFeatureId, + 'point': Point(e.point.x.toDouble(), e.point.y.toDouble()), + 'origin': _dragOrigin, + 'current': current, + 'delta': current - (_dragPrevious ?? _dragOrigin!), + }; + _dragPrevious = current; + onFeatureDraggedPlatform(payload); } } @@ -145,142 +182,6 @@ class MapboxMapController extends MapboxGlPlatform return false; } - @override - Future> addSymbols(List options, - [List? data]) async { - Map optionsById = { - for (final o in options) - symbolManager.add(Feature( - geometry: Geometry( - type: 'Point', - coordinates: [o.geometry!.longitude, o.geometry!.latitude], - ), - )): o, - }; - symbolManager.updateAll(optionsById); - - return optionsById - .map((id, singleOptions) { - int dataIndex = options.indexOf(singleOptions); - Map? singleData = data != null && data.length >= dataIndex + 1 - ? data[dataIndex] - : null; - return MapEntry(id, Symbol(id, singleOptions, singleData)); - }) - .values - .toList(); - } - - @override - Future updateSymbol(Symbol symbol, SymbolOptions changes) async { - symbolManager.update(symbol.id, changes); - } - - @override - Future getSymbolLatLng(Symbol symbol) async { - var coordinates = symbolManager.getFeature(symbol.id)!.geometry.coordinates; - return LatLng(coordinates[1], coordinates[0]); - } - - @override - Future removeSymbols(Iterable symbolsIds) async { - symbolManager.removeAll(symbolsIds); - } - - @override - Future addLine(LineOptions options, [Map? data]) async { - String lineId = lineManager.add(Feature( - geometry: Geometry( - type: 'LineString', - coordinates: options.geometry! - .map((latLng) => [latLng.longitude, latLng.latitude]) - .toList(), - ), - )); - lineManager.update(lineId, options); - return Line(lineId, options, data); - } - - @override - Future updateLine(Line line, LineOptions changes) async { - lineManager.update(line.id, changes); - } - - @override - Future> getLineLatLngs(Line line) async { - List coordinates = - lineManager.getFeature(line.id)!.geometry.coordinates; - return coordinates.map((c) => LatLng(c[1], c[0])).toList(); - } - - @override - Future removeLine(String lineId) async { - lineManager.remove(lineId); - } - - @override - Future removeLines(Iterable ids) async { - lineManager.removeAll(ids); - } - - @override - Future addCircle(CircleOptions options, [Map? data]) async { - String circleId = circleManager.add(Feature( - geometry: Geometry( - type: 'Point', - coordinates: [options.geometry!.longitude, options.geometry!.latitude], - ), - )); - circleManager.update(circleId, options); - return Circle(circleId, options, data); - } - - @override - Future updateCircle(Circle circle, CircleOptions changes) async { - circleManager.update(circle.id, changes); - } - - @override - Future getCircleLatLng(Circle circle) async { - var coordinates = circleManager.getFeature(circle.id)!.geometry.coordinates; - return LatLng(coordinates[1], coordinates[0]); - } - - @override - Future removeCircle(String circleId) async { - circleManager.remove(circleId); - } - - @override - Future removeCircles(Iterable ids) async { - circleManager.removeAll(ids); - } - - Future addFill(FillOptions options, [Map? data]) async { - String fillId = fillManager.add(Feature( - geometry: Geometry( - type: 'Polygon', - coordinates: Convert.fillGeometryToFeatureGeometry(options.geometry!), - ), - )); - - fillManager.update(fillId, options); - return Fill(fillId, options, data); - } - - Future updateFill(Fill fill, FillOptions changes) async { - fillManager.update(fill.id, changes); - } - - Future removeFill(String fillId) async { - fillManager.remove(fillId); - } - - @override - Future removeFills(Iterable ids) async { - fillManager.removeAll(ids); - } - @override Future queryRenderedFeatures( Point point, List layerIds, List? filter) async { @@ -384,30 +285,6 @@ class MapboxMapController extends MapboxGlPlatform _map.removeSource(sourceId); } - @override - Future setSymbolIconAllowOverlap(bool enable) async { - //TODO: to implement - print('setSymbolIconAllowOverlap not implemented yet'); - } - - @override - Future setSymbolIconIgnorePlacement(bool enable) async { - //TODO: to implement - print('setSymbolIconIgnorePlacement not implemented yet'); - } - - @override - Future setSymbolTextAllowOverlap(bool enable) async { - //TODO: to implement - print('setSymbolTextAllowOverlap not implemented yet'); - } - - @override - Future setSymbolTextIgnorePlacement(bool enable) async { - //TODO: to implement - print('setSymbolTextIgnorePlacement not implemented yet'); - } - CameraPosition? _getCameraPosition() { if (_trackCameraPosition) { final center = _map.getCenter(); @@ -423,27 +300,7 @@ class MapboxMapController extends MapboxGlPlatform void _onStyleLoaded(_) { _mapReady = true; - for (final annotationType in annotationOrder) { - switch (annotationType) { - case 'AnnotationType.symbol': - symbolManager = - SymbolManager(map: _map, onTap: onSymbolTappedPlatform); - break; - case 'AnnotationType.line': - lineManager = LineManager(map: _map, onTap: onLineTappedPlatform); - break; - case 'AnnotationType.circle': - circleManager = - CircleManager(map: _map, onTap: onCircleTappedPlatform); - break; - case 'AnnotationType.fill': - fillManager = FillManager(map: _map, onTap: onFillTappedPlatform); - break; - default: - print( - "Unknown annotation type: \(annotationType), must be either 'fill', 'line', 'circle' or 'symbol'"); - } - } + onMapStyleLoadedPlatform(null); } @@ -460,8 +317,8 @@ class MapboxMapController extends MapboxGlPlatform } void _onMapClick(Event e) { - final features = _map.queryRenderedFeatures( - [e.point.x, e.point.y], {"layers": _featureLayerIdentifiers.toList()}); + final features = _map.queryRenderedFeatures([e.point.x, e.point.y], + {"layers": _interactiveFeatureLayerIds.toList()}); final payload = { 'point': Point(e.point.x.toDouble(), e.point.y.toDouble()), 'latLng': LatLng(e.lngLat.lat.toDouble(), e.lngLat.lng.toDouble()), @@ -702,12 +559,13 @@ class MapboxMapController extends MapboxGlPlatform @override void setStyleString(String? styleString) { //remove old mouseenter callbacks to avoid multicalling - for (var layerId in _featureLayerIdentifiers) { + for (var layerId in _interactiveFeatureLayerIds) { _map.off('mouseenter', layerId, _onMouseEnterFeature); _map.off('mousemouve', layerId, _onMouseEnterFeature); _map.off('mouseleave', layerId, _onMouseLeaveFeature); + if (_dragEnabled) _map.off('mousedown', layerId, _onMouseDown); } - _featureLayerIdentifiers.clear(); + _interactiveFeatureLayerIds.clear(); _map.setStyle(styleString); // catch style loaded for later style changes @@ -754,66 +612,91 @@ class MapboxMapController extends MapboxGlPlatform @override Future removeLayer(String layerId) async { - _featureLayerIdentifiers.remove(layerId); + _interactiveFeatureLayerIds.remove(layerId); _map.removeLayer(layerId); } @override Future addGeoJsonSource(String sourceId, Map geojson, {String? promoteId}) async { + final data = _makeFeatureCollection(geojson); + _addedFeaturesByLayer[sourceId] = data; _map.addSource(sourceId, { "type": 'geojson', - "data": geojson, + "data": geojson, // pass the raw string here to avoid errors if (promoteId != null) "promoteId": promoteId }); } + Feature _makeFeature(Map geojsonFeature) { + return Feature( + geometry: Geometry( + type: geojsonFeature["geometry"]["type"], + coordinates: geojsonFeature["geometry"]["coordinates"]), + properties: geojsonFeature["properties"], + id: geojsonFeature["properties"]?["id"] ?? geojsonFeature["id"]); + } + + FeatureCollection _makeFeatureCollection(Map geojson) { + return FeatureCollection( + features: [for (final f in geojson["features"] ?? []) _makeFeature(f)]); + } + @override Future setGeoJsonSource( String sourceId, Map geojson) async { final source = _map.getSource(sourceId) as GeoJsonSource; - final data = FeatureCollection(features: [ - for (final f in geojson["features"] ?? []) - Feature( - geometry: Geometry( - type: f["geometry"]["type"], - coordinates: f["geometry"]["coordinates"]), - properties: f["properties"], - id: f["id"]) - ]); + final data = _makeFeatureCollection(geojson); + _addedFeaturesByLayer[sourceId] = data; source.setData(data); } @override Future addCircleLayer( String sourceId, String layerId, Map properties, - {String? belowLayerId, String? sourceLayer}) async { + {String? belowLayerId, + String? sourceLayer, + required bool enableInteraction}) async { return _addLayer(sourceId, layerId, properties, "circle", - belowLayerId: belowLayerId, sourceLayer: sourceLayer); + belowLayerId: belowLayerId, + sourceLayer: sourceLayer, + enableInteraction: enableInteraction); } @override Future addFillLayer( String sourceId, String layerId, Map properties, - {String? belowLayerId, String? sourceLayer}) async { + {String? belowLayerId, + String? sourceLayer, + required bool enableInteraction}) async { return _addLayer(sourceId, layerId, properties, "fill", - belowLayerId: belowLayerId, sourceLayer: sourceLayer); + belowLayerId: belowLayerId, + sourceLayer: sourceLayer, + enableInteraction: enableInteraction); } @override Future addLineLayer( String sourceId, String layerId, Map properties, - {String? belowLayerId, String? sourceLayer}) async { + {String? belowLayerId, + String? sourceLayer, + required bool enableInteraction}) async { return _addLayer(sourceId, layerId, properties, "line", - belowLayerId: belowLayerId, sourceLayer: sourceLayer); + belowLayerId: belowLayerId, + sourceLayer: sourceLayer, + enableInteraction: enableInteraction); } @override Future addSymbolLayer( String sourceId, String layerId, Map properties, - {String? belowLayerId, String? sourceLayer}) async { + {String? belowLayerId, + String? sourceLayer, + required bool enableInteraction}) async { return _addLayer(sourceId, layerId, properties, "symbol", - belowLayerId: belowLayerId, sourceLayer: sourceLayer); + belowLayerId: belowLayerId, + sourceLayer: sourceLayer, + enableInteraction: enableInteraction); } @override @@ -821,12 +704,26 @@ class MapboxMapController extends MapboxGlPlatform String sourceId, String layerId, Map properties, {String? belowLayerId, String? sourceLayer}) async { return _addLayer(sourceId, layerId, properties, "hillshade", - belowLayerId: belowLayerId, sourceLayer: sourceLayer); + belowLayerId: belowLayerId, + sourceLayer: sourceLayer, + enableInteraction: false); + } + + @override + Future addRasterLayer( + String sourceId, String layerId, Map properties, + {String? belowLayerId, String? sourceLayer}) async { + await _addLayer(sourceId, layerId, properties, "raster", + belowLayerId: belowLayerId, + sourceLayer: sourceLayer, + enableInteraction: false); } Future _addLayer(String sourceId, String layerId, Map properties, String layerType, - {String? belowLayerId, String? sourceLayer}) async { + {String? belowLayerId, + String? sourceLayer, + required bool enableInteraction}) async { final layout = Map.fromEntries( properties.entries.where((entry) => isLayoutProperty(entry.key))); final paint = Map.fromEntries( @@ -841,17 +738,22 @@ class MapboxMapController extends MapboxGlPlatform if (sourceLayer != null) 'source-layer': sourceLayer }, belowLayerId); - _featureLayerIdentifiers.add(layerId); - if (layerType == "fill") { - _map.on('mousemove', layerId, _onMouseEnterFeature); - } else { - _map.on('mouseenter', layerId, _onMouseEnterFeature); + if (enableInteraction) { + _interactiveFeatureLayerIds.add(layerId); + if (layerType == "fill") { + _map.on('mousemove', layerId, _onMouseEnterFeature); + } else { + _map.on('mouseenter', layerId, _onMouseEnterFeature); + } + _map.on('mouseleave', layerId, _onMouseLeaveFeature); + if (_dragEnabled) _map.on('mousedown', layerId, _onMouseDown); } - _map.on('mouseleave', layerId, _onMouseLeaveFeature); } void _onMouseEnterFeature(_) { - _map.getCanvas().style.cursor = 'pointer'; + if (_draggedFeatureId == null) { + _map.getCanvas().style.cursor = 'pointer'; + } } void _onMouseLeaveFeature(_) { @@ -917,11 +819,48 @@ class MapboxMapController extends MapboxGlPlatform _map.addSource(sourceId, source.toJson()); } + Future addImageSource( + String imageSourceId, Uint8List bytes, LatLngQuad coordinates) { + // TODO: implement addImageSource + throw UnimplementedError(); + } + @override - Future addRasterLayer( - String sourceId, String layerId, Map properties, - {String? belowLayerId, String? sourceLayer}) async { - await _addLayer(sourceId, layerId, properties, "raster", - belowLayerId: belowLayerId, sourceLayer: sourceLayer); + Future addLayer(String imageLayerId, String imageSourceId) { + // TODO: implement addLayer + throw UnimplementedError(); + } + + @override + Future addLayerBelow( + String imageLayerId, String imageSourceId, String belowLayerId) { + // TODO: implement addLayerBelow + throw UnimplementedError(); + } + + @override + Future updateContentInsets(EdgeInsets insets, bool animated) { + // TODO: implement updateContentInsets + throw UnimplementedError(); + } + + @override + Future setFeatureForGeoJsonSource( + String sourceId, Map geojsonFeature) async { + final source = _map.getSource(sourceId) as GeoJsonSource?; + final data = _addedFeaturesByLayer[sourceId]; + + if (source != null && data != null) { + final feature = _makeFeature(geojsonFeature); + final features = data.features.toList(); + final index = features.indexWhere((f) => f.id == feature.id); + if (index >= 0) { + features[index] = feature; + final newData = FeatureCollection(features: features); + _addedFeaturesByLayer[sourceId] = newData; + + source.setData(newData); + } + } } } diff --git a/mapbox_gl_web/pubspec.yaml b/mapbox_gl_web/pubspec.yaml index 38fde8a62..e6e736718 100644 --- a/mapbox_gl_web/pubspec.yaml +++ b/mapbox_gl_web/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: git: url: https://github.com/tobrun/flutter-mapbox-gl.git path: mapbox_gl_platform_interface - mapbox_gl_dart: ^0.2.0-nullsafety + mapbox_gl_dart: ^0.2.1 image: ^3.0.0 dependency_overrides: