अपने Android ऐप्लिकेशन में मैप जोड़ें (Compose के साथ Kotlin)

1. शुरू करने से पहले

इस कोडलैब में, आपको अपने ऐप्लिकेशन में Maps SDK for Android को इंटिग्रेट करने का तरीका बताया गया है. साथ ही, इसकी मुख्य सुविधाओं का इस्तेमाल करने का तरीका भी बताया गया है. इसके लिए, एक ऐसा ऐप्लिकेशन बनाया गया है जो अलग-अलग तरह के मार्कर का इस्तेमाल करके, अमेरिका के कोलोराडो में मौजूद पहाड़ों का मैप दिखाता है. इसके अलावा, आपको मैप पर अन्य शेप बनाने का तरीका भी बताया जाएगा.

कोड लैब पूरा करने के बाद, यह इस तरह दिखेगा:

ज़रूरी शर्तें

आपको क्या करना होगा

  • Android ऐप्लिकेशन में GoogleMap जोड़ने के लिए, Android के लिए Maps SDK टूल के लिए Maps Compose लाइब्रेरी को चालू करें और उसका इस्तेमाल करें
  • मार्कर जोड़ना और उन्हें पसंद के मुताबिक बनाना
  • मैप पर पॉलीगॉन बनाना
  • प्रोग्राम के हिसाब से कैमरे के व्यू पॉइंट को कंट्रोल करना

आपको किन चीज़ों की ज़रूरत होगी

2. सेट अप करें

इसके बाद, आपको Android के लिए Maps SDK चालू करना होगा.

Google Maps Platform सेट अप करना

अगर आपके पास Google Cloud Platform खाता और बिलिंग की सुविधा वाला प्रोजेक्ट नहीं है, तो बिलिंग की सुविधा वाला खाता और प्रोजेक्ट बनाएं. ऐसा करने का तरीका जानने के लिए, कृपया Google Maps Platform का इस्तेमाल शुरू करना देखें.

  1. Cloud Console में, प्रोजेक्ट वाले ड्रॉप-डाउन मेन्यू पर क्लिक करें. इसके बाद, उस प्रोजेक्ट को चुनें जिसे इस कोडलैब के लिए इस्तेमाल करना है.

  1. इस कोडलैब के लिए ज़रूरी Google Maps Platform API और एसडीके को Google Cloud Marketplace में जाकर चालू करें. ऐसा करने के लिए, इस वीडियो या दस्तावेज़ में बताया गया तरीका अपनाएं.
  2. Cloud Console के क्रेडेंशियल पेज पर जाकर, एक एपीआई पासकोड जनरेट करें. ऐसा करने के लिए, इस वीडियो या दस्तावेज़ में बताया गया तरीका अपनाएं. Google Maps Platform का इस्तेमाल करने के लिए, एपीआई पासकोड ज़रूरी है.

3. तुरंत शुरू करना

इस कोडलैब को जल्द से जल्द शुरू करने के लिए, यहां कुछ शुरुआती कोड दिए गए हैं. आपके पास सीधे समाधान पर जाने का विकल्प है. हालांकि, अगर आपको इसे खुद बनाने के लिए सभी चरणों का पालन करना है, तो पढ़ना जारी रखें.

  1. अगर आपने git इंस्टॉल किया है, तो रिपॉज़िटरी को क्लोन करें.
git clone https://github.com/googlemaps-samples/codelab-maps-platform-101-compose.git

इसके अलावा, सोर्स कोड को डाउनलोड करने के लिए, इस बटन पर क्लिक करें.

  1. कोड मिलने के बाद, Android Studio में starter डायरेक्ट्री में मौजूद प्रोजेक्ट खोलें.

4. प्रोजेक्ट में एपीआई पासकोड जोड़ना

इस सेक्शन में, एपीआई पासकोड को सेव करने का तरीका बताया गया है, ताकि आपका ऐप्लिकेशन इसे सुरक्षित तरीके से रेफ़र कर सके. आपको अपने वर्शन कंट्रोल सिस्टम में एपीआई पासकोड की जांच नहीं करनी चाहिए. इसलिए, हम इसे secrets.properties फ़ाइल में सेव करने का सुझाव देते हैं. यह फ़ाइल, आपके प्रोजेक्ट की रूट डायरेक्ट्री की लोकल कॉपी में रखी जाएगी. secrets.properties फ़ाइल के बारे में ज़्यादा जानने के लिए, Gradle प्रॉपर्टी फ़ाइलें देखें.

इस काम को आसान बनाने के लिए, हमारा सुझाव है कि Android के लिए सीक्रेट ग्रेडल प्लग इन का इस्तेमाल करें.

