diff --git a/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt b/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt index a1c5655..24be14b 100644 --- a/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +++ b/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt @@ -29,6 +29,7 @@ import com.google.android.gms.maps.model.MarkerOptions import com.google.android.gms.maps.model.Polygon import com.google.android.gms.maps.model.PolygonOptions import com.google.android.gms.maps.model.PolylineOptions +import com.google.android.gms.maps.model.MapStyleOptions import com.google.android.gms.maps.model.TileOverlayOptions import com.google.android.gms.maps.model.UrlTileProvider import com.luggmaps.LuggCalloutView @@ -120,6 +121,11 @@ class GoogleMapProvider(private val context: Context) : // Theme private var theme: String = "system" + // POI + private var poiEnabled: Boolean = true + private var poiFilterMode: String = "including" + private var poiFilterCategories: List = emptyList() + // Edge Insets private var edgeInsets: EdgeInsets = EdgeInsets() @@ -221,6 +227,7 @@ class GoogleMapProvider(private val context: Context) : applyEdgeInsets() applyInsetAdjustment() applyTheme() + applyPoiStyle() applyUserLocation() processPendingMarkers() processPendingPolylines() @@ -546,11 +553,23 @@ class GoogleMapProvider(private val context: Context) : applyInsetAdjustment() } - override fun setPoiEnabled(enabled: Boolean) {} + override fun setPoiEnabled(enabled: Boolean) { + if (poiEnabled == enabled) return + poiEnabled = enabled + applyPoiStyle() + } - override fun setPoiFilterMode(mode: String) {} + override fun setPoiFilterMode(mode: String) { + if (poiFilterMode == mode) return + poiFilterMode = mode + applyPoiStyle() + } - override fun setPoiFilterCategories(categories: List) {} + override fun setPoiFilterCategories(categories: List) { + if (poiFilterCategories == categories) return + poiFilterCategories = categories + applyPoiStyle() + } // endregion @@ -1299,6 +1318,128 @@ class GoogleMapProvider(private val context: Context) : override fun onTrimMemory(level: Int) {} + private fun applyPoiStyle() { + if (!poiEnabled) { + googleMap?.setMapStyle(MapStyleOptions(POI_STYLE_HIDE_ALL)) + return + } + + val mappedFeatureTypes = poiFilterCategories + .mapNotNull { POI_CATEGORY_TO_FEATURE_TYPE[it] } + .toSet() + + if (mappedFeatureTypes.isEmpty()) { + googleMap?.setMapStyle(null) + return + } + + val styleJson = buildString { + append("[") + if (poiFilterMode == "excluding") { + mappedFeatureTypes.forEach { featureType -> + append("""{"featureType":"$featureType","elementType":"all","stylers":[{"visibility":"off"}]},""") + } + } else { + // including: hide all POIs/transit, then re-enable only matching feature types + append("""{"featureType":"poi","elementType":"all","stylers":[{"visibility":"off"}]},""") + append("""{"featureType":"transit","elementType":"all","stylers":[{"visibility":"off"}]},""") + mappedFeatureTypes.forEach { featureType -> + append("""{"featureType":"$featureType","elementType":"all","stylers":[{"visibility":"on"}]},""") + } + } + if (endsWith(",")) deleteCharAt(length - 1) + append("]") + } + + googleMap?.setMapStyle(MapStyleOptions(styleJson)) + } + + companion object { + const val DEMO_MAP_ID = "DEMO_MAP_ID" + + private val POI_CATEGORY_TO_FEATURE_TYPE = mapOf( + "airport" to "transit.station.airport", + "amusement-park" to "poi.attraction", + "aquarium" to "poi.attraction", + "atm" to "poi.business", + "bakery" to "poi.business", + "bank" to "poi.business", + "beach" to "poi", + "brewery" to "poi.business", + "cafe" to "poi.business", + "campground" to "poi", + "car-rental" to "poi.business", + "ev-charger" to "poi.business", + "fire-station" to "poi.government", + "fitness-center" to "poi.sports_complex", + "food-market" to "poi.business", + "gas-station" to "poi.business", + "hospital" to "poi.medical", + "hotel" to "poi.business", + "laundry" to "poi.business", + "library" to "poi.government", + "marina" to "poi", + "movie-theater" to "poi.attraction", + "museum" to "poi.attraction", + "national-park" to "poi.park", + "nightlife" to "poi.business", + "park" to "poi.park", + "parking" to "poi.business", + "pharmacy" to "poi.medical", + "police" to "poi.government", + "post-office" to "poi.government", + "public-transport" to "transit", + "restaurant" to "poi.business", + "restroom" to "poi", + "school" to "poi.school", + "stadium" to "poi.sports_complex", + "store" to "poi.business", + "theater" to "poi.attraction", + "university" to "poi.school", + "winery" to "poi.business", + "zoo" to "poi.attraction", + // iOS 18+ + "animal-service" to "poi.business", + "automotive-repair" to "poi.business", + "baseball" to "poi.sports_complex", + "basketball" to "poi.sports_complex", + "beauty" to "poi.business", + "bowling" to "poi.sports_complex", + "castle" to "poi.attraction", + "convention-center" to "poi.business", + "distillery" to "poi.business", + "fairground" to "poi.attraction", + "fishing" to "poi", + "fortress" to "poi.attraction", + "go-kart" to "poi.sports_complex", + "golf" to "poi.sports_complex", + "hiking" to "poi.park", + "kayaking" to "poi", + "landmark" to "poi.attraction", + "mailbox" to "poi.government", + "mini-golf" to "poi.sports_complex", + "music-venue" to "poi.attraction", + "national-monument" to "poi.attraction", + "planetarium" to "poi.attraction", + "rock-climbing" to "poi.sports_complex", + "rv-park" to "poi", + "skate-park" to "poi.sports_complex", + "skating" to "poi.sports_complex", + "skiing" to "poi.sports_complex", + "soccer" to "poi.sports_complex", + "spa" to "poi.business", + "surfing" to "poi", + "swimming" to "poi.sports_complex", + "tennis" to "poi.sports_complex", + "volleyball" to "poi.sports_complex", + ) + + private val POI_STYLE_HIDE_ALL = """[ + {"featureType":"poi","elementType":"all","stylers":[{"visibility":"off"}]}, + {"featureType":"transit","elementType":"all","stylers":[{"visibility":"off"}]} + ]""" + } + private fun applyTheme() { val colorScheme = when (theme) { "dark" -> MapColorScheme.DARK @@ -1321,8 +1462,4 @@ class GoogleMapProvider(private val context: Context) : } // endregion - - companion object { - const val DEMO_MAP_ID = "DEMO_MAP_ID" - } } diff --git a/docs/MAPVIEW.md b/docs/MAPVIEW.md index 6611e54..07b1c9d 100644 --- a/docs/MAPVIEW.md +++ b/docs/MAPVIEW.md @@ -36,8 +36,8 @@ import { MapView } from '@lugg/maps'; | `edgeInsets` | `EdgeInsets` | - | Map content edge insets | | `userLocationEnabled` | `boolean` | `false` | Show current user location on the map | | `userLocationButtonEnabled` | `boolean` | `false` | Show native my-location button (Android only) | -| `poiEnabled` | `boolean` | `true` | Show points of interest (Apple Maps only) | -| `poiFilter` | [`PoiFilter`](#poifilter) | - | Filter POI categories (Apple Maps only) | +| `poiEnabled` | `boolean` | `true` | Show points of interest. On Android, hides POIs and transit stops via map style. | +| `poiFilter` | [`PoiFilter`](#poifilter) | - | Filter POI categories. On Android, categories map to Google Maps Style feature types (coarser granularity). | | `theme` | [`MapTheme`](#maptheme) | `'system'` | Map color theme | | `insetAdjustment` | `'automatic' \| 'never'` | `'never'` | Safe area inset adjustment behavior | | `onPress` | `(event: MapPressEvent) => void` | - | Called when the map is pressed |