From 55616f05f1a74807692b74a3662408175d06b74b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Tue, 1 Jul 2025 14:34:33 +0200 Subject: [PATCH 1/6] feat: added isochrone maps --- demo/build.gradle.kts | 1 + demo/src/main/AndroidManifest.xml | 3 + .../android/utils/demo/ApiKeyValidator.kt | 2 +- .../utils/demo/IsochroneMapActivity.java | 141 ++++++ .../maps/android/utils/demo/MainActivity.java | 1 + .../main/res/layout/activity_isochrone.xml | 56 +++ gradle/libs.versions.toml | 4 +- .../isochrone/IsochroneMapProvider.java | 431 ++++++++++++++++++ 8 files changed, 637 insertions(+), 2 deletions(-) create mode 100644 demo/src/main/java/com/google/maps/android/utils/demo/IsochroneMapActivity.java create mode 100644 demo/src/main/res/layout/activity_isochrone.xml create mode 100644 library/src/main/java/com/google/maps/android/isochrone/IsochroneMapProvider.java diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts index 9556a50af..710097bd7 100644 --- a/demo/build.gradle.kts +++ b/demo/build.gradle.kts @@ -65,6 +65,7 @@ dependencies { implementation(libs.kotlinx.coroutines.android) implementation(libs.kotlinx.coroutines.core) implementation(libs.material) + implementation(libs.play.services.location) testImplementation(libs.junit) testImplementation(libs.truth) diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index a504a7619..1573afb00 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -67,6 +67,9 @@ + diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/ApiKeyValidator.kt b/demo/src/main/java/com/google/maps/android/utils/demo/ApiKeyValidator.kt index 8dcb036e6..ab6713a04 100644 --- a/demo/src/main/java/com/google/maps/android/utils/demo/ApiKeyValidator.kt +++ b/demo/src/main/java/com/google/maps/android/utils/demo/ApiKeyValidator.kt @@ -58,7 +58,7 @@ internal fun keyHasValidFormat(apiKey: String): Boolean { * @param context The context to retrieve the API key from. * @return The API key if found, `null` otherwise. */ -private fun getMapsApiKey(context: Context): String? { +fun getMapsApiKey(context: Context): String? { try { val bundle = context.packageManager .getApplicationInfo(context.packageName, PackageManager.GET_META_DATA) diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/IsochroneMapActivity.java b/demo/src/main/java/com/google/maps/android/utils/demo/IsochroneMapActivity.java new file mode 100644 index 000000000..7cc97a8dc --- /dev/null +++ b/demo/src/main/java/com/google/maps/android/utils/demo/IsochroneMapActivity.java @@ -0,0 +1,141 @@ +package com.google.maps.android.utils.demo; + +import static com.google.maps.android.utils.demo.ApiKeyValidatorKt.getMapsApiKey; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.location.Location; +import android.os.Bundle; +import android.util.Log; +import android.view.Gravity; +import android.view.ViewGroup.LayoutParams; +import android.widget.FrameLayout; +import android.widget.ProgressBar; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; + +import com.google.android.gms.location.FusedLocationProviderClient; +import com.google.android.gms.location.LocationServices; +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.MapView; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.model.LatLng; +import com.google.maps.android.isochrone.IsochroneMapProvider; + +public class IsochroneMapActivity extends AppCompatActivity implements OnMapReadyCallback { + + private static final String TAG = "IsochroneMapActivity"; + + private FrameLayout rootLayout; + private MapView mapView; + private ProgressBar progressBar; + + private GoogleMap map; + private FusedLocationProviderClient fusedLocationClient; + + private ActivityResultLauncher requestPermissionLauncher; + + private IsochroneMapProvider isochroneMapProvider; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + rootLayout = new FrameLayout(this); + mapView = new MapView(this); + + progressBar = new ProgressBar(this); + progressBar.setIndeterminate(true); + progressBar.setVisibility(android.view.View.GONE); + + FrameLayout.LayoutParams mapParams = new FrameLayout.LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT); + + FrameLayout.LayoutParams progressParams = new FrameLayout.LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT, + Gravity.CENTER); + + rootLayout.addView(mapView, mapParams); + rootLayout.addView(progressBar, progressParams); + + setContentView(rootLayout); + + mapView.onCreate(savedInstanceState); + mapView.getMapAsync(this); + + fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); + + requestPermissionLauncher = registerForActivityResult( + new ActivityResultContracts.RequestPermission(), + isGranted -> { + if (isGranted) { + getLocationAndDraw(); + } else { + Log.e(TAG, "Location permission denied"); + } + }); + } + + @Override + public void onMapReady(@NonNull GoogleMap googleMap) { + map = googleMap; + map.getUiSettings().setZoomControlsEnabled(true); + checkPermissionAndStart(); + } + + private void checkPermissionAndStart() { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) + != PackageManager.PERMISSION_GRANTED) { + requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION); + } else { + getLocationAndDraw(); + } + } + + private void getLocationAndDraw() { + fusedLocationClient.getLastLocation().addOnSuccessListener(location -> { + if (location != null) { + LatLng origin = new LatLng(location.getLatitude(), location.getLongitude()); + map.moveCamera(CameraUpdateFactory.newLatLngZoom(origin, 14f)); + + if (isochroneMapProvider == null) { + String apiKey = getMapsApiKey(this); + isochroneMapProvider = new IsochroneMapProvider(map, apiKey, new IsochroneMapProvider.LoadingListener() { + @Override + public void onLoadingStarted() { + runOnUiThread(() -> progressBar.setVisibility(android.view.View.VISIBLE)); + } + + @Override + public void onLoadingFinished() { + runOnUiThread(() -> progressBar.setVisibility(android.view.View.GONE)); + } + }, IsochroneMapProvider.TransportMode.BICYCLING); + isochroneMapProvider.setUiThreadExecutor(this::runOnUiThread); + } + + isochroneMapProvider.drawIsochrones(origin, new int[]{2,4,6,9}, IsochroneMapProvider.ColorSchema.GREEN_RED); + + } else { + Log.e(TAG, "Location is null"); + } + }); + } + + + // MapView lifecycle methods + @Override protected void onResume() { super.onResume(); mapView.onResume(); } + @Override protected void onStart() { super.onStart(); mapView.onStart(); } + @Override protected void onStop() { super.onStop(); mapView.onStop(); } + @Override protected void onPause() { mapView.onPause(); super.onPause(); } + @Override protected void onDestroy() { mapView.onDestroy(); super.onDestroy(); } + @Override public void onLowMemory() { super.onLowMemory(); mapView.onLowMemory(); } +} diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.java b/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.java index 4f22adbfc..6e95a1446 100644 --- a/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.java +++ b/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.java @@ -56,6 +56,7 @@ protected void onCreate(Bundle savedInstanceState) { addDemo("Multi Layer", MultiLayerDemoActivity.class); addDemo("AnimationUtil sample", AnimationUtilDemoActivity.class); addDemo("Street View Demo", StreetViewDemoActivity.class); + addDemo("Isochrone Map", IsochroneMapActivity.class); } private void addDemo(String demoName, Class activityClass) { diff --git a/demo/src/main/res/layout/activity_isochrone.xml b/demo/src/main/res/layout/activity_isochrone.xml new file mode 100644 index 000000000..20653cae8 --- /dev/null +++ b/demo/src/main/res/layout/activity_isochrone.xml @@ -0,0 +1,56 @@ + + + + + + + + + +