feat: Data-Driven styling samples (#1771)

* fix: import rememberMarkerState and use the marker state correctly

* feat: dataset styling Kotlin samples

* feat: added DataDrivenBoundariesActivity.kt

* feat: added DataDrivenBoundariesActivity

* feat: added documentation

* feat: added header

* chore: changed MY_MAP_ID to DEMO_MAP_ID

* feat: moved data files to raw

* feat: added DataDrivenBoundariesActivity

* feat: removed unused files

* feat: map id

* chore: replace System.out.println with Log.d

* feat: added region tags

* feat: added different set of DDS

* feat: added different set of DDS

* feat: replacing files

feat: replacing files

* feat: replacing files

* feat: compileSdk 35

* feat: compileSdk 35

* chore: updated README file

* feat: trying to force different datasets

* feat: updated samples

* feat: Add data-driven styling for datasets

This commit introduces data-driven styling for datasets in the ApiDemos application.

The following changes were made:

Added a custom Application class (ApiDemoApplication) to check for the presence and validity of the API key during startup.
Added a new data structure (DataSet) to hold information about each dataset, including its label, dataset ID, location, and styling callback.
Updated the DataDrivenDatasetStylingActivity to use the new data structure and styling callbacks.
Rename the dataset input files to better reflect there contents.

* fix: moves the dataset ids to the secrets.properties file

* feat: significant rewrite to of DataDrivenDatasetStylingActivity

* Makes the app look better on full screen with a cutout
* Uses material elements
* Switch to using secrets for the data set ids
  * more of a convenience to prevent having to remove the ids when submitting
* moves dataset data files to common area since they are not used directly by the app
* Detailed instructions for uploading the data to console (WIP)

* fix: exports the data driven styling activities so they can be run directly

* feat: added header

* feat: change ubuntu-latest

* feat: change ubuntu-latest

* feat: change ubuntu-latest

---------

Co-authored-by: dkhawk <107309+dkhawk@users.noreply.github.com>
This commit is contained in:
Enrique López Mañas 2025-02-14 07:37:43 +09:00 committed by GitHub
parent 95f70aafdd
commit f4654e60a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
128 changed files with 2192 additions and 136 deletions

View File

@ -45,8 +45,12 @@ jobs:
- name: Build and check
run: |
cd ApiDemos
for dir in ./*/ ; do ( cd "$dir" && ./gradlew buildDebugPreBundle ); done
for dir in ./*/ ; do
if [[ "$dir" != "./resources/" ]]; then
( cd "$dir" && ./gradlew buildDebugPreBundle )
fi
done
build-WearOS:
runs-on: ubuntu-latest
timeout-minutes: 45

View File

@ -1,5 +1,5 @@
/*
* Copyright 2024 Google LLC
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -33,6 +33,7 @@ limitations under the License.
android:maxSdkVersion="22" />
<application
android:name=".ApiDemoApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/demo_title"
@ -89,6 +90,14 @@ limitations under the License.
android:name=".CircleDemoActivity"
android:exported="true"
android:label="@string/circle_demo_label" />
<activity
android:name=".DataDrivenBoundariesActivity"
android:exported="true"
android:label="@string/data_driven_boundaries_label" />
<activity
android:name=".DataDrivenDatasetStylingActivity"
android:exported="true"
android:label="@string/data_driven_styling_label" />
<activity
android:name=".EventsDemoActivity"
android:exported="true"

View File

@ -0,0 +1,78 @@
/*
* Copyright 2025 Google LLC
*
* 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.example.mapdemo;
import android.app.Application;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import java.util.Objects;
/**
* {@code ApiDemoApplication} is a custom Application class for the API demo.
*
* <p>This class is responsible for application-wide initialization and setup,
* such as checking for the presence and validity of the API key during the
* application's startup.</p>
*
* <p>It extends the {@link Application} class and overrides the {@link #onCreate()}
* method to perform these initialization tasks.</p>
*/
public class ApiDemoApplication extends Application {
private static final String TAG = "ApiDemoApplication";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate called");
checkApiKey();
}
/**
* Checks if the API key for Google Maps is properly configured in the application's metadata.
* <p>
* This method retrieves the API key from the application's metadata, specifically looking for
* a string value associated with the key "com.google.android.geo.API_KEY".
* The key must be present, not blank, and not set to the placeholder value "DEFAULT_API_KEY".
* <p>
* If any of these checks fail, a Toast message is displayed indicating that the API key is missing or
* incorrectly configured, and a RuntimeException is thrown.
* <p>
*/
private void checkApiKey() {
try {
ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
Bundle bundle = Objects.requireNonNull(appInfo.metaData);
String apiKey = bundle.getString("com.google.android.geo.API_KEY"); // Key name is important!
if (apiKey == null || apiKey.isBlank() || apiKey.equals("DEFAULT_API_KEY")) {
Toast.makeText(this, "API Key was not set in secrets.properties", Toast.LENGTH_LONG).show();
throw new RuntimeException("API Key was not set in secrets.properties");
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Package name not found.", e);
throw new RuntimeException("Error getting package info.", e);
} catch (NullPointerException e) {
Log.e(TAG, "Error accessing meta-data.", e); // Handle the case where meta-data is completely missing.
throw new RuntimeException("Error accessing meta-data in manifest", e);
}
}
}

View File

@ -0,0 +1,228 @@
// Copyright 2025 Google LLC
//
// 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.example.mapdemo;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.Feature;
import com.google.android.gms.maps.model.FeatureClickEvent;
import com.google.android.gms.maps.model.FeatureLayer;
import com.google.android.gms.maps.model.FeatureLayerOptions;
import com.google.android.gms.maps.model.FeatureStyle;
import com.google.android.gms.maps.model.FeatureType;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MapCapabilities;
import com.google.android.gms.maps.model.PlaceFeature;
import java.util.ArrayList;
import java.util.List;
/**
* This sample showcases how to use the Data-driven styling for boundaries. For more information
* on how the Data-driven styling for boundaries work, check out the following link:
* https://developers.google.com/maps/documentation/android-sdk/dds-boundaries/overview
*/
// [START maps_android_data_driven_styling_boundaries]
public class DataDrivenBoundariesActivity extends AppCompatActivity implements OnMapReadyCallback,
FeatureLayer.OnFeatureClickListener {
private GoogleMap map;
private FeatureLayer localityLayer = null;
private FeatureLayer areaLevel1Layer = null;
private FeatureLayer countryLayer = null;
private static final String TAG = DataDrivenBoundariesActivity.class.getName();
private static final LatLng HANA_HAWAII = new LatLng(20.7522, -155.9877); // Hana, Hawaii
private static final LatLng CENTER_US = new LatLng(39.8283, -98.5795); // Approximate geographical center of the contiguous US
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.data_driven_boundaries_demo);
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
if (mapFragment != null) {
mapFragment.getMapAsync(this);
}
findViewById(R.id.button_hawaii).setOnClickListener(view -> centerMapOnLocation(HANA_HAWAII, 13.5f));
findViewById(R.id.button_us).setOnClickListener(view -> centerMapOnLocation(CENTER_US, 1f));
}
private void centerMapOnLocation(LatLng location, float zoomLevel) {
map.moveCamera(CameraUpdateFactory.newLatLngZoom(location, zoomLevel));
}
@RequiresApi(Build.VERSION_CODES.O)
@Override
public void onMapReady(GoogleMap googleMap) {
this.map = googleMap;
MapCapabilities capabilities = map.getMapCapabilities();
Log.d(TAG, "Data-driven Styling is available: " + capabilities.isDataDrivenStylingAvailable());
// Get the LOCALITY feature layer.
localityLayer = googleMap.getFeatureLayer(
new FeatureLayerOptions.Builder()
.featureType(FeatureType.LOCALITY)
.build()
);
// Apply style factory function to LOCALITY layer.
styleLocalityLayer();
// Get the ADMINISTRATIVE_AREA_LEVEL_1 feature layer.
areaLevel1Layer = googleMap.getFeatureLayer(
new FeatureLayerOptions.Builder()
.featureType(FeatureType.ADMINISTRATIVE_AREA_LEVEL_1)
.build()
);
// Apply style factory function to ADMINISTRATIVE_AREA_LEVEL_1 layer.
styleAreaLevel1Layer();
// Get the COUNTRY feature layer.
countryLayer = googleMap.getFeatureLayer(
new FeatureLayerOptions.Builder()
.featureType(FeatureType.COUNTRY)
.build()
);
// Register the click event handler for the Country layer.
countryLayer.addOnFeatureClickListener(this);
// Apply default style to all countries on load to enable clicking.
styleCountryLayer();
}
private void styleLocalityLayer() {
// Create the style factory function.
FeatureLayer.StyleFactory styleFactory = feature -> {
// Check if the feature is an instance of PlaceFeature,
// which contains a place ID.
if (feature instanceof PlaceFeature placeFeature) {
// Determine if the place ID is for Hana, HI.
if ("ChIJ0zQtYiWsVHkRk8lRoB1RNPo".equals(placeFeature.getPlaceId())) {
// Use FeatureStyle.Builder to configure the FeatureStyle object
// returned by the style factory function.
return new FeatureStyle.Builder()
// Define a style with purple fill at 50% opacity and
// solid purple border.
.fillColor(0x80810FCB)
.strokeColor(0xFF810FCB)
.build();
}
}
return null;
};
// Apply the style factory function to the feature layer.
if (localityLayer != null) {
localityLayer.setFeatureStyle(styleFactory);
}
}
private void styleAreaLevel1Layer() {
FeatureLayer.StyleFactory styleFactory = feature -> {
if (feature instanceof PlaceFeature placeFeature) {
// Return a hueColor in the range [-299,299]. If the value is
// negative, add 300 to make the value positive.
int hueColor = placeFeature.getPlaceId().hashCode() % 300;
if (hueColor < 0) {
hueColor += 300;
}
return new FeatureStyle.Builder()
// Set the fill color for the state based on the hashed hue color.
.fillColor(Color.HSVToColor(150, new float[]{hueColor, 1f, 1f}))
.build();
}
return null;
};
// Apply the style factory function to the feature layer.
if (areaLevel1Layer != null) {
areaLevel1Layer.setFeatureStyle(styleFactory);
}
}
// Set default fill and border for all countries to ensure that they respond
// to click events.
@RequiresApi(Build.VERSION_CODES.O)
private void styleCountryLayer() {
FeatureLayer.StyleFactory styleFactory = feature -> new FeatureStyle.Builder()
// Set the fill color for the country as white with a 10% opacity.
// This requires minApi 26
.fillColor(Color.argb(0.1f, 0f, 0f, 0f))
// Set border color to solid black.
.strokeColor(Color.BLACK)
.build();
// Apply the style factory function to the country feature layer.
if (countryLayer != null) {
countryLayer.setFeatureStyle(styleFactory);
}
}
// Define the click event handler.
@Override
public void onFeatureClick(FeatureClickEvent event) {
// Get the list of features affected by the click using
// getPlaceIds() defined below.
List<String> selectedPlaceIds = getPlaceIds(event.getFeatures());
if (!selectedPlaceIds.isEmpty()) {
FeatureLayer.StyleFactory styleFactory = feature -> {
// Use PlaceFeature to get the placeID of the country.
if (feature instanceof PlaceFeature) {
if (selectedPlaceIds.contains(((PlaceFeature) feature).getPlaceId())) {
return new FeatureStyle.Builder()
// Set the fill color to red.
.fillColor(Color.RED)
.build();
}
}
return null;
};
// Apply the style factory function to the feature layer.
if (countryLayer != null) {
countryLayer.setFeatureStyle(styleFactory);
}
}
}
// Get a List of place IDs from the FeatureClickEvent object.
private List<String> getPlaceIds(List<Feature> features) {
List<String> placeIds = new ArrayList<>();
for (Feature feature : features) {
if (feature instanceof PlaceFeature) {
placeIds.add(((PlaceFeature) feature).getPlaceId());
}
}
return placeIds;
}
}
// [END maps_android_data_driven_styling_boundaries]

