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}"
)
}
}
}
Solved! Go to Solution.
@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.
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
)
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.
@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.
@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!