diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index 28653478e..23150152e 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -60,6 +60,7 @@ import com.mapbox.mapboxsdk.plugins.annotation.Line; import com.mapbox.mapboxsdk.plugins.annotation.LineManager; import com.mapbox.geojson.Feature; +import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions; import com.mapbox.mapboxsdk.style.expressions.Expression; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; @@ -226,17 +227,6 @@ private CameraPosition getCameraPosition() { return trackCameraPosition ? mapboxMap.getCameraPosition() : null; } - private SymbolBuilder newSymbolBuilder() { - return new SymbolBuilder(symbolManager); - } - - private void removeSymbol(String symbolId) { - final SymbolController symbolController = symbols.remove(symbolId); - if (symbolController != null) { - symbolController.remove(symbolManager); - } - } - private SymbolController symbol(String symbolId) { final SymbolController symbol = symbols.get(symbolId); if (symbol == null) { @@ -556,18 +546,44 @@ public void onError(@NonNull String message) { }); break; } - case "symbol#add": { - final SymbolBuilder symbolBuilder = newSymbolBuilder(); - Convert.interpretSymbolOptions(call.argument("options"), symbolBuilder); - final Symbol symbol = symbolBuilder.build(); - final String symbolId = String.valueOf(symbol.getId()); - symbols.put(symbolId, new SymbolController(symbol, true, this)); - result.success(symbolId); + case "symbols#addAll": { + 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, true, this)); + } + } + } + result.success(newSymbolIds); break; } - case "symbol#remove": { - final String symbolId = call.argument("symbol"); - removeSymbol(symbolId); + case "symbols#removeAll": { + final ArrayList symbolIds = call.argument("symbols"); + 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; } diff --git a/android/src/main/java/com/mapbox/mapboxgl/SymbolBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/SymbolBuilder.java index d0c4cf01b..5f0583138 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/SymbolBuilder.java +++ b/android/src/main/java/com/mapbox/mapboxgl/SymbolBuilder.java @@ -13,17 +13,15 @@ import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions; class SymbolBuilder implements SymbolOptionsSink { - private final SymbolManager symbolManager; private final SymbolOptions symbolOptions; private static boolean customImage; - SymbolBuilder(SymbolManager symbolManager) { - this.symbolManager = symbolManager; + SymbolBuilder() { this.symbolOptions = new SymbolOptions(); } - Symbol build() { - return symbolManager.create(symbolOptions); + public SymbolOptions getSymbolOptions(){ + return this.symbolOptions; } @Override diff --git a/android/src/main/java/com/mapbox/mapboxgl/SymbolController.java b/android/src/main/java/com/mapbox/mapboxgl/SymbolController.java index 5b94d52d9..bd101032f 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/SymbolController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/SymbolController.java @@ -34,6 +34,10 @@ boolean onTap() { return consumeTapEvents; } + public Symbol getSymbol(){ + return this.symbol; + } + void remove(SymbolManager symbolManager) { symbolManager.delete(symbol); } diff --git a/example/lib/place_symbol.dart b/example/lib/place_symbol.dart index 64f972582..20d9d2264 100644 --- a/example/lib/place_symbol.dart +++ b/example/lib/place_symbol.dart @@ -89,18 +89,50 @@ class PlaceSymbolBodyState extends State { } void _add(String iconImage) { - controller.addSymbol( - SymbolOptions( - geometry: LatLng( - center.latitude + sin(_symbolCount * pi / 6.0) / 20.0, - center.longitude + cos(_symbolCount * pi / 6.0) / 20.0, - ), - iconImage: iconImage, + List availableNumbers = Iterable.generate(12).toList(); + controller.symbols.forEach( + (s) => availableNumbers.removeWhere((i) => i == s.data['count']) + ); + if (availableNumbers.isNotEmpty) { + controller.addSymbol( + _getSymbolOptions(iconImage, availableNumbers.first), + {'count': availableNumbers.first} + ); + setState(() { + _symbolCount += 1; + }); + } + } + + SymbolOptions _getSymbolOptions(String iconImage, int symbolCount){ + return SymbolOptions( + geometry: LatLng( + center.latitude + sin(symbolCount * pi / 6.0) / 20.0, + center.longitude + cos(symbolCount * pi / 6.0) / 20.0, ), + iconImage: iconImage, ); - setState(() { - _symbolCount += 1; - }); + } + + Future _addAll(String iconImage) async { + List symbolsToAddNumbers = Iterable.generate(12).toList(); + controller.symbols.forEach( + (s) => symbolsToAddNumbers.removeWhere((i) => i == s.data['count']) + ); + + if (symbolsToAddNumbers.isNotEmpty) { + final List symbolOptionsList = symbolsToAddNumbers.map( + (i) => _getSymbolOptions(iconImage, i) + ).toList(); + controller.addSymbols( + symbolOptionsList, + symbolsToAddNumbers.map((i) => {'count': i}).toList() + ); + + setState(() { + _symbolCount += symbolOptionsList.length; + }); + } } void _remove() { @@ -111,6 +143,14 @@ class PlaceSymbolBodyState extends State { }); } + void _removeAll() { + controller.removeSymbols(controller.symbols); + setState(() { + _selectedSymbol = null; + _symbolCount = 0; + }); + } + void _changePosition() { final LatLng current = _selectedSymbol.options.geometry; final Offset offset = Offset( @@ -257,6 +297,11 @@ class PlaceSymbolBodyState extends State { onPressed: () => (_symbolCount == 12) ? null : _add("airport-15"), ), + FlatButton( + child: const Text('add all'), + onPressed: () => + (_symbolCount == 12) ? null : _addAll("airport-15"), + ), FlatButton( child: const Text('add (custom icon)'), onPressed: () => (_symbolCount == 12) @@ -271,6 +316,10 @@ class PlaceSymbolBodyState extends State { child: Text('${_iconAllowOverlap ? 'disable' : 'enable'} icon overlap'), onPressed: _changeIconOverlap, ), + FlatButton( + child: const Text('remove all'), + onPressed: (_symbolCount == 0) ? null : _removeAll, + ), FlatButton( child: const Text('add (asset image)'), onPressed: () => (_symbolCount == 12) diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index 9d8707dc7..babd0df66 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -151,23 +151,22 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma mapView.setCamera(camera, animated: true) } result(nil) - case "symbol#add": + case "symbols#addAll": guard let symbolAnnotationController = symbolAnnotationController 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 symbol. - let coordinate = CLLocationCoordinate2DMake(geometry[0], geometry[1]) - let symbol = MGLSymbolStyleAnnotation(coordinate: coordinate) - Convert.interpretSymbolOptions(options: arguments["options"], delegate: symbol) - // Load icon image from asset if an icon name is supplied. - if let iconImage = options["iconImage"] as? String { - addIconImageToMap(iconImageName: iconImage) + + if let options = arguments["options"] as? [[String: Any]] { + var symbols: [MGLSymbolStyleAnnotation] = []; + for o in options { + if let symbol = getSymbolForOptions(options: o) { + symbols.append(symbol) + } } - symbolAnnotationController.addStyleAnnotation(symbol) - result(symbol.identifier) + if !symbols.isEmpty { + symbolAnnotationController.addStyleAnnotations(symbols) + } + + result(symbols.map { $0.identifier }) } else { result(nil) } @@ -189,17 +188,18 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } } result(nil) - case "symbol#remove": + case "symbols#removeAll": 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 } + guard let symbolIds = arguments["symbols"] as? [String] else { return } + var symbols: [MGLSymbolStyleAnnotation] = []; for symbol in symbolAnnotationController.styleAnnotations(){ - if symbol.identifier == symbolId { - symbolAnnotationController.removeStyleAnnotation(symbol) - break; + if symbolIds.contains(symbol.identifier) { + symbols.append(symbol as! MGLSymbolStyleAnnotation) } } + symbolAnnotationController.removeStyleAnnotations(symbols) result(nil) case "symbol#getGeometry": guard let symbolAnnotationController = symbolAnnotationController else { return } @@ -327,7 +327,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma 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]? = nil for line in lineAnnotationController.styleAnnotations() { if line.identifier == lineId { @@ -358,6 +358,22 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } } + 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 self.mapView.style?.image(forName: iconImageName) == nil { diff --git a/lib/src/controller.dart b/lib/src/controller.dart index cac4fa8d9..bee93b804 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -321,13 +321,21 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes with the added symbol once listeners have /// been notified. Future addSymbol(SymbolOptions options, [Map data]) async { - final SymbolOptions effectiveOptions = - SymbolOptions.defaultOptions.copyWith(options); - final symbol = - await MapboxGlPlatform.getInstance(_id).addSymbol(effectiveOptions); - _symbols[symbol.id] = symbol; + List result = await addSymbols([options], [data]); + + return result.first; + } + + + Future> addSymbols(List options, [List data]) async { + final List effectiveOptions = options.map( + (o) => SymbolOptions.defaultOptions.copyWith(o) + ).toList(); + + final symbols = await MapboxGlPlatform.getInstance(_id).addSymbols(effectiveOptions, data); + symbols.forEach((s) => _symbols[s.id] = s); notifyListeners(); - return symbol; + return symbols; } /// Updates the specified [symbol] with the given [changes]. The symbol must @@ -368,7 +376,17 @@ class MapboxMapController extends ChangeNotifier { Future removeSymbol(Symbol symbol) async { assert(symbol != null); assert(_symbols[symbol.id] == symbol); - await _removeSymbol(symbol.id); + await _removeSymbols([symbol.id]); + notifyListeners(); + } + + Future removeSymbols(Iterable symbols) async { + assert(symbols.length > 0); + symbols.forEach((s) { + assert(_symbols[s.id] == s); + }); + + await _removeSymbols(symbols.map((s) => s.id)); notifyListeners(); } @@ -381,9 +399,7 @@ class MapboxMapController extends ChangeNotifier { Future clearSymbols() async { assert(_symbols != null); final List symbolIds = List.from(_symbols.keys); - for (String id in symbolIds) { - await _removeSymbol(id); - } + _removeSymbols(symbolIds); notifyListeners(); } @@ -392,9 +408,9 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once the symbol has been removed from /// [_symbols]. - Future _removeSymbol(String id) async { - await MapboxGlPlatform.getInstance(_id).removeSymbol(id); - _symbols.remove(id); + Future _removeSymbols(Iterable ids) async { + await MapboxGlPlatform.getInstance(_id).removeSymbols(ids); + _symbols.removeWhere((k, s) => ids.contains(k)); } /// Adds a line to the map, configured using the specified custom [options]. 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 a51266baf..b55d11870 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 @@ -111,16 +111,16 @@ abstract class MapboxGlPlatform { Future getTelemetryEnabled() async { throw UnimplementedError('getTelemetryEnabled() has not been implemented.'); } - - Future addSymbol(SymbolOptions options, [Map data]) async { - throw UnimplementedError('addSymbol() 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 removeSymbol(String symbolId) async { + Future removeSymbols(Iterable symbolsIds) async { throw UnimplementedError('removeSymbol() 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 52a179136..71698c199 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 @@ -182,14 +182,25 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { } @override - Future addSymbol(SymbolOptions options, [Map data]) async { - final String symbolId = await _channel.invokeMethod( - 'symbol#add', + Future> addSymbols(List options, [List data]) async { + final List symbolIds = await _channel.invokeMethod( + 'symbols#addAll', { - 'options': options.toJson(), + 'options': options.map((o) => o.toJson()).toList(), }, ); - return Symbol(symbolId, options, data); + 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 @@ -212,9 +223,9 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { } @override - Future removeSymbol(String symbolId) async { - await _channel.invokeMethod('symbol#remove', { - 'symbol': symbolId, + Future removeSymbols(Iterable ids) async { + await _channel.invokeMethod('symbols#removeAll', { + 'symbols': ids.toList(), }); } diff --git a/mapbox_gl_web/lib/src/feature_manager/feature_manager.dart b/mapbox_gl_web/lib/src/feature_manager/feature_manager.dart index e1899644b..34ebe17fe 100644 --- a/mapbox_gl_web/lib/src/feature_manager/feature_manager.dart +++ b/mapbox_gl_web/lib/src/feature_manager/feature_manager.dart @@ -39,13 +39,27 @@ abstract class FeatureManager { return '${feature.id}'; } + void updateFeature(Feature feature) { - _features['${feature.id}'] = feature; - _updateSource(); + updateFeatures([feature]); } + + void updateFeatures(Iterable features) { + features.forEach( + (feature) => _features['${feature.id}'] = feature + ); + _updateSource(); + } + void remove(String featureId) { - _features.remove(featureId); + removeAll([featureId]); + } + + void removeAll(Iterable featuresIds) { + featuresIds.forEach( + (featureId) => _features.remove(featureId) + ); _updateSource(); } diff --git a/mapbox_gl_web/lib/src/feature_manager/symbol_manager.dart b/mapbox_gl_web/lib/src/feature_manager/symbol_manager.dart index a73ecc60a..7ed68a210 100644 --- a/mapbox_gl_web/lib/src/feature_manager/symbol_manager.dart +++ b/mapbox_gl_web/lib/src/feature_manager/symbol_manager.dart @@ -76,9 +76,21 @@ class SymbolManager extends FeatureManager { @override void update(String lineId, SymbolOptions changes) { - Feature olfFeature = getFeature(lineId); - Feature newFeature = Convert.interpretSymbolOptions(changes, olfFeature); - updateFeature(newFeature); + updateAll({lineId: changes}); + } + + + void updateAll(Map changesById) { + List featuresWithUpdatedOptions = []; + changesById.forEach( + (id, options) => featuresWithUpdatedOptions.add( + Convert.interpretSymbolOptions( + options, + getFeature(id) + ) + ) + ); + updateFeatures(featuresWithUpdatedOptions); } @override diff --git a/mapbox_gl_web/lib/src/mapbox_map_controller.dart b/mapbox_gl_web/lib/src/mapbox_map_controller.dart index 5a2a55ed2..67f72ff73 100644 --- a/mapbox_gl_web/lib/src/mapbox_map_controller.dart +++ b/mapbox_gl_web/lib/src/mapbox_map_controller.dart @@ -136,15 +136,31 @@ class MapboxMapController extends MapboxGlPlatform } @override - Future addSymbol(SymbolOptions options, [Map data]) async { - String symbolId = symbolManager.add(Feature( - geometry: Geometry( - type: 'Point', - coordinates: [options.geometry.longitude, options.geometry.latitude], + Future> addSymbols(List options, [List data]) async { + Map optionsById = Map.fromIterable( + options, + key: (o) => symbolManager.add( + Feature( + geometry: Geometry( + type: 'Point', + coordinates: [o.geometry.longitude, o.geometry.latitude], + ), + ) ), - )); - symbolManager.update(symbolId, options); - return Symbol(symbolId, options, data); + value: (o) => 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 @@ -153,8 +169,8 @@ class MapboxMapController extends MapboxGlPlatform } @override - Future removeSymbol(String symbolId) async { - symbolManager.remove(symbolId); + Future removeSymbols(Iterable symbolsIds) async { + symbolManager.removeAll(symbolsIds); } @override