अपने Google Maps प्रोजेक्ट में, Android के लिए Secrets Gradle Plugin इंस्टॉल करने के लिए:

  1. Android Studio में, अपनी टॉप-लेवल build.gradle.kts फ़ाइल खोलें. इसके बाद, buildscript में मौजूद dependencies एलिमेंट में यह कोड जोड़ें.
    buildscript {
        dependencies {
            classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
        }
    }
    
  2. मॉड्यूल-लेवल की build.gradle.kts फ़ाइल खोलें और plugins एलिमेंट में यह कोड जोड़ें.
    plugins {
        // ...
        id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
    }
    
  3. अपने मॉड्यूल-लेवल की build.gradle.kts फ़ाइल में, पक्का करें कि targetSdk और compileSdk की वैल्यू कम से कम 34 पर सेट हो.
  4. फ़ाइल सेव करें और अपने प्रोजेक्ट को Gradle के साथ सिंक करें.
  5. अपनी टॉप-लेवल डायरेक्ट्री में secrets.properties फ़ाइल खोलें. इसके बाद, यह कोड जोड़ें. YOUR_API_KEY को अपने एपीआई पासकोड से बदलें. अपनी कुंजी को इस फ़ाइल में सेव करें, क्योंकि secrets.properties को वर्शन कंट्रोल सिस्टम में शामिल नहीं किया जाता है.
    MAPS_API_KEY=YOUR_API_KEY
    
  6. फ़ाइल सेव करें.
  7. अपनी टॉप-लेवल डायरेक्ट्री में local.defaults.properties फ़ाइल बनाएं. यह फ़ाइल, secrets.properties फ़ाइल वाले फ़ोल्डर में ही होनी चाहिए. इसके बाद, यह कोड जोड़ें.
        MAPS_API_KEY=DEFAULT_API_KEY
    
    इस फ़ाइल का मकसद, एपीआई पासकोड के लिए बैकअप की जगह उपलब्ध कराना है. ऐसा तब किया जाता है, जब secrets.properties फ़ाइल नहीं मिलती है, ताकि बिल्ड न रुकें. ऐसा तब होता है, जब आपने वर्शन कंट्रोल सिस्टम से ऐप्लिकेशन को क्लोन किया हो और आपने एपीआई पासकोड देने के लिए, अब तक स्थानीय तौर पर secrets.properties फ़ाइल नहीं बनाई हो.
  8. फ़ाइल सेव करें.
  9. अपनी AndroidManifest.xml फ़ाइल में, com.google.android.geo.API_KEY पर जाएं और android:value एट्रिब्यूट को अपडेट करें. अगर <meta-data> टैग मौजूद नहीं है, तो इसे <application> टैग के चाइल्ड के तौर पर बनाएं.
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="${MAPS_API_KEY}" />
    
  10. Android Studio में, अपने मॉड्यूल-लेवल की build.gradle.kts फ़ाइल खोलें और secrets प्रॉपर्टी में बदलाव करें. अगर secrets प्रॉपर्टी मौजूद नहीं है, तो उसे जोड़ें.प्लगिन की प्रॉपर्टी में बदलाव करके, propertiesFileName को secrets.properties पर सेट करें, defaultPropertiesFileName को local.defaults.properties पर सेट करें, और अन्य प्रॉपर्टी सेट करें.
    secrets {
        // Optionally specify a different file name containing your secrets.
        // The plugin defaults to "local.properties"
        propertiesFileName = "secrets.properties"
    
        // A properties file containing default secret values. This file can be
        // checked in version control.
        defaultPropertiesFileName = "local.defaults.properties"
    }
    

5. Google Maps जोड़ना

इस सेक्शन में, आपको एक Google मैप जोड़ना होगा, ताकि ऐप्लिकेशन लॉन्च करने पर वह लोड हो जाए.

Maps Compose डिपेंडेंसी जोड़ना

अब आपके ऐप्लिकेशन में एपीआई पासकोड को ऐक्सेस किया जा सकता है. इसलिए, अगला चरण यह है कि अपने ऐप्लिकेशन की build.gradle.kts फ़ाइल में, Maps SDK for Android की डिपेंडेंसी जोड़ें. Jetpack Compose का इस्तेमाल करके ऐप्लिकेशन बनाने के लिए, Maps Compose लाइब्रेरी का इस्तेमाल करें. यह लाइब्रेरी, Maps SDK for Android के एलिमेंट को कंपोज़ेबल फ़ंक्शन और डेटा टाइप के तौर पर उपलब्ध कराती है.

build.gradle.kts

ऐप्लिकेशन लेवल की build.gradle.kts फ़ाइल में, कंपोज़ नहीं की गई Maps SDK for Android डिपेंडेंसी बदलें:

dependencies {
    // ...

    // Google Maps SDK -- these are here for the data model.  Remove these dependencies and replace
    // with the compose versions.
    implementation("com.google.android.gms:play-services-maps:18.2.0")
    // KTX for the Maps SDK for Android library
    implementation("com.google.maps.android:maps-ktx:5.0.0")
    // KTX for the Maps SDK for Android Utility Library
    implementation("com.google.maps.android:maps-utils-ktx:5.0.0")
}

इन्हें कंपोज़ेबल फ़ंक्शन के साथ इस्तेमाल किया जा सकता है:

dependencies {
    // ...

    // Google Maps Compose library
    val mapsComposeVersion = "4.4.1"
    implementation("com.google.maps.android:maps-compose:$mapsComposeVersion")
    // Google Maps Compose utility library
    implementation("com.google.maps.android:maps-compose-utils:$mapsComposeVersion")
    // Google Maps Compose widgets library
    implementation("com.google.maps.android:maps-compose-widgets:$mapsComposeVersion")
}

Google मैप कंपोज़ेबल जोड़ना

MountainMap.kt में, MapMountain कंपोज़ेबल में नेस्ट किए गए Box कंपोज़ेबल के अंदर GoogleMap कंपोज़ेबल जोड़ें.

import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.GoogleMapComposable
// ...

@Composable
fun MountainMap(
    paddingValues: PaddingValues,
    viewState: MountainsScreenViewState.MountainList,
    eventFlow: Flow<MountainsScreenEvent>,
    selectedMarkerType: MarkerType,
) {
    var isMapLoaded by remember { mutableStateOf(false) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(paddingValues)
    ) {
        // Add GoogleMap here
        GoogleMap(
            modifier = Modifier.fillMaxSize(),
            onMapLoaded = { isMapLoaded = true }
        )

        // ...
    }
}

अब ऐप्लिकेशन बनाएं और उसे चलाएं. बधाई हो! आपको एक मैप दिखेगा. यह मैप, कुख्यात नल आइलैंड पर केंद्रित होगा. इसे अक्षांश शून्य और देशांतर शून्य के नाम से भी जाना जाता है. बाद में, आपको मैप को अपनी पसंद की जगह और ज़ूम लेवल पर सेट करने का तरीका बताया जाएगा. फ़िलहाल, अपनी पहली जीत का जश्न मनाएं!

6. क्लाउड पर मैप की स्टाइलिंग

क्लाउड पर मैप की स्टाइलिंग की सुविधाओं का इस्तेमाल करके, अपने मैप में मनमुताबिक स्टाइल जोड़े जा सकते हैं.

मैप आईडी बनाना

अगर आपने अब तक मैप आईडी नहीं बनाया है और उसे अपने मैप के स्टाइल से नहीं जोड़ा है, तो इसका तरीका जानने और अपनाने के लिए, मैप के आईडी गाइड देखें:

  1. मैप आईडी बनाना.
  2. मैप के किसी स्टाइल से एक मैप आईडी जोड़ना.