View File

@ -0,0 +1,367 @@
// Copyright 2025 Google LLC
//
// 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.example.mapdemo;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.ColorUtils;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.DatasetFeature;
import com.google.android.gms.maps.model.Feature;
import com.google.android.gms.maps.model.FeatureClickEvent;
import com.google.android.gms.maps.model.FeatureLayer;
import com.google.android.gms.maps.model.FeatureLayerOptions;
import com.google.android.gms.maps.model.FeatureStyle;
import com.google.android.gms.maps.model.FeatureType;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MapCapabilities;
import java.util.List;
import java.util.Map;
/**
* This sample showcases how to use the Data-driven styling for datasets. For more information
* on how the Data-driven styling for boundaries work, check out the following link:
* https://developers.google.com/maps/documentation/android-sdk/dds-datasets/overview
* <p>
* This is meant to work with the datasets in the res/raw directory.
*/
// [START maps_android_data_driven_styling_datasets]
public class DataDrivenDatasetStylingActivity extends AppCompatActivity implements OnMapReadyCallback, FeatureLayer.OnFeatureClickListener {
private record DataSet(
String label,
String datasetId,
LatLng location,
DataDrivenDatasetStylingActivity.DataSet.StylingCallback callback) {
public interface StylingCallback {
void styleDatasetLayer();
}
}
/**
* An array of DataSet objects representing different geographic locations and their associated data.
* Each DataSet contains:
* - A human-readable name (e.g., "Boulder", "New York").
* - A unique Dataset ID, which should correspond to a dataset id in the Datasets console tab.
* - The central latitude and longitude coordinates (LatLng) of the location.
* - A styling function (method reference) that defines how to style the data from that dataset on a map.
* <p>
* This array is used to configure which datasets are available for display and how they should be presented.
* Each element of the array should be a new DataSet object.
* Modify the constructor arguments of each DataSet to define your specific data and styles.
* The styling function will receive a `Layer` to which it can add elements.
* <p>
* Example:
* - `new DataSet("Boulder", "Boulder-DataSet-Id", new LatLng(40.0150, -105.2705), this::styleBoulderDatasetLayer)`
* This creates a DataSet for Boulder, identified by "Boulder-DataSet-Id", centered on the given coordinates,
* and styled using the `styleBoulderDatasetLayer` method.
* <p>
* Note: We have use the secrets plugin to allow us to configure the Dataset IDs in our secrets.properties file.
*/
private final DataSet[] dataSets = new DataSet[] {
new DataSet("Boulder", BuildConfig.BOULDER_DATASET_ID, new LatLng(40.0150, -105.2705), this::styleBoulderDatasetLayer),
new DataSet("New York", BuildConfig.NEW_YORK_DATASET_ID, new LatLng(40.786244, -73.962684), this::styleNYCDatasetLayer),
new DataSet("Kyoto", BuildConfig.KYOTO_DATASET_ID, new LatLng(35.005081, 135.764385), this::styleKyotoDatasetsLayer),
};
private DataSet findDataSetByLabel(String label) {
for (DataSet dataSet : dataSets) {
if (dataSet.label().equalsIgnoreCase(label)) { // Case-insensitive comparison
return dataSet;
}
}
return null; // Return null if no match is found
}
private static final float ZOOM_LEVEL = 13.5f;
private static final String TAG = DataDrivenDatasetStylingActivity.class.getName();
private static FeatureLayer datasetLayer = null;
private GoogleMap map;
private String lastGlobalId = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.data_driven_styling_demo);
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
if (mapFragment != null) {
mapFragment.getMapAsync(this);
}
int[] buttonIds = {R.id.button_boulder, R.id.button_ny, R.id.button_kyoto};
for (int buttonId : buttonIds) {
findViewById(buttonId).setOnClickListener(view -> switchDataSet(((Button) view).getText().toString()));
}
}
/**
* Switches the currently displayed dataset to the one specified by the provided label.
* <p>
* This method attempts to find a DataSet object associated with the given label.
* If a matching DataSet is found, it updates the map's feature layer to display the
* data from that dataset. It also applies styling to the new dataset layer and centers
* the map on the dataset's specified location.
* <p>
* If no matching DataSet is found, a toast message is displayed indicating the failure.
* <p>
* @param label The label of the dataset to switch to. This label should correspond to a
* dataset that has been previously added or loaded.
*/
private void switchDataSet(String label) {
DataSet dataSet = findDataSetByLabel(label);
if (dataSet == null) {
Toast.makeText(this, "Failed to find dataset" + label, Toast.LENGTH_SHORT).show();
} else {
datasetLayer = map.getFeatureLayer(
new FeatureLayerOptions.Builder()
.featureType(FeatureType.DATASET)
.datasetId(dataSet.datasetId())
.build()
);
dataSet.callback.styleDatasetLayer();
centerMapOnLocation(dataSet.location());
}
}
@Override
public void onMapReady(@NonNull GoogleMap googleMap) {
this.map = googleMap;
MapCapabilities capabilities = map.getMapCapabilities();
Log.d(TAG, "Data-driven Styling is available: " + capabilities.isDataDrivenStylingAvailable());
// Switch to the default dataset which must happen before adding the feature click listener
switchDataSet("Boulder");
datasetLayer.addOnFeatureClickListener(this);
}
private void styleNYCDatasetLayer() {
FeatureLayer.StyleFactory styleFactory = feature -> {
int fillColor = Color.GREEN;
int strokeColor = Color.YELLOW;
float pointRadius = 12F;
if (feature instanceof DatasetFeature) {
Map<String, String> furColors = ((DatasetFeature) feature).getDatasetAttributes();
String furColor = furColors.get("Color");
if (furColor != null) {
switch (furColor) {
case "Black+":
fillColor = Color.BLACK;
strokeColor = Color.BLACK;
break;
case "Cinnamon+":
fillColor = 0xFF8B0000; // dark red color
strokeColor = 0xFF8B0000;
break;
case "Cinnamon+Gray":
fillColor = 0xFF8B0000; // dark red color
strokeColor = 0xFF8B0000;
pointRadius = 10F;
break;
case "Cinnamon+White":
fillColor = 0xFF8B0000; // dark red color
strokeColor = Color.WHITE;
pointRadius = 10F;
break;
case "Gray+":
fillColor = Color.GRAY;
break;
case "Gray+Cinnamon":
fillColor = Color.GRAY;
strokeColor = 0xFF8B0000; // dark red color
pointRadius = 10F;
break;
case "Gray+Cinnamon, White":
fillColor = Color.LTGRAY;
strokeColor = 0xFF8B0000; // dark red color
pointRadius = 10F;
break;
case "Gray+White":
fillColor = Color.GRAY;
strokeColor = Color.WHITE;
pointRadius = 10F;
break;
}
}
return new FeatureStyle.Builder()
.fillColor(fillColor)
.strokeColor(strokeColor)
.pointRadius(pointRadius)
.build();
}
return null;
};
if (datasetLayer != null) {
datasetLayer.setFeatureStyle(styleFactory);
}
}
private void styleKyotoDatasetsLayer() {
// Create the style factory function.
FeatureLayer.StyleFactory styleFactory = feature -> {
// Check if the feature is an instance of DatasetFeature.
if (feature instanceof DatasetFeature datasetFeature) {
// Determine the value of the typecategory attribute.
Map<String, String> typeCategories = datasetFeature.getDatasetAttributes();
String typeCategory = typeCategories.get("type");
// Set default colors to green.
int fillColor;
int strokeColor;
if ("temple".equals(typeCategory)) {
// Color temples areas blue.
fillColor = Color.BLUE;
strokeColor = Color.BLUE;
} else {
// Color all other areas green.
fillColor = Color.GREEN;
strokeColor = Color.GREEN;
}
return new FeatureStyle.Builder()
.fillColor(fillColor)
.strokeColor(strokeColor)
.strokeWidth(2F)
.build();
}
return null;
};
// Apply the style factory function to the feature layer.
if (datasetLayer != null) {
datasetLayer.setFeatureStyle(styleFactory);
}
}
private void styleBoulderDatasetLayer() {
final int EASY = Color.GREEN;
final int MODERATE = Color.BLUE;
final int DIFFICULT = Color.RED;
// Create the style factory function.
FeatureLayer.StyleFactory styleFactory = feature -> {
// Set default colors to yellow and point radius to 8.
int fillColor = Color.GREEN;
int strokeColor = Color.YELLOW;
float pointRadius = 8F;
float strokeWidth = 3F;
// Check if the feature is an instance of DatasetFeature.
if (feature instanceof DatasetFeature datasetFeature) {
Map<String, String> attributes = datasetFeature.getDatasetAttributes();
String difficulty = attributes.get("OSMPTrailsOSMPDIFFICULTY");
String name = attributes.get("OSMPTrailsOSMPTRAILNAME");
String dogsAllowed = attributes.get("OSMPTrailsOSMPDOGREGGEN");
if ("Easy".equals(difficulty)) {
fillColor = EASY;
} else if ("Moderate".equals(difficulty)) {
fillColor = MODERATE;
} else if ("Difficult".equals(difficulty)) {
fillColor = DIFFICULT;
} else {
Log.w(TAG, name + " -> Unknown difficulty: " + difficulty);
fillColor = Color.MAGENTA;
}
if ("No Dogs".equals(dogsAllowed)) {
fillColor = ColorUtils.setAlphaComponent(fillColor, 66);
strokeWidth = 5f;
} else if ("LVS".equals(dogsAllowed)) {
// No change needed
} else if ("LR".equals(dogsAllowed) || "RV".equals(dogsAllowed)) {
fillColor = ColorUtils.setAlphaComponent(fillColor, 75);
} else {
Log.w(TAG, name + " -> Unknown dogs reg: " + dogsAllowed);
}
strokeColor = fillColor;
return new FeatureStyle.Builder()
.fillColor(fillColor)
.strokeColor(strokeColor)
.pointRadius(pointRadius)
.strokeWidth(strokeWidth)
.build();
}
return null;
};
// Apply the style factory function to the feature layer.
if (datasetLayer != null) {
datasetLayer.setFeatureStyle(styleFactory);
}
}
private void centerMapOnLocation(LatLng location) {
map.moveCamera(CameraUpdateFactory.newLatLngZoom(location, ZOOM_LEVEL));
}
@Override
public void onFeatureClick(FeatureClickEvent event) {
List<Feature> clickFeatures = event.getFeatures();
lastGlobalId = null;
if (clickFeatures.get(0) instanceof DatasetFeature) {
lastGlobalId = ((DatasetFeature) clickFeatures.get(0)).getDatasetAttributes().get("globalid");
styleDatasetsLayerClickEvent();
}
}
private void styleDatasetsLayerClickEvent() {
FeatureLayer.StyleFactory styleFactory = feature -> {
if (feature instanceof DatasetFeature) {
Map<String, String> globalIDs = ((DatasetFeature) feature).getDatasetAttributes();
String globalID = globalIDs.get("globalid");
int fillColor = Color.GREEN;
int strokeColor = Color.GREEN;
if (globalID != null && globalID.equals(lastGlobalId)) {
fillColor = Color.BLUE;
strokeColor = Color.BLUE;
}
return new FeatureStyle.Builder()
.fillColor(fillColor)
.strokeColor(strokeColor)
.build();
}
return null;
};
if (datasetLayer != null) {
datasetLayer.setFeatureStyle(styleFactory);
}
}
}
// [END maps_android_data_driven_styling_datasets]

