Select to view content in your preferred language

Jetpack compose Add WMS layer when WMS layer names are not known.

389
4
Jump to solution
09-29-2024 10:28 PM
NishanKhadka
Emerging Contributor
I migrated from Android View to Jetpack Compose for the esri map. But cannot seem to find a way to add WMS layer properly.
Would appreciate it if I could be pointed towards the right direction.Is there some way to add WMS similar to graphic overlays, or grids or locations??
Data class:
data class
ArcGisMapData(
var map: ArcGISMap,
var graphicOverlays: List<GraphicsOverlay>,
var locationDisplay: LocationDisplay)
Composable function:
@Composable
fun EsriMap(map:ArcGISMap,locationDisplay:LocationDisplay,graphicsOverlays:List<GraphicOverlay>){
val mapState by mapViewModel.mapStateFlow.collectAsStateWithLifeCycle()
MapView
(
modifier = Modifier
.fillMaxSize(),
arcGISMap = mapState.map,
graphicsOverlays = mapState.graphicOverlays,
locationDisplay = mapState.locationDisplay)
ViewModel Class
class
MapViewModel : ViewModel() {
private val _mapStateDataFlow = MutableStateFlow(
ArcGisMapData(
map = ArcGISMap(Basemap(WebTiledLayer.create("https://tile.openstreetmap.org/{level}/{col}/{row}.png"))),
graphicOverlays = listOf(GraphicsOverlay()),locationDisplay = LocationDisplay())
val mapStateFlow = _mapStateDataFlow.asStateFlow()

init{
viewmodelScope.launch{
val wmsService =
WmsService(url = "http://geodata.nationaalgeoregister.nl/cbsprovincies/ows?SERVICE=WMS&request=Getcapabilities")
wmsService.load().onSuccess {
val wmsLayer = WmsLayer(wmsService.serviceInfo!!.layerInfos)
wmsLayer.load().onSuccess {
Timber.d("load success")
// TODO("Load new instance of ArcGIS Map with wms added to operational layer")
}.onFailure { exception ->
Timber.e(
exception, "Failed to load WMS layer"
)
// Handle error, e.g., show a message to the user
}
}.onFailure { e ->
Timber.e(
e, "Failed to load WMS Service: ${entry.path}"
)
}
}
}
0 Kudos
1 Solution

Accepted Solutions
Shubham_Sharma
Esri Contributor

@NishanKhadka Thanks for clarifying! I believe you would need to retrieve the sublayerInfos which should help load all the WMS layers from the service without providing the layerName: 

 

val layerInfos = wmsService.serviceInfo!!.layerInfos[0].sublayerInfos
val wmsLayer = WmsLayer(layerInfos)
wmsLayer.load().onSuccess {
    // Add to map's operational layers...
}

 

Just to note, each layerInfo may contain subLayers, and the subLayers may contain additional subLayers. I would inspect the service you are using to configure which layers to/not to display on the MapView. 

View solution in original post

4 Replies
Shubham_Sharma
Esri Contributor

While reading the code I noticed that you are using a LocationDisplay with the Composable MapView. To be able to set a LocationDisplay, you would need to define an ApplicationContext:

 

ArcGISEnvironment.applicationContext = this

// Alternatively, you can set the application context using the 
// LocationDisplay helper function from a Composable scope:

// Checks that ArcGISEnvironment.applicationContext is set and if not, sets one.
val myLocationDisplay = rememberLocationDisplay()

 

I can load a WMS layer by modifying your code using the above changes: 


import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.ViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.arcgismaps.ApiKey
import com.arcgismaps.ArcGISEnvironment
import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.Basemap
import com.arcgismaps.mapping.layers.WebTiledLayer
import com.arcgismaps.mapping.layers.WmsLayer
import com.arcgismaps.mapping.layers.WmsService
import com.arcgismaps.mapping.view.GraphicsOverlay
import com.arcgismaps.mapping.view.LocationDisplay
import com.arcgismaps.toolkit.geoviewcompose.MapView
import com.esri.arcgismaps.sample.sampleslib.theme.SampleAppTheme
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

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)
        // application context is required for LocationDisplay
        ArcGISEnvironment.applicationContext = this

        setContent {
            SampleAppTheme {
                EsriMap()
            }
        }
    }

    @Composable
    fun EsriMap() {
        val mapViewModel: MapViewModel = viewModel()
        val mapState by mapViewModel.mapStateFlow.collectAsStateWithLifecycle()

        MapView(
            modifier = Modifier
                .fillMaxSize(),
            arcGISMap = mapState.map,
            graphicsOverlays = mapState.graphicOverlays,
            locationDisplay = mapState.locationDisplay
        )
    }
}

class MapViewModel : ViewModel() {
    private val _mapStateDataFlow = MutableStateFlow(
        ArcGisMapData(
            map = ArcGISMap(Basemap(WebTiledLayer.create("https://tile.openstreetmap.org/{level}/{col}/{row}.png"))),
            graphicOverlays = listOf(GraphicsOverlay()),
            locationDisplay = LocationDisplay()
        )
    )

    val mapStateFlow = _mapStateDataFlow.asStateFlow()

