diff --git a/Samples.xcodeproj/project.pbxproj b/Samples.xcodeproj/project.pbxproj index 88cec33b1..3447c3f0c 100644 --- a/Samples.xcodeproj/project.pbxproj +++ b/Samples.xcodeproj/project.pbxproj @@ -214,7 +214,12 @@ 883C121729C914E100062FF9 /* DownloadPreplannedMapAreaView.MapPicker.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 883C121429C9136600062FF9 /* DownloadPreplannedMapAreaView.MapPicker.swift */; }; 883C121829C914E100062FF9 /* DownloadPreplannedMapAreaView.Model.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = E0D04FF128A5390000747989 /* DownloadPreplannedMapAreaView.Model.swift */; }; 883C121929C914E100062FF9 /* DownloadPreplannedMapAreaView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = E070A0A2286F3B6000F2B606 /* DownloadPreplannedMapAreaView.swift */; }; + 88C5E0ED2DCBC4170091D271 /* ApplyHillshadeRendererToRasterView.SettingsView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 88E52E802DCA703B00F48409 /* ApplyHillshadeRendererToRasterView.SettingsView.swift */; }; 88E52E6C2DC960EA00F48409 /* ApplyFunctionToRasterFromServiceView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 8810FB582DC94A6600874936 /* ApplyFunctionToRasterFromServiceView.swift */; }; + 88E52E6F2DC96C3F00F48409 /* ApplyHillshadeRendererToRasterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E52E6E2DC96C3F00F48409 /* ApplyHillshadeRendererToRasterView.swift */; }; + 88E52E702DC970A800F48409 /* ApplyHillshadeRendererToRasterView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 88E52E6E2DC96C3F00F48409 /* ApplyHillshadeRendererToRasterView.swift */; }; + 88E52E812DCA703B00F48409 /* ApplyHillshadeRendererToRasterView.SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E52E802DCA703B00F48409 /* ApplyHillshadeRendererToRasterView.SettingsView.swift */; }; + 88E52E832DCBB1B400F48409 /* srtm.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 88E52E822DCBB1B400F48409 /* srtm.tiff */; settings = {ASSET_TAGS = (ApplyHillshadeRendererToRaster, ); }; }; 88F93CC129C3D59D0006B28E /* CreateAndEditGeometriesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F93CC029C3D59C0006B28E /* CreateAndEditGeometriesView.swift */; }; 88F93CC229C4D3480006B28E /* CreateAndEditGeometriesView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 88F93CC029C3D59C0006B28E /* CreateAndEditGeometriesView.swift */; }; 9503056E2C46ECB70091B32D /* ShowDeviceLocationUsingIndoorPositioningView.Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9503056D2C46ECB70091B32D /* ShowDeviceLocationUsingIndoorPositioningView.Model.swift */; }; @@ -622,6 +627,8 @@ dstPath = ""; dstSubfolderSpec = 7; files = ( + 88C5E0ED2DCBC4170091D271 /* ApplyHillshadeRendererToRasterView.SettingsView.swift in Copy Source Code Files */, + 88E52E702DC970A800F48409 /* ApplyHillshadeRendererToRasterView.swift in Copy Source Code Files */, 1C293D012DCA7C99000B0822 /* ApplyBlendRendererToHillshadeView.swift in Copy Source Code Files */, 1C293D032DCA7C99000B0822 /* ApplyBlendRendererToHillshadeView.SettingsView.swift in Copy Source Code Files */, 88E52E6C2DC960EA00F48409 /* ApplyFunctionToRasterFromServiceView.swift in Copy Source Code Files */, @@ -985,6 +992,9 @@ 79D84D0D2A815C5B00F45262 /* AddCustomDynamicEntityDataSourceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCustomDynamicEntityDataSourceView.swift; sourceTree = ""; }; 8810FB582DC94A6600874936 /* ApplyFunctionToRasterFromServiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplyFunctionToRasterFromServiceView.swift; sourceTree = ""; }; 883C121429C9136600062FF9 /* DownloadPreplannedMapAreaView.MapPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadPreplannedMapAreaView.MapPicker.swift; sourceTree = ""; }; + 88E52E6E2DC96C3F00F48409 /* ApplyHillshadeRendererToRasterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplyHillshadeRendererToRasterView.swift; sourceTree = ""; }; + 88E52E802DCA703B00F48409 /* ApplyHillshadeRendererToRasterView.SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplyHillshadeRendererToRasterView.SettingsView.swift; sourceTree = ""; }; + 88E52E822DCBB1B400F48409 /* srtm.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = srtm.tiff; sourceTree = ""; }; 88F93CC029C3D59C0006B28E /* CreateAndEditGeometriesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateAndEditGeometriesView.swift; sourceTree = ""; }; 9503056D2C46ECB70091B32D /* ShowDeviceLocationUsingIndoorPositioningView.Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowDeviceLocationUsingIndoorPositioningView.Model.swift; sourceTree = ""; }; 9537AFD62C220EF0000923C5 /* ExchangeSetwithoutUpdates */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ExchangeSetwithoutUpdates; sourceTree = ""; }; @@ -1376,6 +1386,7 @@ D7F2A02C2CD00F1C0008D981 /* Apply dictionary renderer to feature layer */, D70789912CD160FD000DF215 /* Apply dictionary renderer to graphics overlay */, 8810FB572DC94A5600874936 /* Apply function to raster from service */, + 88E52E6D2DC969CF00F48409 /* Apply hillshade renderer to raster */, 955AFAC52C10FD74009C8FE5 /* Apply mosaic rule to rasters */, D771D0C52CD55211004C13CB /* Apply raster rendering rule */, 004A2BA12BED456500C297CE /* Apply scheduled updates to preplanned map area */, @@ -1640,6 +1651,7 @@ D7BEBA9F2CBD9CCA00F882E7 /* 98092369c4ae4d549bbbd45dba993ebc */, D721EEA62ABDFF550040BE46 /* 174150279af74a2ba6f8b87a567f480b */, 792222DB2A81AA5D00619FFE /* a8a942c228af4fac96baa78ad60f511f */, + 88E52E712DC97BA500F48409 /* ae9739163a76437ea02482e1a807b806 */, D7464F202ACE0910007FEE88 /* b5f977c78ec74b3a8857ca86d1d9b318 */, D7F8C03F2B605E720072BFA7 /* b5106355f1634b8996e634c04b6a930a */, D70539042CD0122D00F63F4A /* c78b149a1d52414682c86a5feeb13d30 */, @@ -2088,6 +2100,23 @@ path = "Apply function to raster from service"; sourceTree = ""; }; + 88E52E6D2DC969CF00F48409 /* Apply hillshade renderer to raster */ = { + isa = PBXGroup; + children = ( + 88E52E6E2DC96C3F00F48409 /* ApplyHillshadeRendererToRasterView.swift */, + 88E52E802DCA703B00F48409 /* ApplyHillshadeRendererToRasterView.SettingsView.swift */, + ); + path = "Apply hillshade renderer to raster"; + sourceTree = ""; + }; + 88E52E712DC97BA500F48409 /* ae9739163a76437ea02482e1a807b806 */ = { + isa = PBXGroup; + children = ( + 88E52E822DCBB1B400F48409 /* srtm.tiff */, + ); + path = ae9739163a76437ea02482e1a807b806; + sourceTree = ""; + }; 88F93CBE29C3D4E30006B28E /* Create and edit geometries */ = { isa = PBXGroup; children = ( @@ -3471,6 +3500,7 @@ ApplyBlendRendererToHillshade, ApplyDictionaryRendererToFeatureLayer, ApplyDictionaryRendererToGraphicsOverlay, + ApplyHillshadeRendererToRaster, ApplyScheduledUpdatesToPreplannedMapArea, AugmentRealityToShowTabletopScene, ChangeCameraController, @@ -3545,6 +3575,7 @@ D7C523402BED9BBF00E8221A /* SanFrancisco.tpkx in Resources */, 00E5402027F3CCA200CF66D5 /* Assets.xcassets in Resources */, 4D126D7C29CA3E6000CFB7A7 /* Redlands.nmea in Resources */, + 88E52E832DCBB1B400F48409 /* srtm.tiff in Resources */, 00C94A0D28B53DE1004E42D9 /* raster-file in Resources */, D7464F2B2ACE0965007FEE88 /* SA_EVI_8Day_03May20 in Resources */, D762DA0E2D94C750001052DD /* NapervilleGasUtilities.geodatabase in Resources */, @@ -3793,6 +3824,7 @@ D751018E2A2E962D00B8FA48 /* IdentifyLayerFeaturesView.swift in Sources */, F1E71BF1289473760064C33F /* AddRasterFromFileView.swift in Sources */, 00B04273282EC59E0072E1B4 /* AboutView.swift in Sources */, + 88E52E6F2DC96C3F00F48409 /* ApplyHillshadeRendererToRasterView.swift in Sources */, 7573E81F29D6134C00BEED9C /* TraceUtilityNetworkView.swift in Sources */, D7781D4B2B7ECCB700E53C51 /* NavigateRouteWithReroutingView.Model.swift in Sources */, 4D2ADC6929C50C4C003B367F /* AddDynamicEntityLayerView.SettingsView.swift in Sources */, @@ -3847,6 +3879,7 @@ D7114A0D2BDC6A3300FA68CA /* EditWithBranchVersioningView.Model.swift in Sources */, 00B04FB5283EEBA80026C882 /* DisplayOverviewMapView.swift in Sources */, D718A1E72B570F7500447087 /* OrbitCameraAroundObjectView.Model.swift in Sources */, + 88E52E812DCA703B00F48409 /* ApplyHillshadeRendererToRasterView.SettingsView.swift in Sources */, D71C5F642AAA7A88006599FD /* CreateSymbolStylesFromWebStylesView.swift in Sources */, D7CC33FF2A31475C00198EDF /* ShowLineOfSightBetweenPointsView.swift in Sources */, D70BE5792A5624A80022CA02 /* CategoriesView.swift in Sources */, diff --git a/Shared/Samples/Apply hillshade renderer to raster/ApplyHillshadeRendererToRasterView.SettingsView.swift b/Shared/Samples/Apply hillshade renderer to raster/ApplyHillshadeRendererToRasterView.SettingsView.swift new file mode 100644 index 000000000..e4b268034 --- /dev/null +++ b/Shared/Samples/Apply hillshade renderer to raster/ApplyHillshadeRendererToRasterView.SettingsView.swift @@ -0,0 +1,86 @@ +// 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 ArcGIS +import SwiftUI + +extension ApplyHillshadeRendererToRasterView { + struct SettingsView: View { + /// The renderer that this view updates. + @Binding var renderer: HillshadeRenderer + /// The altitude angle of the renderer. + @State private var altitude: Double = 0 + /// The azimuth angle of the renderer. + @State private var azimuth: Double = 0 + /// The slope type of the renderer. + @State private var slopeType: HillshadeRenderer.SlopeType? + /// The action to dismiss the view. + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + Form { + Section { + LabeledContent("Altitude", value: altitude, format: .number) + Slider(value: $altitude, in: 0...360, step: 1) + } + Section { + LabeledContent("Azimuth", value: azimuth, format: .number) + Slider(value: $azimuth, in: 0...360, step: 1) + } + Section { + Picker("Slope Type", selection: $slopeType) { + Text("None") + .tag(nil as HillshadeRenderer.SlopeType?) + Text("Degree") + .tag(Optional(HillshadeRenderer.SlopeType.degree)) + Text("Percent Rise") + .tag(Optional(HillshadeRenderer.SlopeType.percentRise)) + Text("Scaled") + .tag(Optional(HillshadeRenderer.SlopeType.scaled)) + } + } + } + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button("Done") { dismiss() } + } + } + .onAppear { + // Initialize the state when the view appears. + altitude = renderer.altitude.converted(to: .degrees).value + azimuth = renderer.azimuth.converted(to: .degrees).value + slopeType = renderer.slopeType + } + .onChange(of: [altitude, azimuth]) { updateRenderer(previousRenderer: renderer) } + .onChange(of: slopeType) { updateRenderer(previousRenderer: renderer) } + .navigationTitle("Hillshade Renderer Settings") + .navigationBarTitleDisplayMode(.inline) + } + } + + /// Updates the renderer to the latest state. + func updateRenderer(previousRenderer: HillshadeRenderer) { + renderer = HillshadeRenderer( + altitude: altitude, + azimuth: azimuth, + slopeType: slopeType, + zFactor: previousRenderer.zFactor, + pixelSizeFactor: previousRenderer.pixelSizeFactor, + pixelSizePower: previousRenderer.pixelSizePower, + outputBitDepth: previousRenderer.outputBitDepth + ) + } + } +} diff --git a/Shared/Samples/Apply hillshade renderer to raster/ApplyHillshadeRendererToRasterView.swift b/Shared/Samples/Apply hillshade renderer to raster/ApplyHillshadeRendererToRasterView.swift new file mode 100644 index 000000000..b576b4de7 --- /dev/null +++ b/Shared/Samples/Apply hillshade renderer to raster/ApplyHillshadeRendererToRasterView.swift @@ -0,0 +1,85 @@ +// 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 ArcGIS +import SwiftUI + +struct ApplyHillshadeRendererToRasterView: View { + /// The model used to store the geo model and other expensive objects + /// used in this view. + class Model: ObservableObject { + /// The map that will be shown. + let map: Map + + /// The raster layer in the map. + private let rasterLayer: RasterLayer + + /// The raster renderer. + var renderer: HillshadeRenderer { + didSet { + // When the renderer is updated, update the layer accordingly. + rasterLayer.renderer = renderer + } + } + + init() { + // Gets the raster file URL. + let rasterFileURL = Bundle.main.url(forResource: "srtm", withExtension: "tiff")! + + // Creates a raster with the file URL. + let raster = Raster(fileURL: rasterFileURL) + + // Creates a raster layer using the raster object. + rasterLayer = RasterLayer(raster: raster) + + // Apply the hillshade renderer to the raster layer. + renderer = HillshadeRenderer( + altitude: 45, + azimuth: 315, + slopeType: nil, + zFactor: 0.000016, + pixelSizeFactor: 1, + pixelSizePower: 1, + outputBitDepth: 8 + ) + rasterLayer.renderer = renderer + + // Create our map. + map = Map(basemap: .init(baseLayer: rasterLayer)) + } + } + + /// The view model for the sample. + @StateObject private var model = Model() + + /// A Boolean value indicating if the settings panel is presented. + @State private var isSettingsPanelPresented = false + + var body: some View { + // Creates a map view to display the map. + MapView(map: model.map) + .toolbar { + ToolbarItem(placement: .bottomBar) { + Button("Settings") { + isSettingsPanelPresented = true + } + .popover(isPresented: $isSettingsPanelPresented) { + SettingsView(renderer: $model.renderer) + .presentationDetents([.medium]) + .frame(idealWidth: 320, idealHeight: 380) + } + } + } + } +} diff --git a/Shared/Samples/Apply hillshade renderer to raster/README.md b/Shared/Samples/Apply hillshade renderer to raster/README.md new file mode 100644 index 000000000..1661f308e --- /dev/null +++ b/Shared/Samples/Apply hillshade renderer to raster/README.md @@ -0,0 +1,33 @@ +# Apply hillshade renderer to raster + +Apply a hillshade renderer to a raster. + +![Image of Apply hillshade renderer to raster sample](apply-hillshade-renderer-to-raster.png) +![Image of Apply hillshade renderer to raster sample settings](apply-hillshade-renderer-to-raster-settings.png) + +## Use case + +An environmental agency may track coastal erosion by comparing images of an area taken over a longer period of time with hillshade renderers applied. + +## How to use the sample + +Choose and adjust the settings to update the hillshade renderer on the raster layer. The sample allows you to change the Altitude, Azimuth, and Slope Type. + +## How it works + +1. Create a `Raster` from a grayscale raster file. +2. Create a `RasterLayer` from the raster. +3. Create a `Basemap` from the raster layer and set it to the map. +4. Create a `HillshadeRenderer`, specifying the altitude, azimuth, slope type and other properties. +5. Set the hillshade renderer to be used on the raster layer. + +## Relevant API + +* Basemap +* HillshadeRenderer +* Raster +* RasterLayer + +## Tags + +altitude, angle, azimuth, raster, slope, visualization diff --git a/Shared/Samples/Apply hillshade renderer to raster/README.metadata.json b/Shared/Samples/Apply hillshade renderer to raster/README.metadata.json new file mode 100644 index 000000000..ce2467ad7 --- /dev/null +++ b/Shared/Samples/Apply hillshade renderer to raster/README.metadata.json @@ -0,0 +1,36 @@ +{ + "category": "Visualization", + "description": "Apply a hillshade renderer to a raster.", + "ignore": false, + "images": [ + "apply-hillshade-renderer-to-raster-settings.png", + "apply-hillshade-renderer-to-raster.png" + ], + "keywords": [ + "altitude", + "angle", + "azimuth", + "raster", + "slope", + "visualization", + "Basemap", + "HillshadeRenderer", + "Raster", + "RasterLayer" + ], + "offline_data": [ + "ae9739163a76437ea02482e1a807b806" + ], + "redirect_from": [], + "relevant_apis": [ + "Basemap", + "HillshadeRenderer", + "Raster", + "RasterLayer" + ], + "snippets": [ + "ApplyHillshadeRendererToRasterView.swift", + "ApplyHillshadeRendererToRasterView.SettingsView.swift" + ], + "title": "Apply hillshade renderer to raster" +} diff --git a/Shared/Samples/Apply hillshade renderer to raster/apply-hillshade-renderer-to-raster-settings.png b/Shared/Samples/Apply hillshade renderer to raster/apply-hillshade-renderer-to-raster-settings.png new file mode 100644 index 000000000..d5f4ceaa8 Binary files /dev/null and b/Shared/Samples/Apply hillshade renderer to raster/apply-hillshade-renderer-to-raster-settings.png differ diff --git a/Shared/Samples/Apply hillshade renderer to raster/apply-hillshade-renderer-to-raster.png b/Shared/Samples/Apply hillshade renderer to raster/apply-hillshade-renderer-to-raster.png new file mode 100644 index 000000000..fa0841f4d Binary files /dev/null and b/Shared/Samples/Apply hillshade renderer to raster/apply-hillshade-renderer-to-raster.png differ