Apply basemap style parameters customization for a basemap, such as displaying all labels in a specific language or displaying every label in their corresponding local language.

Use case
When creating an application that’s used in multiple countries, basemaps can reflect the languages and cultures of the users’ location. For example, if an application user is in Greece, displaying the labels on a basemap in Greek reflects the local language. Customizing the language setting on the basemap can be controlled by an application user (such as by setting preferences) or implicitly managed within the application logic (by querying the locale of the platform running the application).
How to use the sample
This sample showcases the workflow of configuring basemap style parameters by displaying a basemap with labels in different languages and launches with a Viewpoint near Bulgaria, Greece, and Turkey, as they use three different alphabets: Cyrillic, Greek, and Latin, respectively. By default, the BasemapStyleLanguageStrategy is set to Local, which displays all labels in their corresponding local language. This can be changed to Global, which displays all labels in English. The SpecificLanguage setting sets all labels to a selected language and overrides the BasemapStyleLanguageStrategy settings.
Pan and zoom to navigate the map and see how different labels are displayed in these countries depending on the selected BasemapStyleLanguageStrategy and SpecificLanguage: all English, all Greek, all Bulgarian, all Turkish, or each their own.
How it works
- Create a
BasemapStyleParametersobject. - Configure customization preferences on the
BasemapStyleParametersobject, for instance:- setting the
LanguageStrategytoBasemapStyleLanguageStrategy.Localor BasemapStyleLanguageStrategy.Specific(Locale.forLanguageTag("el"))changes the label language to Greek.
- setting the
- The
SpecificLanguagealways overrides theLanguageStrategy, which means the specific language needs to be set to an empty string in order to use the language strategy. - Create a basemap using a
BasemapStyleand theBasemapStyleParameters. - Assign the configured basemap to the
Map’sbasemapproperty. - To modify the basemap style, for example if you want to change your preferences, repeat the above steps.
Relevant API
- Basemap
- BasemapStyleLanguageStrategy
- BasemapStyleParameters
- Map
- MapView
About the data
The main data for this sample is the BasemapStyle, which includes basemaps that support both language localization and global language setting. The supported languages, along with their language code, can be found in the API’s documentation.
Additional information
This sample uses the GeoView-Compose Toolkit module to be able to implement a composable MapView.
For ArcGIS Basemap styles, language localization becomes visible when you zoom in closer.
Tags
basemap style, geoview-compose, language, language strategy, map, point, toolkit, viewpoint
Sample Code
/* 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.configurebasemapstyleparameters
import android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Surfaceimport androidx.compose.runtime.Composableimport com.arcgismaps.ApiKeyimport com.arcgismaps.ArcGISEnvironmentimport com.esri.arcgismaps.sample.configurebasemapstyleparameters.screens.MainScreenimport com.esri.arcgismaps.sample.sampleslib.theme.SampleAppTheme
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.ACCESS_TOKEN) enableEdgeToEdge() setContent { SampleAppTheme { SampleApp() } } }
@Composable private fun SampleApp() { Surface( color = MaterialTheme.colorScheme.background ) { MainScreen( sampleName = getString(R.string.configure_basemap_style_parameters_app_name) ) } }}/* 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.configurebasemapstyleparameters.components
import android.app.Applicationimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.setValueimport androidx.lifecycle.AndroidViewModelimport com.arcgismaps.geometry.Pointimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.Basemapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.BasemapStyleLanguageStrategyimport com.arcgismaps.mapping.BasemapStyleParametersimport com.arcgismaps.mapping.Viewpointimport java.util.Locale
class MapViewModel(application: Application) : AndroidViewModel(application) {
val map = ArcGISMap(BasemapStyle.ArcGISLightGray).apply { // Focus the viewpoint on an area where the different languages are best showcased: // Bulgaria / Greece / Turkey, as they use three different alphabets: Cyrillic, Greek, and // Latin, respectively. initialViewpoint = Viewpoint( center = Point(3144804.0, 4904598.0), scale = 1e7 ) }
// a list of language strategies options val languageStrategyOptions = listOf("Global", "Local")
// keep track of selected language strategy state var languageStrategy by mutableStateOf(languageStrategyOptions[1]) private set
// set language strategy and call createNewBasemapStyleParameters fun updateLanguageStrategy(strategy: String) { languageStrategy = strategy createNewBasemapStyleParameters(languageStrategy, specificLanguage) }
// a list of sample language options val specificLanguageOptions = listOf("None", "Bulgarian", "Greek", "Turkish")
// keep track of selected specific language state var specificLanguage by mutableStateOf(specificLanguageOptions[0]) private set
// set specific language and call createNewBasemapStyleParameters fun updateSpecificStrategy(language: String) { specificLanguage = language createNewBasemapStyleParameters(languageStrategy, specificLanguage) }
init { // initialize the app with a local basemap style strategy createNewBasemapStyleParameters(languageStrategy, specificLanguage) }
/** * Basemap is immutable so we need to create a new one to set new parameters. Uses an * OpenStreetMap basemap style, because they support localization. */ private fun createNewBasemapStyleParameters( languageStrategy: String, specificLanguage: String ) { val basemapStyleParameters = BasemapStyleParameters().apply { // A SpecificLanguage setting overrides the BasemapStyleLanguageStrategy settings when // the BasemapStyleParameters.Specific(Locale.forLanguageTag("...") is a non-empty string. // Setting the specific language back to an empty string allows the strategy to be used. this.languageStrategy = when (specificLanguage) { "None" -> { BasemapStyleLanguageStrategy.Specific(Locale.forLanguageTag("")) when (languageStrategy) { // set the language strategy based on the selected radio buttons "Global" -> BasemapStyleLanguageStrategy.Global "Local" -> BasemapStyleLanguageStrategy.Local else -> throw IllegalArgumentException("Invalid language strategy") } } // set the specific language based on the selected drop down option "Bulgarian" -> BasemapStyleLanguageStrategy.Specific(Locale.forLanguageTag("bg")) "Greek" -> BasemapStyleLanguageStrategy.Specific(Locale.forLanguageTag("el")) "Turkish" -> BasemapStyleLanguageStrategy.Specific(Locale.forLanguageTag("tr")) else -> throw IllegalArgumentException("Invalid specific language") } } // set a new basemap with the chosen style parameters map.setBasemap(Basemap(BasemapStyle.OsmLightGray, basemapStyleParameters)) }}/* 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.configurebasemapstyleparameters.screens
import androidx.compose.foundation.borderimport androidx.compose.foundation.clickableimport androidx.compose.foundation.layout.Arrangementimport androidx.compose.foundation.layout.Boximport androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.Rowimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.fillMaxWidthimport androidx.compose.foundation.layout.heightInimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.layout.widthInimport androidx.compose.foundation.layout.wrapContentHeightimport androidx.compose.foundation.layout.wrapContentSizeimport androidx.compose.foundation.layout.wrapContentWidthimport androidx.compose.foundation.selection.selectableimport androidx.compose.material3.BottomSheetScaffoldimport androidx.compose.material3.Buttonimport androidx.compose.material3.DropdownMenuimport androidx.compose.material3.DropdownMenuItemimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.HorizontalDividerimport androidx.compose.material3.RadioButtonimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.rememberBottomSheetScaffoldStateimport androidx.compose.runtime.Composableimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.runtime.setValueimport androidx.compose.ui.Alignmentimport androidx.compose.ui.Modifierimport androidx.compose.ui.graphics.Colorimport androidx.compose.ui.unit.dpimport androidx.lifecycle.viewmodel.compose.viewModelimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.esri.arcgismaps.sample.configurebasemapstyleparameters.components.MapViewModelimport com.esri.arcgismaps.sample.sampleslib.components.SampleTopAppBarimport kotlinx.coroutines.launch
/** * Main screen layout for the sample app */@OptIn(ExperimentalMaterial3Api::class)@Composablefun MainScreen(sampleName: String) { // create a ViewModel to handle MapView interactions val mapViewModel: MapViewModel = viewModel()
// handle the BottomSheetScaffold state val bottomSheetScope = rememberCoroutineScope() val bottomSheetState = rememberBottomSheetScaffoldState().apply { bottomSheetScope.launch { // show the bottom sheet on launch bottomSheetState.expand() } } Scaffold( topBar = { SampleTopAppBar(title = sampleName) }, content = { Box( modifier = Modifier .fillMaxSize() .padding(it), contentAlignment = Alignment.Center ) { MapView( modifier = Modifier .fillMaxSize(), arcGISMap = mapViewModel.map ) // show the "Show controls" button only when the bottom sheet is not visible if (!bottomSheetState.bottomSheetState.isVisible) { Button( modifier = Modifier .align(Alignment.BottomCenter) .padding(bottom = 24.dp), onClick = { bottomSheetScope.launch { bottomSheetState.bottomSheetState.expand() } }, ) { Text( text = "Show controls" ) } } // constrain the bottom sheet to a maximum width of 380dp Box( modifier = Modifier .widthIn(0.dp, 380.dp) ) { BottomSheetScaffold( scaffoldState = bottomSheetState, sheetContent = { Box( // constrain the height of the bottom sheet to 160dp Modifier .heightIn(max = 160.dp) .padding(8.dp) ) { Row(horizontalArrangement = Arrangement.SpaceBetween) { Column(Modifier.weight(0.5f)) { // UI for setting the language strategy LanguageStrategyControls( languageStrategyOptions = mapViewModel.languageStrategyOptions, onLanguageStrategyChange = { languageStrategy -> mapViewModel.updateLanguageStrategy(languageStrategy) }, languageStrategy = mapViewModel.languageStrategy, enabled = (mapViewModel.specificLanguage == "None") ) }
Column(Modifier.weight(0.5f)) { // UI for setting the specific language SpecificLanguageControls( specificLanguageOptions = mapViewModel.specificLanguageOptions, onSpecificLanguageChange = { specificLanguage -> mapViewModel.updateSpecificStrategy(specificLanguage) }, specificLanguage = mapViewModel.specificLanguage ) } } } } ) { } } } } )}
/** * Define the UI radio buttons for selecting language strategy: "Global" or "Local". */@Composableprivate fun LanguageStrategyControls( languageStrategyOptions: List<String>, onLanguageStrategyChange: (String) -> Unit, languageStrategy: String, enabled: Boolean) { Text( text = "Set language strategy:" )
languageStrategyOptions.forEach { text -> Row( Modifier .wrapContentWidth() .wrapContentHeight() .selectable( selected = (text == languageStrategy), onClick = { if (enabled) { onLanguageStrategyChange(text) } } ) ) { RadioButton( selected = (text == languageStrategy), onClick = { if (enabled) { onLanguageStrategyChange(text) } }, enabled = enabled ) Text( text = text, modifier = Modifier .align(Alignment.CenterVertically) .wrapContentHeight() .wrapContentWidth() ) } }}
/** * Define the UI dropdown menu for selecting specific language: "None", "Bulgarian", "Greek" or * "Turkish". */@Composableprivate fun SpecificLanguageControls( specificLanguageOptions: List<String>, onSpecificLanguageChange: (String) -> Unit, specificLanguage: String) { Text( text = "Set specific language:" ) var expanded by remember { mutableStateOf(false) } Box( modifier = Modifier .wrapContentHeight() .wrapContentWidth() .wrapContentSize(Alignment.TopStart) .padding(top = 16.dp) ) { Text( specificLanguage, modifier = Modifier .fillMaxWidth() .clickable(onClick = { expanded = true }) .border( 2.dp, Color.LightGray ) .padding(horizontal = 16.dp, vertical = 12.dp) ) DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false } ) { specificLanguageOptions.forEachIndexed { index, specificLanguage -> DropdownMenuItem( text = { Text(specificLanguage) }, onClick = { onSpecificLanguageChange(specificLanguage) expanded = false }) // show a divider between dropdown menu options if (index < specificLanguageOptions.lastIndex) { HorizontalDivider() } } } }}