View on GitHub Sample viewer app

Display and configure electronic navigational charts per ENC specification.

Screenshot of Configure electronic navigational charts sample

Use case

The S-52 standard defines how Electronic Navigational Chart (ENC) content should be displayed to ensure that data is presented consistently across every charting system. S-52 defines several display options, including variations on symbology to ensure that charts are readable both at night and in direct sunlight.

How to use the sample

When opened, the sample displays an electronic navigational chart. Tap on the map to select ENC features and view the feature’s acronyms and descriptions shown in a callout. Tap “Display Settings” and use the options to adjust some of the ENC mariner display settings, such as the colors and symbology.

How it works

  1. To display ENC content:
    1. On EncEnvironmentSettings, set resourcePath to the local hydrography data directory and sencDataPath to a temporary directory.
    2. Create an EncExchangeSet using URLs to the local ENC exchange set files and load it.
    3. Make an EncCell for each of the EncExchangeSet.datasets and then make an EncLayer from each cell.
    4. Add the layers to the map using ArcGISMap.operationalLayers.add(encLayer) to display the map.
  2. To select ENC features:
    1. Use onSingleTapConfirmed on the map view to get the screen point from the tapped location.
    2. Create a MapViewProxy and use it to identify nearby features to the tapped location with identifyLayers.
    3. From the resulting IdentifyLayerResult, get the EncLayer from layerContent and the EncFeature(s) from geoElements.
    4. Use EncLayer.selectFeature to select the ENC feature(s).
  3. To set ENC display settings:
    1. Get the EncDisplaySettings instance from EncEnvironmentSettings.displaySettings.
    2. Use marinerSettings, textGroupVisibilitySettings, and viewingGroupSettings to access the settings instances and set their properties.
    3. Reset the display settings using resetToDefaults() on the settings instances.

Relevant API

  • EncCell
  • EncDataset
  • EncDisplaySettings
  • EncEnvironmentSettings
  • EncExchangeSet
  • EncLayer
  • EncMarinerSettings
  • EncTextGroupVisibilitySettings
  • IdentifyLayerResult

Offline data

This sample downloads the ENC Exchange Set without updates item from ArcGIS Online automatically.

The latest Hydrography Data can be downloaded from the Esri Developer downloads. The S57DataDictionary.xml file is contained there.

Additional information

Read more about displaying and deploying electronic navigational charts on Esri Developer.

Tags

ENC, hydrography, identify, IHO, layers, maritime, nautical chart, S-52, S-57, select, settings, symbology

Sample code

