kotlingoogle-mapsgoogle-maps-android-api-3

Change markers icons when loading KML from file


In kotlin, I want to load a KML file stored as XML and show it with default icon marker.

Here is my actual code:

val content = requireContext().openFileInput("map.kml")
val layer = KmlLayer(map, content, requireContext())
layer.addLayerToMap()

This load the map, and show it but only with small white marker like that:

enter image description here

How can I put default marker ?

I tried to change the marker option with :

for(marker in layer.placemarks) {
    marker.markerOptions.icon(null)
}

But it's creating a new marker instead of editing the current one.

So, I tried to don't add the layer, and add marker by myself, but it's not working because of the KmlLayer initialized.


Solution

  • To prevent this, I parsed by myself the file.

    Here is my main code:

    CoroutineScope(Dispatchers.Main).launch {
        try {
            val content = requireContext().openFileInput("carte.kmz")
            val placemarks = parseKML(content)
    
            for (placemark in placemarks) {
                addPlacemarkToMap(placemark, map)
            }
        } catch (e: Exception) {
            Connexion.manageError(e)
        }
    }
    

    I added this few classes:

    data class Placemark(
        var name: String = "",
        var type: PlacemarkType = PlacemarkType.UNKNOWN,
        var coordinates: List<LatLng> = emptyList()
    )
    
    enum class PlacemarkType {
        POINT, POLYGON, UNKNOWN
    }
    

    Then, here is the full parsing code:

    private suspend fun parseKML(inputStream: InputStream): List<Placemark> = withContext(Dispatchers.IO) {
        val placemarks = mutableListOf<Placemark>()
    
        try {
            // Lire tout le contenu d'abord
            val bytes = inputStream.readBytes()
    
            // Si c'est un KMZ, extraire le KML
            val kmlBytes = if (isKMZ(bytes)) {
                extractKMLFromKMZ(bytes)
            } else {
                bytes
            }
    
            val factory = XmlPullParserFactory.newInstance()
            val parser = factory.newPullParser()
            parser.setInput(kmlBytes.inputStream(), "UTF-8")
    
            var eventType = parser.eventType
            var currentPlacemark: Placemark? = null
            var currentTag = ""
            var inCoordinates = false
    
            while (eventType != XmlPullParser.END_DOCUMENT) {
                when (eventType) {
                    XmlPullParser.START_TAG -> {
                        currentTag = parser.name
                        when (currentTag) {
                            "Placemark" -> currentPlacemark = Placemark()
                            "Point" -> currentPlacemark?.type = PlacemarkType.POINT
                            "Polygon" -> currentPlacemark?.type = PlacemarkType.POLYGON
                            "coordinates" -> inCoordinates = true
                        }
                    }
    
                    XmlPullParser.TEXT -> {
                        val text = parser.text?.trim() ?: ""
                        if (text.isNotEmpty() && currentPlacemark != null) {
                            when (currentTag) {
                                "name" -> currentPlacemark.name = text
                                "coordinates" -> {
                                    if (inCoordinates) {
                                        currentPlacemark.coordinates = parseCoordinates(text)
                                    }
                                }
                            }
                        }
                    }
    
                    XmlPullParser.END_TAG -> {
                        when (parser.name) {
                            "Placemark" -> {
                                currentPlacemark?.let {
                                    if (it.coordinates.isNotEmpty()) {
                                        placemarks.add(it)
                                    }
                                }
                                currentPlacemark = null
                            }
                            "coordinates" -> inCoordinates = false
                        }
                    }
                }
                eventType = parser.next()
            }
        } catch (e: Exception) {
            println("Error in parseKML: ${e.message}")
            e.printStackTrace()
        }
    
        placemarks
    }
    
    private fun isKMZ(bytes: ByteArray): Boolean {
        return try {
            // Check for ZIP magic number (PK)
            bytes.size >= 2 && bytes[0] == 0x50.toByte() && bytes[1] == 0x4B.toByte()
        } catch (e: Exception) {
            false
        }
    }
    
    private fun extractKMLFromKMZ(bytes: ByteArray): ByteArray {
        val zipStream = ZipInputStream(bytes.inputStream())
        var entry = zipStream.nextEntry
    
        while (entry != null) {
            if (entry.name.endsWith(".kml", ignoreCase = true)) {
                return zipStream.readBytes()
            }
            zipStream.closeEntry()
            entry = zipStream.nextEntry
        }
    
        throw Exception("No KML file found in KMZ")
    }
    
    private fun parseCoordinates(coordinatesText: String): List<LatLng> {
        val coordinates = mutableListOf<LatLng>()
    
        for (coord in coordinatesText.trim().split(Regex("\\s+"))) {
            if (coord.isEmpty()) continue
    
            val parts = coord.split(",")
            if (parts.size >= 2) {
                try {
                    coordinates.add(LatLng(parts[1].toDouble(), parts[0].toDouble()))
                } catch (e: NumberFormatException) {
                    println("Error parsing coordinate: $coord")
                }
            }
        }
    
        return coordinates
    }