View File

@ -53,6 +53,12 @@ public final class DemoDetailsList {
new DemoDetails(R.string.circle_demo_label,
R.string.circle_demo_description,
CircleDemoActivity.class),
new DemoDetails(R.string.data_driven_styling_label,
R.string.data_driven_styling_description,
DataDrivenDatasetStylingActivity.class),
new DemoDetails(R.string.data_driven_boundaries_label,
R.string.data_driven_boundaries_description,
DataDrivenBoundariesActivity.class),
new DemoDetails(R.string.events_demo_label,
R.string.events_demo_description,
EventsDemoActivity.class),

View File

@ -1,5 +1,5 @@
/*
* Copyright 2024 Google LLC
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -0,0 +1,53 @@
<!--
Copyright 2025 Google LLC
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.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Map Fragment -->
<fragment
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
map:backgroundColor="#fff0b2dd"
map:mapId="@string/map_id"
class="com.google.android.gms.maps.SupportMapFragment" />
<!-- Buttons to center the map -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:orientation="horizontal"
android:padding="16dp">
<Button
android:id="@+id/button_hawaii"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hawaii" />
<Button
android:id="@+id/button_us"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="US" />
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,59 @@
<!--
Copyright 2025 Google LLC
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.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Map Fragment -->
<fragment
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
map:backgroundColor="#fff0b2dd"
map:mapId="@string/map_id"
class="com.google.android.gms.maps.SupportMapFragment" />
<!-- Buttons to center the map -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:orientation="horizontal"
android:padding="16dp">
<Button
android:id="@+id/button_boulder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Boulder" />
<Button
android:id="@+id/button_ny"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New York" />
<Button
android:id="@+id/button_kyoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kyoto" />
</LinearLayout>
</RelativeLayout>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
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

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -208,7 +208,7 @@
<!-- Advanced Markers Demo -->
<string name="advanced_markers_demo_label">Advanced Markers</string>
<string name="advanced_markers_demo_details">Showcases the usage of Advanced Markers.</string>
<string name="map_id">MY_MAP_ID</string>
<string name="map_id">DEMO_MAP_ID</string>
<!-- Styling -->
<string name="styled_map_demo_label">Styled Map</string>
@ -293,4 +293,12 @@
<string name="map_color_light_mode">Light</string>
<string name="map_color_dark_mode">Dark</string>
<string name="map_color_follow_system_mode">Follow System\n</string>
<!-- Data Driven Styling Demo -->
<string name="data_driven_styling_label">Data Driven Dataset Styling Demo</string>
<string name="data_driven_styling_description">Demonstrates how to use Data Driven styling for datasets.</string>
<!-- Data Driven Boundaries Demo -->
<string name="data_driven_boundaries_label">Data Driven Boundaries Demo</string>
<string name="data_driven_boundaries_description">Demonstrates how to use Data Driven styling for boundaries.</string>
</resources>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2024 Google LLC
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1 +1,5 @@
MAPS_API_KEY=DEFAULT_API_KEY
BOULDER_DATASET_ID=BOULDER_DATASET_ID
NEW_YORK_DATASET_ID=NEW_YORK_DATASET_ID
KYOTO_DATASET_ID=KYOTO_DATASET_ID
MAP_ID=MAP_ID

View File

@ -1,5 +1,5 @@
/*
* Copyright 2024 Google LLC
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -36,6 +36,30 @@ Alternatively use the `gradlew build` command to build the project directly.
This demo app requires that you add your own Google Maps API key. See [Get an API key](https://developers.google.com/maps/documentation/android-sdk/get-api-key) for more instructions.
# Using Dataset Styles in Android
This guide explains how to create and apply dataset styles in Android by creating a map style, associating it with a map ID, uploading a dataset, and linking the dataset to the map style.
## 1. Create a Map ID
To create a new map ID, follow the steps in [Create a map ID](https://developers.google.com/maps/documentation/get-map-id#create-a-map-id). Make sure to set the **Map type** to **Android**.
## 2. Create a New Map Style
Follow the instructions in [Manage map styles](https://developers.google.com/maps/documentation/android-sdk/cloud-customization/map-styles) to create a new style and [associate it with the map ID you just created](https://developers.google.com/maps/documentation/android-sdk/cloud-customization/map-styles#associate-style-with-map-id).
## 3. Upload a Dataset
To include data-driven styling in your map:
1. Upload the dataset on the [Google Maps Platform Datasets](https://console.cloud.google.com/google/maps-apis/datasets) page.
2. Confirm that the dataset upload is successful (sometimes there can be issues due to an invalid structure).
## 4. Link the Dataset to the Map Style
To enable data-driven styling:
1. Open your dataset in the [Google Maps Platform Datasets](https://console.cloud.google.com/google/maps-apis/datasets) page.
2. Click on the **Preview** of the dataset.
3. Associate the dataset with one of the previously created styles
Support
-------

View File

@ -1,5 +1,5 @@
/*
* Copyright 2024 Google LLC
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -48,8 +48,6 @@ android {
flavorDimensions.add("version")
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
lint {
@ -58,11 +56,16 @@ android {
kotlinOptions {
freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn")
jvmTarget = JavaVersion.VERSION_21.toString()
}
namespace = "com.example.kotlindemos"
}
kotlin {
jvmToolchain(21) // Specify the JVM toolchain version
}
dependencies {
api(libs.appcompat)
implementation(libs.kotlinStdlib)
@ -70,10 +73,13 @@ dependencies {
implementation(libs.recyclerview)
implementation(libs.multidex)
implementation(libs.volley)
implementation(libs.material)
implementation(libs.lifecycleRuntimeKtx)
implementation(libs.mapsKtx)
implementation(libs.activityKtx)
// Below is used to run the easypermissions library to manage location permissions
// EasyPermissions is needed to help us request for permission to access location
implementation(libs.easypermissions)

View File

@ -32,9 +32,11 @@
android:theme="@style/AppTheme">
<!--
To add your Maps API key to this project:
1. Open the root project'sl secrets.properties file
1. Open the root project's secrets.properties file
2. Add this line, where YOUR_API_KEY is your API key:
MAPS_API_KEY=YOUR_API_KEY
Note: Do not check-in your API key!
-->
<meta-data
android:name="com.google.android.geo.API_KEY"
@ -48,7 +50,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".AdvancedMarkersDemoActivity"
android:exported="true" />
@ -73,6 +75,13 @@
<activity
android:name=".CircleDemoActivity"
android:exported="true" />
<activity
android:name=".DataDrivenDatasetStylingActivity"
android:theme="@style/MaterialAppTheme"
android:exported="true" />
<activity
android:name=".DataDrivenBoundariesActivity"
android:exported="true" />
<activity
android:name=".MarkerCloseInfoWindowOnRetapDemoActivity"
android:exported="true" />

View File

@ -37,7 +37,7 @@ private val BANGKOK = LatLng(13.7563, 100.5018)
private val MANILA = LatLng(14.5995, 120.9842)
private val HO_CHI_MINH_CITY = LatLng(10.7769, 106.7009)
const val ZOOM_LEVEL = 3.5f
private const val ZOOM_LEVEL = 3.5f
private val TAG = AdvancedMarkersDemoActivity::class.java.name

View File

@ -0,0 +1,225 @@
// Copyright 2025 Google LLC
//
// 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.example.kotlindemos
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.widget.Button
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.Feature
import com.google.android.gms.maps.model.FeatureClickEvent
import com.google.android.gms.maps.model.FeatureLayer
import com.google.android.gms.maps.model.FeatureLayerOptions
import com.google.android.gms.maps.model.FeatureStyle
import com.google.android.gms.maps.model.FeatureType
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MapCapabilities
import com.google.android.gms.maps.model.PlaceFeature
/**
* This sample showcases how to use the Data-driven styling for boundaries. For more information
* on how the Data-driven styling for boundaries work, check out the following link:
* https://developers.google.com/maps/documentation/android-sdk/dds-boundaries/overview
*/
class DataDrivenBoundariesActivity : AppCompatActivity(), OnMapReadyCallback,
FeatureLayer.OnFeatureClickListener {
private lateinit var map: GoogleMap
private var localityLayer: FeatureLayer? = null
private var areaLevel1Layer: FeatureLayer? = null
private var countryLayer: FeatureLayer? = null
private val HANA_HAWAII = LatLng(20.7522, -155.9877) // Hana, Hawaii
private val CENTER_US = LatLng(39.8283, -98.5795) // Approximate geographical center of the contiguous US
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.data_driven_boundaries_demo)
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment?
mapFragment?.getMapAsync(this)
findViewById<Button>(R.id.button_hawaii).setOnClickListener {
centerMapOnLocation(HANA_HAWAII, 13.5f) // Seattle coordinates
}
findViewById<Button>(R.id.button_us).setOnClickListener {
centerMapOnLocation(CENTER_US, 1f) // New York coordinates
}
}
private fun centerMapOnLocation(location: LatLng, zoomLevel: Float) {
map.moveCamera(CameraUpdateFactory.newLatLngZoom(location, zoomLevel))
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onMapReady(googleMap: GoogleMap) {
map = googleMap
val capabilities: MapCapabilities = map.mapCapabilities
println("Data-driven Styling is available: " + capabilities.isDataDrivenStylingAvailable)
// Get the LOCALITY feature layer.
localityLayer = googleMap.getFeatureLayer(
FeatureLayerOptions.Builder()
.featureType(FeatureType.LOCALITY)
.build()
)
// Apply style factory function to LOCALITY layer.
styleLocalityLayer()
// Get the ADMINISTRATIVE_AREA_LEVEL_1 feature layer.
areaLevel1Layer = googleMap.getFeatureLayer(
FeatureLayerOptions.Builder()
.featureType(FeatureType.ADMINISTRATIVE_AREA_LEVEL_1)
.build()
)
// Apply style factory function to ADMINISTRATIVE_AREA_LEVEL_1 layer.
styleAreaLevel1Layer()
// Get the COUNTRY feature layer.
countryLayer = googleMap.getFeatureLayer(
FeatureLayerOptions.Builder()
.featureType(FeatureType.COUNTRY)
.build()
)
// Register the click event handler for the Country layer.
countryLayer?.addOnFeatureClickListener(this)
// Apply default style to all countries on load to enable clicking.
styleCountryLayer()
}
private fun styleLocalityLayer() {
// Create the style factory function.
val styleFactory = FeatureLayer.StyleFactory { feature: Feature ->
// Check if the feature is an instance of PlaceFeature,
// which contains a place ID.
if (feature is PlaceFeature) {
val placeFeature: PlaceFeature = feature
// Determine if the place ID is for Hana, HI.
if (placeFeature.placeId == "ChIJ0zQtYiWsVHkRk8lRoB1RNPo") {
// Use FeatureStyle.Builder to configure the FeatureStyle object
// returned by the style factory function.
return@StyleFactory FeatureStyle.Builder()
// Define a style with purple fill at 50% opacity and
// solid purple border.
.fillColor(0x80810FCB.toInt())
.strokeColor(0xFF810FCB.toInt())
.build()
}
}
return@StyleFactory null
}
// Apply the style factory function to the feature layer.
localityLayer?.featureStyle = styleFactory
}
private fun styleAreaLevel1Layer() {
val styleFactory = FeatureLayer.StyleFactory { feature: Feature ->
if (feature is PlaceFeature) {
val placeFeature: PlaceFeature = feature
// Return a hueColor in the range [-299,299]. If the value is
// negative, add 300 to make the value positive.
var hueColor: Int = placeFeature.placeId.hashCode() % 300
if (hueColor < 0) {
hueColor += 300
}
return@StyleFactory FeatureStyle.Builder()
// Set the fill color for the state based on the hashed hue color.
.fillColor(Color.HSVToColor(150, floatArrayOf(hueColor.toFloat(), 1f, 1f)))
.build()
}
return@StyleFactory null
}
// Apply the style factory function to the feature layer.
areaLevel1Layer?.featureStyle = styleFactory
}
// Set default fill and border for all countries to ensure that they respond
// to click events.
@RequiresApi(Build.VERSION_CODES.O)
private fun styleCountryLayer() {
val styleFactory = FeatureLayer.StyleFactory { _: Feature ->
return@StyleFactory FeatureStyle.Builder()
// Set the fill color for the country as white with a 10% opacity.
// This requires minApi 26
.fillColor(Color.argb(0.1f, 0f, 0f, 0f))
// Set border color to solid black.
.strokeColor(Color.BLACK)
.build()
}
// Apply the style factory function to the country feature layer.
countryLayer?.featureStyle = styleFactory
}
// Define the click event handler.
override fun onFeatureClick(event: FeatureClickEvent) {
// Get the list of features affected by the click using
// getPlaceIds() defined below.
val selectedPlaceIds = getPlaceIds(event.features)
if (selectedPlaceIds.isNotEmpty()) {
val styleFactory = FeatureLayer.StyleFactory { feature: Feature ->
// Use PlaceFeature to get the placeID of the country.
if (feature is PlaceFeature) {
if (selectedPlaceIds.contains(feature.placeId)) {
return@StyleFactory FeatureStyle.Builder()
// Set the fill color to red.
.fillColor(Color.RED)
.build()
}
}
return@StyleFactory null
}
// Apply the style factory function to the feature layer.
countryLayer?.featureStyle = styleFactory
}
}
// Get a List of place IDs from the FeatureClickEvent object.
private fun getPlaceIds(features: List<Feature>): List<String> {
val placeIds: MutableList<String> = ArrayList()
for (feature in features) {
if (feature is PlaceFeature) {
placeIds.add(feature.placeId)
}
}
return placeIds
}
}

