diff --git a/android/src/main/java/com/mapbox/mapboxgl/CircleBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/CircleBuilder.java index 9d902d509..db7accf56 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/CircleBuilder.java +++ b/android/src/main/java/com/mapbox/mapboxgl/CircleBuilder.java @@ -21,6 +21,10 @@ class CircleBuilder implements CircleOptionsSink { this.circleOptions = new CircleOptions(); } + public CircleOptions getCircleOptions(){ + return this.circleOptions; + } + Circle build() { return circleManager.create(circleOptions); } diff --git a/android/src/main/java/com/mapbox/mapboxgl/CircleController.java b/android/src/main/java/com/mapbox/mapboxgl/CircleController.java index 404d37b8b..76860ea6f 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/CircleController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/CircleController.java @@ -24,6 +24,10 @@ class CircleController implements CircleOptionsSink { this.onTappedListener = onTappedListener; } + public Circle getCircle(){ + return this.circle; + } + boolean onTap() { if (onTappedListener != null) { onTappedListener.onCircleTapped(circle); diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java index 09adcd90a..b70589cba 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java +++ b/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java @@ -22,6 +22,10 @@ class FillBuilder implements FillOptionsSink { this.fillOptions = new FillOptions(); } + public FillOptions getFillOptions(){ + return this.fillOptions; + } + Fill build() { return fillManager.create(fillOptions); } diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillController.java b/android/src/main/java/com/mapbox/mapboxgl/FillController.java index 200b13f76..86bc01137 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/FillController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/FillController.java @@ -27,6 +27,10 @@ class FillController implements FillOptionsSink { this.onTappedListener = onTappedListener; } + public Fill getFill(){ + return this.fill; + } + boolean onTap() { if (onTappedListener != null) { onTappedListener.onFillTapped(fill); diff --git a/android/src/main/java/com/mapbox/mapboxgl/LineBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/LineBuilder.java index a164ac606..5d7276143 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/LineBuilder.java +++ b/android/src/main/java/com/mapbox/mapboxgl/LineBuilder.java @@ -22,6 +22,10 @@ class LineBuilder implements LineOptionsSink { this.lineOptions = new LineOptions(); } + public LineOptions getLineOptions(){ + return this.lineOptions; + } + Line build() { return lineManager.create(lineOptions); } diff --git a/android/src/main/java/com/mapbox/mapboxgl/LineController.java b/android/src/main/java/com/mapbox/mapboxgl/LineController.java index 815401a55..d7715909c 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/LineController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/LineController.java @@ -32,6 +32,10 @@ class LineController implements LineOptionsSink { this.onTappedListener = onTappedListener; } + public Line getLine(){ + return this.line; + } + boolean onTap() { if (onTappedListener != null) { onTappedListener.onLineTapped(line); diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index 75360311f..d8bcce666 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -55,10 +55,13 @@ 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; @@ -627,7 +630,7 @@ public void onError(@NonNull String message) { break; } case "symbols#removeAll": { - final ArrayList symbolIds = call.argument("symbols"); + final ArrayList symbolIds = call.argument("ids"); SymbolController symbolController; List symbolList = new ArrayList(); @@ -699,6 +702,47 @@ public void onError(@NonNull String message) { result.success(null); break; } + case "line#addAll": { + 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": { + 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": { final String lineId = call.argument("line"); final LineController line = line(lineId); @@ -730,6 +774,47 @@ public void onError(@NonNull String message) { result.success(circleId); break; } + case "circle#addAll": { + 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": { + 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": { final String circleId = call.argument("circle"); removeCircle(circleId); @@ -764,6 +849,48 @@ public void onError(@NonNull String message) { result.success(fillId); break; } + + case "fill#addAll": { + 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": { + 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": { final String fillId = call.argument("fill"); removeFill(fillId); diff --git a/example/lib/main.dart b/example/lib/main.dart index bb424a402..ef5385dbd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -8,6 +8,7 @@ import 'package:location/location.dart'; import 'package:mapbox_gl_example/custom_marker.dart'; import 'package:mapbox_gl_example/full_map.dart'; import 'package:mapbox_gl_example/offline_regions.dart'; +import 'package:mapbox_gl_example/place_batch.dart'; import 'animate_camera.dart'; import 'annotation_order_maps.dart'; @@ -38,6 +39,7 @@ final List _allPages = [ OfflineRegionsPage(), AnnotationOrderPage(), CustomMarkerPage(), + BatchAddPage(), ]; class MapsDemo extends StatelessWidget { diff --git a/example/lib/place_batch.dart b/example/lib/place_batch.dart new file mode 100644 index 000000000..df8eec990 --- /dev/null +++ b/example/lib/place_batch.dart @@ -0,0 +1,189 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:mapbox_gl/mapbox_gl.dart'; +import 'package:mapbox_gl_example/main.dart'; + +import 'page.dart'; + +const fillOptions = [ + FillOptions( + geometry: [ + [ + LatLng(-33.719, 151.150), + LatLng(-33.858, 151.150), + LatLng(-33.866, 151.401), + LatLng(-33.747, 151.328), + LatLng(-33.719, 151.150), + ], + [ + LatLng(-33.762, 151.250), + LatLng(-33.827, 151.250), + LatLng(-33.833, 151.347), + LatLng(-33.762, 151.250), + ] + ], + fillColor: "#FF0000", + ), + FillOptions(geometry: [ + [ + LatLng(-33.719, 151.550), + LatLng(-33.858, 151.550), + LatLng(-33.866, 151.801), + LatLng(-33.747, 151.728), + LatLng(-33.719, 151.550), + ], + [ + LatLng(-33.762, 151.650), + LatLng(-33.827, 151.650), + LatLng(-33.833, 151.747), + LatLng(-33.762, 151.650), + ] + ], fillColor: "#FF0000"), +]; + +class BatchAddPage extends ExamplePage { + BatchAddPage() : super(const Icon(Icons.check_circle), 'Batch add/remove'); + + @override + Widget build(BuildContext context) { + return const BatchAddBody(); + } +} + +class BatchAddBody extends StatefulWidget { + const BatchAddBody(); + + @override + State createState() => BatchAddBodyState(); +} + +class BatchAddBodyState extends State { + BatchAddBodyState(); + List _fills = []; + List _circles = []; + List _lines = []; + List _symbols = []; + + static final LatLng center = const LatLng(-33.86711, 151.1947171); + + MapboxMapController controller; + + void _onMapCreated(MapboxMapController controller) { + this.controller = controller; + } + + List makeLinesOptionsForFillOptions( + Iterable options) { + final listOptions = []; + for (final option in options) { + for (final geom in option.geometry) { + listOptions.add(LineOptions(geometry: geom, lineColor: "#00FF00")); + } + } + return listOptions; + } + + List makeCircleOptionsForFillOptions( + Iterable options) { + final circleOptions = []; + for (final option in options) { + // put circles only on the outside + for (final latLng in option.geometry.first) { + circleOptions + .add(CircleOptions(geometry: latLng, circleColor: "#00FF00")); + } + } + return circleOptions; + } + + List makeSymbolOptionsForFillOptions( + Iterable options) { + final symbolOptions = []; + for (final option in options) { + // put symbols only on the inner most ring if it exists + if (option.geometry.length > 1) + for (final latLng in option.geometry.last) { + symbolOptions + .add(SymbolOptions(iconImage: 'hospital-11', geometry: latLng)); + } + } + return symbolOptions; + } + + void _add() async { + if (_fills.isEmpty) { + _fills = await controller.addFills(fillOptions); + _lines = await controller + .addLines(makeLinesOptionsForFillOptions(fillOptions)); + _circles = await controller + .addCircles(makeCircleOptionsForFillOptions(fillOptions)); + _symbols = await controller + .addSymbols(makeSymbolOptionsForFillOptions(fillOptions)); + } + } + + void _remove() { + controller.removeFills(_fills); + controller.removeLines(_lines); + controller.removeCircles(_circles); + controller.removeSymbols(_symbols); + _fills.clear(); + _lines.clear(); + _circles.clear(); + _symbols.clear(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: SizedBox( + height: 200.0, + child: MapboxMap( + accessToken: MapsDemo.ACCESS_TOKEN, + onMapCreated: _onMapCreated, + initialCameraPosition: const CameraPosition( + target: LatLng(-33.8, 151.511), + zoom: 8.2, + ), + annotationOrder: const [ + AnnotationType.fill, + AnnotationType.line, + AnnotationType.circle, + AnnotationType.symbol, + ], + ), + ), + ), + Expanded( + child: SingleChildScrollView( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Row( + children: [ + Column( + children: [ + TextButton( + child: const Text('batch add'), onPressed: _add), + TextButton( + child: const Text('batch remove'), + onPressed: _remove), + ], + ), + ], + ) + ], + ), + ), + ), + ], + ); + } +} diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index 989b4ab2b..494d9ba44 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -74,6 +74,11 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } } } + func removeAllForController(controller: MGLAnnotationController, ids: [String]){ + let idSet = Set(ids) + let annotations = controller.styleAnnotations() + controller.removeStyleAnnotations(annotations.filter { idSet.contains($0.identifier) }) + } func onMethodCall(methodCall: FlutterMethodCall, result: @escaping FlutterResult) { switch(methodCall.method) { @@ -281,16 +286,11 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma case "symbols#removeAll": guard let symbolAnnotationController = symbolAnnotationController else { return } guard let arguments = methodCall.arguments as? [String: Any] else { return } - guard let symbolIds = arguments["symbols"] as? [String] else { return } - var symbols: [MGLSymbolStyleAnnotation] = []; + guard let symbolIds = arguments["ids"] as? [String] else { return } - for symbol in symbolAnnotationController.styleAnnotations(){ - if symbolIds.contains(symbol.identifier) { - symbols.append(symbol as! MGLSymbolStyleAnnotation) - } - } - symbolAnnotationController.removeStyleAnnotations(symbols) + 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 } @@ -346,6 +346,34 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } 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? = nil + 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 } @@ -371,6 +399,16 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } } 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 } @@ -390,6 +428,38 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } else { result(nil) } + + case "line#addAll": + guard let lineAnnotationController = lineAnnotationController else { return } + guard let arguments = methodCall.arguments as? [String: Any] else { return } + // Parse geometry + var identifier: String? = nil + if let allOptions = arguments["options"] as? [[String: Any]]{ + var lines: [MGLLineStyleAnnotation] = []; + + for options in allOptions { + if let geometry = options["geometry"] as? [[Double]] { + guard geometry.count > 0 else { break } + // Convert geometry to coordinate and create a line. + var lineCoordinates: [CLLocationCoordinate2D] = [] + for coordinate in geometry { + lineCoordinates.append(CLLocationCoordinate2DMake(coordinate[0], coordinate[1])) + } + let line = MGLLineStyleAnnotation(coordinates: lineCoordinates, count: UInt(lineCoordinates.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 } @@ -415,6 +485,15 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } } 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 "line#getGeometry": guard let lineAnnotationController = lineAnnotationController else { return } guard let arguments = methodCall.arguments as? [String: Any] else { return } @@ -451,7 +530,40 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma 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? = nil + 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 } @@ -464,6 +576,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma break; } } + result(nil) case "fill#remove": guard let fillAnnotationController = fillAnnotationController else { return } @@ -477,6 +590,15 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } } 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 } @@ -491,6 +613,8 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma self.mapView.style?.setImage(image, forName: name) } result(nil) + + case "style#addImageSource": guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let imageSourceId = arguments["imageSourceId"] as? String else { return } diff --git a/lib/src/controller.dart b/lib/src/controller.dart index b98803bf9..ba65c74c6 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -100,7 +100,7 @@ class MapboxMapController extends ChangeNotifier { .add((cameraPosition) { _isCameraMoving = false; if (cameraPosition != null) { - _cameraPosition = cameraPosition; + _cameraPosition = cameraPosition; } if (onCameraIdle != null) { onCameraIdle(); @@ -362,6 +362,14 @@ class MapboxMapController extends ChangeNotifier { return result.first; } + /// Adds multiple symbols to the map, configured using the specified custom + /// [options]. + /// + /// Change listeners are notified once the symbol has been added on the + /// platform side. + /// + /// The returned [Future] completes with the added symbol once listeners have + /// been notified. Future> addSymbols(List options, [List data]) async { final List effectiveOptions = @@ -416,13 +424,18 @@ class MapboxMapController extends ChangeNotifier { notifyListeners(); } + /// Removes the specified [symbols] from the map. The symbols must be current + /// members of the [symbols] set. + /// + /// Change listeners are notified once the symbol has been removed on the + /// platform side. + /// + /// The returned [Future] completes once listeners have been notified. Future removeSymbols(Iterable symbols) async { - assert(symbols.length > 0); - symbols.forEach((s) { - assert(_symbols[s.id] == s); - }); + final ids = symbols.where((s) => _symbols[s.id] == s).map((s) => s.id); + assert(symbols.length == ids.length); - await _removeSymbols(symbols.map((s) => s.id)); + await _removeSymbols(ids); notifyListeners(); } @@ -434,8 +447,8 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes once listeners have been notified. Future clearSymbols() async { assert(_symbols != null); - final List symbolIds = List.from(_symbols.keys); - _removeSymbols(symbolIds); + await MapboxGlPlatform.getInstance(_id).removeLines(_symbols.keys); + _symbols.clear(); notifyListeners(); } @@ -466,8 +479,24 @@ class MapboxMapController extends ChangeNotifier { return line; } + /// Adds multiple lines to the map, configured using the specified custom [options]. + /// + /// Change listeners are notified once the lines have been added on the + /// platform side. + /// + /// The returned [Future] completes with the added line once listeners have + /// been notified. + Future> addLines(List options, + [List data]) async { + final lines = + await MapboxGlPlatform.getInstance(_id).addLines(options, data); + lines.forEach((l) => _lines[l.id] = l); + notifyListeners(); + return lines; + } + /// Updates the specified [line] with the given [changes]. The line must - /// be a current member of the [lines] set. + /// be a current member of the [lines] set.‚ /// /// Change listeners are notified once the line has been updated on the /// platform side. @@ -504,7 +533,25 @@ class MapboxMapController extends ChangeNotifier { Future removeLine(Line line) async { assert(line != null); assert(_lines[line.id] == line); - await _removeLine(line.id); + + await MapboxGlPlatform.getInstance(_id).removeLine(line.id); + _lines.remove(line.id); + notifyListeners(); + } + + /// Removes the specified [lines] from the map. The lines must be current + /// members of the [lines] set. + /// + /// Change listeners are notified once the lines have been removed on the + /// platform side. + /// + /// 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); + + await MapboxGlPlatform.getInstance(_id).removeLines(ids); + ids.forEach((id) => _lines.remove(id)); notifyListeners(); } @@ -517,22 +564,11 @@ class MapboxMapController extends ChangeNotifier { Future clearLines() async { assert(_lines != null); final List lineIds = List.from(_lines.keys); - for (String id in lineIds) { - await _removeLine(id); - } + await MapboxGlPlatform.getInstance(_id).removeLines(lineIds); + _lines.clear(); notifyListeners(); } - /// Helper method to remove a single line from the map. Consumed by - /// [removeLine] and [clearLines]. - /// - /// The returned [Future] completes once the line has been removed from - /// [_lines]. - Future _removeLine(String id) async { - await MapboxGlPlatform.getInstance(_id).removeLine(id); - _lines.remove(id); - } - /// Adds a circle to the map, configured using the specified custom [options]. /// /// Change listeners are notified once the circle has been added on the @@ -550,6 +586,23 @@ class MapboxMapController extends ChangeNotifier { return circle; } + /// Adds multiple circles to the map, configured using the specified custom + /// [options]. + /// + /// Change listeners are notified once the circles have been added on the + /// platform side. + /// + /// The returned [Future] completes with the added circle once listeners have + /// been notified. + Future> addCircles(List options, + [List data]) async { + final circles = + await MapboxGlPlatform.getInstance(_id).addCircles(options, data); + circles.forEach((c) => _circles[c.id] = c); + notifyListeners(); + return circles; + } + /// Updates the specified [circle] with the given [changes]. The circle must /// be a current member of the [circles] set. /// @@ -588,34 +641,42 @@ class MapboxMapController extends ChangeNotifier { Future removeCircle(Circle circle) async { assert(circle != null); assert(_circles[circle.id] == circle); - await _removeCircle(circle.id); + + await MapboxGlPlatform.getInstance(_id).removeCircle(circle.id); + _circles.remove(circle.id); + notifyListeners(); } - /// Removes all [circles] from the map. + /// Removes the specified [circles] from the map. The circles must be current + /// members of the [circles] set. /// - /// Change listeners are notified once all circles have been removed on the + /// Change listeners are notified once the circles have been removed on the /// platform side. /// /// The returned [Future] completes once listeners have been notified. - Future clearCircles() async { - assert(_circles != null); - final List circleIds = List.from(_circles.keys); - for (String id in circleIds) { - await _removeCircle(id); - } + Future removeCircles(Iterable circles) async { + assert(circles != null); + final ids = circles.where((c) => _circles[c.id] == c).map((c) => c.id); + assert(circles.length == ids.length); + + await MapboxGlPlatform.getInstance(_id).removeCircles(ids); + ids.forEach((id) => _circles.remove(id)); notifyListeners(); } - /// Helper method to remove a single circle from the map. Consumed by - /// [removeCircle] and [clearCircles]. + /// Removes all [circles] from the map. /// - /// The returned [Future] completes once the circle has been removed from - /// [_circles]. - Future _removeCircle(String id) async { - await MapboxGlPlatform.getInstance(_id).removeCircle(id); + /// Change listeners are notified once all circles have been removed on the + /// platform side. + /// + /// The returned [Future] completes once listeners have been notified. + Future clearCircles() async { + assert(_circles != null); + await MapboxGlPlatform.getInstance(_id).removeCircles(_circles.keys); + _circles.clear(); - _circles.remove(id); + notifyListeners(); } /// Adds a fill to the map, configured using the specified custom [options]. @@ -635,6 +696,23 @@ class MapboxMapController extends ChangeNotifier { return fill; } + /// Adds multiple fills to the map, configured using the specified custom + /// [options]. + /// + /// Change listeners are notified once the fills has been added on the + /// platform side. + /// + /// The returned [Future] completes with the added fills once listeners have + /// been notified. + Future> addFills(List options, + [List data]) async { + final circles = + await MapboxGlPlatform.getInstance(_id).addFills(options, data); + circles.forEach((f) => _fills[f.id] = f); + notifyListeners(); + return circles; + } + /// Updates the specified [fill] with the given [changes]. The fill must /// be a current member of the [fills] set. /// @@ -659,10 +737,9 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes once listeners have been notified. Future clearFills() async { assert(_fills != null); - final List fillIds = List.from(_fills.keys); - for (String id in fillIds) { - await _removeFill(id); - } + await MapboxGlPlatform.getInstance(_id).removeFills(_fills.keys); + _fills.clear(); + notifyListeners(); } @@ -676,19 +753,26 @@ class MapboxMapController extends ChangeNotifier { Future removeFill(Fill fill) async { assert(fill != null); assert(_fills[fill.id] == fill); - await _removeFill(fill.id); + await MapboxGlPlatform.getInstance(_id).removeFill(fill.id); + _fills.remove(fill.id); + notifyListeners(); } - /// Helper method to remove a single fill from the map. Consumed by - /// [removeFill] and [clearFills]. + /// Removes the specified [fills] from the map. The fills must be current + /// members of the [fills] set. /// - /// The returned [Future] completes once the fill has been removed from - /// [_fills]. - Future _removeFill(String id) async { - await MapboxGlPlatform.getInstance(_id).removeFill(id); + /// Change listeners are notified once the fills have been removed on the + /// platform side. + /// + /// 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); - _fills.remove(id); + await MapboxGlPlatform.getInstance(_id).removeFills(ids); + ids.forEach((id) => _fills.remove(id)); + notifyListeners(); } Future queryRenderedFeatures( 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 85b93307b..dd7f091c9 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 @@ -63,8 +63,9 @@ abstract class MapboxGlPlatform { ArgumentCallbacks(); final ArgumentCallbacks onMapIdlePlatform = ArgumentCallbacks(); - - final ArgumentCallbacks onUserLocationUpdatedPlatform = ArgumentCallbacks(); + + final ArgumentCallbacks onUserLocationUpdatedPlatform = + ArgumentCallbacks(); Future initPlatform(int id) async { throw UnimplementedError('initPlatform() has not been implemented.'); @@ -116,8 +117,9 @@ abstract class MapboxGlPlatform { Future getTelemetryEnabled() async { throw UnimplementedError('getTelemetryEnabled() has not been implemented.'); } - - Future> addSymbols(List options, [List data]) async { + + Future> addSymbols(List options, + [List data]) async { throw UnimplementedError('addSymbols() has not been implemented.'); } @@ -133,6 +135,11 @@ abstract class MapboxGlPlatform { 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.'); } @@ -141,10 +148,19 @@ abstract class MapboxGlPlatform { 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.'); } @@ -165,11 +181,20 @@ abstract class MapboxGlPlatform { 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.'); } - FutureupdateFill(Fill fill, FillOptions changes) async { + 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.'); } @@ -177,6 +202,10 @@ abstract class MapboxGlPlatform { throw UnimplementedError('removeFill() has not been implemented.'); } + Future removeFills(Iterable fillIds) async { + throw UnimplementedError('removeFills() has not been implemented.'); + } + Future queryRenderedFeatures( Point point, List layerIds, List filter) async { throw UnimplementedError( @@ -228,8 +257,8 @@ abstract class MapboxGlPlatform { 'setSymbolTextIgnorePlacement() has not been implemented.'); } - Future addImageSource(String imageSourceId, Uint8List bytes, - LatLngQuad coordinates) async { + Future addImageSource( + String imageSourceId, Uint8List bytes, LatLngQuad coordinates) async { throw UnimplementedError('addImageSource() has not been implemented.'); } @@ -241,7 +270,8 @@ abstract class MapboxGlPlatform { throw UnimplementedError('addLayer() has not been implemented.'); } - Future addLayerBelow(String imageLayerId, String imageSourceId, String belowLayerId) async { + Future addLayerBelow( + String imageLayerId, String imageSourceId, String belowLayerId) async { throw UnimplementedError('addLayerBelow() has not been implemented.'); } @@ -249,9 +279,8 @@ abstract class MapboxGlPlatform { throw UnimplementedError('removeLayer() has not been implemented.'); } - Future toScreenLocation(LatLng latLng) async{ - throw UnimplementedError( - 'toScreenLocation() has not been implemented.'); + Future toScreenLocation(LatLng latLng) async { + throw UnimplementedError('toScreenLocation() has not been implemented.'); } Future> toScreenLocationBatch(Iterable latLngs) async{ @@ -264,7 +293,7 @@ abstract class MapboxGlPlatform { 'toLatLng() has not been implemented.'); } - Future getMetersPerPixelAtLatitude(double latitude) async{ + Future getMetersPerPixelAtLatitude(double latitude) async { throw UnimplementedError( 'getMetersPerPixelAtLatitude() has not been implemented.'); } 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 0f5e02873..bf5ff4c45 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 @@ -83,7 +83,8 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { final dynamic heading = call.arguments['heading']; if (onUserLocationUpdatedPlatform != null) { onUserLocationUpdatedPlatform(UserLocation( - position: LatLng(userLocation['position'][0], userLocation['position'][1]), + position: LatLng( + userLocation['position'][0], userLocation['position'][1]), altitude: userLocation['altitude'], bearing: userLocation['bearing'], speed: userLocation['speed'], @@ -259,7 +260,7 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { @override Future removeSymbols(Iterable ids) async { await _channel.invokeMethod('symbols#removeAll', { - 'symbols': ids.toList(), + 'ids': ids.toList(), }); } @@ -274,6 +275,27 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { 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', { @@ -302,6 +324,13 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { }); } + @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( @@ -313,6 +342,25 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { 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', { @@ -337,6 +385,13 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { }); } + @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( @@ -348,6 +403,27 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { 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', { @@ -363,6 +439,13 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { }); } + @override + Future removeFills(Iterable ids) async { + await _channel.invokeMethod('fill#removeAll', { + 'ids': ids.toList(), + }); + } + @override Future queryRenderedFeatures( Point point, List layerIds, List filter) async { @@ -515,10 +598,11 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { } @override - Future addImageSource(String imageSourceId, Uint8List bytes, - LatLngQuad coordinates) async { + Future addImageSource( + String imageSourceId, Uint8List bytes, LatLngQuad coordinates) async { try { - return await _channel.invokeMethod('style#addImageSource', { + return await _channel + .invokeMethod('style#addImageSource', { 'imageSourceId': imageSourceId, 'bytes': bytes, 'length': bytes.length, @@ -532,10 +616,10 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { @override Future toScreenLocation(LatLng latLng) async { try { - var screenPosMap = await _channel - .invokeMethod('map#toScreenLocation', { + var screenPosMap = + await _channel.invokeMethod('map#toScreenLocation', { 'latitude': latLng.latitude, - 'longitude':latLng.longitude, + 'longitude': latLng.longitude, }); return Point(screenPosMap['x'], screenPosMap['y']); } on PlatformException catch (e) { @@ -565,14 +649,13 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { @override Future removeImageSource(String imageSourceId) async { try { - return await _channel.invokeMethod('style#removeImageSource', { - 'imageSourceId': imageSourceId - }); + return await _channel.invokeMethod('style#removeImageSource', + {'imageSourceId': imageSourceId}); } on PlatformException catch (e) { return new Future.error(e); } } - + @override Future addLayer(String imageLayerId, String imageSourceId) async { try { @@ -586,9 +669,11 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { } @override - Future addLayerBelow(String imageLayerId, String imageSourceId, String belowLayerId) async { + Future addLayerBelow( + String imageLayerId, String imageSourceId, String belowLayerId) async { try { - return await _channel.invokeMethod('style#addLayerBelow', { + return await _channel + .invokeMethod('style#addLayerBelow', { 'imageLayerId': imageLayerId, 'imageSourceId': imageSourceId, 'belowLayerId': belowLayerId @@ -597,25 +682,24 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { return new Future.error(e); } } - + @override Future removeLayer(String imageLayerId) async { try { - return await _channel.invokeMethod('style#removeLayer', { - 'imageLayerId': imageLayerId - }); + return await _channel.invokeMethod( + 'style#removeLayer', {'imageLayerId': imageLayerId}); } on PlatformException catch (e) { return new Future.error(e); } } - + @override Future toLatLng(Point screenLocation) async { try { - var latLngMap = await _channel - .invokeMethod('map#toLatLng', { + var latLngMap = + await _channel.invokeMethod('map#toLatLng', { 'x': screenLocation.x, - 'y':screenLocation.y, + 'y': screenLocation.y, }); return LatLng(latLngMap['latitude'], latLngMap['longitude']); } on PlatformException catch (e) { @@ -624,7 +708,7 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { } @override - Future getMetersPerPixelAtLatitude(double latitude) async{ + Future getMetersPerPixelAtLatitude(double latitude) async { try { var latLngMap = await _channel .invokeMethod('map#getMetersPerPixelAtLatitude', { @@ -635,5 +719,4 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { return new Future.error(e); } } - }