ConfigureElectronicNavigationalChartsScreenViewModel.kt ConfigureElectronicNavigationalChartsScreenViewModel.kt DownloadActivity.kt MainActivity.kt ConfigureElectronicNavigationalChartsScreen.kt
/* Copyright 2024 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
*
* http://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.
*
*/
package com.esri.arcgismaps.sample.configureelectronicnavigationalcharts.components
import android.app.Application
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.dp
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.arcgismaps.hydrography.EncAreaSymbolizationType
import com.arcgismaps.hydrography.EncCell
import com.arcgismaps.hydrography.EncColorScheme
import com.arcgismaps.hydrography.EncEnvironmentSettings
import com.arcgismaps.hydrography.EncExchangeSet
import com.arcgismaps.hydrography.EncFeature
import com.arcgismaps.hydrography.EncPointSymbolizationType
import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.BasemapStyle
import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.mapping.layers.EncLayer
import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
import com.esri.arcgismaps.sample.configureelectronicnavigationalcharts.R
import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.io.File
class ConfigureElectronicNavigationalChartsScreenViewModel(application: Application) :
AndroidViewModel(application) {
private val provisionPath: String by lazy {
application.getExternalFilesDir(null)?.path.toString() +
File.separator +
application.getString(R.string.configure_electronic_navigational_charts_app_name)
}
// Paths to ENC data and hydrology resources
private val encResourcesPath = provisionPath + application.getString(R.string.enc_res_dir)
private val encDataPath = provisionPath + application.getString(R.string.enc_data_dir)
// Create an ENC exchange set from the local ENC data
private val encExchangeSet = EncExchangeSet(listOf(encDataPath))
private val encEnvironmentSettings: EncEnvironmentSettings = EncEnvironmentSettings
private val encMarinerSettings = encEnvironmentSettings.displaySettings.marinerSettings
// Create an empty map, to be updated once ENC data is loaded
var arcGISMap by mutableStateOf(ArcGISMap())
// Passed to the composable MapView to support identify operations.
val mapViewProxy = MapViewProxy()
private val _selectedEncFeature = MutableStateFlow<EncFeature?>(null)
val selectedEncFeature = _selectedEncFeature.asStateFlow()
var currentColorScheme by mutableStateOf(encMarinerSettings.colorScheme)
private set
var currentAreaSymbolizationType by mutableStateOf(encMarinerSettings.areaSymbolizationType)
private set
var currentPointSymbolizationType by mutableStateOf(encMarinerSettings.pointSymbolizationType)
private set
// Create a message dialog view model for handling error messages
val messageDialogVM = MessageDialogViewModel()
init {
// Provide ENC environment with location of ENC resources and configure SENC caching location
encEnvironmentSettings.resourcePath = encResourcesPath
encEnvironmentSettings.sencDataPath = application.externalCacheDir?.path
configureEncDisplaySettings()
viewModelScope.launch {
encExchangeSet.load().onSuccess {
// Set the map to the oceans basemap style, and initial viewpoint
arcGISMap = ArcGISMap(BasemapStyle.ArcGISOceans).apply {
initialViewpoint = Viewpoint(-32.5, 60.95, 67e3)
}
encExchangeSet.datasets.forEach { encDataset ->
// Create a layer for each ENC dataset and add it to the map
val encCell = EncCell(encDataset)
val encLayer = EncLayer(encCell)
arcGISMap.operationalLayers.add(encLayer)
encLayer.load().onFailure { error -> messageDialogVM.showMessageDialog(error) }
}
}.onFailure { error -> messageDialogVM.showMessageDialog(error) }
}
}
fun updateColorScheme(colorScheme: EncColorScheme) {
encMarinerSettings.colorScheme = colorScheme
currentColorScheme = colorScheme
}
fun updateAreaSymbolizationType(areaSymbolizationType: EncAreaSymbolizationType) {
encMarinerSettings.areaSymbolizationType = areaSymbolizationType
currentAreaSymbolizationType = areaSymbolizationType
}
fun updatePointSymbolizationType(pointSymbolizationType: EncPointSymbolizationType) {
encMarinerSettings.pointSymbolizationType = pointSymbolizationType
currentPointSymbolizationType = pointSymbolizationType
}
/**
* Identifies the ENC feature at the tapped screen coordinate and selects it for display.
*/
fun identify(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
arcGISMap.operationalLayers.filterIsInstance<EncLayer>().forEach { encLayer ->
encLayer.clearSelection()
}
viewModelScope.launch {
mapViewProxy.identifyLayers(singleTapConfirmedEvent.screenCoordinate, 10.dp)
.onSuccess { identifyResults ->
val encIdentifyResult = identifyResults.firstOrNull {
it.geoElements.filterIsInstance<EncFeature>().isNotEmpty()
}
val encLayer = encIdentifyResult?.layerContent as? EncLayer
val encFeature = encIdentifyResult?.geoElements
?.filterIsInstance<EncFeature>()
?.firstOrNull()
if (encLayer != null && encFeature != null) {
encLayer.selectFeature(encFeature)
_selectedEncFeature.value = encFeature
} else {
_selectedEncFeature.value = null
}
}.onFailure { error ->
_selectedEncFeature.value = null
messageDialogVM.showMessageDialog(error)
}
}
}
override fun onCleared() {
super.onCleared()
encEnvironmentSettings.resourcePath = null
encEnvironmentSettings.sencDataPath = null
encEnvironmentSettings.displaySettings.marinerSettings.resetToDefaults()
encEnvironmentSettings.displaySettings.textGroupVisibilitySettings.resetToDefaults()
encEnvironmentSettings.displaySettings.viewingGroupSettings.resetToDefaults()
}
/**
* Disables a subset of text and viewing groups so the charts start less cluttered.
*/
private fun configureEncDisplaySettings() {
encEnvironmentSettings.displaySettings.textGroupVisibilitySettings.apply {
includeGeographicNames = false
includeNatureOfSeabed = false
}
encEnvironmentSettings.displaySettings.viewingGroupSettings.apply {
includeDepthContours = false
includeLights = false
includeSpotSoundings = false
}
currentColorScheme = encMarinerSettings.colorScheme
currentAreaSymbolizationType = encMarinerSettings.areaSymbolizationType
currentPointSymbolizationType = encMarinerSettings.pointSymbolizationType
}
}
val colorSchemes: List<EncColorScheme> = listOf(
EncColorScheme.Day, EncColorScheme.Dusk, EncColorScheme.Night
)
val areaSymbolizationTypes: List<EncAreaSymbolizationType> = listOf(
EncAreaSymbolizationType.Plain, EncAreaSymbolizationType.Symbolized
)
val pointSymbolizationTypes: List<EncPointSymbolizationType> = listOf(
EncPointSymbolizationType.PaperChart, EncPointSymbolizationType.Simplified
)