View File

@ -0,0 +1,360 @@
// Copyright 2025 Google LLC
//
// 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.example.kotlindemos
import android.graphics.Color
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.maps.OnMapReadyCallback
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.widget.Button
import android.widget.LinearLayout
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.annotation.ColorInt
import androidx.core.graphics.ColorUtils
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.model.DatasetFeature
import com.google.android.gms.maps.model.Feature
import com.google.android.gms.maps.model.FeatureClickEvent
import com.google.android.gms.maps.model.FeatureLayer
import com.google.android.gms.maps.model.FeatureLayerOptions
import com.google.android.gms.maps.model.FeatureStyle
import com.google.android.gms.maps.model.FeatureType
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MapCapabilities
import androidx.core.view.WindowCompat
private val TAG = DataDrivenDatasetStylingActivity::class.java.name
/**
* This sample showcases how to use the Data-driven styling for datasets. For more information
* on how the Data-driven styling for boundaries work, check out the following link:
* https://developers.google.com/maps/documentation/android-sdk/dds-datasets/overview
*/
class DataDrivenDatasetStylingActivity : AppCompatActivity(), OnMapReadyCallback, FeatureLayer.OnFeatureClickListener {
private lateinit var mapContainer: ViewGroup
private lateinit var map: GoogleMap
private val zoomLevel = 13.5f
private var datasetLayer: FeatureLayer? = null
// The global id of the clicked dataset feature.
private var lastGlobalId: String? = null
private data class DataSet(
val datasetId: String,
val location: LatLng,
val callback: DataDrivenDatasetStylingActivity.() -> Unit
)
private val dataSets = mutableMapOf<String, DataSet>()
private lateinit var buttonLayout: LinearLayout
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
if (dataSets.isEmpty()) {
with(dataSets) {
put(getString(R.string.boulder), DataSet(BuildConfig.BOULDER_DATASET_ID, LatLng(40.0150, -105.2705)) { styleBoulderDataset() })
put(getString(R.string.new_york), DataSet(BuildConfig.NEW_YORK_DATASET_ID, LatLng(40.786244, -73.962684)) { styleNYCDataset() })
put(getString(R.string.kyoto), DataSet(BuildConfig.KYOTO_DATASET_ID, LatLng(35.005081, 135.764385)) { styleKyotoDataset() })
}
}
setContentView(R.layout.data_driven_styling_demo)
mapContainer = findViewById(R.id.map_container)
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment?
mapFragment?.getMapAsync(this)
// Set the click listener for each of the buttons
listOf(R.id.button_kyoto, R.id.button_ny, R.id.button_boulder).forEach { viewId ->
findViewById<Button>(viewId).setOnClickListener { view ->
switchToDataset((view as Button).text.toString())
}
}
buttonLayout = findViewById<View>(R.id.button_kyoto).parent as LinearLayout;
handleCutout()
}
private fun handleCutout() {
WindowCompat.setDecorFitsSystemWindows(window, false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.decorView.setOnApplyWindowInsetsListener { _, windowInsets ->
val insets = windowInsets.getInsets(WindowInsets.Type.systemBars())
val topInset = insets.top
mapContainer.setPadding(0, topInset, 0, 0)
windowInsets
}
} else {
window.decorView.setOnApplyWindowInsetsListener { view, windowInsets ->
val topInset = windowInsets.systemWindowInsetTop
mapContainer.setPadding(0, topInset, 0, 0)
windowInsets
}
}
}
/**
* Switches the currently displayed dataset on the map to the one identified by the given label.
*
* This function retrieves a DataSetInfo object from the `dataSets` map using the provided `label`.
* If a dataset with the given label exists, it does the following:
* 1. Creates a new FeatureLayer for the specified dataset.
* 2. Sets the `datasetLayer` property to the newly created FeatureLayer.
* 3. Executes the callback function associated with the dataset, passing the current activity instance (this).
* 4. Centers the map on the location associated with the dataset.
* If no dataset with the given label is found, it displays a Toast message indicating an unknown dataset.
*
* @param label The label identifying the dataset to switch to. This label should correspond to a key in the `dataSets` map.
* @throws IllegalStateException if `map` is not initialized.
*/
private fun switchToDataset(label: String) {
dataSets[label]?.let { dataSet ->
datasetLayer = map.getFeatureLayer(
with(FeatureLayerOptions.Builder()) {
featureType(FeatureType.DATASET)
// Specify the dataset ID.
datasetId(dataSet.datasetId)
}.build()
)
dataSet.callback(this)
centerMapOnLocation(dataSet.location)
} ?: run {
Toast.makeText(this, "Unknown dataset: $label", Toast.LENGTH_SHORT).show()
}
}
override fun onMapReady(googleMap: GoogleMap) {
map = googleMap
val capabilities: MapCapabilities = map.mapCapabilities
println("Data-driven Styling is available: " + capabilities.isDataDrivenStylingAvailable)
switchToDataset("Boulder")
// Register the click event handler for the Datasets layer.
datasetLayer?.addOnFeatureClickListener(this)
}
private fun styleNYCDataset() {
data class Style(
@ColorInt val fillColor: Int,
@ColorInt val strokeColor: Int,
val pointRadius: Float,
)
val largePointRadius = 8F
val smallPointRadius = 6F
val darkRedBrown = resources.getColor(R.color.darkRedBrown)
val styleFactory = FeatureLayer.StyleFactory { feature: Feature ->
if (feature is DatasetFeature) {
val furColors: MutableMap<String, String> = feature.datasetAttributes
val furColor = furColors["Color"]
val style = when (furColor) {
"Black+" -> Style(Color.BLACK, Color.BLACK, largePointRadius)
"Cinnamon+" -> Style(darkRedBrown, darkRedBrown, largePointRadius)
"Cinnamon+Gray" -> Style(darkRedBrown, darkRedBrown, smallPointRadius)
"Cinnamon+White" -> Style(darkRedBrown, Color.WHITE, smallPointRadius)
"Gray+" -> Style(Color.GRAY, Color.YELLOW, largePointRadius) // Default stroke
"Gray+Cinnamon" -> Style(Color.GRAY, darkRedBrown, smallPointRadius)
"Gray+Cinnamon, White" -> Style(Color.LTGRAY, darkRedBrown, smallPointRadius)
"Gray+White" -> Style(Color.GRAY, Color.WHITE, smallPointRadius)
else -> Style(Color.GREEN, Color.YELLOW, largePointRadius) // Default style if furColor is null or doesn't match
}
return@StyleFactory FeatureStyle.Builder()
.fillColor(style.fillColor)
.strokeColor(style.strokeColor)
.pointRadius(style.pointRadius)
.build()
}
return@StyleFactory null
}
// Apply the style factory function to the feature layer.
datasetLayer?.featureStyle = styleFactory
}
private fun styleKyotoDataset() {
// Create the style factory function.
val styleFactory = FeatureLayer.StyleFactory { feature: Feature ->
// Check if the feature is an instance of DatasetFeature.
if (feature is DatasetFeature) {
// Determine the value of the typecategory attribute.
val typeCategories: MutableMap<String, String> = feature.datasetAttributes
val typeCategory = typeCategories["type"]
// Set default colors to green.
val fillColor: Int
val strokeColor: Int
when (typeCategory) {
"temple" -> {
// Color temples areas blue.
fillColor = Color.BLUE
strokeColor = Color.BLUE
}
else -> {
// Color all other areas green.
fillColor = Color.GREEN
strokeColor = Color.GREEN
}
}
return@StyleFactory FeatureStyle.Builder()
.fillColor(fillColor)
.strokeColor(strokeColor)
.strokeWidth(2F)
.build()
}
return@StyleFactory null
}
// Apply the style factory function to the feature layer.
datasetLayer?.featureStyle = styleFactory
}
private fun styleBoulderDataset() {
val EASY = Color.GREEN
val MODERATE = Color.BLUE
val DIFFICULT = Color.RED
// Create the style factory function.
val styleFactory = FeatureLayer.StyleFactory { feature: Feature ->
// Set default colors to to yellow and point radius to 8.
var fillColor = Color.GREEN
var strokeColor = Color.YELLOW
var pointRadius = 8F
var strokeWidth = 3F
// Check if the feature is an instance of DatasetFeature.
if (feature is DatasetFeature) {
val attributes: MutableMap<String, String> = feature.datasetAttributes
val difficulty = attributes["OSMPTrailsOSMPDIFFICULTY"]
val name = attributes["OSMPTrailsOSMPTRAILNAME"]
val dogsAllowed = attributes["OSMPTrailsOSMPDOGREGGEN"]
when (difficulty) {
"Easy" -> {
fillColor = EASY
}
"Moderate" -> {
fillColor = MODERATE
}
"Difficult" -> {
fillColor = DIFFICULT
}
else -> {
Log.w(TAG, "$name -> Unknown difficulty: $difficulty")
fillColor = Color.MAGENTA
}
}
when (dogsAllowed) {
"No Dogs" -> {
fillColor = ColorUtils.setAlphaComponent(fillColor, 66)
strokeWidth = 5f
}
"LVS" -> {
}
"LR", "RV" -> {
fillColor = ColorUtils.setAlphaComponent(fillColor, 75)
}
else -> {
Log.w(TAG, "$name -> Unknown dogs reg: $dogsAllowed")
}
}
strokeColor = fillColor
return@StyleFactory FeatureStyle.Builder()
.fillColor(fillColor)
.strokeColor(strokeColor)
.pointRadius(pointRadius)
.strokeWidth(strokeWidth)
.build()
}
return@StyleFactory null
}
// Apply the style factory function to the feature layer.
datasetLayer?.featureStyle = styleFactory
}
private fun centerMapOnLocation(location: LatLng) {
map.moveCamera(CameraUpdateFactory.newLatLngZoom(location, zoomLevel))
}
// Define the click event handler to set lastGlobalId to globalid of selected feature.
override fun onFeatureClick(event: FeatureClickEvent) {
// Get the dataset feature affected by the click.
val clickFeatures: MutableList<Feature> = event.features
lastGlobalId = null
if (clickFeatures[0] is DatasetFeature) {
lastGlobalId = ((clickFeatures[0] as DatasetFeature).datasetAttributes["globalid"])
// Remember to reset the Style Factory.
styleDatasetsLayerClickEvent()
}
}
// Set fill and border for all features.
private fun styleDatasetsLayerClickEvent() {
// Create the style factory function.
val styleFactory = FeatureLayer.StyleFactory { feature: Feature ->
// Check if the feature is an instance of DatasetFeature.
if (feature is DatasetFeature) {
val globalIDs: MutableMap<String, String> = feature.datasetAttributes
// Determine globalid attribute.
val globalID = globalIDs["globalid"]
// Set default colors to to green.
var fillColor = Color.GREEN
var strokeColor = Color.GREEN
if (globalID == lastGlobalId) {
// Color selected area blue.
fillColor = Color.BLUE
strokeColor = Color.BLUE
}
return@StyleFactory FeatureStyle.Builder()
.fillColor(fillColor)
.strokeColor(strokeColor)
.build()
}
return@StyleFactory null
}
// Apply the style factory function to the dataset feature layer.
datasetLayer?.featureStyle = styleFactory
}
}

