diff --git a/arcgis_map_sdk/README.md b/arcgis_map_sdk/README.md index 613f8c94..d7bce9a3 100644 --- a/arcgis_map_sdk/README.md +++ b/arcgis_map_sdk/README.md @@ -109,6 +109,7 @@ Checkout the example app `example/lib/main.dart` for more details. | toggleBaseMap | ✅ | ✅ | ✅ | | moveCamera | ✅ | ✅ | ✅ | | moveCameraToPoints | | ✅ | ✅ | +| use AutoPanMode | | ✅ | ✅ | | exportImage | | ✅ | ✅ | | zoomIn | ✅ | ✅ | ✅ | | zoomOut | ✅ | ✅ | ✅ | diff --git a/arcgis_map_sdk/lib/src/arcgis_location_display.dart b/arcgis_map_sdk/lib/src/arcgis_location_display.dart index a0a70b6a..3cb5e267 100644 --- a/arcgis_map_sdk/lib/src/arcgis_location_display.dart +++ b/arcgis_map_sdk/lib/src/arcgis_location_display.dart @@ -47,6 +47,26 @@ class ArcgisLocationDisplay { return ArcgisMapPlatform.instance.stopLocationDisplayDataSource(_mapId!); } + void setAutoPanMode(AutoPanMode autoPanMode) { + _assertAttached(); + ArcgisMapPlatform.instance.setAutoPanMode(autoPanMode.name, _mapId!); + } + + Future getAutoPanMode() { + _assertAttached(); + return ArcgisMapPlatform.instance.getAutoPanMode(_mapId!); + } + + void setWanderExtentFactor(double factor) { + _assertAttached(); + return ArcgisMapPlatform.instance.setWanderExtentFactor(factor, _mapId!); + } + + Future getWanderExtentFactor() { + _assertAttached(); + return ArcgisMapPlatform.instance.getWanderExtentFactor(_mapId!); + } + Future setDefaultSymbol(Symbol symbol) { _assertAttached(); return ArcgisMapPlatform.instance diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ArcgisMapView.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ArcgisMapView.kt index f8a23270..d29d61e2 100644 --- a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ArcgisMapView.kt +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ArcgisMapView.kt @@ -26,6 +26,7 @@ import com.esri.arcgisruntime.mapping.Viewpoint import com.esri.arcgisruntime.mapping.view.AnimationCurve import com.esri.arcgisruntime.mapping.view.Graphic import com.esri.arcgisruntime.mapping.view.GraphicsOverlay +import com.esri.arcgisruntime.mapping.view.LocationDisplay.AutoPanMode import com.esri.arcgisruntime.mapping.view.MapView import com.esri.arcgisruntime.symbology.Symbol import com.google.gson.reflect.TypeToken @@ -160,6 +161,10 @@ internal class ArcgisMapView( result ) + "set_auto_pan_mode" -> onSetAutoPanMode(call = call, result = result) + "get_auto_pan_mode" -> onGetAutoPanMode(call = call, result = result) + "set_wander_extent_factor" -> onSetWanderExtentFactor(call = call, result = result) + "get_wander_extent_factor" -> onGetWanderExtentFactor(call = call, result = result) "location_display_set_accuracy_symbol" -> onSetLocationDisplayAccuracySymbol( call, result @@ -267,6 +272,75 @@ internal class ArcgisMapView( } } + private fun onSetAutoPanMode( + call: MethodCall, + result: MethodChannel.Result + ) { + try { + val mode = call.arguments as String? + if (mode == null) { + result.error( + "missing_data", + "Invalid argument, expected an autoPanMode as string", + null, + ) + return + } + val autoPanMode = mode.autoPanModeFromString() + if (autoPanMode != null) { + mapView.locationDisplay.autoPanMode = autoPanMode + result.success(true) + } else { + result.error( + "invalid_data", + "Invalid argument, expected an AutoPanMode but got $mode", + null, + ) + } + } catch (e: Throwable) { + result.finishWithError(e, "Setting AutoPanMode failed.") + } + } + + private fun onGetAutoPanMode( + call: MethodCall, + result: MethodChannel.Result + ) { + try { + return result.success(mapView.locationDisplay.autoPanMode.name) + } catch (e: Throwable) { + result.finishWithError(e, "Getting AutoPanMode failed.") + } + } + + private fun onSetWanderExtentFactor( + call: MethodCall, + result: MethodChannel.Result + ) { + try { + val factor = call.arguments as Double? + if (factor == null) { + result.error( + "missing_data", + "Invalid argument, expected an WanderExtentFactor as Float", + null, + ) + return + } + mapView.locationDisplay.wanderExtentFactor = factor.toFloat() + result.success(true) + } catch (e: Throwable) { + result.finishWithError(e, "Setting WanderExtentFactor failed.") + } + } + + private fun onGetWanderExtentFactor( + call: MethodCall, + result: MethodChannel.Result + ) { + return result.success(mapView.locationDisplay.wanderExtentFactor) + } + private fun onSetLocationDisplayDataSourceType(call: MethodCall, result: MethodChannel.Result) { if (mapView.locationDisplay.locationDataSource.status == LocationDataSource.Status.STARTED) { result.error( @@ -296,7 +370,11 @@ internal class ArcgisMapView( } } - else -> result.error("invalid_data", "Unknown data source type ${call.arguments}", null) + else -> result.error( + "invalid_data", + "Unknown data source type ${call.arguments}", + null, + ) } } @@ -640,3 +718,10 @@ private fun LoadStatusChangedEvent.jsonValue() = when (newLoadStatus) { else -> "unknown" } +private fun String.autoPanModeFromString() = when (this) { + "compassNavigation" -> AutoPanMode.COMPASS_NAVIGATION + "navigation" -> AutoPanMode.NAVIGATION + "recenter" -> AutoPanMode.RECENTER + "off" -> AutoPanMode.OFF + else -> null +} diff --git a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift index 4e116db9..145d011b 100644 --- a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift +++ b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift @@ -88,7 +88,6 @@ class ArcgisMapView: NSObject, FlutterPlatformView { } map.basemap = AGSBasemap(baseLayers: layers, referenceLayers: nil) } - map.minScale = convertZoomLevelToMapScale(mapOptions.minZoom) map.maxScale = convertZoomLevelToMapScale(mapOptions.maxZoom) @@ -160,6 +159,10 @@ class ArcgisMapView: NSObject, FlutterPlatformView { case "location_display_set_data_source_type" : onSetLocationDisplayDataSourceType(call, result) case "update_is_attribution_text_visible": onUpdateIsAttributionTextVisible(call, result) case "export_image" : onExportImage(result) + case "set_auto_pan_mode": onSetAutoPanMode(call, result) + case "get_auto_pan_mode": onGetAutoPanMode(call, result) + case "set_wander_extent_factor": onSetWanderExtentFactor( call, result) + case "get_wander_extent_factor": onGetWanderExtentFactor( call, result) default: result(FlutterError(code: "Unimplemented", message: "No method matching the name \(call.method)", details: nil)) } @@ -459,7 +462,6 @@ class ArcgisMapView: NSObject, FlutterPlatformView { operationWithSymbol(call, result) { mapView.locationDisplay.pingAnimationSymbol = $0 } } - private func onSetLocationDisplayUseCourseSymbolOnMove(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { guard let active = call.arguments as? Bool else { result(FlutterError(code: "missing_data", message: "Invalid arguments.", details: nil)) @@ -486,6 +488,45 @@ class ArcgisMapView: NSObject, FlutterPlatformView { result(true) } + private func onSetAutoPanMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + guard let mode = call.arguments as? String else { + result(FlutterError(code: "missing_data", message: "Invalid argument, expected an AutoPanMode as string.", details: nil)) + return + } + + guard let autoPanMode = mode.autoPanModeFromString() else { + result(FlutterError(code: "invalid_data", message: "Invalid argument, expected an AutoPanMode but got \(mode).", details: nil)) + return + } + + mapView.locationDisplay.autoPanMode = autoPanMode + result(true) + } + + private func onGetAutoPanMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + // autoPanMode.rawValue is any of [0; 3]: + // https://developers.arcgis.com/ios/api-reference/_a_g_s_location_display_8h.html + guard let stringName = mapView.locationDisplay.autoPanMode.toName() else { + result(FlutterError(code: "invalid_data", message: "AutoPanMode has invalid state", details: nil)) + return + } + return result(stringName) + } + + private func onSetWanderExtentFactor(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + guard let factor = call.arguments as? Double else { + result(FlutterError(code: "missing_data", message: "Invalid argument, expected an WanderExtentFactor as Double.", details: nil)) + return + } + + mapView.locationDisplay.wanderExtentFactor = Float(factor) + result(true) + } + + private func onGetWanderExtentFactor(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + return result(mapView.locationDisplay.wanderExtentFactor) + } + private func onSetLocationDisplayDataSourceType(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { if(mapView.locationDisplay.dataSource.status == .started) { result(FlutterError(code: "invalid_state", message: "Current data source is running. Make sure to stop it before setting a new data source", details: nil)) @@ -518,14 +559,14 @@ class ArcgisMapView: NSObject, FlutterPlatformView { mapView.isAttributionTextVisible = isVisible result(true) } - + private func onExportImage(_ result: @escaping FlutterResult) { mapView.exportImage { image, error in if let error = error { result(FlutterError(code: "export_error", message: error.localizedDescription, details: nil)) return } - + if let image = image, let imageData = image.pngData() { result(FlutterStandardTypedData(bytes: imageData)) } else { @@ -727,3 +768,37 @@ extension AGSLoadStatus { } } } + +extension String { + func autoPanModeFromString() -> AGSLocationDisplayAutoPanMode? { + switch self { + case "compassNavigation": + return .compassNavigation + case "navigation": + return .navigation + case "recenter": + return .recenter + case "off": + return .off + default: + return nil + } + } +} + +extension AGSLocationDisplayAutoPanMode { + func toName() -> String? { + switch self { + case .off: + return "off" + case .recenter: + return "recenter" + case .navigation: + return "navigation" + case .compassNavigation: + return "compassNavigation" + @unknown default: + return nil + } + } +} diff --git a/arcgis_map_sdk_ios/ios/Classes/Models/ArcgisMapOptions.swift b/arcgis_map_sdk_ios/ios/Classes/Models/ArcgisMapOptions.swift index 2c49e048..a4165cc5 100644 --- a/arcgis_map_sdk_ios/ios/Classes/Models/ArcgisMapOptions.swift +++ b/arcgis_map_sdk_ios/ios/Classes/Models/ArcgisMapOptions.swift @@ -25,3 +25,4 @@ struct ArcgisMapOptions: Codable { let yMax: Int let isAttributionTextVisible: Bool? } + diff --git a/arcgis_map_sdk_method_channel/lib/src/method_channel_arcgis_map_plugin.dart b/arcgis_map_sdk_method_channel/lib/src/method_channel_arcgis_map_plugin.dart index 1f12134f..ad317a51 100644 --- a/arcgis_map_sdk_method_channel/lib/src/method_channel_arcgis_map_plugin.dart +++ b/arcgis_map_sdk_method_channel/lib/src/method_channel_arcgis_map_plugin.dart @@ -41,6 +41,33 @@ class MethodChannelArcgisMapPlugin extends ArcgisMapPlatform { throw UnimplementedError('setMouseCursor() has not been implemented'); } + @override + void setAutoPanMode(String autoPanMode, int mapId) { + _methodChannelBuilder(mapId).invokeMethod("set_auto_pan_mode", autoPanMode); + } + + @override + Future getAutoPanMode(int mapId) { + return _methodChannelBuilder(mapId) + .invokeMethod("get_auto_pan_mode") + .then((value) => AutoPanMode.values.byName(value!)); + } + + @override + void setWanderExtentFactor(double factor, int mapId) { + _methodChannelBuilder(mapId).invokeMethod( + "set_wander_extent_factor", + factor, + ); + } + + @override + Future getWanderExtentFactor(int mapId) { + return _methodChannelBuilder(mapId) + .invokeMethod("get_wander_extent_factor") + .then((value) => value!); + } + @override void updateGraphicSymbol({ required int mapId, diff --git a/arcgis_map_sdk_platform_interface/lib/arcgis_map_sdk_platform_interface.dart b/arcgis_map_sdk_platform_interface/lib/arcgis_map_sdk_platform_interface.dart index fffaabfd..83b7fd82 100644 --- a/arcgis_map_sdk_platform_interface/lib/arcgis_map_sdk_platform_interface.dart +++ b/arcgis_map_sdk_platform_interface/lib/arcgis_map_sdk_platform_interface.dart @@ -10,6 +10,7 @@ export 'package:arcgis_map_sdk_platform_interface/src/models/user_position.dart' export 'package:arcgis_map_sdk_platform_interface/src/models/view_position.dart'; export 'package:arcgis_map_sdk_platform_interface/src/types/arcgis_map_options.dart'; export 'package:arcgis_map_sdk_platform_interface/src/types/attributes.dart'; +export 'package:arcgis_map_sdk_platform_interface/src/types/auto_pan_mode.dart'; export 'package:arcgis_map_sdk_platform_interface/src/types/bounding_box.dart'; export 'package:arcgis_map_sdk_platform_interface/src/types/default_widget.dart'; export 'package:arcgis_map_sdk_platform_interface/src/types/elevation_mode.dart'; diff --git a/arcgis_map_sdk_platform_interface/lib/src/arcgis_map_sdk_platform_interface.dart b/arcgis_map_sdk_platform_interface/lib/src/arcgis_map_sdk_platform_interface.dart index f78f7bff..fe78fac6 100644 --- a/arcgis_map_sdk_platform_interface/lib/src/arcgis_map_sdk_platform_interface.dart +++ b/arcgis_map_sdk_platform_interface/lib/src/arcgis_map_sdk_platform_interface.dart @@ -68,6 +68,26 @@ class ArcgisMapPlatform extends PlatformInterface { throw UnimplementedError('setMouseCursor() has not been implemented'); } + void setAutoPanMode(String autoPanMode, int mapId) { + throw UnimplementedError('setAutoPanMode() has not been implemented'); + } + + Future getAutoPanMode(int mapId) { + throw UnimplementedError('getAutoPanMode() has not been implemented'); + } + + void setWanderExtentFactor(double factor, int mapId) { + throw UnimplementedError( + 'setWanderExtentFactor() has not been implemented', + ); + } + + Future getWanderExtentFactor(int mapId) { + throw UnimplementedError( + 'getWanderExtentFactor() has not been implemented', + ); + } + void updateGraphicSymbol({ required int mapId, required String layerId, diff --git a/arcgis_map_sdk_platform_interface/lib/src/types/auto_pan_mode.dart b/arcgis_map_sdk_platform_interface/lib/src/types/auto_pan_mode.dart new file mode 100644 index 00000000..5eae6c22 --- /dev/null +++ b/arcgis_map_sdk_platform_interface/lib/src/types/auto_pan_mode.dart @@ -0,0 +1 @@ +enum AutoPanMode { off, compassNavigation, navigation, recenter } diff --git a/example/lib/location_indicator_example_page.dart b/example/lib/location_indicator_example_page.dart index c9a0df25..523f7122 100644 --- a/example/lib/location_indicator_example_page.dart +++ b/example/lib/location_indicator_example_page.dart @@ -31,6 +31,9 @@ class _LocationIndicatorExamplePageState bool _useCourseSymbolForMovement = false; bool _isManualLocationSource = false; + var _activeAutoPanMode = AutoPanMode.off; + var _wanderExtentFactor = 0.5; + @override Widget build(BuildContext context) { return Scaffold( @@ -109,6 +112,10 @@ class _LocationIndicatorExamplePageState : "Enable course indicator", ), ), + ElevatedButton( + onPressed: _openAutoPanModeSelection, + child: Text("Change AutoPanMode-Selection"), + ), SizedBox(height: MediaQuery.paddingOf(context).bottom), ], ), @@ -169,7 +176,9 @@ class _LocationIndicatorExamplePageState Future _simulateLocationChange() async { final display = _controller!.locationDisplay as ArcgisManualLocationDisplay; - await _controller!.moveCamera(point: _mockLocations.first); + if (_activeAutoPanMode == AutoPanMode.off) { + await _controller!.moveCamera(point: _mockLocations.first); + } for (final latLng in _mockLocations) { if (!mounted) break; if (!_isManualLocationSource) break; @@ -179,9 +188,63 @@ class _LocationIndicatorExamplePageState latLng: latLng, accuracy: Random().nextInt(100).toDouble(), velocity: Random().nextInt(100).toDouble(), + heading: Random().nextInt(100).toDouble(), ), ); await Future.delayed(const Duration(milliseconds: 600)); } } + + Future _openAutoPanModeSelection() async { + var newSetWanderExtentFactor = _wanderExtentFactor; + final newSetAutoPanMode = await showModalBottomSheet( + context: context, + builder: (context) { + return Scaffold( + appBar: AppBar(), + body: Column( + children: [ + ...AutoPanMode.values.map( + (e) => CheckboxListTile( + title: Text(e.name), + value: e == _activeAutoPanMode, + onChanged: (active) { + // always pop the tapped value to trigger enabling + Navigator.of(context).pop(e); + }, + ), + ), + if (_activeAutoPanMode == AutoPanMode.recenter) + StatefulBuilder(builder: (context, setState) { + return Column( + children: [ + Slider( + divisions: 10, + value: newSetWanderExtentFactor, + onChanged: (newValue) { + setState(() { + newSetWanderExtentFactor = newValue; + }); + }), + Text("$newSetWanderExtentFactor"), + ], + ); + }) + ], + ), + ); + }, + ); + + if (newSetAutoPanMode != null) { + // No need to use setState + _activeAutoPanMode = newSetAutoPanMode; + _controller?.locationDisplay.setAutoPanMode(newSetAutoPanMode); + } + if (newSetWanderExtentFactor != null && + _activeAutoPanMode == AutoPanMode.recenter) { + _wanderExtentFactor = newSetWanderExtentFactor!; + _controller?.locationDisplay.setWanderExtentFactor(_wanderExtentFactor); + } + } } diff --git a/example/lib/main.dart b/example/lib/main.dart index 741444ee..51f5e92b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -748,13 +748,6 @@ class _ExampleMapState extends State { ], ), ), - if (!kIsWeb) - ElevatedButton( - onPressed: () => _makePolylineVisible( - points: [_firstPinCoordinates, _secondPinCoordinates], - ), - child: const Text('Zoom to polyline'), - ), Row( children: [ const Text(