अपने ऐप्लिकेशन में मैप आईडी जोड़ना

आपने जो मैप आईडी बनाया है उसका इस्तेमाल करने के लिए, GoogleMap कंपोज़ेबल को इंस्टैंटिएट करते समय, मैप आईडी का इस्तेमाल करके GoogleMapOptions ऑब्जेक्ट बनाएं. इसके बाद, इसे कंस्ट्रक्टर में googleMapOptionsFactory पैरामीटर को असाइन करें.

GoogleMap(
    // ...
    googleMapOptionsFactory = {
        GoogleMapOptions().mapId("MyMapId")
    }
)

यह प्रोसेस पूरी करने के बाद, ऐप्लिकेशन चलाएं और अपने चुने गए स्टाइल में मैप देखें!

7. मार्कर का डेटा लोड करना

इस ऐप्लिकेशन का मुख्य काम, लोकल स्टोरेज से पहाड़ों का कलेक्शन लोड करना और उन पहाड़ों को GoogleMap में दिखाना है. इस चरण में, आपको माउंटेन डेटा लोड करने और उसे यूज़र इंटरफ़ेस (यूआई) पर दिखाने के लिए उपलब्ध कराए गए इंफ़्रास्ट्रक्चर के बारे में जानकारी मिलेगी.

पहाड़

Mountain डेटा क्लास में, हर पर्वत के बारे में पूरा डेटा होता है.

data class Mountain(
    val id: Int,
    val name: String,
    val location: LatLng,
    val elevation: Meters,
)

ध्यान दें कि बाद में पहाड़ों को उनकी ऊंचाई के हिसाब से बांटा जाएगा. कम से कम 14,000 फ़ीट ऊंचे पहाड़ों को फ़ोर्टीनर कहा जाता है. स्टार्टर कोड में एक एक्सटेंशन फ़ंक्शन शामिल होता है. यह फ़ंक्शन आपके लिए इस जांच को पूरा करता है.

/**
 * Extension function to determine whether a mountain is a "14er", i.e., has an elevation greater
 * than 14,000 feet (~4267 meters).
 */
fun Mountain.is14er() = elevation >= 14_000.feet

MountainsScreenViewState

MountainsScreenViewState क्लास में, व्यू को रेंडर करने के लिए ज़रूरी सारा डेटा होता है. यह Loading या MountainList स्थिति में हो सकता है. यह इस बात पर निर्भर करता है कि पहाड़ों की सूची लोड हो गई है या नहीं.

/**
 * Sealed class representing the state of the mountain map view.
 */
sealed class MountainsScreenViewState {
  data object Loading : MountainsScreenViewState()
  data class MountainList(
    // List of the mountains to display
    val mountains: List<Mountain>,

    // Bounding box that contains all of the mountains
    val boundingBox: LatLngBounds,

    // Switch indicating whether all the mountains or just the 14ers
    val showingAllPeaks: Boolean = false,
  ) : MountainsScreenViewState()
}

ये क्लास उपलब्ध हैं: MountainsRepository और MountainsViewModel

स्टार्टर प्रोजेक्ट में, आपको MountainsRepository क्लास दी गई है. यह क्लास, पहाड़ों की उन जगहों की सूची को पढ़ती है जो GPS Exchange Format या GPX फ़ाइल में सेव की गई हैं top_peaks.gpx. mountainsRepository.loadMountains() को कॉल करने पर, StateFlow<List<Mountain>> मिलता है.

MountainsRepository

class MountainsRepository(@ApplicationContext val context: Context) {
  private val _mountains = MutableStateFlow(emptyList<Mountain>())
  val mountains: StateFlow<List<Mountain>> = _mountains
  private var loaded = false

  /**
   * Loads the list of mountains from the list of mountains from the raw resource.
   */
  suspend fun loadMountains(): StateFlow<List<Mountain>> {
    if (!loaded) {
      loaded = true
      _mountains.value = withContext(Dispatchers.IO) {
        context.resources.openRawResource(R.raw.top_peaks).use { inputStream ->
          readMountains(inputStream)
        }
      }
    }
    return mountains
  }

  /**
   * Reads the [Waypoint]s from the given [inputStream] and returns a list of [Mountain]s.
   */
  private fun readMountains(inputStream: InputStream) =
    readWaypoints(inputStream).mapIndexed { index, waypoint ->
      waypoint.toMountain(index)
    }.toList()

  // ...
}

MountainsViewModel

MountainsViewModel एक ViewModel क्लास है. यह पहाड़ों के कलेक्शन को लोड करती है और mountainsScreenViewState के ज़रिए, उस कलेक्शन के साथ-साथ यूज़र इंटरफ़ेस (यूआई) की स्थिति के अन्य हिस्सों को भी दिखाती है. mountainsScreenViewState एक हॉट StateFlow है. यूज़र इंटरफ़ेस (यूआई) इसे collectAsState एक्सटेंशन फ़ंक्शन का इस्तेमाल करके, बदलने वाली स्थिति के तौर पर देख सकता है.

MountainsViewModel, ऐप्लिकेशन की सभी स्थितियों को सेव करता है. ऐसा, साउंड आर्किटेक्चर के सिद्धांतों के मुताबिक किया जाता है. यूज़र इंटरफ़ेस, onEvent तरीके का इस्तेमाल करके, उपयोगकर्ता के इंटरैक्शन को व्यू मॉडल में भेजता है.

