Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New Sample] Add ENC exchange set #238

Merged
merged 15 commits into from
Oct 11, 2024
Merged
1 change: 1 addition & 0 deletions add-enc-exchange-set/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
37 changes: 37 additions & 0 deletions add-enc-exchange-set/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Add ENC exchange set

Display nautical charts per the ENC specification.

![Image showing the add ENC exchange set app](add-enc-exchange-set.png)

## Use case

The [ENC specification](https://docs.iho.int/iho_pubs/standard/S-57Ed3.1/20ApB1.pdf) describes how hydrographic data should be displayed digitally.

An ENC exchange set is a catalog of data files which can be loaded as cells. The cells contain information on how symbols should be displayed in relation to one another, so as to represent information such as depth and obstacles accurately.

## How to use the sample

Run the sample and view the ENC data. Pan and zoom around the map. Take note of the high level of detail in the data and the smooth rendering of the layer.

## How it works

1. Specify the path to a local CATALOG.031 file to create an `EncExchangeSet`.
2. After loading the exchange set, get the `EncDataset` objects in the exchange set with `EncExchangeSet.datasets`.
3. Create an `EncCell` for each dataset. Then create an `EncLayer` for each cell.
4. Add the ENC layer to a map's operational layers collection to display it.

## Relevant API

* EncCell
* EncDataset
* EncExchangeSet
* EncLayer

## Offline data

This sample downloads the [ENC Exchange Set without updates](https://www.arcgis.com/home/item.html?id=9d2987a825c646468b3ce7512fb76e2d) and [Hydrography dataset resources](https://www.arcgis.com/home/item.html?id=5028bf3513ff4c38b28822d010a4937c) from ArcGIS Online.

## Tags

data, ENC, hydrographic, layers, maritime, nautical chart
01smito01 marked this conversation as resolved.
Show resolved Hide resolved
41 changes: 41 additions & 0 deletions add-enc-exchange-set/README.metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"category": "Layers",
"description": "Display nautical charts per the ENC specification.",
"formal_name": "AddEncExchangeSet",
"ignore": false,
"images": [
"add-enc-exchange-set.png"
],
"keywords": [
"ENC",
"data",
"hydrographic",
"layers",
"maritime",
"nautical chart",
"EncCell",
"EncDataset",
"EncExchangeSet",
"EncLayer"
],
"language": "kotlin",
"provision_from": [
"https://www.arcgis.com/home/item.html?id=9d2987a825c646468b3ce7512fb76e2d",
"https://www.arcgis.com/home/item.html?id=5028bf3513ff4c38b28822d010a4937c"
],
"provision_to": [],
"redirect_from": "",
"relevant_apis": [
"EncCell",
"EncDataset",
"EncExchangeSet",
"EncLayer"
],
"snippets": [
"src/main/java/com/esri/arcgismaps/sample/addencexchangeset/MainActivity.kt",
"src/main/java/com/esri/arcgismaps/sample/addencexchangeset/DownloadActivity.kt",
"src/main/java/com/esri/arcgismaps/sample/addencexchangeset/components/MapViewModel.kt",
"src/main/java/com/esri/arcgismaps/sample/addencexchangeset/screens/MainScreen.kt"
],
"title": "Add ENC exchange set"
}
Binary file added add-enc-exchange-set/add-enc-exchange-set.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 54 additions & 0 deletions add-enc-exchange-set/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}

android {
compileSdk = libs.versions.compileSdk.get().toInt()

defaultConfig {
applicationId = "com.esri.arcgismaps.sample.addencexchangeset"
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.targetSdk.get().toInt()
versionCode = libs.versions.versionCode.get().toInt()
versionName = libs.versions.versionName.get()
buildConfigField("String", "API_KEY", project.properties["API_KEY"].toString())
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
}

buildFeatures {
compose = true
buildConfig = true
}

composeOptions {
kotlinCompilerExtensionVersion = libs.versions.kotlinCompilerExt.get()
}

namespace = "com.esri.arcgismaps.sample.addencexchangeset"
}

