diff --git a/example/lib/example_compass.dart b/example/lib/example_compass.dart index 5cf6ba3..1964759 100644 --- a/example/lib/example_compass.dart +++ b/example/lib/example_compass.dart @@ -16,6 +16,7 @@ import 'package:arcgis_maps/arcgis_maps.dart'; import 'package:arcgis_maps_toolkit_example/example_compass_custom.dart'; +import 'package:arcgis_maps_toolkit_example/example_compass_local_scene.dart'; import 'package:arcgis_maps_toolkit_example/example_compass_map.dart'; import 'package:arcgis_maps_toolkit_example/example_compass_scene.dart'; import 'package:flutter/material.dart'; @@ -54,6 +55,11 @@ enum CompassExample { 'Example of compass used with a scene. Default styling.', ExampleCompassScene.new, ), + compassLocalScene( + 'Compass Local Scene', + 'Example of compass used with a local scene. Default styling.', + ExampleCompassLocalScene.new, + ), compassCustom( 'Compass Custom', 'Example of compass used with a map with custom styling.', diff --git a/example/lib/example_compass_local_scene.dart b/example/lib/example_compass_local_scene.dart new file mode 100644 index 0000000..f38fc90 --- /dev/null +++ b/example/lib/example_compass_local_scene.dart @@ -0,0 +1,116 @@ +// +// Copyright 2025 Esri +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import 'package:arcgis_maps/arcgis_maps.dart'; +import 'package:arcgis_maps_toolkit/arcgis_maps_toolkit.dart'; +import 'package:flutter/material.dart'; + +void main() { + // Supply your apiKey using the --dart-define-from-file command line argument. + const apiKey = String.fromEnvironment('API_KEY'); + // Alternatively, replace the above line with the following and hard-code your apiKey here: + // const apiKey = ''; // Your API Key here. + if (apiKey.isEmpty) { + throw Exception('apiKey undefined'); + } else { + ArcGISEnvironment.apiKey = apiKey; + } + + runApp(const MaterialApp(home: ExampleCompassLocalScene())); +} + +class ExampleCompassLocalScene extends StatefulWidget { + const ExampleCompassLocalScene({super.key}); + + @override + State createState() => + _ExampleCompassLocalSceneState(); +} + +class _ExampleCompassLocalSceneState extends State { + // Create a local scene view controller. + final _localSceneViewController = ArcGISLocalSceneView.createController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Compass Local Scene')), + body: Stack( + children: [ + // Add a local scene view to the widget tree and set a controller. + ArcGISLocalSceneView( + controllerProvider: () => _localSceneViewController, + onLocalSceneViewReady: onLocalSceneViewReady, + ), + // Create a compass and display on top of the local scene view in a stack. + // Pass the compass the corresponding local scene view controller. + Compass(controllerProvider: () => _localSceneViewController), + ], + ), + ); + } + + void onLocalSceneViewReady() { + // Create a scene with a topographic basemap and a local scene viewing mode. + final scene = ArcGISScene.withBasemapStyle( + BasemapStyle.arcGISTopographic, + viewingMode: SceneViewingMode.local, + ); + + // Create the 3d scene layer. + final sceneLayer = ArcGISSceneLayer.withUri( + Uri.parse( + 'https://www.arcgis.com/home/item.html?id=61da8dc1a7bc4eea901c20ffb3f8b7af', + ), + ); + + // Add world elevation source to the scene's surface. + final elevationSource = ArcGISTiledElevationSource.withUri( + Uri.parse( + 'https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer', + ), + ); + scene.baseSurface.elevationSources.add(elevationSource); + + // Add the scene layer to the scene's operational layers. + scene.operationalLayers.add(sceneLayer); + + // Set the scene's initial viewpoint. + final camera = Camera.withLocation( + location: ArcGISPoint( + x: 19455578.6821, + y: -5056336.2227, + z: 1699.3366, + spatialReference: SpatialReference.webMercator, + ), + heading: 338.7410, + pitch: 40.3763, + roll: 0, + ); + scene.initialViewpoint = Viewpoint.withPointScaleCamera( + center: ArcGISPoint( + x: 19455026.8116, + y: -5054995.7415, + spatialReference: SpatialReference.webMercator, + ), + scale: 8314.6991, + camera: camera, + ); + + // Apply the scene to the local scene view controller. + _localSceneViewController.arcGISScene = scene; + } +} diff --git a/example/lib/example_overview_map.dart b/example/lib/example_overview_map.dart index 43ec7ac..a364b2e 100644 --- a/example/lib/example_overview_map.dart +++ b/example/lib/example_overview_map.dart @@ -15,6 +15,7 @@ // import 'package:arcgis_maps/arcgis_maps.dart'; +import 'package:arcgis_maps_toolkit_example/example_overview_map_with_local_scene.dart'; import 'package:arcgis_maps_toolkit_example/example_overview_map_with_map.dart'; import 'package:arcgis_maps_toolkit_example/example_overview_map_with_scene.dart'; import 'package:flutter/material.dart'; @@ -52,6 +53,11 @@ enum OverviewMapExample { 'Overview Map with scene', 'Example of overview map with scene.', ExampleOverviewMapWithScene.new, + ), + overviewMapWithLocalScene( + 'Overview Map with local scene', + 'Example of overview map with local scene.', + ExampleOverviewMapWithLocalScene.new, ); const OverviewMapExample(this.title, this.subtitle, this.constructor); diff --git a/example/lib/example_overview_map_with_local_scene.dart b/example/lib/example_overview_map_with_local_scene.dart new file mode 100644 index 0000000..72c9fa1 --- /dev/null +++ b/example/lib/example_overview_map_with_local_scene.dart @@ -0,0 +1,120 @@ +// +// Copyright 2025 Esri +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import 'package:arcgis_maps/arcgis_maps.dart'; +import 'package:arcgis_maps_toolkit/arcgis_maps_toolkit.dart'; +import 'package:flutter/material.dart'; + +void main() { + // Supply your apiKey using the --dart-define-from-file command line argument. + const apiKey = String.fromEnvironment('API_KEY'); + // Alternatively, replace the above line with the following and hard-code your apiKey here: + // const apiKey = ''; // Your API Key here. + if (apiKey.isEmpty) { + throw Exception('apiKey undefined'); + } else { + ArcGISEnvironment.apiKey = apiKey; + } + + runApp(const MaterialApp(home: ExampleOverviewMapWithLocalScene())); +} + +class ExampleOverviewMapWithLocalScene extends StatefulWidget { + const ExampleOverviewMapWithLocalScene({super.key}); + + @override + State createState() => + _ExampleOverviewMapWithLocalSceneState(); +} + +class _ExampleOverviewMapWithLocalSceneState + extends State { + // Create a local scene view controller. + final _localSceneViewController = ArcGISLocalSceneView.createController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('OverviewMap with Local Scene')), + body: Stack( + children: [ + // Add a local scene view to the widget tree and set a controller. + ArcGISLocalSceneView( + controllerProvider: () => _localSceneViewController, + onLocalSceneViewReady: onLocalSceneViewReady, + ), + // Create an overview map and display on top of the local scene view in a stack. + // Pass the overview map the corresponding local scene view controller. + OverviewMap( + controllerProvider: () => _localSceneViewController, + map: ArcGISMap.withBasemapStyle(BasemapStyle.arcGISTopographic), + ), + ], + ), + ); + } + + void onLocalSceneViewReady() { + // Create a scene with a topographic basemap and a local scene viewing mode. + final scene = ArcGISScene.withBasemapStyle( + BasemapStyle.arcGISTopographic, + viewingMode: SceneViewingMode.local, + ); + + // Create the 3d scene layer. + final sceneLayer = ArcGISSceneLayer.withUri( + Uri.parse( + 'https://www.arcgis.com/home/item.html?id=61da8dc1a7bc4eea901c20ffb3f8b7af', + ), + ); + + // Add world elevation source to the scene's surface. + final elevationSource = ArcGISTiledElevationSource.withUri( + Uri.parse( + 'https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer', + ), + ); + scene.baseSurface.elevationSources.add(elevationSource); + + // Add the scene layer to the scene's operational layers. + scene.operationalLayers.add(sceneLayer); + + // Set the scene's initial viewpoint. + final camera = Camera.withLocation( + location: ArcGISPoint( + x: 19455578.6821, + y: -5056336.2227, + z: 1699.3366, + spatialReference: SpatialReference.webMercator, + ), + heading: 338.7410, + pitch: 40.3763, + roll: 0, + ); + scene.initialViewpoint = Viewpoint.withPointScaleCamera( + center: ArcGISPoint( + x: 19455026.8116, + y: -5054995.7415, + spatialReference: SpatialReference.webMercator, + ), + scale: 8314.6991, + camera: camera, + ); + + // Apply the scene to the local scene view controller. + _localSceneViewController.arcGISScene = scene; + } +} diff --git a/example/lib/example_overview_map_with_map.dart b/example/lib/example_overview_map_with_map.dart index cc72186..fe87038 100644 --- a/example/lib/example_overview_map_with_map.dart +++ b/example/lib/example_overview_map_with_map.dart @@ -57,7 +57,7 @@ class _ExampleOverviewMapWithMapState extends State { ), // Create an overview map and display on top of the map view in a stack. // Pass the overview map the corresponding map view controller. - OverviewMap.withMapView(controllerProvider: () => _mapViewController), + OverviewMap(controllerProvider: () => _mapViewController), ], ), ); diff --git a/example/lib/example_overview_map_with_scene.dart b/example/lib/example_overview_map_with_scene.dart index 6c94e30..5fc453e 100644 --- a/example/lib/example_overview_map_with_scene.dart +++ b/example/lib/example_overview_map_with_scene.dart @@ -58,7 +58,7 @@ class _ExampleOverviewMapWithSceneState ), // Create an overview map and display on top of the scene view in a stack. // Pass the overview map the corresponding scene view controller. - OverviewMap.withSceneView( + OverviewMap( controllerProvider: () => _sceneViewController, map: ArcGISMap.withBasemapStyle(BasemapStyle.arcGISImageryStandard), ), diff --git a/lib/src/compass/compass.dart b/lib/src/compass/compass.dart index e15ca39..2e3f4af 100644 --- a/lib/src/compass/compass.dart +++ b/lib/src/compass/compass.dart @@ -17,7 +17,7 @@ part of '../../arcgis_maps_toolkit.dart'; /// A [Compass] (also known as a "north arrow") is a widget that visualizes the -/// current rotation of an [ArcGISMapView] or [ArcGISSceneView]. +/// current rotation of an [ArcGISMapView], [ArcGISSceneView], or [ArcGISLocalSceneView]. /// /// # Overview /// @@ -27,8 +27,8 @@ part of '../../arcgis_maps_toolkit.dart'; /// * Will reset the map/scene rotation to North when tapped on. /// /// ## Usage -/// A [Compass] is generally placed in a [Stack] on top of an [ArcGISMapView] or [ArcGISSceneView]. -/// The compass must be provided the same [ArcGISMapViewController] or [ArcGISSceneViewController] as the corresponding map view or scene view. +/// A [Compass] is generally placed in a [Stack] on top of an [ArcGISMapView], [ArcGISSceneView], or [ArcGISLocalSceneView]. +/// The compass must be provided the same [ArcGISMapViewController], [ArcGISSceneViewController], or [ArcGISLocalSceneViewController] as the corresponding map view, scene view, or local scene view. /// ```dart /// @override /// Widget build(BuildContext context) { @@ -61,7 +61,7 @@ class Compass extends StatefulWidget { /// A function that provides a [GeoViewController] to listen to and /// control. This should return the same controller that is provided to the - /// corresponding [ArcGISMapView] or [ArcGISSceneView]. + /// corresponding [ArcGISMapView], [ArcGISSceneView], or [ArcGISLocalSceneView]. final GeoViewController Function() controllerProvider; /// Whether the compass should automatically hide when the map is oriented @@ -69,7 +69,7 @@ class Compass extends StatefulWidget { final bool automaticallyHides; /// The alignment of the compass within the parent widget. Defaults to [Alignment.topRight]. The compass should generally be placed - /// in a [Stack] on top of the corresponding [ArcGISMapView] or [ArcGISSceneView]. + /// in a [Stack] on top of the corresponding [ArcGISMapView], [ArcGISSceneView], or [ArcGISLocalSceneView]. final Alignment alignment; /// The padding around the compass. Defaults to 10 pixels on all sides. @@ -107,21 +107,28 @@ class _CompassState extends State { _controller = widget.controllerProvider(); - if (_controller is ArcGISMapViewController) { - final controller = _controller as ArcGISMapViewController; - _angleDegrees = controller.rotation; - _rotationSubscription = controller.onRotationChanged.listen((rotation) { - setState(() => _angleDegrees = rotation); - }); - } else { - final controller = _controller as ArcGISSceneViewController; - _angleDegrees = controller.getCurrentViewpointCamera().heading; - _viewpointSubscription = controller.onViewpointChanged.listen((_) { - final heading = controller.getCurrentViewpointCamera().heading; - if (heading != _angleDegrees) { - setState(() => _angleDegrees = heading); - } - }); + switch (_controller) { + case final ArcGISMapViewController controller: + _angleDegrees = controller.rotation; + _rotationSubscription = controller.onRotationChanged.listen((rotation) { + setState(() => _angleDegrees = rotation); + }); + case final ArcGISSceneViewController controller: + _angleDegrees = controller.getCurrentViewpointCamera().heading; + _viewpointSubscription = controller.onViewpointChanged.listen((_) { + final heading = controller.getCurrentViewpointCamera().heading; + if (heading != _angleDegrees) { + setState(() => _angleDegrees = heading); + } + }); + case final ArcGISLocalSceneViewController controller: + _angleDegrees = controller.getCurrentViewpointCamera().heading; + _viewpointSubscription = controller.onViewpointChanged.listen((_) { + final heading = controller.getCurrentViewpointCamera().heading; + if (heading != _angleDegrees) { + setState(() => _angleDegrees = heading); + } + }); } _iconBuilder = widget.iconBuilder ?? defaultIconBuilder; @@ -160,20 +167,27 @@ class _CompassState extends State { } void onPressed() { - if (_controller is ArcGISMapViewController) { - (_controller as ArcGISMapViewController).setViewpointRotation( - angleDegrees: 0, - ); - } else { - final controller = _controller as ArcGISSceneViewController; - final currentCamera = controller.getCurrentViewpointCamera(); - controller.setViewpointCameraAnimated( - camera: currentCamera.rotateTo( - heading: 0, - pitch: currentCamera.pitch, - roll: currentCamera.roll, - ), - ); + switch (_controller) { + case final ArcGISMapViewController controller: + controller.setViewpointRotation(angleDegrees: 0); + case final ArcGISSceneViewController controller: + final currentCamera = controller.getCurrentViewpointCamera(); + controller.setViewpointCameraAnimated( + camera: currentCamera.rotateTo( + heading: 0, + pitch: currentCamera.pitch, + roll: currentCamera.roll, + ), + ); + case final ArcGISLocalSceneViewController controller: + final currentCamera = controller.getCurrentViewpointCamera(); + controller.setViewpointCameraAnimated( + camera: currentCamera.rotateTo( + heading: 0, + pitch: currentCamera.pitch, + roll: currentCamera.roll, + ), + ); } } diff --git a/lib/src/overview_map/overview_map.dart b/lib/src/overview_map/overview_map.dart index c0b21e4..26c3629 100644 --- a/lib/src/overview_map/overview_map.dart +++ b/lib/src/overview_map/overview_map.dart @@ -16,23 +16,23 @@ part of '../../arcgis_maps_toolkit.dart'; -/// [OverviewMap] is a small, secondary map view (sometimes called an “inset map”), superimposed on an existing [ArcGISMapView] or [ArcGISSceneView], which shows a representation of the current visible area (for an [ArcGISMapView]) or viewpoint (for an [ArcGISSceneView]). +/// [OverviewMap] is a small, secondary map view (sometimes called an “inset map”), superimposed on an existing [ArcGISMapView], [ArcGISSceneView], or [ArcGISLocalSceneView], which shows a representation of the current visible area (for an [ArcGISMapView]) or viewpoint (for an [ArcGISSceneView] or [ArcGISLocalSceneView]). /// /// # Overview /// For an [OverviewMap] on an [ArcGISMapView], the map view's `visibleArea` property will be represented in the overview map as a polygon, which will rotate as the map view rotates. -/// For an [OverviewMap] on an [ArcGISSceneView], the center point of the scene view's `currentViewpoint` property will be represented in the overview map by a point. +/// For an [OverviewMap] on an [ArcGISSceneView] or [ArcGISLocalSceneView], the center point of the scene view's `currentViewpoint` property will be represented in the overview map by a point. /// /// ## Features -/// * Displays a representation of the current visible area or viewpoint for a connected map view or scene view, respectively. +/// * Displays a representation of the current visible area or viewpoint for the connected map view, scene view, or local scene view. /// * Supports a configurable scaling factor for setting the overview map’s zoom level relative to the connected view. -/// * Supports a configurable symbol for visualizing the current visible area or viewpoint representation (a `SimpleFillSymbol` for a connected map view; a `SimpleMarkerSymbol` for a connected scene view). +/// * Supports a configurable symbol for visualizing the current visible area or viewpoint representation (a `SimpleFillSymbol` for a connected map view; a `SimpleMarkerSymbol` for a connected scene view or local scene view). /// * Supports using a custom map in the overview map display. /// /// Note: [OverviewMap] uses metered ArcGIS Location Platform basemaps by default, so you will need to implement authentication using a supported method. See [Security and authentication](https://developers.arcgis.com/documentation/security-and-authentication/) documentation for more information. /// /// ## Usage -/// An [OverviewMap] is generally placed in a [Stack] on top of an [ArcGISMapView] or [ArcGISSceneView]. -/// The overview map must be provided the same [ArcGISMapViewController] or [ArcGISSceneViewController] as the corresponding map view or scene view. +/// An [OverviewMap] is generally placed in a [Stack] on top of an [ArcGISMapView], [ArcGISSceneView], or [ArcGISLocalSceneView]. +/// The overview map must be provided the same [ArcGISMapViewController], [ArcGISSceneViewController], or [ArcGISLocalSceneViewController] as the corresponding map view, scene view, or local scene view. /// ```dart /// @override /// Widget build(BuildContext context) { @@ -40,15 +40,15 @@ part of '../../arcgis_maps_toolkit.dart'; /// body: Stack( /// children: [ /// ArcGISMapView(controllerProvider: controllerProvider), -/// OverviewMap.withMapView(controllerProvider: controllerProvider), +/// OverviewMap(controllerProvider: controllerProvider), /// ], /// ), /// ); /// } /// ``` class OverviewMap extends StatefulWidget { - /// Private constructor for use by the factory constructors. - const OverviewMap._internal({ + /// Create an OverviewMap widget. + const OverviewMap({ required this.controllerProvider, super.key, this.alignment = Alignment.topRight, @@ -60,6 +60,7 @@ class OverviewMap extends StatefulWidget { }); /// Create an OverviewMap widget with [ArcGISMapViewController]. + @Deprecated('Use OverviewMap.new() instead.') factory OverviewMap.withMapView({ required ArcGISMapViewController Function() controllerProvider, Key? key, @@ -70,7 +71,7 @@ class OverviewMap extends StatefulWidget { ArcGISMap? map, Widget Function(BuildContext, Widget)? containerBuilder, }) { - return OverviewMap._internal( + return OverviewMap( controllerProvider: controllerProvider, key: key, alignment: alignment, @@ -82,7 +83,8 @@ class OverviewMap extends StatefulWidget { ); } - /// Create an OverviewMap widget with [ArcGISSceneViewController] . + /// Create an OverviewMap widget with [ArcGISSceneViewController]. + @Deprecated('Use OverviewMap.new() instead.') factory OverviewMap.withSceneView({ required ArcGISSceneViewController Function() controllerProvider, Key? key, @@ -93,7 +95,7 @@ class OverviewMap extends StatefulWidget { ArcGISMap? map, Widget Function(BuildContext, Widget)? containerBuilder, }) { - return OverviewMap._internal( + return OverviewMap( controllerProvider: controllerProvider, key: key, alignment: alignment, @@ -106,11 +108,11 @@ class OverviewMap extends StatefulWidget { } /// A function that provides the [GeoViewController] of the target map. This should return the same controller that is provided to the - /// corresponding [ArcGISMapView] or [ArcGISSceneView]. + /// corresponding [ArcGISMapView], [ArcGISSceneView], or [ArcGISLocalSceneView]. final GeoViewController Function() controllerProvider; /// The alignment of the overview map within the parent widget. Defaults to [Alignment.topRight]. The overview map should generally be placed - /// in a [Stack] on top of the corresponding [ArcGISMapView] or [ArcGISSceneView]. + /// in a [Stack] on top of the corresponding [ArcGISMapView], [ArcGISSceneView], or [ArcGISLocalSceneView]. final Alignment alignment; /// The padding around the overview map. Defaults to 10 pixels on all sides. @@ -121,7 +123,7 @@ class OverviewMap extends StatefulWidget { /// The symbol used to represent the current viewpoint. /// - For [ArcGISMapView]: a [SimpleFillSymbol] is used to draw the visible area. - /// - For [ArcGISSceneView]: a [SimpleMarkerSymbol] is used to draw the current viewpoint's center. + /// - For [ArcGISSceneView] or [ArcGISLocalSceneView]: a [SimpleMarkerSymbol] is used to draw the current viewpoint's center. final ArcGISSymbol? symbol; /// The map to use as the overview map. Defaults to a map with the ArcGIS Topographic basemap style. @@ -212,12 +214,12 @@ class _OverviewMapState extends State { Geometry? geometry; Geometry? sceneGeometry; - if (_controller is ArcGISMapViewController) { - geometry = (_controller as ArcGISMapViewController).visibleArea; - sceneGeometry = null; - } else if (_controller is ArcGISSceneViewController) { - sceneGeometry = viewpoint.targetGeometry; - geometry = null; + switch (_controller) { + case final ArcGISMapViewController controller: + geometry = controller.visibleArea; + case ArcGISSceneViewController(): + case ArcGISLocalSceneViewController(): + sceneGeometry = viewpoint.targetGeometry; } if (geometry != null) { @@ -244,17 +246,19 @@ class _OverviewMapState extends State { // Returns a default symbol based on the type of GeoView. ArcGISSymbol _defaultSymbolFor(GeoViewController controller) { - if (controller is ArcGISMapViewController) { - return SimpleFillSymbol( - color: Colors.transparent, - outline: SimpleLineSymbol(color: Colors.red), - ); + switch (controller) { + case ArcGISMapViewController(): + return SimpleFillSymbol( + color: Colors.transparent, + outline: SimpleLineSymbol(color: Colors.red), + ); + default: + return SimpleMarkerSymbol( + style: SimpleMarkerSymbolStyle.cross, + color: Colors.red, + size: 20, + ); } - return SimpleMarkerSymbol( - style: SimpleMarkerSymbolStyle.cross, - color: Colors.red, - size: 20, - ); } Widget _defaultContainerBuilder(BuildContext context, Widget child) {