@HiltViewModel
class MountainsViewModel
@Inject
constructor(
  mountainsRepository: MountainsRepository
) : ViewModel() {
  private val _eventChannel = Channel<MountainsScreenEvent>()

  // Event channel to send events to the UI
  internal fun getEventChannel() = _eventChannel.receiveAsFlow()

  // Whether or not to show all of the high peaks
  private var showAllMountains = MutableStateFlow(false)

  val mountainsScreenViewState =
    mountainsRepository.mountains.combine(showAllMountains) { allMountains, showAllMountains ->
      if (allMountains.isEmpty()) {
        MountainsScreenViewState.Loading
      } else {
        val filteredMountains =
          if (showAllMountains) allMountains else allMountains.filter { it.is14er() }
        val boundingBox = filteredMountains.map { it.location }.toLatLngBounds()
        MountainsScreenViewState.MountainList(
          mountains = filteredMountains,
          boundingBox = boundingBox,
          showingAllPeaks = showAllMountains,
        )
      }
    }.stateIn(
      scope = viewModelScope,
      started = SharingStarted.WhileSubscribed(5000),
      initialValue = MountainsScreenViewState.Loading
    )

  init {
    // Load the full set of mountains
    viewModelScope.launch {
      mountainsRepository.loadMountains()
    }
  }

  // Handle user events
  fun onEvent(event: MountainsViewModelEvent) {
    when (event) {
      OnZoomAll -> onZoomAll()
      OnToggleAllPeaks -> toggleAllPeaks()
    }
  }

  private fun onZoomAll() {
    sendScreenEvent(MountainsScreenEvent.OnZoomAll)
  }

  private fun toggleAllPeaks() {
    showAllMountains.value = !showAllMountains.value
  }

  // Send events back to the UI via the event channel
  private fun sendScreenEvent(event: MountainsScreenEvent) {
    viewModelScope.launch { _eventChannel.send(event) }
  }
}

अगर आपको इन क्लास को लागू करने के बारे में ज़्यादा जानना है, तो GitHub पर जाकर इन्हें ऐक्सेस किया जा सकता है. इसके अलावा, Android Studio में MountainsRepository और MountainsViewModel क्लास खोली जा सकती हैं.

ViewModel का इस्तेमाल करना

MainActivity में व्यू मॉडल का इस्तेमाल, viewState पाने के लिए किया जाता है. इस कोडलैब में, बाद में मार्कर रेंडर करने के लिए viewState का इस्तेमाल किया जाएगा. ध्यान दें कि यह कोड, स्टार्टर प्रोजेक्ट में पहले से ही शामिल है. इसे यहां सिर्फ़ रेफ़रंस के लिए दिखाया गया है.

val viewModel: MountainsViewModel by viewModels()
val screenViewState = viewModel.mountainsScreenViewState.collectAsState()
val viewState = screenViewState.value

8. कैमरे को सही जगह पर रखें

GoogleMap डिफ़ॉल्ट रूप से, अक्षांश शून्य और देशांतर शून्य पर होता है. आपको जिन मार्कर को रेंडर करना है वे अमेरिका के कोलोराडो राज्य में मौजूद हैं. व्यू मॉडल से मिले viewState में, LatLngBounds मौजूद होता है. इसमें सभी मार्कर शामिल होते हैं.

MountainMap.kt में, बाउंडिंग बॉक्स के सेंटर पर सेट किया गया CameraPositionState बनाएं. GoogleMap के cameraPositionState पैरामीटर को, अभी-अभी बनाए गए cameraPositionState वैरिएबल पर सेट करें.

fun MountainMap(
    // ...
) {
    // ...
    val cameraPositionState = rememberCameraPositionState {
        position = CameraPosition.fromLatLngZoom(viewState.boundingBox.center, 5f)
    }

    GoogleMap(
        // ...
        cameraPositionState = cameraPositionState,
    )
}

अब कोड चलाएं और देखें कि मैप, कोलोराडो पर फ़ोकस कर रहा है.

मार्कर की सीमाओं पर ज़ूम करें

मैप को मार्कर पर फ़ोकस करने के लिए, MountainMap.kt फ़ाइल के आखिर में zoomAll फ़ंक्शन जोड़ें. ध्यान दें कि इस फ़ंक्शन के लिए CoroutineScope की ज़रूरत होती है, क्योंकि कैमरे को नई जगह पर ले जाने की प्रोसेस एसिंक्रोनस होती है. इसे पूरा होने में समय लगता है.

fun zoomAll(
    scope: CoroutineScope,
    cameraPositionState: CameraPositionState,
    boundingBox: LatLngBounds
) {
    scope.launch {
        cameraPositionState.animate(
            update = CameraUpdateFactory.newLatLngBounds(boundingBox, 64),
            durationMs = 1000
        )
    }
}

इसके बाद, मार्कर कलेक्शन के आस-पास की सीमाएं बदलने पर या जब उपयोगकर्ता TopApp बार में ज़ूम एक्सटेंट बटन पर क्लिक करता है, तब zoomAll फ़ंक्शन को लागू करने के लिए कोड जोड़ें. ध्यान दें कि ज़ूम एक्सटेंट बटन को पहले से ही व्यू मॉडल को इवेंट भेजने के लिए कॉन्फ़िगर किया गया है. आपको सिर्फ़ व्यू मॉडल से उन इवेंट को इकट्ठा करना होगा और जवाब में zoomAll फ़ंक्शन को कॉल करना होगा.

एक्सटेंट बटन

fun MountainMap(
    // ...
) {
    // ...
    val scope = rememberCoroutineScope()

    LaunchedEffect(key1 = viewState.boundingBox) {
        zoomAll(scope, cameraPositionState, viewState.boundingBox)
    }

    LaunchedEffect(true) {
        eventFlow.collect { event ->
            when (event) {
                MountainsScreenEvent.OnZoomAll -> {
                    zoomAll(scope, cameraPositionState, viewState.boundingBox)
                }
            }
        }
    }
}

अब ऐप्लिकेशन चलाने पर, मैप उस जगह पर फ़ोकस करेगा जहां मार्कर लगाए जाएंगे. मैप की जगह बदली जा सकती है और उसे ज़ूम किया जा सकता है. ज़ूम एक्सटेंट बटन पर क्लिक करने से, मैप मार्कर वाली जगह पर फिर से फ़ोकस करेगा. यह तो कमाल हो गया! हालांकि, मैप में कुछ न कुछ दिखना चाहिए. अगले चरण में आपको यही करना है!

9. बुनियादी मार्कर

