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..b84f6b6d9 --- /dev/null +++ b/arcgis_map_sdk/lib/src/arcgis_location_display.dart @@ -0,0 +1,69 @@ +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) { + _assertAttached(); + return ArcgisMapPlatform.instance + .updateLocationDisplaySourcePositionManually( + _mapId!, + position, + ); + } +} + +class ArcgisLocationDisplay { + int? _mapId; + final String type = "system"; + + ArcgisLocationDisplay({int? mapId}) : _mapId = mapId; + + void attachToMap(int mapId) => _mapId = mapId; + + void deattachFromMap() => _mapId = null; + + Future startSource() { + _assertAttached(); + return ArcgisMapPlatform.instance.startLocationDisplayDataSource(_mapId!); + } + + Future stopSource() { + _assertAttached(); + return ArcgisMapPlatform.instance.stopLocationDisplayDataSource(_mapId!); + } + + Future setDefaultSymbol(Symbol symbol) { + _assertAttached(); + return ArcgisMapPlatform.instance + .setLocationDisplayDefaultSymbol(_mapId!, symbol); + } + + Future setAccuracySymbol(Symbol symbol) { + _assertAttached(); + return ArcgisMapPlatform.instance + .setLocationDisplayAccuracySymbol(_mapId!, symbol); + } + + Future setPingAnimationSymbol(Symbol symbol) { + _assertAttached(); + return ArcgisMapPlatform.instance + .setLocationDisplayPingAnimationSymbol(_mapId!, symbol); + } + + Future setUseCourseSymbolOnMovement(bool useCourseSymbol) { + _assertAttached(); + return ArcgisMapPlatform.instance + .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 8b84a44d8..117019fe3 100644 --- a/arcgis_map_sdk/lib/src/arcgis_map_controller.dart +++ b/arcgis_map_sdk/lib/src/arcgis_map_controller.dart @@ -1,13 +1,18 @@ +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 ArcgisMapController { ArcgisMapController._({ required this.mapId, - }); + }) : _locationDisplay = ArcgisLocationDisplay(mapId: mapId); final int mapId; + late ArcgisLocationDisplay _locationDisplay; + + ArcgisLocationDisplay get locationDisplay => _locationDisplay; + static Future init( int id, ) async { @@ -249,4 +254,15 @@ class ArcgisMapController { List getVisibleGraphicIds() { return ArcgisMapPlatform.instance.getVisibleGraphicIds(mapId); } + + Future setLocationDisplay(ArcgisLocationDisplay locationDisplay) { + return ArcgisMapPlatform.instance + .setLocationDisplay(mapId, locationDisplay.type) + .whenComplete( + () { + _locationDisplay.deattachFromMap(); + _locationDisplay = locationDisplay..attachToMap(mapId); + }, + ); + } } 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..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 @@ -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,172 @@ 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) + result.success(true) + } + + 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..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) } } } - private 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/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift b/arcgis_map_sdk_ios/ios/Classes/ArcgisMapView.swift index bc376210a..37812edf2 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,20 @@ 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()) - ) - */ - + setMapInteractive(mapOptions.isInteractive) setupMethodChannel() } - + private func setupMethodChannel() { methodChannel.setMethodCallHandler({ [self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in switch (call.method) { @@ -126,12 +119,20 @@ 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_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) + 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)) } }) } - + private func onZoomIn(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let lodFactor = (call.arguments! as! Dictionary)["lodFactor"]! as! Int let currentZoomLevel = getZoomLevel(mapView.mapScale) @@ -144,7 +145,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 +158,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 +230,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 +238,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 +254,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 +267,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 +286,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 +294,105 @@ 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) { + 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 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 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(position) + result(true) + } + + 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: "invalid_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 { + result(FlutterError(code: "missing_data", message: "Invalid arguments", details: nil)) + return + } + let symbol = try GraphicsParser().parseSymbol(args) + handler(symbol) + result(true) + } + catch { + result(FlutterError(code: "unknown_error", message: "Error while adding graphic. \(error)", details: nil)) + } + } } extension AGSBasemapStyle: CaseIterable { 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": 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..0d266a535 --- /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(_ position: UserPosition) { + let loc = AGSLocation( + 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 new file mode 100644 index 000000000..bbf2e5bf1 --- /dev/null +++ b/arcgis_map_sdk_ios/ios/Classes/Models/UserPosition.swift @@ -0,0 +1,15 @@ +// +// 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? + let velocity: 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 552f6c894..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 @@ -210,4 +210,70 @@ 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 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(), + ); + } + + @override + Future setUseCourseSymbolOnMovement(int mapId, bool useCourseSymbol) { + return _methodChannelBuilder(mapId).invokeMethod( + "location_display_set_use_course_symbol_on_move", + 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..4b98639d6 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,15 @@ extension LatLngJsonExtension on LatLng { }; } +extension UserPositionExtension on UserPosition { + Map toMap() => { + 'latLng': latLng.toMap(), + 'accuracy': accuracy, + 'heading': heading, + 'velocity': velocity, + }; +} + 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 7344db61c..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 @@ -202,4 +202,58 @@ 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 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.', + ); + } + + Future setUseCourseSymbolOnMovement(int mapId, bool useCourseSymbol) { + throw UnimplementedError( + '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..3d666b175 --- /dev/null +++ b/arcgis_map_sdk_platform_interface/lib/src/models/user_position.dart @@ -0,0 +1,15 @@ +import 'package:arcgis_map_sdk_platform_interface/arcgis_map_sdk_platform_interface.dart'; + +class UserPosition { + final LatLng latLng; + final double? accuracy; + final double? heading; + final double? velocity; + + const UserPosition({ + required this.latLng, + this.accuracy, + this.velocity, + this.heading, + }); +} 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 @@ + + + + + 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..c9a0df25e --- /dev/null +++ b/example/lib/location_indicator_example_page.dart @@ -0,0 +1,187 @@ +import 'dart:math'; + +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 _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) { + 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: 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: _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"), + ), + 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), + ], + ), + ); + } + + 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: 16, + ); + } + + Future _configureLocationDisplay(MaterialColor color) async { + await _controller!.locationDisplay.setUseCourseSymbolOnMovement( + _useCourseSymbolForMovement, + ); + await _controller!.locationDisplay.setDefaultSymbol( + SimpleMarkerSymbol( + color: color.shade100, + outlineColor: color.shade500, + radius: 24, + ), + ); + await _controller!.locationDisplay.setPingAnimationSymbol( + SimpleMarkerSymbol( + color: color.shade50, + outlineColor: color.shade900, + ), + ); + await _controller!.locationDisplay.setAccuracySymbol( + 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)); + } + } +} 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: