-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]>
- Loading branch information
Showing
128 changed files
with
2,189 additions
and
133 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
ApiDemos/java/app/src/main/java/com/example/mapdemo/ApiDemoApplication.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
228 changes: 228 additions & 0 deletions
228
ApiDemos/java/app/src/main/java/com/example/mapdemo/DataDrivenBoundariesActivity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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] |
Oops, something went wrong.