इस चरण में, आपको मैप में मार्कर जोड़ने होते हैं. ये मार्कर, उन लोकप्रिय जगहों को दिखाते हैं जिन्हें आपको मैप पर हाइलाइट करना है. आपको स्टार्टर प्रोजेक्ट में दी गई पहाड़ों की सूची का इस्तेमाल करना होगा. साथ ही, इन जगहों को मैप पर मार्कर के तौर पर जोड़ना होगा.

GoogleMap में कॉन्टेंट ब्लॉक जोड़कर शुरुआत करें. मार्कर कई तरह के होंगे. इसलिए, हर टाइप में ब्रांच करने के लिए when स्टेटमेंट जोड़ें. इसके बाद, अगले चरणों में हर टाइप को लागू करें.

GoogleMap(
    // ...
) {
    when (selectedMarkerType) {
        MarkerType.Basic -> {
            BasicMarkersMapContent(
                mountains = viewState.mountains,
            )
        }

        MarkerType.Advanced -> {
            AdvancedMarkersMapContent(
                mountains = viewState.mountains,
            )
        }

        MarkerType.Clustered -> {
            ClusteringMarkersMapContent(
                mountains = viewState.mountains,
            )
        }
    }
}

मार्कर जोड़ना

@GoogleMapComposable की मदद से BasicMarkersMapContent को एनोटेट करें. ध्यान दें कि GoogleMap कॉन्टेंट ब्लॉक में सिर्फ़ @GoogleMapComposable फ़ंक्शन का इस्तेमाल किया जा सकता है. mountains ऑब्जेक्ट में Mountain ऑब्जेक्ट की सूची होती है. आपको उस सूची में मौजूद हर पर्वत के लिए एक मार्कर जोड़ना होगा. इसके लिए, Mountain ऑब्जेक्ट से मिली जगह, नाम, और ऊंचाई की जानकारी का इस्तेमाल करें. इस जगह का इस्तेमाल, Marker के स्टेट पैरामीटर को सेट करने के लिए किया जाता है. इससे मार्कर की पोज़िशन कंट्रोल होती है.

// ...
import com.google.android.gms.maps.model.Marker
import com.google.maps.android.compose.GoogleMapComposable
import com.google.maps.android.compose.Marker
import com.google.maps.android.compose.rememberMarkerState

@Composable
@GoogleMapComposable
fun BasicMarkersMapContent(
    mountains: List<Mountain>,
    onMountainClick: (Marker) -> Boolean = { false }
) {
    mountains.forEach { mountain ->
        Marker(
            state = rememberMarkerState(position = mountain.location),
            title = mountain.name,
            snippet = mountain.elevation.toElevationString(),
            tag = mountain,
            onClick = { marker ->
                onMountainClick(marker)
                false
            },
            zIndex = if (mountain.is14er()) 5f else 2f
        )
    }
}

अब ऐप्लिकेशन चलाएं. आपको अभी-अभी जोड़े गए मार्कर दिखेंगे!

मार्कर को पसंद के मुताबिक बनाना

आपने जो मार्कर जोड़े हैं उन्हें पसंद के मुताबिक बनाने के लिए, कई विकल्प उपलब्ध हैं. इससे मार्कर को अलग से दिखाने और लोगों को काम की जानकारी देने में मदद मिलती है. इस टास्क में, हर मार्कर की इमेज को पसंद के मुताबिक बनाकर, उनमें से कुछ को एक्सप्लोर किया जाएगा.

स्टार्टर प्रोजेक्ट में, vectorToBitmap नाम का एक हेल्पर फ़ंक्शन शामिल होता है. इसका इस्तेमाल, @DrawableResource से BitmapDescriptor बनाने के लिए किया जाता है.

स्टार्टर कोड में एक माउंटेन आइकॉन, baseline_filter_hdr_24.xml शामिल है. इसका इस्तेमाल करके, मार्कर को पसंद के मुताबिक बनाया जा सकता है.

vectorToBitmap फ़ंक्शन, वेक्टर ड्रॉएबल को BitmapDescriptor में बदलता है, ताकि इसे Maps Library के साथ इस्तेमाल किया जा सके. आइकॉन के रंग, BitmapParameters इंस्टेंस का इस्तेमाल करके सेट किए जाते हैं.

data class BitmapParameters(
    @DrawableRes val id: Int,
    @ColorInt val iconColor: Int,
    @ColorInt val backgroundColor: Int? = null,
    val backgroundAlpha: Int = 168,
    val padding: Int = 16,
)

fun vectorToBitmap(context: Context, parameters: BitmapParameters): BitmapDescriptor {
    // ...
}

vectorToBitmap फ़ंक्शन का इस्तेमाल करके, दो कस्टम BitmapDescriptor बनाएं. एक 4,267 मीटर से ज़्यादा ऊंचे पहाड़ों के लिए और दूसरा सामान्य पहाड़ों के लिए. इसके बाद, आइकॉन सेट करने के लिए Marker कंपोज़ेबल के icon पैरामीटर का इस्तेमाल करें. साथ ही, आइकॉन के हिसाब से ऐंकर की जगह बदलने के लिए, anchor पैरामीटर सेट करें. सर्कुलर आइकॉन के लिए, सेंटर का इस्तेमाल करना बेहतर होता है.

@Composable
@GoogleMapComposable
fun BasicMarkersMapContent(
    // ...
) {
    // Create mountainIcon and fourteenerIcon
    val mountainIcon = vectorToBitmap(
        LocalContext.current,
        BitmapParameters(
            id = R.drawable.baseline_filter_hdr_24,
            iconColor = MaterialTheme.colorScheme.secondary.toArgb(),
            backgroundColor = MaterialTheme.colorScheme.secondaryContainer.toArgb(),
        )
    )

    val fourteenerIcon = vectorToBitmap(
        LocalContext.current,
        BitmapParameters(
            id = R.drawable.baseline_filter_hdr_24,
            iconColor = MaterialTheme.colorScheme.onPrimary.toArgb(),
            backgroundColor = MaterialTheme.colorScheme.primary.toArgb(),
        )
    )

    mountains.forEach { mountain ->
        val icon = if (mountain.is14er()) fourteenerIcon else mountainIcon
        Marker(
            // ...
            anchor = Offset(0.5f, 0.5f),
            icon = icon,
        )
    }
}