dependencies {
// lib dependencies from rootProject build.gradle.kts
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.activity.compose)
// Jetpack Compose Bill of Materials
implementation(platform(libs.androidx.compose.bom))
// Jetpack Compose dependencies
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui.tooling)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(project(":samples-lib"))
// Toolkit dependencies
implementation(platform(libs.arcgis.maps.kotlin.toolkit.bom))
implementation(libs.arcgis.maps.kotlin.toolkit.geoview.compose)
}
21 changes: 21 additions & 0 deletions add-enc-exchange-set/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
30 changes: 30 additions & 0 deletions add-enc-exchange-set/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".DownloadActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:exported="true"
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* 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.addencexchangeset

import android.content.Intent
import android.os.Bundle
import com.esri.arcgismaps.sample.sampleslib.DownloaderActivity

class DownloadActivity : DownloaderActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
downloadAndStartSample(
Intent(this, MainActivity::class.java),
// get the app name of the sample
getString(R.string.app_name),
listOf(
// ArcGIS Portal item containing ENC hydrography resources
"https://www.arcgis.com/home/item.html?id=5028bf3513ff4c38b28822d010a4937c",
// ArcGIS Portal item containing the ENC dataset
"https://www.arcgis.com/home/item.html?id=9d2987a825c646468b3ce7512fb76e2d"
)

01smito01 marked this conversation as resolved.
Show resolved Hide resolved
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/* 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.addencexchangeset

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import com.arcgismaps.ApiKey
import com.arcgismaps.ArcGISEnvironment
import com.esri.arcgismaps.sample.sampleslib.theme.SampleAppTheme
import com.esri.arcgismaps.sample.addencexchangeset.screens.MainScreen

class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// authentication with an API key or named user is
// required to access basemaps and other location services
ArcGISEnvironment.apiKey = ApiKey.create(BuildConfig.API_KEY)

setContent {
SampleAppTheme {
SampleApp()
}
}
}

@Composable
private fun SampleApp() {
Surface(
color = MaterialTheme.colorScheme.background
) {
MainScreen(
sampleName = getString(R.string.app_name)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/* 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.addencexchangeset.components

import android.app.Application
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.arcgismaps.geometry.Envelope
import com.arcgismaps.geometry.GeometryEngine
import com.arcgismaps.hydrography.EncCell
import com.arcgismaps.hydrography.EncEnvironmentSettings
import com.arcgismaps.hydrography.EncExchangeSet
import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.BasemapStyle
import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.mapping.layers.EncLayer
import com.esri.arcgismaps.sample.addencexchangeset.R
import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
import kotlinx.coroutines.launch
import java.io.File

class MapViewModel(application: Application) : AndroidViewModel(application) {
private val provisionPath: String by lazy {
application.getExternalFilesDir(null)?.path.toString() + File.separator + application.getString(
R.string.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

// Create a map with the oceans basemap style
val arcGISMap by mutableStateOf(ArcGISMap(BasemapStyle.ArcGISOceans))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you use a delegated property with mutable state, it should be var, otherwise if it's not going to change you can just expose the value directly


// 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

viewModelScope.launch {
encExchangeSet.load().onSuccess {

// set the map's viewpoint to the combined extent of all datasets in the exchange set
arcGISMap.initialViewpoint = encExchangeSet.extentOrNull()?.let { extent ->
Viewpoint(extent)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for this to run after the map has been displayed? What happens when the initial viewpoint is set after the map is displayed?

Copy link
Collaborator Author

@01smito01 01smito01 Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes to the initial viewpoint are ignored after a map is added into a GeoView. Since this is in a launched block I'd expect that yes, this could be applied (and ignored) after MapViewModel initialisation has otherwise completed and the map gets added to the MapView.

In practice this works just fine, but I can revert to updating the viewpoint through a MapViewProxy if this is too flimsy.

}

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 { err ->
messageDialogVM.showMessageDialog(
"Error loading ENC layer",
err.message.toString()
)
}
}
}.onFailure { err ->
messageDialogVM.showMessageDialog(
"Error loading ENC exchange set",
err.message.toString()
)
}
}
}
}

/**
* Get the combined extent of every dataset in the exchange set.
*/
private fun EncExchangeSet.extentOrNull(): Envelope? {
var extent: Envelope? = null

datasets.forEach { dataset ->
if (extent == null) {
extent = dataset.extent
}

if (extent != null && dataset.extent != null){
// update the combined extent of the exchange set if geometry engine returns non-null
extent = GeometryEngine.combineExtentsOrNull(extent!!, dataset.extent!!) ?: extent
}
}
return extent
}
Loading
Loading