View File

@ -67,6 +67,16 @@ class DemoDetailsList {
R.string.close_info_window_demo_details,
MarkerCloseInfoWindowOnRetapDemoActivity::class.java
),
DemoDetails(
R.string.data_driven_styling_label,
R.string.data_driven_styling_details,
DataDrivenDatasetStylingActivity::class.java
),
DemoDetails(
R.string.data_driven_boundaries_label,
R.string.data_driven_boundaries_details,
DataDrivenBoundariesActivity::class.java
),
DemoDetails(
R.string.events_demo_label,
R.string.events_demo_details,

View File

@ -1,5 +1,5 @@
/*
* Copyright 2024 Google LLC
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -0,0 +1,53 @@
<!--
Copyright 2025 Google LLC
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.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Map Fragment -->
<fragment
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
map:backgroundColor="#fff0b2dd"
map:mapId="@string/map_id"
class="com.google.android.gms.maps.SupportMapFragment" />
<!-- Buttons to center the map -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:orientation="horizontal"
android:padding="16dp">
<Button
android:id="@+id/button_hawaii"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hawaii" />
<Button
android:id="@+id/button_us"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="US" />
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,84 @@
<!--
Copyright 2025 Google LLC
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.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="@+id/map_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/top_bar"
android:layout_width="0dp"
android:layout_height="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title="@string/app_name"
app:titleTextColor="?attr/colorOnPrimary"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
/>
<fragment
android:id="@+id/map"
android:layout_width="0dp"
android:layout_height="0dp"
map:backgroundColor="#fff0b2dd"
map:mapId="@string/map_id"
class="com.google.android.gms.maps.SupportMapFragment"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/top_bar" />
<LinearLayout
android:id="@+id/button_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/top_bar">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_boulder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:padding="8dp"
android:text="@string/boulder" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_ny"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:padding="8dp"
android:text="@string/new_york" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_kyoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:padding="8dp"
android:text="@string/kyoto" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

Some files were not shown because too many files have changed in this diff Show More