ऐप्लिकेशन चलाएं और अपनी पसंद के मुताबिक बनाए गए मार्कर देखें. पहाड़ों का पूरा सेट देखने के लिए, Show all स्विच को टॉगल करें. पहाड़ों पर अलग-अलग मार्कर होंगे. यह इस बात पर निर्भर करेगा कि पहाड़ की ऊंचाई 14,000 फ़ीट से ज़्यादा है या नहीं.

10. ऐडवांस मार्कर

AdvancedMarker, बुनियादी Markers में अतिरिक्त सुविधाएं जोड़ते हैं. इस चरण में, टकराव की स्थिति में पिन के व्यवहार को सेट किया जाएगा और पिन की स्टाइल को कॉन्फ़िगर किया जाएगा.

@GoogleMapComposable को AdvancedMarkersMapContent फ़ंक्शन में जोड़ें. हर mountains के लिए, एक AdvancedMarker जोड़ें.

@Composable
@GoogleMapComposable
fun AdvancedMarkersMapContent(
    mountains: List<Mountain>,
    onMountainClick: (Marker) -> Boolean = { false },
) {
    mountains.forEach { mountain ->
        AdvancedMarker(
            state = rememberMarkerState(position = mountain.location),
            title = mountain.name,
            snippet = mountain.elevation.toElevationString(),
            collisionBehavior = AdvancedMarkerOptions.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL,
            onClick = { marker ->
                onMountainClick(marker)
                false
            }
        )
    }
}

collisionBehavior पैरामीटर पर ध्यान दें. इस पैरामीटर को REQUIRED_AND_HIDES_OPTIONAL पर सेट करने से, आपका मार्कर कम प्राथमिकता वाले किसी भी मार्कर की जगह ले लेगा. बेसिक मार्कर की तुलना में, अडवांस मार्कर को ज़ूम इन करके देखा जा सकता है. बेसिक मार्कर में, आपके मार्कर और बेस मैप में एक ही जगह पर रखे गए मार्कर, दोनों शामिल होंगे. ऐडवांस मार्कर की वजह से, कम प्राथमिकता वाला मार्कर छिप जाएगा.

ऐडवांस मार्कर देखने के लिए, ऐप्लिकेशन चलाएं. पक्का करें कि आपने सबसे नीचे मौजूद नेविगेशन बार में, Advanced markers टैब चुना हो.

पसंद के मुताबिक बनाया गया AdvancedMarkers

आइकॉन में प्राइमरी और सेकंडरी कलर स्कीम का इस्तेमाल किया गया है, ताकि 4,267 मीटर से ज़्यादा ऊंचे पहाड़ों और अन्य पहाड़ों के बीच अंतर किया जा सके. vectorToBitmap फ़ंक्शन का इस्तेमाल करके, दो BitmapDescriptor बनाएं. एक 14,000 फ़ीट से ज़्यादा ऊंचाई वाले पहाड़ों के लिए और दूसरा अन्य पहाड़ों के लिए. इन आइकॉन का इस्तेमाल करके, हर टाइप के लिए कस्टम pinConfig बनाएं. आखिर में, is14er() फ़ंक्शन के आधार पर, पिन को उससे जुड़े AdvancedMarker पर लागू करें.

@Composable
@GoogleMapComposable
fun AdvancedMarkersMapContent(
    mountains: List<Mountain>,
    onMountainClick: (Marker) -> Boolean = { false },
) {
    val mountainIcon = vectorToBitmap(
        LocalContext.current,
        BitmapParameters(
            id = R.drawable.baseline_filter_hdr_24,
            iconColor = MaterialTheme.colorScheme.onSecondary.toArgb(),
        )
    )

    val mountainPin = with(PinConfig.builder()) {
        setGlyph(PinConfig.Glyph(mountainIcon))
        setBackgroundColor(MaterialTheme.colorScheme.secondary.toArgb())
        setBorderColor(MaterialTheme.colorScheme.onSecondary.toArgb())
        build()
    }

    val fourteenerIcon = vectorToBitmap(
        LocalContext.current,
        BitmapParameters(
            id = R.drawable.baseline_filter_hdr_24,
            iconColor = MaterialTheme.colorScheme.onPrimary.toArgb(),
        )
    )

    val fourteenerPin = with(PinConfig.builder()) {
        setGlyph(PinConfig.Glyph(fourteenerIcon))
        setBackgroundColor(MaterialTheme.colorScheme.primary.toArgb())
        setBorderColor(MaterialTheme.colorScheme.onPrimary.toArgb())
        build()
    }

    mountains.forEach { mountain ->
        val pin = if (mountain.is14er()) fourteenerPin else mountainPin
        AdvancedMarker(
            state = rememberMarkerState(position = mountain.location),
            title = mountain.name,
            snippet = mountain.elevation.toElevationString(),
            collisionBehavior = AdvancedMarkerOptions.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL,
            pinConfig = pin,
            onClick = { marker ->
                onMountainClick(marker)
                false
            }
        )
    }
}

11. क्लस्टर किए गए मार्कर

इस चरण में, ज़ूम के आधार पर सामान को ग्रुप करने की सुविधा जोड़ने के लिए, Clustering कंपोज़ेबल का इस्तेमाल किया जाएगा.

Clustering कंपोज़ेबल के लिए, ClusterItem का कलेक्शन ज़रूरी है. MountainClusterItem, ClusterItem इंटरफ़ेस लागू करता है. इस क्लास को ClusteringMarkersMapContent.kt फ़ाइल में जोड़ें.

data class MountainClusterItem(
    val mountain: Mountain,
    val snippetString: String
) : ClusterItem {
    override fun getPosition() = mountain.location
    override fun getTitle() = mountain.name
    override fun getSnippet() = snippetString
    override fun getZIndex() = 0f
}