    init {
        viewModelScope.launch {
            val wmsService =
                WmsService(url = "http://geodata.nationaalgeoregister.nl/cbsprovincies/ows?SERVICE=WMS&request=Getcapabilities")
            wmsService.load().onSuccess {
                val wmsLayer = WmsLayer(wmsService.serviceInfo!!.layerInfos)
                wmsLayer.load().onSuccess {
                    Log.e("TAG", "load success")
                }.onFailure { exception ->
                    Log.e("TAG", "${exception.message}: Failed to load WMS layer")
                }
            }.onFailure { exception ->
                Log.e("TAG", "${exception.message}: Failed to load WMS layer")
            }
        }
    }
}

data class ArcGisMapData(
    var map: ArcGISMap,
    var graphicOverlays: List<GraphicsOverlay>,
    var locationDisplay: LocationDisplay
)

 

0 Kudos
NishanKhadka
Emerging Contributor

Thank you for the quick response @Shubham_Sharma, I have the context and apikey for ArcGIS environement provided from the app's application class.
What I am struggling with is displaying the WMS overlay on the map. 
For instance, if I follow the sample example provided and add the WMS layer the following way:

val map =
ArcGISMap(Basemap(WebTiledLayer.create("https://tile.openstreetmap.org/{level}/{col}/{row}.png")))
val wmsLayer = WmsLayer(url = "https://nowcoast.noaa.gov/geoserver/observations/weather_radar/wms",layerNames = listOf("conus_base_reflectivity_mosaic"))
map.operationalLayers.add(wmsLayer)
_mapStateDataFlow.update { dataState ->
dataState.copy(map = map)
}
wmsLayer.load().onSuccess {
Timber.d("load successful")

}.onFailure {
Timber.d("Failed to load")
}

 It loads and displays the WMS layer on the map.

But, if I do not know the layerNames, then 
I will need to use 

https://nowcoast.noaa.gov/geoserver/observations/weather_radar/wms?service=WMS&request=GetCapabilities

as specified in WmsLayer (ArcGIS Runtime SDK for Android 100.15.6)

But when I do the following:

val map =
ArcGISMap(Basemap(WebTiledLayer.create("https://tile.openstreetmap.org/{level}/{col}/{row}.png")))
val wmsService =
WmsService(url = "https://nowcoast.noaa.gov/geoserver/observations/weather_radar/wms?service=WMS&request=GetCapabilities")
wmsService.load().onSuccess {
if (wmsService.serviceInfo != null) {
val wmsLayer = WmsLayer(wmsService.serviceInfo!!.layerInfos)
wmsLayer.isVisible = true
map.operationalLayers.add(wmsLayer)
_mapStateDataFlow.update { dataState ->
dataState.copy(map = map)
}
wmsLayer.load().onSuccess {
Timber.d("load successful")

}.onFailure {
Timber.d("Failed to load")
}
}
}

The log comes as load successful, but I don't see any overlay on the map.  I have attached screenshots below of when I do it as in the Kotlin Sample with known layerName (WMS overlay is displayed) and another where I do not know the layerNames (WMS overlay not displayed). Please do let me know if I am missing something.

One thing I noticed is : when I do it as in the sample - and log the url from the WmsLayer instance,


val wmsLayer = WmsLayer(url = "https://nowcoast.noaa.gov/geoserver/observations/weather_radar/wms",layerNames = listOf("conus_base_reflectivity_mosaic"))

Timber.d("url is ${wmsLayer.url}") //prints above url

I get the provided url "https://nowcoast.noaa.gov/geoserver/observations/weather_radar/wms" from it.

But when I do the same thing after creating the WmsLayer from WmsLayer(wmsService.serviceInfo!!.layerInfos),

val wmsService =
WmsService(url = "https://nowcoast.noaa.gov/geoserver/observations/weather_radar/wms?service=WMS&request=GetCapabilities")
wmsService.load().onSuccess {
if (wmsService.serviceInfo != null) {
val wmsLayer = WmsLayer(wmsService.serviceInfo!!.layerInfos)
Timber.d("url is ${wmsLayer.url}") // prints empty string
}


 
I get empty url. I wonder if that's why I do not get the overlays displayed as there is no url to fetch from. 

0 Kudos
Shubham_Sharma
Esri Contributor

@NishanKhadka Thanks for clarifying! I believe you would need to retrieve the sublayerInfos which should help load all the WMS layers from the service without providing the layerName: 

 

val layerInfos = wmsService.serviceInfo!!.layerInfos[0].sublayerInfos
val wmsLayer = WmsLayer(layerInfos)
wmsLayer.load().onSuccess {
    // Add to map's operational layers...
}

 

Just to note, each layerInfo may contain subLayers, and the subLayers may contain additional subLayers. I would inspect the service you are using to configure which layers to/not to display on the MapView. 

NishanKhadka
Emerging Contributor

@Shubham_Sharma Thank you for your guidance!  It now make sense to use the WmsService class to load the layers available and configure which layers/sublayers to load on the map. Then load it using the WmsLayer class.  Accepting this as solution! Thanks again!

0 Kudos