From f1187f30515fe8d8783d793863b18ae73cce493b Mon Sep 17 00:00:00 2001 From: Julian Bissekkou Date: Sun, 26 Nov 2023 15:09:56 +0100 Subject: [PATCH 1/8] initial draft on iOS --- .../lib/src/arcgis_map_controller.dart | 21 +- .../ios/Classes/ArcgisMapView.swift | 184 +++++++++++------- .../src/method_channel_arcgis_map_plugin.dart | 18 ++ .../arcgis_map_sdk_platform_interface.dart | 18 ++ example/ios/Podfile.lock | 6 + example/ios/Runner/Info.plist | 4 + .../lib/location_indicator_example_page.dart | 68 +++++++ example/lib/main.dart | 15 +- example/pubspec.lock | 80 ++++++++ example/pubspec.yaml | 1 + 10 files changed, 339 insertions(+), 76 deletions(-) create mode 100644 example/lib/location_indicator_example_page.dart diff --git a/arcgis_map_sdk/lib/src/arcgis_map_controller.dart b/arcgis_map_sdk/lib/src/arcgis_map_controller.dart index 8b84a44d8..8d6400a34 100644 --- a/arcgis_map_sdk/lib/src/arcgis_map_controller.dart +++ b/arcgis_map_sdk/lib/src/arcgis_map_controller.dart @@ -1,12 +1,31 @@ import 'package:arcgis_map_sdk_platform_interface/arcgis_map_sdk_platform_interface.dart'; import 'package:flutter/services.dart'; +class ArcgisLocationDisplay { + final int mapId; + + ArcgisLocationDisplay._(this.mapId); + + Future startSource() { + return ArcgisMapPlatform.instance.startLocationDisplayDataSource(mapId); + } + + Future stopSource() { + return ArcgisMapPlatform.instance.stopLocationDisplayDataSource(mapId); + } + + Future setDataSource() { + return ArcgisMapPlatform.instance.setLocationDisplayDataSource(mapId); + } +} + class ArcgisMapController { ArcgisMapController._({ required this.mapId, - }); + }) : locationDisplay = ArcgisLocationDisplay._(mapId); final int mapId; + final ArcgisLocationDisplay locationDisplay; static Future init( int id, diff --git a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift index bc376210a..a7e56f929 100644 --- a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift +++ b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift @@ -2,62 +2,62 @@ import ArcGIS import Foundation class ArcgisMapView: NSObject, FlutterPlatformView { - + private let defaultGraphicsOverlay = AGSGraphicsOverlay() - + private let methodChannel: FlutterMethodChannel private let zoomEventChannel: FlutterEventChannel private let zoomStreamHandler = ZoomStreamHandler() private let centerPositionEventChannel: FlutterEventChannel private let centerPositionStreamHandler = CenterPositionStreamHandler() - + private var mapScaleObservation: NSKeyValueObservation? private var mapVisibleAreaObservation: NSKeyValueObservation? - + private var mapView: AGSMapView private let map = AGSMap() private let graphicsOverlay = AGSGraphicsOverlay() private let userIndicatorGraphic = AGSGraphic() private let pinGraphic = AGSGraphic() private let routeLineGraphic = AGSGraphic() - + private var routeLineGraphics = [AGSGraphic]() - + private var routePoints = Array() - - + + private static let defaultDuration = 0.8 - + func view() -> UIView { return mapView } - + init( - frame: CGRect, - viewIdentifier viewId: Int64, - mapOptions: ArcgisMapOptions, - binaryMessenger messenger: FlutterBinaryMessenger + frame: CGRect, + viewIdentifier viewId: Int64, + mapOptions: ArcgisMapOptions, + binaryMessenger messenger: FlutterBinaryMessenger ) { methodChannel = FlutterMethodChannel( - name: "dev.fluttercommunity.arcgis_map_sdk/\(viewId)", - binaryMessenger: messenger + name: "dev.fluttercommunity.arcgis_map_sdk/\(viewId)", + binaryMessenger: messenger ) zoomEventChannel = FlutterEventChannel( - name: "dev.fluttercommunity.arcgis_map_sdk/\(viewId)/zoom", - binaryMessenger: messenger + name: "dev.fluttercommunity.arcgis_map_sdk/\(viewId)/zoom", + binaryMessenger: messenger ) zoomEventChannel.setStreamHandler(zoomStreamHandler) centerPositionEventChannel = FlutterEventChannel( - name: "dev.fluttercommunity.arcgis_map_sdk/\(viewId)/centerPosition", - binaryMessenger: messenger + name: "dev.fluttercommunity.arcgis_map_sdk/\(viewId)/centerPosition", + binaryMessenger: messenger ) centerPositionEventChannel.setStreamHandler(centerPositionStreamHandler) - + AGSArcGISRuntimeEnvironment.apiKey = mapOptions.apiKey mapView = AGSMapView.init(frame: frame) - + super.init() - + if mapOptions.basemap != nil { map.basemap = AGSBasemap(style: parseBaseMapStyle(mapOptions.basemap!)) } else { @@ -66,13 +66,13 @@ class ArcgisMapView: NSObject, FlutterPlatformView { } map.basemap = AGSBasemap(baseLayers: layers, referenceLayers: nil) } - + map.minScale = getMapScale(mapOptions.minZoom) map.maxScale = getMapScale(mapOptions.maxZoom) - + mapView.map = map mapView.graphicsOverlays.add(defaultGraphicsOverlay) - + mapScaleObservation = mapView.observe(\.mapScale) { [weak self] (map, notifier) in DispatchQueue.main.async { guard let self = self else { @@ -94,27 +94,27 @@ class ArcgisMapView: NSObject, FlutterPlatformView { self.centerPositionStreamHandler.add(center: LatLng(latitude: wgs84Center.y, longitude: wgs84Center.x)) } } - - + + let viewport = AGSViewpoint( - latitude: mapOptions.initialCenter.latitude, - longitude: mapOptions.initialCenter.longitude, - scale: getMapScale(Int(mapOptions.zoom)) + latitude: mapOptions.initialCenter.latitude, + longitude: mapOptions.initialCenter.longitude, + scale: getMapScale(Int(mapOptions.zoom)) ) mapView.setViewpoint(viewport, duration: 0) { _ in } - + /* - map.maxExtent = AGSEnvelope( - min: AGSPoint(x: Double(mapOptions.xMin), y: Double(mapOptions.yMin), spatialReference: .wgs84()), - max: AGSPoint(x: Double(mapOptions.xMin), y: Double(mapOptions.yMax), spatialReference: .wgs84()) - ) - */ - + map.maxExtent = AGSEnvelope( + min: AGSPoint(x: Double(mapOptions.xMin), y: Double(mapOptions.yMin), spatialReference: .wgs84()), + max: AGSPoint(x: Double(mapOptions.xMin), y: Double(mapOptions.yMax), spatialReference: .wgs84()) + ) + */ + setMapInteractive(mapOptions.isInteractive) setupMethodChannel() } - + private func setupMethodChannel() { methodChannel.setMethodCallHandler({ [self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in switch (call.method) { @@ -126,12 +126,15 @@ class ArcgisMapView: NSObject, FlutterPlatformView { case "add_graphic": onAddGraphic(call, result) case "remove_graphic": onRemoveGraphic(call, result) case "toggle_base_map" : onToggleBaseMap(call, result) + case "location_display_start_data_source" : onStartLocationDisplayDataSource(call, result) + case "location_display_stop_data_source" : onStopLocationDisplayDataSource(call, result) + case "location_display_set_data_source" : onSetLocationDisplayDataSource(call, result) default: result(FlutterError(code: "Unimplemented", message: "No method matching the name\(call.method)", details: nil)) } }) } - + private func onZoomIn(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let lodFactor = (call.arguments! as! Dictionary)["lodFactor"]! as! Int let currentZoomLevel = getZoomLevel(mapView.mapScale) @@ -144,7 +147,7 @@ class ArcgisMapView: NSObject, FlutterPlatformView { result(true) } } - + private func onZoomOut(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let lodFactor = (call.arguments! as! Dictionary)["lodFactor"]! as! Int let currentZoomLevel = getZoomLevel(mapView.mapScale) @@ -157,40 +160,40 @@ class ArcgisMapView: NSObject, FlutterPlatformView { result(success) } } - + private func onAddViewPadding(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let dict = call.arguments as! Dictionary let padding: ViewPadding = try! JsonUtil.objectOfJson(dict) - + mapView.contentInset = UIEdgeInsets( - top: padding.top, - left: padding.left, - bottom: padding.bottom, - right: padding.right + top: padding.top, + left: padding.left, + bottom: padding.bottom, + right: padding.right ) - + result(true) } - + private func onMoveCamera(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let dict = call.arguments as! Dictionary let point: LatLng = try! JsonUtil.objectOfJson(dict["point"] as! Dictionary) let zoomLevel = dict["zoomLevel"] as? Int - + let animationDict = dict["animationOptions"] as? Dictionary let animationOptions: AnimationOptions? = animationDict == nil ? nil : try? JsonUtil.objectOfJson(animationDict!) - + let scale = zoomLevel != nil ? getMapScale(zoomLevel!) : mapView.mapScale - + mapView.setViewpoint( - AGSViewpoint(center: point.toAGSPoint(), scale: scale), - duration: (animationOptions?.duration ?? 0) / 1000, - curve: animationOptions?.arcgisAnimationCurve() ?? .linear + AGSViewpoint(center: point.toAGSPoint(), scale: scale), + duration: (animationOptions?.duration ?? 0) / 1000, + curve: animationOptions?.arcgisAnimationCurve() ?? .linear ) { success in result(success) } } - + private func onAddGraphic(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let parser = GraphicsParser() var newGraphics = [AGSGraphic]() @@ -229,7 +232,7 @@ class ArcgisMapView: NSObject, FlutterPlatformView { } result(true) } - + private func onRemoveGraphic(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let graphicId = call.arguments as! String let newGraphics = defaultGraphicsOverlay.graphics.filter({ element in @@ -237,13 +240,13 @@ class ArcgisMapView: NSObject, FlutterPlatformView { let id = graphic.attributes["id"] as? String return id != graphicId }) - + defaultGraphicsOverlay.graphics.removeAllObjects() defaultGraphicsOverlay.graphics.addObjects(from: newGraphics) result(true) } - + private func onToggleBaseMap(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let baseMapString = call.arguments as! String map.basemap = AGSBasemap(style: parseBaseMapStyle(baseMapString)) @@ -253,11 +256,11 @@ class ArcgisMapView: NSObject, FlutterPlatformView { private func onSetInteraction(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let enabled = (call.arguments! as! Dictionary)["enabled"]! as! Bool - + setMapInteractive(enabled) result(true) } - + private func setMapInteractive(_ enabled: Bool) { mapView.interactionOptions.isZoomEnabled = enabled mapView.interactionOptions.isPanEnabled = enabled @@ -266,17 +269,17 @@ class ArcgisMapView: NSObject, FlutterPlatformView { mapView.interactionOptions.isEnabled = enabled // don't set "isMagnifierEnabled" since we don't want to use this feature } - -private func parseBaseMapStyle(_ string: String) -> AGSBasemapStyle { - let baseMapStyle = AGSBasemapStyle.allCases.first { enumValue in - enumValue.getJsonValue() == string - } - if baseMapStyle == nil { - NSLog("Warning: Could not find a base map style matching the input string. Defaulting to .arcGISImageryStandard.") + + private func parseBaseMapStyle(_ string: String) -> AGSBasemapStyle { + let baseMapStyle = AGSBasemapStyle.allCases.first { enumValue in + enumValue.getJsonValue() == string + } + if baseMapStyle == nil { + NSLog("Warning: Could not find a base map style matching the input string. Defaulting to .arcGISImageryStandard.") + } + return baseMapStyle ?? .arcGISImageryStandard } - return baseMapStyle ?? .arcGISImageryStandard -} - + /** * Convert map scale to zoom level * https://developers.arcgis.com/documentation/mapping-apis-and-services/reference/zoom-levels-and-scale/#conversion-tool @@ -285,7 +288,7 @@ private func parseBaseMapStyle(_ string: String) -> AGSBasemapStyle { let result = -1.443 * log(scale) + 29.14 return Int(result.rounded()) } - + /** * Convert zoom level to map scale * https://developers.arcgis.com/documentation/mapping-apis-and-services/reference/zoom-levels-and-scale/#conversion-tool @@ -293,6 +296,43 @@ private func parseBaseMapStyle(_ string: String) -> AGSBasemapStyle { private func getMapScale(_ zoomLevel: Int) -> Double { 591657527 * (exp(-0.693 * Double(zoomLevel))) } + + + private func onStartLocationDisplayDataSource(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + let pointSymbol = AGSSimpleMarkerSymbol(style: .circle, color: UIColor.red, size: 15.0) + pointSymbol.outline = AGSSimpleLineSymbol(style: .solid, color: UIColor.yellow, width: 3.0) + + let accuracySymbol = AGSSimpleFillSymbol(style: .solid, color: UIColor.blue, outline: AGSSimpleLineSymbol(style: .solid, color: UIColor.systemBlue, width: 1.5)) + + let pingAnimationSymbol = AGSSimpleMarkerSymbol(style: .circle, color: UIColor.purple, size: 200) + + mapView.locationDisplay.defaultSymbol = pointSymbol + mapView.locationDisplay.accuracySymbol = accuracySymbol + mapView.locationDisplay.pingAnimationSymbol = pingAnimationSymbol + + mapView.locationDisplay.dataSource.start { error in + if let error = error { + let flutterError = FlutterError( + code: "generic_error", + message: "Failed to start data source: \(error.localizedDescription)", + details: nil + ) + result(flutterError) + } else { + result(true) + } + } + } + + private func onStopLocationDisplayDataSource(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + mapView.locationDisplay.dataSource.stop { + result(true) + } + } + + private func onSetLocationDisplayDataSource(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + result(true) + } } extension AGSBasemapStyle: CaseIterable { 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 552f6c894..c323ba513 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 @@ -210,4 +210,22 @@ class MethodChannelArcgisMapPlugin extends ArcgisMapPlatform { 'getVisibleGraphicIds() has not been implemented.', ); } + + @override + Future startLocationDisplayDataSource(int mapId) { + return _methodChannelBuilder(mapId) + .invokeMethod("location_display_start_data_source"); + } + + @override + Future stopLocationDisplayDataSource(int mapId) { + return _methodChannelBuilder(mapId) + .invokeMethod("location_display_stop_data_source"); + } + + @override + Future setLocationDisplayDataSource(int mapId) { + return _methodChannelBuilder(mapId) + .invokeMethod("location_display_set_data_source"); + } } 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 7344db61c..4b4b3222e 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 @@ -202,4 +202,22 @@ class ArcgisMapPlatform extends PlatformInterface { 'getVisibleGraphicIds() has not been implemented.', ); } + + Future startLocationDisplayDataSource(int mapId) { + throw UnimplementedError( + 'startLocationDisplayDataSource() has not been implemented.', + ); + } + + Future stopLocationDisplayDataSource(int mapId) { + throw UnimplementedError( + 'stopLocationDisplayDataSource() has not been implemented.', + ); + } + + Future setLocationDisplayDataSource(int mapId) { + throw UnimplementedError( + 'setLocationDisplayDataSource() has not been implemented.', + ); + } } diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 390c17b81..90b8570f9 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -6,10 +6,13 @@ PODS: - ArcGIS-Runtime-Toolkit-iOS - Flutter - Flutter (1.0.0) + - geolocator_apple (1.2.0): + - Flutter DEPENDENCIES: - arcgis_map_sdk_ios (from `.symlinks/plugins/arcgis_map_sdk_ios/ios`) - Flutter (from `Flutter`) + - geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`) SPEC REPOS: trunk: @@ -21,12 +24,15 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/arcgis_map_sdk_ios/ios" Flutter: :path: Flutter + geolocator_apple: + :path: ".symlinks/plugins/geolocator_apple/ios" SPEC CHECKSUMS: ArcGIS-Runtime-SDK-iOS: 6ab51d28f8831ac73c00d34998cff3a555fe304f ArcGIS-Runtime-Toolkit-iOS: e30bb45bd0bd0152bcb1ec73f9b99022a5c7d02d arcgis_map_sdk_ios: ee1d8dee42e0c11c95cd26afa2a8cb0e7a69cb23 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + geolocator_apple: cc556e6844d508c95df1e87e3ea6fa4e58c50401 PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 7f553465b..ec0ed6d9b 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -2,6 +2,10 @@ + NSLocationAlwaysUsageDescription + This app requires the location to demonstrate arcgis location apis + NSLocationWhenInUseUsageDescription + This app requires the location to demonstrate arcgis location apis CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName diff --git a/example/lib/location_indicator_example_page.dart b/example/lib/location_indicator_example_page.dart new file mode 100644 index 000000000..1e85b95d0 --- /dev/null +++ b/example/lib/location_indicator_example_page.dart @@ -0,0 +1,68 @@ +import 'package:arcgis_example/main.dart'; +import 'package:arcgis_map_sdk/arcgis_map_sdk.dart'; +import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; + +class LocationIndicatorExamplePage extends StatefulWidget { + const LocationIndicatorExamplePage({super.key}); + + @override + State createState() => + _LocationIndicatorExamplePageState(); +} + +class _LocationIndicatorExamplePageState + extends State { + final _snackBarKey = GlobalKey(); + ArcgisMapController? _controller; + bool _isStarted = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _snackBarKey, + appBar: AppBar(), + floatingActionButton: FloatingActionButton( + child: Icon(_isStarted ? Icons.stop : Icons.play_arrow), + onPressed: () async { + try { + _isStarted + ? await _controller!.locationDisplay.stopSource() + : await _controller!.locationDisplay.startSource(); + } catch (e, stack) { + if (!mounted) return; + ScaffoldMessenger.of(_snackBarKey.currentContext!) + .showSnackBar(SnackBar(content: Text("$e"))); + debugPrint("$e"); + debugPrintStack(stackTrace: stack); + } + + if (!mounted) return; + setState(() => _isStarted = !_isStarted); + }, + ), + body: ArcgisMap( + apiKey: arcGisApiKey, + initialCenter: const LatLng(51.16, 10.45), + zoom: 13, + basemap: BaseMap.arcgisNavigationNight, + mapStyle: MapStyle.twoD, + onMapCreated: (controller) { + _controller = controller; + _requestLocationPermission(); + }, + ), + ); + } + + Future _requestLocationPermission() async { + await Geolocator.requestPermission(); + final location = await Geolocator.getLastKnownPosition(); + if (!mounted || location == null) return; + + await _controller!.moveCamera( + point: LatLng(location.latitude, location.longitude), + zoomLevel: 12, + ); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index f04335778..e35436729 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:core'; +import 'package:arcgis_example/location_indicator_example_page.dart'; import 'package:arcgis_example/map_elements.dart'; import 'package:arcgis_example/vector_layer_example_page.dart'; import 'package:arcgis_map_sdk/arcgis_map_sdk.dart'; @@ -467,11 +468,13 @@ class _ExampleMapState extends State { ), ), ElevatedButton( - onPressed: () { - _routeToVectorLayerMap(); - }, + onPressed: _routeToVectorLayerMap, child: const Text("Show Vector layer example"), ), + ElevatedButton( + onPressed: _routeToLocationIndicatorExample, + child: const Text("Location indicator example"), + ), ElevatedButton( onPressed: () { setState(() { @@ -684,4 +687,10 @@ class _ExampleMapState extends State { MaterialPageRoute(builder: (_) => const VectorLayerExamplePage()), ); } + + void _routeToLocationIndicatorExample() { + Navigator.of(context).push( + MaterialPageRoute(builder: (_) => const LocationIndicatorExamplePage()), + ); + } } diff --git a/example/pubspec.lock b/example/pubspec.lock index be84cb57b..0c7245f75 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -83,6 +83,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" cupertino_icons: dependency: "direct main" description: @@ -114,6 +122,54 @@ packages: description: flutter source: sdk version: "0.0.0" + geolocator: + dependency: "direct main" + description: + name: geolocator + sha256: e946395fc608842bb2f6c914807e9183f86f3cb787f6b8f832753e5251036f02 + url: "https://pub.dev" + source: hosted + version: "10.1.0" + geolocator_android: + dependency: transitive + description: + name: geolocator_android + sha256: "741579fa6c9e412984d2bdb2fbaa54e3c3f7587c60aeacfe6e058358a11f40f8" + url: "https://pub.dev" + source: hosted + version: "4.4.0" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + sha256: ab90ae811c42ec2f6021e01eca71df00dee6ff1e69d2c2dafd4daeb0b793f73d + url: "https://pub.dev" + source: hosted + version: "2.3.2" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + sha256: "6c8d494d6948757c56720b778af742f6973f31fca1f702a7539b8917e4a2468a" + url: "https://pub.dev" + source: hosted + version: "4.2.0" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + sha256: "59083f7e0871b78299918d92bf930a14377f711d2d1156c558cd5ebae6c20d58" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + geolocator_windows: + dependency: transitive + description: + name: geolocator_windows + sha256: a92fae29779d5c6dc60e8411302f5221ade464968fe80a36d330e80a71f087af + url: "https://pub.dev" + source: hosted + version: "0.2.2" intl: dependency: transitive description: @@ -199,6 +255,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -239,6 +303,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: df5a4d8f22ee4ccd77f8839ac7cb274ebc11ef9adcce8b92be14b797fe889921 + url: "https://pub.dev" + source: hosted + version: "4.2.1" vector_math: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 5b58833c3..44bf06e79 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: arcgis_map_sdk: ^0.8.0 cupertino_icons: ^1.0.0 + geolocator: ^10.1.0 flutter: sdk: flutter flutter_web_plugins: From 94cc4a9fbcdaaea26086d5e5d5e1e5de55de42c8 Mon Sep 17 00:00:00 2001 From: Julian Bissekkou Date: Mon, 27 Nov 2023 08:09:35 +0100 Subject: [PATCH 2/8] add symbol methods to controller --- .../lib/src/arcgis_map_controller.dart | 15 +++++++++ .../ios/Classes/ArcgisMapView.swift | 33 +++++++++---------- .../src/method_channel_arcgis_map_plugin.dart | 27 +++++++++++++++ .../arcgis_map_sdk_platform_interface.dart | 21 ++++++++++++ .../lib/location_indicator_example_page.dart | 22 +++++++++++++ 5 files changed, 100 insertions(+), 18 deletions(-) diff --git a/arcgis_map_sdk/lib/src/arcgis_map_controller.dart b/arcgis_map_sdk/lib/src/arcgis_map_controller.dart index 8d6400a34..7c0b870bb 100644 --- a/arcgis_map_sdk/lib/src/arcgis_map_controller.dart +++ b/arcgis_map_sdk/lib/src/arcgis_map_controller.dart @@ -17,6 +17,21 @@ class ArcgisLocationDisplay { Future setDataSource() { return ArcgisMapPlatform.instance.setLocationDisplayDataSource(mapId); } + + Future setDefaultSymbol(Symbol symbol) { + return ArcgisMapPlatform.instance + .setLocationDisplayDefaultSymbol(mapId, symbol); + } + + Future setAccuracySymbol(Symbol symbol) { + return ArcgisMapPlatform.instance + .setLocationDisplayAccuracySymbol(mapId, symbol); + } + + Future setPingAnimationSymbol(Symbol symbol) { + return ArcgisMapPlatform.instance + .setLocationDisplayPingAnimationSymbol(mapId, symbol); + } } class ArcgisMapController { diff --git a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift index a7e56f929..7e62b8d62 100644 --- a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift +++ b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift @@ -104,13 +104,6 @@ class ArcgisMapView: NSObject, FlutterPlatformView { mapView.setViewpoint(viewport, duration: 0) { _ in } - /* - map.maxExtent = AGSEnvelope( - min: AGSPoint(x: Double(mapOptions.xMin), y: Double(mapOptions.yMin), spatialReference: .wgs84()), - max: AGSPoint(x: Double(mapOptions.xMin), y: Double(mapOptions.yMax), spatialReference: .wgs84()) - ) - */ - setMapInteractive(mapOptions.isInteractive) setupMethodChannel() } @@ -129,6 +122,9 @@ class ArcgisMapView: NSObject, FlutterPlatformView { case "location_display_start_data_source" : onStartLocationDisplayDataSource(call, result) case "location_display_stop_data_source" : onStopLocationDisplayDataSource(call, result) case "location_display_set_data_source" : onSetLocationDisplayDataSource(call, result) + case "location_display_set_default_symbol": onSetLocationDisplayDefaultSymbol(call, result) + case "location_display_set_accuracy_symbol": onSetLocationDisplayAccuracySymbol(call, result) + case "location_display_set_ping_animation_symbol" : onSetLocationDisplayPingAnimationSymbol(call, result) default: result(FlutterError(code: "Unimplemented", message: "No method matching the name\(call.method)", details: nil)) } @@ -299,17 +295,6 @@ class ArcgisMapView: NSObject, FlutterPlatformView { private func onStartLocationDisplayDataSource(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - let pointSymbol = AGSSimpleMarkerSymbol(style: .circle, color: UIColor.red, size: 15.0) - pointSymbol.outline = AGSSimpleLineSymbol(style: .solid, color: UIColor.yellow, width: 3.0) - - let accuracySymbol = AGSSimpleFillSymbol(style: .solid, color: UIColor.blue, outline: AGSSimpleLineSymbol(style: .solid, color: UIColor.systemBlue, width: 1.5)) - - let pingAnimationSymbol = AGSSimpleMarkerSymbol(style: .circle, color: UIColor.purple, size: 200) - - mapView.locationDisplay.defaultSymbol = pointSymbol - mapView.locationDisplay.accuracySymbol = accuracySymbol - mapView.locationDisplay.pingAnimationSymbol = pingAnimationSymbol - mapView.locationDisplay.dataSource.start { error in if let error = error { let flutterError = FlutterError( @@ -333,6 +318,18 @@ class ArcgisMapView: NSObject, FlutterPlatformView { private func onSetLocationDisplayDataSource(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { result(true) } + + private func onSetLocationDisplayDefaultSymbol(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + result(true) + } + + private func onSetLocationDisplayAccuracySymbol(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + result(true) + } + + private func onSetLocationDisplayPingAnimationSymbol(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + result(true) + } } extension AGSBasemapStyle: CaseIterable { 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 c323ba513..5f8654846 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 @@ -228,4 +228,31 @@ class MethodChannelArcgisMapPlugin extends ArcgisMapPlatform { return _methodChannelBuilder(mapId) .invokeMethod("location_display_set_data_source"); } + + @override + Future setLocationDisplayDefaultSymbol(int mapId, Symbol symbol) { + return _methodChannelBuilder(mapId).invokeMethod( + "location_display_set_default_symbol", + symbol.toJson(), + ); + } + + @override + Future setLocationDisplayAccuracySymbol(int mapId, Symbol symbol) { + return _methodChannelBuilder(mapId).invokeMethod( + "location_display_set_accuracy_symbol", + symbol.toJson(), + ); + } + + @override + Future setLocationDisplayPingAnimationSymbol( + int mapId, + Symbol symbol, + ) { + return _methodChannelBuilder(mapId).invokeMethod( + "location_display_set_ping_animation_symbol", + symbol.toJson(), + ); + } } 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 4b4b3222e..9f41b7660 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 @@ -220,4 +220,25 @@ class ArcgisMapPlatform extends PlatformInterface { 'setLocationDisplayDataSource() has not been implemented.', ); } + + Future setLocationDisplayDefaultSymbol(int mapId, Symbol symbol) { + throw UnimplementedError( + 'setLocationDisplayDefaultSymbol() has not been implemented.', + ); + } + + Future setLocationDisplayAccuracySymbol(int mapId, Symbol symbol) { + throw UnimplementedError( + 'setLocationDisplayAccuracySymbol() has not been implemented.', + ); + } + + Future setLocationDisplayPingAnimationSymbol( + int mapId, + Symbol symbol, + ) { + throw UnimplementedError( + 'setLocationDisplayPingAnimationSymbol() has not been implemented.', + ); + } } diff --git a/example/lib/location_indicator_example_page.dart b/example/lib/location_indicator_example_page.dart index 1e85b95d0..6751c41eb 100644 --- a/example/lib/location_indicator_example_page.dart +++ b/example/lib/location_indicator_example_page.dart @@ -50,6 +50,7 @@ class _LocationIndicatorExamplePageState onMapCreated: (controller) { _controller = controller; _requestLocationPermission(); + _configureLocationDisplay(); }, ), ); @@ -65,4 +66,25 @@ class _LocationIndicatorExamplePageState zoomLevel: 12, ); } + + Future _configureLocationDisplay() async { + await _controller!.locationDisplay.setDefaultSymbol( + SimpleMarkerSymbol( + color: Colors.pink, + outlineColor: Colors.amberAccent, + ), + ); + await _controller!.locationDisplay.setPingAnimationSymbol( + SimpleMarkerSymbol( + color: Colors.blueAccent, + outlineColor: Colors.blue.withOpacity(0.5), + ), + ); + await _controller!.locationDisplay.setAccuracySymbol( + SimpleMarkerSymbol( + color: Colors.deepPurple, + outlineColor: Colors.deepPurple, + ), + ); + } } From e66d60f317d72b8674853c619fd7f6452f8da7cf Mon Sep 17 00:00:00 2001 From: Julian Bissekkou Date: Mon, 27 Nov 2023 08:29:02 +0100 Subject: [PATCH 3/8] parse symbol --- arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift | 13 +++++++++++++ arcgis_map_sdk_ios/ios/Classes/GraphicsParser.swift | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift index 7e62b8d62..07953e735 100644 --- a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift +++ b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift @@ -320,6 +320,19 @@ class ArcgisMapView: NSObject, FlutterPlatformView { } private func onSetLocationDisplayDefaultSymbol(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + do { + guard let args = call.arguments as? [String: Any] else { + result(FlutterError(code: "missing_data", message: "Invalid arguments", details: nil)) + return + } + let symbol = try GraphicsParser().parseSymbol(args) + mapView.locationDisplay.defaultSymbol = symbol + result(true) + } + catch { + result(FlutterError(code: "unknown_error", message: "Error while adding graphic. \(error)", details: nil)) + return + } result(true) } diff --git a/arcgis_map_sdk_ios/ios/Classes/GraphicsParser.swift b/arcgis_map_sdk_ios/ios/Classes/GraphicsParser.swift index 62aebc2a6..f38f381df 100644 --- a/arcgis_map_sdk_ios/ios/Classes/GraphicsParser.swift +++ b/arcgis_map_sdk_ios/ios/Classes/GraphicsParser.swift @@ -82,7 +82,7 @@ class GraphicsParser { // region symbol parsing - private func parseSymbol(_ dictionary: [String: Any]) throws -> AGSSymbol { + func parseSymbol(_ dictionary: [String: Any]) throws -> AGSSymbol { let type = dictionary["type"] as! String; switch (type) { case "simple-marker": From a5c6a2910e51ff8b2303149fb8676e8190a1d388 Mon Sep 17 00:00:00 2001 From: Julian Bissekkou Date: Mon, 27 Nov 2023 09:30:57 +0100 Subject: [PATCH 4/8] implement useCourseSymbolOnMovement --- .../lib/src/arcgis_map_controller.dart | 5 ++ .../ios/Classes/ArcgisMapView.swift | 36 ++++++--- .../src/method_channel_arcgis_map_plugin.dart | 8 ++ .../arcgis_map_sdk_platform_interface.dart | 6 ++ .../lib/location_indicator_example_page.dart | 75 +++++++++++++------ 5 files changed, 98 insertions(+), 32 deletions(-) diff --git a/arcgis_map_sdk/lib/src/arcgis_map_controller.dart b/arcgis_map_sdk/lib/src/arcgis_map_controller.dart index 7c0b870bb..4cb7b60e5 100644 --- a/arcgis_map_sdk/lib/src/arcgis_map_controller.dart +++ b/arcgis_map_sdk/lib/src/arcgis_map_controller.dart @@ -32,6 +32,11 @@ class ArcgisLocationDisplay { return ArcgisMapPlatform.instance .setLocationDisplayPingAnimationSymbol(mapId, symbol); } + + Future setUseCourseSymbolOnMovement(bool useCourseSymbol) { + return ArcgisMapPlatform.instance + .setUseCourseSymbolOnMovement(mapId, useCourseSymbol); + } } class ArcgisMapController { diff --git a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift index 07953e735..99f4a790f 100644 --- a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift +++ b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift @@ -125,6 +125,7 @@ class ArcgisMapView: NSObject, FlutterPlatformView { case "location_display_set_default_symbol": onSetLocationDisplayDefaultSymbol(call, result) case "location_display_set_accuracy_symbol": onSetLocationDisplayAccuracySymbol(call, result) case "location_display_set_ping_animation_symbol" : onSetLocationDisplayPingAnimationSymbol(call, result) + case "location_display_set_use_course_symbol_on_move" : onSetLocationDisplayUseCourseSymbolOnMove(call, result) default: result(FlutterError(code: "Unimplemented", message: "No method matching the name\(call.method)", details: nil)) } @@ -320,28 +321,41 @@ class ArcgisMapView: NSObject, FlutterPlatformView { } private func onSetLocationDisplayDefaultSymbol(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + operationWithSymbol(call, result) { mapView.locationDisplay.defaultSymbol = $0 } + } + + private func onSetLocationDisplayAccuracySymbol(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + operationWithSymbol(call, result) { mapView.locationDisplay.accuracySymbol = $0 } + } + + private func onSetLocationDisplayPingAnimationSymbol(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + 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)) + return + } + + mapView.locationDisplay.useCourseSymbolOnMovement = active + result(true) + } + + private func operationWithSymbol(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, handler: (AGSSymbol) -> Void) { do { guard let args = call.arguments as? [String: Any] else { result(FlutterError(code: "missing_data", message: "Invalid arguments", details: nil)) return } let symbol = try GraphicsParser().parseSymbol(args) - mapView.locationDisplay.defaultSymbol = symbol + handler(symbol) result(true) } catch { result(FlutterError(code: "unknown_error", message: "Error while adding graphic. \(error)", details: nil)) - return } - result(true) - } - - private func onSetLocationDisplayAccuracySymbol(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - result(true) - } - - private func onSetLocationDisplayPingAnimationSymbol(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - result(true) } } 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 5f8654846..01b42ac41 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 @@ -255,4 +255,12 @@ class MethodChannelArcgisMapPlugin extends ArcgisMapPlatform { symbol.toJson(), ); } + + @override + Future setUseCourseSymbolOnMovement(int mapId, bool useCourseSymbol) { + return _methodChannelBuilder(mapId).invokeMethod( + "location_display_set_use_course_symbol_on_move", + useCourseSymbol, + ); + } } 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 9f41b7660..5c40144bc 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 @@ -241,4 +241,10 @@ class ArcgisMapPlatform extends PlatformInterface { 'setLocationDisplayPingAnimationSymbol() has not been implemented.', ); } + + Future setUseCourseSymbolOnMovement(int mapId, bool useCourseSymbol) { + throw UnimplementedError( + 'setUseCourseSymbolOnMovement() has not been implemented.', + ); + } } diff --git a/example/lib/location_indicator_example_page.dart b/example/lib/location_indicator_example_page.dart index 6751c41eb..60f4f70a8 100644 --- a/example/lib/location_indicator_example_page.dart +++ b/example/lib/location_indicator_example_page.dart @@ -16,6 +16,7 @@ class _LocationIndicatorExamplePageState final _snackBarKey = GlobalKey(); ArcgisMapController? _controller; bool _isStarted = false; + bool _useCourseSymbolForMovement = false; @override Widget build(BuildContext context) { @@ -41,17 +42,48 @@ class _LocationIndicatorExamplePageState setState(() => _isStarted = !_isStarted); }, ), - body: ArcgisMap( - apiKey: arcGisApiKey, - initialCenter: const LatLng(51.16, 10.45), - zoom: 13, - basemap: BaseMap.arcgisNavigationNight, - mapStyle: MapStyle.twoD, - onMapCreated: (controller) { - _controller = controller; - _requestLocationPermission(); - _configureLocationDisplay(); - }, + body: Column( + children: [ + Expanded( + child: ArcgisMap( + apiKey: arcGisApiKey, + initialCenter: const LatLng(51.16, 10.45), + zoom: 13, + basemap: BaseMap.arcgisNavigationNight, + mapStyle: MapStyle.twoD, + onMapCreated: (controller) { + _controller = controller; + _requestLocationPermission(); + _configureLocationDisplay(Colors.blue); + }, + ), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () => _configureLocationDisplay(Colors.green), + child: Text("tint indicator green"), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: () => _configureLocationDisplay(Colors.red), + child: Text("tint indicator red"), + ), + ElevatedButton( + onPressed: () { + setState( + () => + _useCourseSymbolForMovement = !_useCourseSymbolForMovement, + ); + _configureLocationDisplay(Colors.red); + }, + child: Text( + _useCourseSymbolForMovement + ? "Disable course indicator" + : "Enable course indicator", + ), + ), + SizedBox(height: MediaQuery.paddingOf(context).bottom), + ], ), ); } @@ -63,28 +95,29 @@ class _LocationIndicatorExamplePageState await _controller!.moveCamera( point: LatLng(location.latitude, location.longitude), - zoomLevel: 12, + zoomLevel: 16, ); } - Future _configureLocationDisplay() async { + Future _configureLocationDisplay(MaterialColor color) async { + await _controller!.locationDisplay.setUseCourseSymbolOnMovement( + _useCourseSymbolForMovement, + ); await _controller!.locationDisplay.setDefaultSymbol( SimpleMarkerSymbol( - color: Colors.pink, - outlineColor: Colors.amberAccent, + color: color.shade100, + outlineColor: color.shade500, + radius: 24, ), ); await _controller!.locationDisplay.setPingAnimationSymbol( SimpleMarkerSymbol( - color: Colors.blueAccent, - outlineColor: Colors.blue.withOpacity(0.5), + color: color.shade50, + outlineColor: color.shade900, ), ); await _controller!.locationDisplay.setAccuracySymbol( - SimpleMarkerSymbol( - color: Colors.deepPurple, - outlineColor: Colors.deepPurple, - ), + SimpleLineSymbol(color: color.shade800, width: 3), ); } } From c0a6e9b0051b1d2dd474f10e2d0aeef89d90cbab Mon Sep 17 00:00:00 2001 From: Julian Bissekkou Date: Mon, 27 Nov 2023 14:03:36 +0100 Subject: [PATCH 5/8] implement new type of location display data source --- arcgis_map_sdk/lib/arcgis_map_sdk.dart | 1 + .../lib/src/arcgis_location_display.dart | 55 +++++++++++++++++++ .../lib/src/arcgis_map_controller.dart | 52 ++++-------------- .../ios/Classes/ArcgisMapView.swift | 40 ++++++++++++++ .../Classes/ManualLocationDataSource.swift | 22 ++++++++ .../ios/Classes/Models/UserPosition.swift | 14 +++++ .../src/method_channel_arcgis_map_plugin.dart | 19 +++++++ .../lib/src/model_extension.dart | 8 +++ .../arcgis_map_sdk_platform_interface.dart | 1 + .../arcgis_map_sdk_platform_interface.dart | 15 +++++ .../lib/src/models/user_position.dart | 13 +++++ 11 files changed, 200 insertions(+), 40 deletions(-) create mode 100644 arcgis_map_sdk/lib/src/arcgis_location_display.dart create mode 100644 arcgis_map_sdk_ios/ios/Classes/ManualLocationDataSource.swift create mode 100644 arcgis_map_sdk_ios/ios/Classes/Models/UserPosition.swift create mode 100644 arcgis_map_sdk_platform_interface/lib/src/models/user_position.dart diff --git a/arcgis_map_sdk/lib/arcgis_map_sdk.dart b/arcgis_map_sdk/lib/arcgis_map_sdk.dart index c1b43b7d6..de66d2dda 100644 --- a/arcgis_map_sdk/lib/arcgis_map_sdk.dart +++ b/arcgis_map_sdk/lib/arcgis_map_sdk.dart @@ -1,6 +1,7 @@ // ignore: unnecessary_library_directive library arcgis_map; +export 'package:arcgis_map_sdk/src/arcgis_location_display.dart'; export 'package:arcgis_map_sdk/src/arcgis_map_controller.dart'; export 'package:arcgis_map_sdk/src/arcgis_map_sdk.dart'; export 'package:arcgis_map_sdk_platform_interface/arcgis_map_sdk_platform_interface.dart'; diff --git a/arcgis_map_sdk/lib/src/arcgis_location_display.dart b/arcgis_map_sdk/lib/src/arcgis_location_display.dart new file mode 100644 index 000000000..1fdc629b0 --- /dev/null +++ b/arcgis_map_sdk/lib/src/arcgis_location_display.dart @@ -0,0 +1,55 @@ +import 'package:arcgis_map_sdk_platform_interface/arcgis_map_sdk_platform_interface.dart'; + +class ArcgisManualLocationDisplay extends ArcgisLocationDisplay { + @override + String get type => "manual"; + + ArcgisManualLocationDisplay(super.mapId); + + Future updateLocation(UserPosition position) { + return ArcgisMapPlatform.instance + .updateLocationDisplaySourcePositionManually( + mapId, + position, + ); + } +} + +class ArcgisLocationDisplay { + final int mapId; + final String type = "system"; + + ArcgisLocationDisplay(this.mapId); + + Future startSource() { + return ArcgisMapPlatform.instance.startLocationDisplayDataSource(mapId); + } + + Future stopSource() { + return ArcgisMapPlatform.instance.stopLocationDisplayDataSource(mapId); + } + + Future setDataSource() { + return ArcgisMapPlatform.instance.setLocationDisplayDataSource(mapId); + } + + Future setDefaultSymbol(Symbol symbol) { + return ArcgisMapPlatform.instance + .setLocationDisplayDefaultSymbol(mapId, symbol); + } + + Future setAccuracySymbol(Symbol symbol) { + return ArcgisMapPlatform.instance + .setLocationDisplayAccuracySymbol(mapId, symbol); + } + + Future setPingAnimationSymbol(Symbol symbol) { + return ArcgisMapPlatform.instance + .setLocationDisplayPingAnimationSymbol(mapId, symbol); + } + + Future setUseCourseSymbolOnMovement(bool useCourseSymbol) { + return ArcgisMapPlatform.instance + .setUseCourseSymbolOnMovement(mapId, useCourseSymbol); + } +} diff --git a/arcgis_map_sdk/lib/src/arcgis_map_controller.dart b/arcgis_map_sdk/lib/src/arcgis_map_controller.dart index 4cb7b60e5..39635463e 100644 --- a/arcgis_map_sdk/lib/src/arcgis_map_controller.dart +++ b/arcgis_map_sdk/lib/src/arcgis_map_controller.dart @@ -1,51 +1,17 @@ +import 'package:arcgis_map_sdk/src/arcgis_location_display.dart'; import 'package:arcgis_map_sdk_platform_interface/arcgis_map_sdk_platform_interface.dart'; import 'package:flutter/services.dart'; -class ArcgisLocationDisplay { - final int mapId; - - ArcgisLocationDisplay._(this.mapId); - - Future startSource() { - return ArcgisMapPlatform.instance.startLocationDisplayDataSource(mapId); - } - - Future stopSource() { - return ArcgisMapPlatform.instance.stopLocationDisplayDataSource(mapId); - } - - Future setDataSource() { - return ArcgisMapPlatform.instance.setLocationDisplayDataSource(mapId); - } - - Future setDefaultSymbol(Symbol symbol) { - return ArcgisMapPlatform.instance - .setLocationDisplayDefaultSymbol(mapId, symbol); - } - - Future setAccuracySymbol(Symbol symbol) { - return ArcgisMapPlatform.instance - .setLocationDisplayAccuracySymbol(mapId, symbol); - } - - Future setPingAnimationSymbol(Symbol symbol) { - return ArcgisMapPlatform.instance - .setLocationDisplayPingAnimationSymbol(mapId, symbol); - } - - Future setUseCourseSymbolOnMovement(bool useCourseSymbol) { - return ArcgisMapPlatform.instance - .setUseCourseSymbolOnMovement(mapId, useCourseSymbol); - } -} - class ArcgisMapController { ArcgisMapController._({ required this.mapId, - }) : locationDisplay = ArcgisLocationDisplay._(mapId); + }) : _locationDisplay = ArcgisLocationDisplay(mapId); final int mapId; - final ArcgisLocationDisplay locationDisplay; + + late ArcgisLocationDisplay _locationDisplay; + + ArcgisLocationDisplay get locationDisplay => _locationDisplay; static Future init( int id, @@ -288,4 +254,10 @@ class ArcgisMapController { List getVisibleGraphicIds() { return ArcgisMapPlatform.instance.getVisibleGraphicIds(mapId); } + + Future setLocationDisplay(ArcgisLocationDisplay locationDisplay) { + return ArcgisMapPlatform.instance + .setLocationDisplay(mapId, locationDisplay.type) + .whenComplete(() => _locationDisplay = locationDisplay); + } } diff --git a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift index 99f4a790f..dc53f9bb0 100644 --- a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift +++ b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift @@ -126,6 +126,8 @@ class ArcgisMapView: NSObject, FlutterPlatformView { case "location_display_set_accuracy_symbol": onSetLocationDisplayAccuracySymbol(call, result) case "location_display_set_ping_animation_symbol" : onSetLocationDisplayPingAnimationSymbol(call, result) case "location_display_set_use_course_symbol_on_move" : onSetLocationDisplayUseCourseSymbolOnMove(call, result) + case "location_display_update_display_source_position_manually" : onUpdateLocationDisplaySourcePositionManually(call, result) + case "location_display_set_data_source_type" : onSetLocationDisplayDataSourceType(call, result) default: result(FlutterError(code: "Unimplemented", message: "No method matching the name\(call.method)", details: nil)) } @@ -343,6 +345,44 @@ class ArcgisMapView: NSObject, FlutterPlatformView { result(true) } + private func onUpdateLocationDisplaySourcePositionManually(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + let dataSource = mapView.locationDisplay.dataSource + guard let source = dataSource as? ManualLocationDataSource else { + result(FlutterError(code: "invalid_state", message: "Expected ManualLocationDataSource but got \(dataSource)", details: nil)) + return + } + + guard let dict = call.arguments as? Dictionary, let position: UserPosition = try? JsonUtil.objectOfJson(dict) else { + result(FlutterError(code: "missing_data", message: "Expected arguments to contain data of UserPosition.", details: nil)) + return + } + + source.setNewLocation(coordinate: position.latLng, accuracy: position.accuracy, course: position.heading) + } + + 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)) + return + } + + guard let type = call.arguments as? String else { + result(FlutterError(code: "missing_data", message: "Invalid argument, expected a type of data source as string.", details: nil)) + return + } + + switch(type) { + case "manual" : + mapView.locationDisplay.dataSource = ManualLocationDataSource() + result(true) + case "system" : + mapView.locationDisplay.dataSource = AGSCLLocationDataSource() + result(true) + default: + result(FlutterError(code: "missing_data", message: "Unknown data source type \(String(describing: type))", details: nil)) + } + } + private func operationWithSymbol(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, handler: (AGSSymbol) -> Void) { do { guard let args = call.arguments as? [String: Any] else { diff --git a/arcgis_map_sdk_ios/ios/Classes/ManualLocationDataSource.swift b/arcgis_map_sdk_ios/ios/Classes/ManualLocationDataSource.swift new file mode 100644 index 000000000..6ddbee41d --- /dev/null +++ b/arcgis_map_sdk_ios/ios/Classes/ManualLocationDataSource.swift @@ -0,0 +1,22 @@ +// +// ManualLocationDataSource.swift +// arcgis_map_sdk_ios +// +// Created by Julian Bissekkou on 27.11.23. +// + +import Foundation +import ArcGIS + +class ManualLocationDataSource: AGSLocationDataSource { + public func setNewLocation(coordinate: LatLng, accuracy: Double?, course: Double?) { + let loc = AGSLocation( + position: coordinate.toAGSPoint(), + horizontalAccuracy: accuracy ?? 0, + velocity: 0, + course: course ?? 0, + lastKnown: false + ) + didUpdate(loc) + } +} diff --git a/arcgis_map_sdk_ios/ios/Classes/Models/UserPosition.swift b/arcgis_map_sdk_ios/ios/Classes/Models/UserPosition.swift new file mode 100644 index 000000000..aeac0bfd2 --- /dev/null +++ b/arcgis_map_sdk_ios/ios/Classes/Models/UserPosition.swift @@ -0,0 +1,14 @@ +// +// UserPosition.swift +// arcgis_map_sdk_ios +// +// Created by Julian Bissekkou on 27.11.23. +// + +import Foundation + +struct UserPosition: Codable { + let latLng: LatLng + let accuracy: Double? + let heading: Double? +} 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 01b42ac41..7800f06a2 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 @@ -263,4 +263,23 @@ class MethodChannelArcgisMapPlugin extends ArcgisMapPlatform { useCourseSymbol, ); } + + @override + Future updateLocationDisplaySourcePositionManually( + int mapId, + UserPosition position, + ) { + return _methodChannelBuilder(mapId).invokeMethod( + "location_display_update_display_source_position_manually", + position.toMap(), + ); + } + + @override + Future setLocationDisplay(int mapId, String type) { + return _methodChannelBuilder(mapId).invokeMethod( + "location_display_set_data_source_type", + type, + ); + } } diff --git a/arcgis_map_sdk_method_channel/lib/src/model_extension.dart b/arcgis_map_sdk_method_channel/lib/src/model_extension.dart index ac78bc4f1..8fca1914c 100644 --- a/arcgis_map_sdk_method_channel/lib/src/model_extension.dart +++ b/arcgis_map_sdk_method_channel/lib/src/model_extension.dart @@ -20,6 +20,14 @@ extension LatLngJsonExtension on LatLng { }; } +extension UserPositionExtension on UserPosition { + Map toMap() => { + 'latLng': latLng.toMap(), + 'accuracy': accuracy, + 'heading': heading, + }; +} + extension ArcgisMapOptionsJsonExtension on ArcgisMapOptions { Map toMap() { return { 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 984039cc0..fffaabfd5 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 @@ -6,6 +6,7 @@ export 'package:arcgis_map_sdk_platform_interface/src/events/map_event.dart'; export 'package:arcgis_map_sdk_platform_interface/src/models/animation_options.dart'; export 'package:arcgis_map_sdk_platform_interface/src/models/basemap.dart'; export 'package:arcgis_map_sdk_platform_interface/src/models/ground.dart'; +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'; 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 5c40144bc..aa2f9b318 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 @@ -247,4 +247,19 @@ class ArcgisMapPlatform extends PlatformInterface { 'setUseCourseSymbolOnMovement() has not been implemented.', ); } + + Future updateLocationDisplaySourcePositionManually( + int mapId, + UserPosition position, + ) { + throw UnimplementedError( + 'updateLocationDisplaySourcePositionManually() has not been implemented.', + ); + } + + Future setLocationDisplay(int mapId, String type) { + throw UnimplementedError( + 'setLocationDisplay() has not been implemented.', + ); + } } diff --git a/arcgis_map_sdk_platform_interface/lib/src/models/user_position.dart b/arcgis_map_sdk_platform_interface/lib/src/models/user_position.dart new file mode 100644 index 000000000..4c21b88b4 --- /dev/null +++ b/arcgis_map_sdk_platform_interface/lib/src/models/user_position.dart @@ -0,0 +1,13 @@ +import 'package:arcgis_map_sdk_platform_interface/arcgis_map_sdk_platform_interface.dart'; + +class UserPosition { + final LatLng latLng; + final double? accuracy; + final double? heading; + + const UserPosition({ + required this.latLng, + required this.accuracy, + this.heading, + }); +} From df29f97397fba3defe71dfe53b42cc795c176cfc Mon Sep 17 00:00:00 2001 From: Julian Bissekkou Date: Tue, 28 Nov 2023 16:49:29 +0100 Subject: [PATCH 6/8] finish iOS location indicator impl --- .../lib/src/arcgis_location_display.dart | 42 ++++++++---- .../lib/src/arcgis_map_controller.dart | 9 ++- .../ios/Classes/ArcgisMapView.swift | 3 +- .../Classes/ManualLocationDataSource.swift | 10 +-- .../ios/Classes/Models/UserPosition.swift | 1 + .../lib/src/model_extension.dart | 1 + .../lib/src/models/user_position.dart | 4 +- .../lib/location_indicator_example_page.dart | 64 +++++++++++++++++++ 8 files changed, 114 insertions(+), 20 deletions(-) diff --git a/arcgis_map_sdk/lib/src/arcgis_location_display.dart b/arcgis_map_sdk/lib/src/arcgis_location_display.dart index 1fdc629b0..de57a2221 100644 --- a/arcgis_map_sdk/lib/src/arcgis_location_display.dart +++ b/arcgis_map_sdk/lib/src/arcgis_location_display.dart @@ -1,55 +1,75 @@ import 'package:arcgis_map_sdk_platform_interface/arcgis_map_sdk_platform_interface.dart'; +import 'package:flutter/cupertino.dart'; class ArcgisManualLocationDisplay extends ArcgisLocationDisplay { @override String get type => "manual"; - ArcgisManualLocationDisplay(super.mapId); + ArcgisManualLocationDisplay({super.mapId}); Future updateLocation(UserPosition position) { + _assertAttached(); return ArcgisMapPlatform.instance .updateLocationDisplaySourcePositionManually( - mapId, + _mapId!, position, ); } } class ArcgisLocationDisplay { - final int mapId; + int? _mapId; final String type = "system"; - ArcgisLocationDisplay(this.mapId); + ArcgisLocationDisplay({int? mapId}) : _mapId = mapId; + + void attachToMap(int mapId) => _mapId = mapId; + + void deattachFromMap() => _mapId = null; Future startSource() { - return ArcgisMapPlatform.instance.startLocationDisplayDataSource(mapId); + _assertAttached(); + return ArcgisMapPlatform.instance.startLocationDisplayDataSource(_mapId!); } Future stopSource() { - return ArcgisMapPlatform.instance.stopLocationDisplayDataSource(mapId); + _assertAttached(); + return ArcgisMapPlatform.instance.stopLocationDisplayDataSource(_mapId!); } Future setDataSource() { - return ArcgisMapPlatform.instance.setLocationDisplayDataSource(mapId); + _assertAttached(); + return ArcgisMapPlatform.instance.setLocationDisplayDataSource(_mapId!); } Future setDefaultSymbol(Symbol symbol) { + _assertAttached(); return ArcgisMapPlatform.instance - .setLocationDisplayDefaultSymbol(mapId, symbol); + .setLocationDisplayDefaultSymbol(_mapId!, symbol); } Future setAccuracySymbol(Symbol symbol) { + _assertAttached(); return ArcgisMapPlatform.instance - .setLocationDisplayAccuracySymbol(mapId, symbol); + .setLocationDisplayAccuracySymbol(_mapId!, symbol); } Future setPingAnimationSymbol(Symbol symbol) { + _assertAttached(); return ArcgisMapPlatform.instance - .setLocationDisplayPingAnimationSymbol(mapId, symbol); + .setLocationDisplayPingAnimationSymbol(_mapId!, symbol); } Future setUseCourseSymbolOnMovement(bool useCourseSymbol) { + _assertAttached(); return ArcgisMapPlatform.instance - .setUseCourseSymbolOnMovement(mapId, useCourseSymbol); + .setUseCourseSymbolOnMovement(_mapId!, useCourseSymbol); + } + + void _assertAttached() { + assert( + _mapId != null, + "LocationDisplay has not been attached to any map. Make sure to call ArcgisMapController.setLocationDisplay.", + ); } } diff --git a/arcgis_map_sdk/lib/src/arcgis_map_controller.dart b/arcgis_map_sdk/lib/src/arcgis_map_controller.dart index 39635463e..117019fe3 100644 --- a/arcgis_map_sdk/lib/src/arcgis_map_controller.dart +++ b/arcgis_map_sdk/lib/src/arcgis_map_controller.dart @@ -5,7 +5,7 @@ import 'package:flutter/services.dart'; class ArcgisMapController { ArcgisMapController._({ required this.mapId, - }) : _locationDisplay = ArcgisLocationDisplay(mapId); + }) : _locationDisplay = ArcgisLocationDisplay(mapId: mapId); final int mapId; @@ -258,6 +258,11 @@ class ArcgisMapController { Future setLocationDisplay(ArcgisLocationDisplay locationDisplay) { return ArcgisMapPlatform.instance .setLocationDisplay(mapId, locationDisplay.type) - .whenComplete(() => _locationDisplay = locationDisplay); + .whenComplete( + () { + _locationDisplay.deattachFromMap(); + _locationDisplay = locationDisplay..attachToMap(mapId); + }, + ); } } diff --git a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift index dc53f9bb0..5944fdb85 100644 --- a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift +++ b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift @@ -357,7 +357,8 @@ class ArcgisMapView: NSObject, FlutterPlatformView { return } - source.setNewLocation(coordinate: position.latLng, accuracy: position.accuracy, course: position.heading) + source.setNewLocation(position) + result(true) } private func onSetLocationDisplayDataSourceType(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { diff --git a/arcgis_map_sdk_ios/ios/Classes/ManualLocationDataSource.swift b/arcgis_map_sdk_ios/ios/Classes/ManualLocationDataSource.swift index 6ddbee41d..0d266a535 100644 --- a/arcgis_map_sdk_ios/ios/Classes/ManualLocationDataSource.swift +++ b/arcgis_map_sdk_ios/ios/Classes/ManualLocationDataSource.swift @@ -9,12 +9,12 @@ import Foundation import ArcGIS class ManualLocationDataSource: AGSLocationDataSource { - public func setNewLocation(coordinate: LatLng, accuracy: Double?, course: Double?) { + public func setNewLocation(_ position: UserPosition) { let loc = AGSLocation( - position: coordinate.toAGSPoint(), - horizontalAccuracy: accuracy ?? 0, - velocity: 0, - course: course ?? 0, + position: position.latLng.toAGSPoint(), + horizontalAccuracy: position.accuracy ?? 0, + velocity: position.velocity ?? 0, + course: position.heading ?? 0, lastKnown: false ) didUpdate(loc) diff --git a/arcgis_map_sdk_ios/ios/Classes/Models/UserPosition.swift b/arcgis_map_sdk_ios/ios/Classes/Models/UserPosition.swift index aeac0bfd2..bbf2e5bf1 100644 --- a/arcgis_map_sdk_ios/ios/Classes/Models/UserPosition.swift +++ b/arcgis_map_sdk_ios/ios/Classes/Models/UserPosition.swift @@ -11,4 +11,5 @@ struct UserPosition: Codable { let latLng: LatLng let accuracy: Double? let heading: Double? + let velocity: Double? } diff --git a/arcgis_map_sdk_method_channel/lib/src/model_extension.dart b/arcgis_map_sdk_method_channel/lib/src/model_extension.dart index 8fca1914c..4b98639d6 100644 --- a/arcgis_map_sdk_method_channel/lib/src/model_extension.dart +++ b/arcgis_map_sdk_method_channel/lib/src/model_extension.dart @@ -25,6 +25,7 @@ extension UserPositionExtension on UserPosition { 'latLng': latLng.toMap(), 'accuracy': accuracy, 'heading': heading, + 'velocity': velocity, }; } diff --git a/arcgis_map_sdk_platform_interface/lib/src/models/user_position.dart b/arcgis_map_sdk_platform_interface/lib/src/models/user_position.dart index 4c21b88b4..3d666b175 100644 --- a/arcgis_map_sdk_platform_interface/lib/src/models/user_position.dart +++ b/arcgis_map_sdk_platform_interface/lib/src/models/user_position.dart @@ -4,10 +4,12 @@ class UserPosition { final LatLng latLng; final double? accuracy; final double? heading; + final double? velocity; const UserPosition({ required this.latLng, - required this.accuracy, + this.accuracy, + this.velocity, this.heading, }); } diff --git a/example/lib/location_indicator_example_page.dart b/example/lib/location_indicator_example_page.dart index 60f4f70a8..c9a0df25e 100644 --- a/example/lib/location_indicator_example_page.dart +++ b/example/lib/location_indicator_example_page.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:arcgis_example/main.dart'; import 'package:arcgis_map_sdk/arcgis_map_sdk.dart'; import 'package:flutter/material.dart'; @@ -13,10 +15,21 @@ class LocationIndicatorExamplePage extends StatefulWidget { class _LocationIndicatorExamplePageState extends State { + final _mockLocations = [ + LatLng(48.1234963, 11.5910182), + LatLng(48.1239241, 11.45897063), + LatLng(48.123876, 11.590120), + LatLng(48.123876, 11.590120), + LatLng(48.123740, 11.589015), + LatLng(48.123164, 11.588585), + LatLng(48.1234963, 11.5910182), + ]; + final _snackBarKey = GlobalKey(); ArcgisMapController? _controller; bool _isStarted = false; bool _useCourseSymbolForMovement = false; + bool _isManualLocationSource = false; @override Widget build(BuildContext context) { @@ -59,6 +72,20 @@ class _LocationIndicatorExamplePageState ), ), const SizedBox(height: 16), + ElevatedButton( + onPressed: _switchLocationSource, + child: Text( + _isManualLocationSource + ? "Use auto location source" + : "Use manual location source", + ), + ), + if (_isManualLocationSource) ...[ + ElevatedButton( + onPressed: _simulateLocationChange, + child: Text("simulate location change"), + ), + ], ElevatedButton( onPressed: () => _configureLocationDisplay(Colors.green), child: Text("tint indicator green"), @@ -120,4 +147,41 @@ class _LocationIndicatorExamplePageState SimpleLineSymbol(color: color.shade800, width: 3), ); } + + Future _switchLocationSource() async { + await _controller!.locationDisplay.stopSource(); + await _controller!.setLocationDisplay( + _isManualLocationSource + ? ArcgisLocationDisplay() + : ArcgisManualLocationDisplay(), + ); + setState(() => _isManualLocationSource = !_isManualLocationSource); + + if (!_isManualLocationSource) { + final location = await Geolocator.getLastKnownPosition(); + if (location == null) return; + await _controller!.moveCamera( + point: LatLng(location.latitude, location.longitude), + ); + } + } + + Future _simulateLocationChange() async { + final display = _controller!.locationDisplay as ArcgisManualLocationDisplay; + + await _controller!.moveCamera(point: _mockLocations.first); + for (final latLng in _mockLocations) { + if (!mounted) break; + if (!_isManualLocationSource) break; + + await display.updateLocation( + UserPosition( + latLng: latLng, + accuracy: Random().nextInt(100).toDouble(), + velocity: Random().nextInt(100).toDouble(), + ), + ); + await Future.delayed(const Duration(milliseconds: 600)); + } + } } From eca8c59f2421986ccd7a04dd277b42cfbfc91862 Mon Sep 17 00:00:00 2001 From: Julian Bissekkou Date: Wed, 29 Nov 2023 13:51:22 +0100 Subject: [PATCH 7/8] implement android --- .../lib/src/arcgis_location_display.dart | 6 - .../arcgis_map_sdk_android/ArcgisMapView.kt | 174 +++++++++++++++++- .../ManualLocationDataSource.kt | 27 +++ .../model/UserPosition.kt | 8 + .../model/ViewPadding.kt | 4 +- .../util/GraphicsParser.kt | 2 +- .../ios/Classes/ArcgisMapView.swift | 7 +- .../src/method_channel_arcgis_map_plugin.dart | 6 - .../arcgis_map_sdk_platform_interface.dart | 6 - 9 files changed, 204 insertions(+), 36 deletions(-) create mode 100644 arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ManualLocationDataSource.kt create mode 100644 arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/UserPosition.kt diff --git a/arcgis_map_sdk/lib/src/arcgis_location_display.dart b/arcgis_map_sdk/lib/src/arcgis_location_display.dart index de57a2221..b84f6b6d9 100644 --- a/arcgis_map_sdk/lib/src/arcgis_location_display.dart +++ b/arcgis_map_sdk/lib/src/arcgis_location_display.dart @@ -1,5 +1,4 @@ import 'package:arcgis_map_sdk_platform_interface/arcgis_map_sdk_platform_interface.dart'; -import 'package:flutter/cupertino.dart'; class ArcgisManualLocationDisplay extends ArcgisLocationDisplay { @override @@ -37,11 +36,6 @@ class ArcgisLocationDisplay { return ArcgisMapPlatform.instance.stopLocationDisplayDataSource(_mapId!); } - Future setDataSource() { - _assertAttached(); - return ArcgisMapPlatform.instance.setLocationDisplayDataSource(_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 c7086c526..92fd5f6c6 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 @@ -8,6 +8,8 @@ import com.esri.arcgisruntime.geometry.GeometryEngine import com.esri.arcgisruntime.geometry.Point import com.esri.arcgisruntime.geometry.SpatialReferences import com.esri.arcgisruntime.layers.ArcGISVectorTiledLayer +import com.esri.arcgisruntime.location.AndroidLocationDataSource +import com.esri.arcgisruntime.location.LocationDataSource import com.esri.arcgisruntime.mapping.ArcGISMap import com.esri.arcgisruntime.mapping.Basemap import com.esri.arcgisruntime.mapping.BasemapStyle @@ -16,10 +18,12 @@ 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.MapView +import com.esri.arcgisruntime.symbology.Symbol import com.google.gson.reflect.TypeToken import dev.fluttercommunity.arcgis_map_sdk_android.model.AnimationOptions import dev.fluttercommunity.arcgis_map_sdk_android.model.ArcgisMapOptions import dev.fluttercommunity.arcgis_map_sdk_android.model.LatLng +import dev.fluttercommunity.arcgis_map_sdk_android.model.UserPosition import dev.fluttercommunity.arcgis_map_sdk_android.model.ViewPadding import dev.fluttercommunity.arcgis_map_sdk_android.util.GraphicsParser import io.flutter.plugin.common.BinaryMessenger @@ -37,7 +41,7 @@ import kotlin.math.roundToInt * A starting point for documentation can be found here: https://developers.arcgis.com/android/maps-2d/tutorials/display-a-map/ * */ internal class ArcgisMapView( - context: Context, + private val context: Context, private val viewId: Int, private val binaryMessenger: BinaryMessenger, private val mapOptions: ArcgisMapOptions, @@ -109,19 +113,171 @@ internal class ArcgisMapView( private fun setupMethodChannel() { methodChannel.setMethodCallHandler { call, result -> when (call.method) { - "zoom_in" -> onZoomIn(call = call, result = result) - "zoom_out" -> onZoomOut(call = call, result = result) - "add_view_padding" -> onAddViewPadding(call = call, result = result) - "set_interaction" -> onSetInteraction(call = call, result = result) - "move_camera" -> onMoveCamera(call = call, result = result) - "add_graphic" -> onAddGraphic(call = call, result = result) - "remove_graphic" -> onRemoveGraphic(call = call, result = result) - "toggle_base_map" -> onToggleBaseMap(call = call, result = result) + "zoom_in" -> onZoomIn(call, result) + "zoom_out" -> onZoomOut(call, result) + "add_view_padding" -> onAddViewPadding(call, result) + "set_interaction" -> onSetInteraction(call, result) + "move_camera" -> onMoveCamera(call, result) + "add_graphic" -> onAddGraphic(call, result) + "remove_graphic" -> onRemoveGraphic(call, result) + "toggle_base_map" -> onToggleBaseMap(call, result) + "location_display_start_data_source" -> onStartLocationDisplayDataSource(result) + "location_display_stop_data_source" -> onStopLocationDisplayDataSource(result) + "location_display_set_default_symbol" -> onSetLocationDisplayDefaultSymbol( + call, + result + ) + + "location_display_set_accuracy_symbol" -> onSetLocationDisplayAccuracySymbol( + call, + result + ) + + "location_display_set_ping_animation_symbol" -> onSetLocationDisplayPingAnimationSymbol( + call, + result + ) + + "location_display_set_use_course_symbol_on_move" -> onSetLocationDisplayUseCourseSymbolOnMove( + call, + result + ) + + "location_display_update_display_source_position_manually" -> onUpdateLocationDisplaySourcePositionManually( + call, + result + ) + + "location_display_set_data_source_type" -> onSetLocationDisplayDataSourceType( + call, + result + ) + else -> result.notImplemented() } } } + private fun onStartLocationDisplayDataSource(result: MethodChannel.Result) { + val future = mapView.locationDisplay.locationDataSource.startAsync() + future.addDoneListener { + try { + result.success(future.get()) + } catch (e: Exception) { + result.error("Error", e.message, null) + } + } + } + + private fun onStopLocationDisplayDataSource(result: MethodChannel.Result) { + val future = mapView.locationDisplay.locationDataSource.stopAsync() + future.addDoneListener { + try { + result.success(future.get()) + } catch (e: Exception) { + result.error("Error", e.message, null) + } + } + } + + private fun onSetLocationDisplayDefaultSymbol(call: MethodCall, result: MethodChannel.Result) { + operationWithSymbol(call, result) { symbol -> + mapView.locationDisplay.defaultSymbol = symbol + } + } + + private fun onSetLocationDisplayAccuracySymbol(call: MethodCall, result: MethodChannel.Result) { + operationWithSymbol(call, result) { symbol -> + mapView.locationDisplay.accuracySymbol = symbol + } + } + + private fun onSetLocationDisplayPingAnimationSymbol( + call: MethodCall, + result: MethodChannel.Result + ) { + operationWithSymbol(call, result) { symbol -> + mapView.locationDisplay.pingAnimationSymbol = symbol + } + } + + private fun onSetLocationDisplayUseCourseSymbolOnMove( + call: MethodCall, + result: MethodChannel.Result + ) { + val active = call.arguments as? Boolean + if (active == null) { + result.error("missing_data", "Invalid arguments.", null) + return + } + + mapView.locationDisplay.isUseCourseSymbolOnMovement = active + result.success(true) + } + + private fun onUpdateLocationDisplaySourcePositionManually( + call: MethodCall, + result: MethodChannel.Result + ) { + val dataSource = + mapView.locationDisplay.locationDataSource as? ManualLocationDisplayDataSource + if (dataSource == null) { + result.error( + "invalid_state", + "Expected ManualLocationDataSource but got $dataSource", + null + ) + return + } + + val optionParams = call.arguments as Map + val position = optionParams.parseToClass() + + dataSource.setNewLocation(position) + } + + private fun onSetLocationDisplayDataSourceType(call: MethodCall, result: MethodChannel.Result) { + if (mapView.locationDisplay.locationDataSource.status == LocationDataSource.Status.STARTED) { + result.error( + "invalid_state", + "Current data source is running. Make sure to stop it before setting a new data source", + null + ) + return + } + + when (call.arguments as String) { + "manual" -> { + mapView.locationDisplay.locationDataSource = ManualLocationDisplayDataSource() + result.success(true) + } + + "system" -> { + mapView.locationDisplay.locationDataSource = AndroidLocationDataSource(context) + result.success(true) + } + + else -> result.error("invalid_data", "Unknown data source type ${call.arguments}", null) + } + } + + + private fun operationWithSymbol( + call: MethodCall, + result: MethodChannel.Result, + function: (Symbol) -> Unit + ) { + try { + val map = call.arguments as Map + val symbol = GraphicsParser.parseSymbol(map) + function(symbol) + result.success(true) + } catch (e: Throwable) { + result.error("unknown_error", "Error while adding graphic. $e)", null) + return + } + } + private fun setupEventChannel() { zoomStreamHandler = ZoomStreamHandler() centerPositionStreamHandler = CenterPositionStreamHandler() diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ManualLocationDataSource.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ManualLocationDataSource.kt new file mode 100644 index 000000000..293b24bea --- /dev/null +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ManualLocationDataSource.kt @@ -0,0 +1,27 @@ +package dev.fluttercommunity.arcgis_map_sdk_android + +import com.esri.arcgisruntime.location.LocationDataSource +import dev.fluttercommunity.arcgis_map_sdk_android.model.UserPosition +import dev.fluttercommunity.arcgis_map_sdk_android.model.toAGSPoint + +class ManualLocationDisplayDataSource : LocationDataSource() { + + override fun onStart() { + this.onStartCompleted(null) + } + + override fun onStop() { + + } + + fun setNewLocation(userPosition: UserPosition) { + val loc = Location( + userPosition.latLng.toAGSPoint(), + userPosition.accuracy ?: 0.0, + userPosition.velocity ?: 0.0, + userPosition.heading ?: 0.0, + false + ) + updateLocation(loc) + } +} diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/UserPosition.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/UserPosition.kt new file mode 100644 index 000000000..31931e4bc --- /dev/null +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/UserPosition.kt @@ -0,0 +1,8 @@ +package dev.fluttercommunity.arcgis_map_sdk_android.model + +data class UserPosition( + val latLng: LatLng, + val accuracy: Double?, + val heading: Double?, + val velocity: Double? +) diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/ViewPadding.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/ViewPadding.kt index 2d14a0885..b40099743 100644 --- a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/ViewPadding.kt +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/ViewPadding.kt @@ -1,8 +1,8 @@ package dev.fluttercommunity.arcgis_map_sdk_android.model -class ViewPadding( +data class ViewPadding( val left: Double, val top: Double, val right: Double, val bottom: Double, -) \ No newline at end of file +) diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/util/GraphicsParser.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/util/GraphicsParser.kt index 08eaf2af2..a6fa8e23c 100644 --- a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/util/GraphicsParser.kt +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/util/GraphicsParser.kt @@ -89,7 +89,7 @@ class GraphicsParser { } } - private fun parseSymbol(map: Map): Symbol { + fun parseSymbol(map: Map): Symbol { val symbolMap = map["symbol"] as Map val symbol = when (val type = symbolMap["type"]) { diff --git a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift index 5944fdb85..37812edf2 100644 --- a/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift +++ b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift @@ -121,7 +121,6 @@ class ArcgisMapView: NSObject, FlutterPlatformView { case "toggle_base_map" : onToggleBaseMap(call, result) case "location_display_start_data_source" : onStartLocationDisplayDataSource(call, result) case "location_display_stop_data_source" : onStopLocationDisplayDataSource(call, result) - case "location_display_set_data_source" : onSetLocationDisplayDataSource(call, result) case "location_display_set_default_symbol": onSetLocationDisplayDefaultSymbol(call, result) case "location_display_set_accuracy_symbol": onSetLocationDisplayAccuracySymbol(call, result) case "location_display_set_ping_animation_symbol" : onSetLocationDisplayPingAnimationSymbol(call, result) @@ -318,10 +317,6 @@ class ArcgisMapView: NSObject, FlutterPlatformView { } } - private func onSetLocationDisplayDataSource(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - result(true) - } - private func onSetLocationDisplayDefaultSymbol(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { operationWithSymbol(call, result) { mapView.locationDisplay.defaultSymbol = $0 } } @@ -380,7 +375,7 @@ class ArcgisMapView: NSObject, FlutterPlatformView { mapView.locationDisplay.dataSource = AGSCLLocationDataSource() result(true) default: - result(FlutterError(code: "missing_data", message: "Unknown data source type \(String(describing: type))", details: nil)) + result(FlutterError(code: "invalid_data", message: "Unknown data source type \(String(describing: type))", details: nil)) } } 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 7800f06a2..196d25102 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 @@ -223,12 +223,6 @@ class MethodChannelArcgisMapPlugin extends ArcgisMapPlatform { .invokeMethod("location_display_stop_data_source"); } - @override - Future setLocationDisplayDataSource(int mapId) { - return _methodChannelBuilder(mapId) - .invokeMethod("location_display_set_data_source"); - } - @override Future setLocationDisplayDefaultSymbol(int mapId, Symbol symbol) { return _methodChannelBuilder(mapId).invokeMethod( 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 aa2f9b318..ff1871cc1 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 @@ -215,12 +215,6 @@ class ArcgisMapPlatform extends PlatformInterface { ); } - Future setLocationDisplayDataSource(int mapId) { - throw UnimplementedError( - 'setLocationDisplayDataSource() has not been implemented.', - ); - } - Future setLocationDisplayDefaultSymbol(int mapId, Symbol symbol) { throw UnimplementedError( 'setLocationDisplayDefaultSymbol() has not been implemented.', From 36d8a3aaa8e6e3828ec4abe980f9dd935c4c1643 Mon Sep 17 00:00:00 2001 From: Julian Bissekkou Date: Thu, 30 Nov 2023 14:08:23 +0100 Subject: [PATCH 8/8] fix bug for manual location source on android --- .../arcgis_map_sdk_android/ArcgisMapView.kt | 1 + .../arcgis_map_sdk_android/util/GraphicsParser.kt | 13 +++++++------ example/android/app/src/main/AndroidManifest.xml | 4 ++++ 3 files changed, 12 insertions(+), 6 deletions(-) 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 92fd5f6c6..919a9c901 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 @@ -234,6 +234,7 @@ internal class ArcgisMapView( val position = optionParams.parseToClass() dataSource.setNewLocation(position) + result.success(true) } private fun onSetLocationDisplayDataSourceType(call: MethodCall, result: MethodChannel.Result) { diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/util/GraphicsParser.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/util/GraphicsParser.kt index a6fa8e23c..cff99e2e3 100644 --- a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/util/GraphicsParser.kt +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/util/GraphicsParser.kt @@ -47,10 +47,11 @@ class GraphicsParser { private fun parsePoint(map: Map): List { val point = (map["point"] as Map).parseToClass() + val symbolMap = map["symbol"] as Map val pointGraphic = Graphic().apply { geometry = point.toAGSPoint() - symbol = parseSymbol(map) + symbol = parseSymbol(symbolMap) } return listOf(pointGraphic) @@ -58,6 +59,7 @@ class GraphicsParser { private fun parsePolyline(map: Map): List { val points = parseToClass>>>(map["paths"]!!) + val symbolMap = map["symbol"] as Map return points.map { subPoints -> Graphic().apply { @@ -72,26 +74,25 @@ class GraphicsParser { if (z != null) Point(x, y, z, SpatialReferences.getWgs84()) else Point(x, y, SpatialReferences.getWgs84()) })) - symbol = parseSymbol(map) + symbol = parseSymbol(symbolMap) } } } private fun parsePolygon(map: Map): List { val rings = parseToClass>>>(map["rings"]!!) + val symbolMap = map["symbol"] as Map return rings.map { ring -> Graphic().apply { geometry = Polygon(PointCollection(ring.map { LatLng(it[0], it[1]).toAGSPoint() })) - symbol = parseSymbol(map) + symbol = parseSymbol(symbolMap) } } } - fun parseSymbol(map: Map): Symbol { - val symbolMap = map["symbol"] as Map - + fun parseSymbol(symbolMap: Map): Symbol { val symbol = when (val type = symbolMap["type"]) { "simple-marker" -> parseSimpleMarkerSymbol(symbolMap) "picture-marker" -> parsePictureMarkerSymbol(symbolMap) diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 3f41384db..5d3c10d75 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,9 @@ + + + +