अब पहाड़ों की सूची से MountainClusterItem बनाने के लिए कोड जोड़ें. ध्यान दें कि यह कोड, उपयोगकर्ता की भाषा के हिसाब से डिसप्ले यूनिट में बदलने के लिए UnitsConverter का इस्तेमाल करता है. इसे CompositionLocal का इस्तेमाल करके, MainActivity में सेट अप किया जाता है

@OptIn(MapsComposeExperimentalApi::class)
@Composable
@GoogleMapComposable
fun ClusteringMarkersMapContent(
    mountains: List<Mountain>,
    // ...
) {
    val unitsConverter = LocalUnitsConverter.current
    val resources = LocalContext.current.resources

    val mountainClusterItems by remember(mountains) {
        mutableStateOf(
            mountains.map { mountain ->
                MountainClusterItem(
                    mountain = mountain,
                    snippetString = unitsConverter.toElevationString(resources, mountain.elevation)
                )
            }
        )
    }

    Clustering(
        items = mountainClusterItems,
    )
}

इस कोड की मदद से, मार्कर को ज़ूम लेवल के हिसाब से क्लस्टर किया जाता है. बहुत बढ़िया!

क्लस्टर को पसंद के मुताबिक बनाना

अन्य मार्कर टाइप की तरह, क्लस्टर किए गए मार्कर को पसंद के मुताबिक बनाया जा सकता है. Clustering कंपोज़ेबल का clusterItemContent पैरामीटर, कस्टम कंपोज़ेबल ब्लॉक सेट करता है. इससे क्लस्टर न किए गए आइटम को रेंडर किया जा सकता है. मार्कर बनाने के लिए, @Composable फ़ंक्शन लागू करें. SingleMountain फ़ंक्शन, पसंद के मुताबिक बैकग्राउंड के रंग की स्कीम के साथ कंपोज़ेबल Material 3 Icon को रेंडर करता है.

ClusteringMarkersMapContent.kt में, मार्कर के लिए कलर स्कीम तय करने वाली डेटा क्लास बनाएं:

data class IconColor(val iconColor: Color, val backgroundColor: Color, val borderColor: Color)

इसके अलावा, ClusteringMarkersMapContent.kt में किसी दी गई कलर स्कीम के लिए आइकॉन रेंडर करने वाला कंपोज़ेबल फ़ंक्शन बनाएं:

@Composable
private fun SingleMountain(
    colors: IconColor,
) {
    Icon(
        painterResource(id = R.drawable.baseline_filter_hdr_24),
        tint = colors.iconColor,
        contentDescription = "",
        modifier = Modifier
            .size(32.dp)
            .padding(1.dp)
            .drawBehind {
                drawCircle(color = colors.backgroundColor, style = Fill)
                drawCircle(color = colors.borderColor, style = Stroke(width = 3f))
            }
            .padding(4.dp)
    )
}

अब 4,267 मीटर से ज़्यादा ऊंचाई वाले पहाड़ों के लिए एक कलर स्कीम और अन्य पहाड़ों के लिए दूसरी कलर स्कीम बनाएं. clusterItemContent ब्लॉक में, कलर स्कीम चुनें. यह इस बात पर निर्भर करती है कि दिया गया पहाड़ 4,000 मीटर से ज़्यादा ऊंचा है या नहीं.

fun ClusteringMarkersMapContent(
    mountains: List<Mountain>,
    // ...
) {
  // ...

  val backgroundAlpha = 0.6f

  val fourteenerColors = IconColor(
      iconColor = MaterialTheme.colorScheme.onPrimary,
      backgroundColor = MaterialTheme.colorScheme.primary.copy(alpha = backgroundAlpha),
      borderColor = MaterialTheme.colorScheme.primary
  )

  val otherColors = IconColor(
      iconColor = MaterialTheme.colorScheme.secondary,
      backgroundColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = backgroundAlpha),
      borderColor = MaterialTheme.colorScheme.secondary
  )

  // ...
  Clustering(
      items = mountainClusterItems,
      clusterItemContent = { mountainItem ->
          val colors = if (mountainItem.mountain.is14er()) {
              fourteenerColors
          } else {
              otherColors
          }
          SingleMountain(colors)
      },
  )
}

अब ऐप्लिकेशन चलाकर, अलग-अलग आइटम के पसंद के मुताबिक बनाए गए वर्शन देखें.

12. मैप पर जानकारी देने के लिए ड्रॉ करना

आपने मैप पर ड्रॉ करने का एक तरीका पहले ही देख लिया है. यह तरीका मार्कर जोड़ने का है. हालांकि, Maps SDK for Android में कई अन्य तरीके भी उपलब्ध हैं. इनकी मदद से, मैप पर काम की जानकारी दिखाई जा सकती है.

उदाहरण के लिए, अगर आपको मैप पर रास्ते और इलाके दिखाने हैं, तो इन्हें मैप पर दिखाने के लिए Polyline और Polygon का इस्तेमाल किया जा सकता है. इसके अलावा, अगर आपको किसी इमेज को ज़मीन की सतह पर फ़िट करना है, तो GroundOverlay का इस्तेमाल करें.

इस टास्क में, आपको शेप बनाने का तरीका सिखाया जाएगा. खास तौर पर, कोलोराडो राज्य के चारों ओर आउटलाइन बनाने का तरीका. कोलोराडो की सीमा को 37°N और 41°N अक्षांश और 102°03'W और 109°03'W के बीच के तौर पर तय किया गया है. इससे आउटलाइन बनाना काफ़ी आसान हो जाता है.

स्टार्टर कोड में, डिग्री-मिनट-सेकंड नोटेशन को डेसिमल डिग्री में बदलने के लिए, DMS क्लास शामिल है.

enum class Direction(val sign: Int) {
    NORTH(1),
    EAST(1),
    SOUTH(-1),
    WEST(-1)
}

/**
 * Degrees, minutes, seconds utility class
 */
data class DMS(
    val direction: Direction,
    val degrees: Double,
    val minutes: Double = 0.0,
    val seconds: Double = 0.0,
)

fun DMS.toDecimalDegrees(): Double =
    (degrees + (minutes / 60) + (seconds / 3600)) * direction.sign

