基于Google Maps Android SDK实现300米内场所分类功能技术问询
Hey there! Let's walk through how to build this feature—getting nearby places within 300 meters of the user's location and categorizing them by type—using your existing MapsActivity code as a starting point.
Your current code has a TODO for handling permissions, so let's flesh that out first. Users need to grant location access before we can do anything meaningful with their position.
First, add a permission request constant at the top of your class:
private static final int REQUEST_LOCATION_PERMISSION = 1;
Then, replace the permission check block in onMapReady with code that requests permissions if they're missing:
@Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL); // Check for location permissions if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // Request permissions ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_LOCATION_PERMISSION); return; } // Permissions granted, enable my location mMap.setMyLocationEnabled(true); // Now proceed to get user's location and fetch nearby places fetchUserLocation(); }
Add the permission result handler method to your class:
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_LOCATION_PERMISSION) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { mMap.setMyLocationEnabled(true); fetchUserLocation(); } } else { // Handle permission denial (show a toast, etc.) Toast.makeText(this, "Location permission is required to use this feature", Toast.LENGTH_SHORT).show(); } } }
We'll use Google's FusedLocationProviderClient (the recommended way to get location on Android) to get the user's precise position.
First, add the client as a class variable:
private FusedLocationProviderClient fusedLocationClient;
Initialize it in onCreate:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_maps); // Initialize FusedLocationProviderClient fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map); mapFragment.getMapAsync(this); }
Now add the fetchUserLocation method to get the last known location (or request location updates if needed):
private void fetchUserLocation() { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { return; } fusedLocationClient.getLastLocation() .addOnSuccessListener(this, location -> { if (location != null) { // Got user's location: lat/lng LatLng userLocation = new LatLng(location.getLatitude(), location.getLongitude()); // Move map camera to user's location mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(userLocation, 17)); // Now fetch nearby places fetchNearbyPlaces(userLocation); } else { // Handle case where location is null (request updates, show error) Toast.makeText(this, "Could not get your location", Toast.LENGTH_SHORT).show(); } }); }
To fetch nearby venues, you'll need to use the Google Places API for Android. Here's how to set it up:
Step 3.1: Enable Places API & Get API Key
- Head to Google Cloud Console
- Enable the Places API for your project
- Generate an API key (restrict it to your app's package name and SHA-1 for security)
Step 3.2: Add Dependencies & Manifest Config
Add this dependency to your app-level build.gradle (Module level):
dependencies { // ... other dependencies implementation 'com.google.android.libraries.places:places:3.4.0' }
Add your API key to AndroidManifest.xml inside the <application> tag:
<meta-data android:name="com.google.android.geo.API_KEY" android:value="YOUR_GOOGLE_MAPS_API_KEY"/>
Step 3.3: Implement Nearby Search
Add Places client variables to your class:
private PlacesClient placesClient; private static final int RADIUS_METERS = 300; // 300 meters
Initialize placesClient in onCreate:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_maps); // Initialize Places API Places.initialize(getApplicationContext(), "YOUR_GOOGLE_MAPS_API_KEY"); placesClient = Places.createClient(this); fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map); mapFragment.getMapAsync(this); }
Now add the fetchNearbyPlaces method to search for venues within 300 meters:
private void fetchNearbyPlaces(LatLng userLocation) { // Build nearby search request FindCurrentPlaceRequest request = FindCurrentPlaceRequest.newInstance(getPlaceFields()); placesClient.findCurrentPlace(request) .addOnSuccessListener(response -> { // Process the list of places List<Place> nearbyPlaces = response.getPlaceLikelihoods() .stream() .map(PlaceLikelihood::getPlace) .collect(Collectors.toList()); // Categorize the places categorizePlaces(nearbyPlaces); }) .addOnFailureListener(e -> { // Handle error Toast.makeText(this, "Failed to fetch nearby places: " + e.getMessage(), Toast.LENGTH_SHORT).show(); }); } // Define which place fields we want to retrieve private List<Place.Field> getPlaceFields() { return Arrays.asList( Place.Field.ID, Place.Field.NAME, Place.Field.LAT_LNG, Place.Field.TYPES, Place.Field.ADDRESS ); }
Now let's take the list of nearby places and group them into categories like Restaurants, Stores, etc. We'll use a HashMap to map category names to lists of places.
Add the categorizePlaces method:
private void categorizePlaces(List<Place> places) { // Define category mappings (adjust types based on your needs) Map<String, List<Place>> categorizedPlaces = new HashMap<>(); categorizedPlaces.put("Restaurants", new ArrayList<>()); categorizedPlaces.put("Stores", new ArrayList<>()); categorizedPlaces.put("Cafés", new ArrayList<>()); categorizedPlaces.put("Other", new ArrayList<>()); for (Place place : places) { List<String> placeTypes = place.getTypes(); boolean placed = false; // Check if place matches any category if (placeTypes.contains("restaurant")) { categorizedPlaces.get("Restaurants").add(place); placed = true; } else if (placeTypes.contains("store") || placeTypes.contains("shopping_mall")) { categorizedPlaces.get("Stores").add(place); placed = true; } else if (placeTypes.contains("cafe")) { categorizedPlaces.get("Cafés").add(place); placed = true; } else { categorizedPlaces.get("Other").add(place); } } // Now you can use categorizedPlaces to display results (e.g., in a RecyclerView, markers on map) // Example: Print counts to logcat Log.d("CategorizedPlaces", "Restaurants: " + categorizedPlaces.get("Restaurants").size()); Log.d("CategorizedPlaces", "Stores: " + categorizedPlaces.get("Stores").size()); Log.d("CategorizedPlaces", "Cafés: " + categorizedPlaces.get("Cafés").size()); Log.d("CategorizedPlaces", "Other: " + categorizedPlaces.get("Other").size()); // Optional: Add markers for each place on the map addPlaceMarkers(categorizedPlaces); } // Optional: Add markers for categorized places private void addPlaceMarkers(Map<String, List<Place>> categorizedPlaces) { // Define marker colors for each category Map<String, Float> categoryColors = new HashMap<>(); categoryColors.put("Restaurants", BitmapDescriptorFactory.HUE_RED); categoryColors.put("Stores", BitmapDescriptorFactory.HUE_BLUE); categoryColors.put("Cafés", BitmapDescriptorFactory.HUE_YELLOW); categoryColors.put("Other", BitmapDescriptorFactory.HUE_GRAY); for (Map.Entry<String, List<Place>> entry : categorizedPlaces.entrySet()) { String category = entry.getKey(); List<Place> places = entry.getValue(); float color = categoryColors.get(category); for (Place place : places) { mMap.addMarker(new MarkerOptions() .position(place.getLatLng()) .title(place.getName()) .snippet(place.getAddress()) .icon(BitmapDescriptorFactory.defaultMarker(color))); } } }
Here's how your complete MapsActivity will look with all the above changes:
import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; import androidx.fragment.app.FragmentActivity; import android.Manifest; import android.content.pm.PackageManager; import android.location.Location; import android.os.Bundle; import android.util.Log; import android.widget.Toast; 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.OnMapReadyCallback; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MarkerOptions; import com.google.android.gms.tasks.OnSuccessListener; import com.google.android.libraries.places.api.Places; import com.google.android.libraries.places.api.model.Place; import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest; import com.google.android.libraries.places.api.net.PlacesClient; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class MapsActivity extends FragmentActivity implements OnMapReadyCallback { private GoogleMap mMap; private FusedLocationProviderClient fusedLocationClient; private PlacesClient placesClient; private static final int REQUEST_LOCATION_PERMISSION = 1; private static final int RADIUS_METERS = 300; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_maps); // Initialize Places API Places.initialize(getApplicationContext(), "YOUR_GOOGLE_MAPS_API_KEY"); placesClient = Places.createClient(this); // Initialize FusedLocationProviderClient fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map); mapFragment.getMapAsync(this); } @Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL); // Check for location permissions if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_LOCATION_PERMISSION); return; } mMap.setMyLocationEnabled(true); fetchUserLocation(); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_LOCATION_PERMISSION) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { mMap.setMyLocationEnabled(true); fetchUserLocation(); } } else { Toast.makeText(this, "Location permission is required to use this feature", Toast.LENGTH_SHORT).show(); } } } private void fetchUserLocation() { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { return; } fusedLocationClient.getLastLocation() .addOnSuccessListener(this, location -> { if (location != null) { LatLng userLocation = new LatLng(location.getLatitude(), location.getLongitude()); mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(userLocation, 17)); fetchNearbyPlaces(userLocation); } else { Toast.makeText(this, "Could not get your location", Toast.LENGTH_SHORT).show(); } }); } private void fetchNearbyPlaces(LatLng userLocation) { FindCurrentPlaceRequest request = FindCurrentPlaceRequest.newInstance(getPlaceFields()); placesClient.findCurrentPlace(request) .addOnSuccessListener(response -> { List<Place> nearbyPlaces = response.getPlaceLikelihoods() .stream() .map(PlaceLikelihood::getPlace) .collect(Collectors.toList()); categorizePlaces(nearbyPlaces); }) .addOnFailureListener(e -> { Toast.makeText(this, "Failed to fetch nearby places: " + e.getMessage(), Toast.LENGTH_SHORT).show(); }); } private List<Place.Field> getPlaceFields() { return Arrays.asList( Place.Field.ID, Place.Field.NAME, Place.Field.LAT_LNG, Place.Field.TYPES, Place.Field.ADDRESS ); } private void categorizePlaces(List<Place> places) { Map<String, List<Place>> categorizedPlaces = new HashMap<>(); categorizedPlaces.put("Restaurants", new ArrayList<>()); categorizedPlaces.put("Stores", new ArrayList<>()); categorizedPlaces.put("Cafés", new ArrayList<>()); categorizedPlaces.put("Other", new ArrayList<>()); for (Place place : places) { List<String> placeTypes = place.getTypes(); boolean placed = false; if (placeTypes.contains("restaurant")) { categorizedPlaces.get("Restaurants").add(place); placed = true; } else if (placeTypes.contains("store") || placeTypes.contains("shopping_mall")) { categorizedPlaces.get("Stores").add(place); placed = true; } else if (placeTypes.contains("cafe")) { categorizedPlaces.get("Cafés").add(place); placed = true; } else { categorizedPlaces.get("Other").add(place); } } // Log category counts Log.d("CategorizedPlaces", "Restaurants: " + categorizedPlaces.get("Restaurants").size()); Log.d("CategorizedPlaces", "Stores: " + categorizedPlaces.get("Stores").size()); Log.d("CategorizedPlaces", "Cafés: " + categorizedPlaces.get("Cafés").size()); Log.d("CategorizedPlaces", "Other: " + categorizedPlaces.get("Other").size()); addPlaceMarkers(categorizedPlaces); } private void addPlaceMarkers(Map<String, List<Place>> categorizedPlaces) { Map<String, Float> categoryColors = new HashMap<>(); categoryColors.put("Restaurants", BitmapDescriptorFactory.HUE_RED); categoryColors.put("Stores", BitmapDescriptorFactory.HUE_BLUE); categoryColors.put("Cafés", BitmapDescriptorFactory.HUE_YELLOW); categoryColors.put("Other", BitmapDescriptorFactory.HUE_GRAY); for (Map.Entry<String, List<Place>> entry : categorizedPlaces.entrySet()) { String category = entry.getKey(); List<Place> places = entry.getValue();