डीएमएस क्लास की मदद से, कोलोराडो की सीमा को बनाया जा सकता है. इसके लिए, आपको चार कोनों की LatLng जगह की जानकारी देनी होगी और उन्हें Polygon के तौर पर रेंडर करना होगा. MountainMap.kt में यह कोड जोड़ें

@Composable
@GoogleMapComposable
fun ColoradoPolygon() {
    val north = 41.0
    val south = 37.0
    val east = DMS(WEST, 102.0, 3.0).toDecimalDegrees()
    val west = DMS(WEST, 109.0, 3.0).toDecimalDegrees()

    val locations = listOf(
        LatLng(north, east),
        LatLng(south, east),
        LatLng(south, west),
        LatLng(north, west),
    )

    Polygon(
        points = locations,
        strokeColor = MaterialTheme.colorScheme.tertiary,
        strokeWidth = 3F,
        fillColor = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.3f),
    )
}

अब GoogleMap कॉन्टेंट ब्लॉक में ColoradoPolyon() को कॉल करें.

@Composable
fun MountainMap(
    // ...
) {
   Box(
    // ...
    ) {
        GoogleMap(
            // ...
        ) {
            ColoradoPolygon()
        }
    }
}

अब ऐप्लिकेशन, कोलोराडो राज्य को हाइलाइट करता है. साथ ही, उसमें हल्का रंग भरता है.

13. KML लेयर और स्केल बार जोड़ना

इस आखिरी सेक्शन में, आपको अलग-अलग पर्वत श्रृंखलाओं के बारे में सामान्य जानकारी देनी होगी. साथ ही, मैप में स्केल बार जोड़ना होगा.

पर्वत शृंखलाओं की जानकारी

आपने पहले, कोलोराडो के चारों ओर एक आउटलाइन बनाई थी. यहां आपको मैप में ज़्यादा जटिल आकार जोड़ने हैं. स्टार्टर कोड में कीहोल मार्कअप लैंग्वेज या KML फ़ाइल शामिल होती है. इसमें अहम पर्वत श्रृंखलाओं की जानकारी होती है. Android Utility Library के लिए Maps SDK में, मैप में KML लेयर जोड़ने का फ़ंक्शन होता है. when ब्लॉक के बाद, GoogleMap कॉन्टेंट ब्लॉक में MapEffect कॉल जोड़ें.MountainMap.kt MapEffect फ़ंक्शन को GoogleMap ऑब्जेक्ट के साथ कॉल किया जाता है. यह उन एपीआई और लाइब्रेरी के बीच एक उपयोगी ब्रिज के तौर पर काम कर सकता है जिनमें कंपोज़िशन की सुविधा नहीं होती और जिनके लिए GoogleMap ऑब्जेक्ट की ज़रूरत होती है.

  fun MountainMap(
    // ...
) {
    var isMapLoaded by remember { mutableStateOf(false) }
    val context = LocalContext.current

    GoogleMap(
      // ...
    ) {
      // ...

      when (selectedMarkerType) {
        // ...
      }

      // This code belongs inside the GoogleMap content block, but outside of
      // the 'when' statement
      MapEffect(key1 = true) {map ->
          val layer = KmlLayer(map, R.raw.mountain_ranges, context)
          layer.addLayerToMap()
      }
    }

मैप स्केल जोड़ना

आखिरी टास्क के तौर पर, आपको मैप में स्केल जोड़ना होगा. ScaleBar, स्केल कंपोज़ेबल को लागू करता है. इसे मैप में जोड़ा जा सकता है. ध्यान दें कि ScaleBar नहीं है

@GoogleMapComposable है. इसलिए, इसे GoogleMap कॉन्टेंट में नहीं जोड़ा जा सकता. इसके बजाय, इसे मैप वाले Box में जोड़ें.

Box(
  // ...
) {
    GoogleMap(
      // ...
    ) {
        // ...
    }

    ScaleBar(
        modifier = Modifier
            .padding(top = 5.dp, end = 15.dp)
            .align(Alignment.TopEnd),
        cameraPositionState = cameraPositionState
    )
    // ...
}

पूरी तरह से लागू किए गए कोडलैब को देखने के लिए, ऐप्लिकेशन चलाएं.

14. समाधान का कोड पाना

पूरे हो चुके कोडलैब का कोड डाउनलोड करने के लिए, इन कमांड का इस्तेमाल किया जा सकता है:

  1. अगर आपने git इंस्टॉल किया है, तो रिपॉज़िटरी को क्लोन करें.
$ git clone https://github.com/googlemaps-samples/codelab-maps-platform-101-compose.git

इसके अलावा, सोर्स कोड को डाउनलोड करने के लिए, इस बटन पर क्लिक करें.

  1. कोड मिलने के बाद, Android Studio में solution डायरेक्ट्री में मौजूद प्रोजेक्ट खोलें.

15. बधाई हो

बधाई हो! आपने काफ़ी कॉन्टेंट कवर कर लिया है. हमें उम्मीद है कि आपको Android के लिए Maps SDK में उपलब्ध मुख्य सुविधाओं के बारे में बेहतर जानकारी मिल गई होगी.

ज़्यादा जानें

  • Android के लिए Maps SDK - अपने Android ऐप्लिकेशन के लिए, डाइनैमिक, इंटरैक्टिव, और पसंद के मुताबिक बनाए गए मैप, जगह की जानकारी, और भू-स्थानिक अनुभव बनाएं.
  • Maps Compose Library - यह ओपन सोर्स कंपोज़ेबल फ़ंक्शन और डेटा टाइप का एक सेट है. इसका इस्तेमाल Jetpack Compose के साथ करके, अपना ऐप्लिकेशन बनाया जा सकता है.
  • android-maps-compose - GitHub पर मौजूद सैंपल कोड, जिसमें इस कोडलैब में बताई गई सभी सुविधाओं के बारे में बताया गया है.
  • Google Maps Platform की मदद से Android ऐप्लिकेशन बनाने के लिए, Kotlin के ज़्यादा कोडलैब