Android Maps v2 - animate camera to include most m

2019-01-19 23:42发布

问题:

I have a set of points coming from a webservice that need to be displayed on a map.

I have a current solution working nicely for most cases, using the well-known LatLngBounds.Builder, CameraUpdateFactory.newLatLngBounds and map.animateCamera.

I have some cases which give problems though: when the points are too far away, the map centers on max zoom level on the barycenter of those points. For example: I have 10 points in France and 2 points in Hawai. Maps centers more or less on the caribeans at min zoom level. Hence on screen I got nothing shown, the user has to scroll to actually see something is there.

So my question is:

is there a way to get the map to zoom out far enough so that I can see all points (that would be prefered)

Or: which would be the best way to filter out those cases where just a few points are very far away from the majority and pick a set of point to zoom on (in my example, I would choose to zoom on the 10 points in France and forget about the ones in Hawai).

回答1:

Put all the LatLng of the markers in the list and pass them to this method and at the last line in the newLatLngBounds(bounds, 50)) the 50 represent the padding between the map edge and the most outer marker in each side

public void centerIncidentRouteOnMap(List<LatLng> copiedPoints) {
        double minLat = Integer.MAX_VALUE;
        double maxLat = Integer.MIN_VALUE;
        double minLon = Integer.MAX_VALUE;
        double maxLon = Integer.MIN_VALUE;
        for (LatLng point : copiedPoints) {
            maxLat = Math.max(point.latitude, maxLat);
            minLat = Math.min(point.latitude, minLat);
            maxLon = Math.max(point.longitude, maxLon);
            minLon = Math.min(point.longitude, minLon);
        }
        final LatLngBounds bounds = new LatLngBounds.Builder().include(new LatLng(maxLat, maxLon)).include(new LatLng(minLat, minLon)).build();
        mapFragment.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50));
    }


回答2:

Spotted an error in my previous code and decided to sit down and rewrite it.

I have done something similar before where I had ~4500 markers and wanted to select those within a certain distance of a specific location. Took that code and generalized it to be used with any kind of Markers.

The code that I will post below contains two methods that you can use:

selectLowDistanceMarkers

Measures distance between each and every marker and selects only those that does not have a long distance to any of the other markers. This requires a O(n+n^2) runtime due to the comparison between every marker and an examination afterwards.

getSurroundingMarkers

If you already know a postition that you would like to zoom in to, then this method does the same as above. This method is way less CPU heavy as it only has to do a O(n) run through all the markers and compare them to the given position.

private List<Marker> selectLowDistanceMarkers(List<Marker> markers,
        int maxDistanceMeters) {

    List<Marker> acceptedMarkers = new ArrayList<Marker>();

    if (markers == null) return acceptedMarkers;

    Map<Marker, Float> longestDist = new HashMap<Marker, Float>();

    for (Marker marker1 : markers) {

        // in this for loop we remember the max distance for each marker
        // think of a map with a flight company's routes from an airport
        // these lines is drawn for each airport
        // marker1 being the airport and marker2 destinations

        for (Marker marker2 : markers) {
            if (!marker1.equals(marker2)) {
                float distance = distBetween(marker1.getPosition(),
                        marker2.getPosition());
                if (longestDist.containsKey(marker1)) {
                    // possible we have a longer distance
                    if (distance > longestDist.get(marker1))
                        longestDist.put(marker1, distance);
                } else {
                    // first distance
                    longestDist.put(marker1, distance);
                }
            }
        }
    }


    // examine the distances collected
    for (Marker marker: longestDist.keySet()) {
        if (longestDist.get(marker) <= maxDistanceMeters) acceptedMarkers.add(marker);
    }

    return acceptedMarkers;
}

private List<Marker> getSurroundingMarkers(List<Marker> markers,
        LatLng origin, int maxDistanceMeters) {
    List<Marker> surroundingMarkers = surroundingMarkers = new ArrayList<Marker>();
    if (markers == null) return surroundingMarkers ;


        for (Marker marker : markers) {

            double dist = distBetween(origin, marker.getPosition());

            if (dist < getHydrantsLoadradius()) {
                surroundingMarkers.add(marker);
            }
        }


    return surroundingMarkers;
}

private float distBetween(LatLng pos1, LatLng pos2) {
    return distBetween(pos1.latitude, pos1.longitude, pos2.latitude,
            pos2.longitude);
}

/** distance in meters **/
private float distBetween(double lat1, double lng1, double lat2, double lng2) {
    double earthRadius = 3958.75;
    double dLat = Math.toRadians(lat2 - lat1);
    double dLng = Math.toRadians(lng2 - lng1);
    double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
            + Math.cos(Math.toRadians(lat1))
            * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2)
            * Math.sin(dLng / 2);
    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    double dist = earthRadius * c;

    int meterConversion = 1609;

    return (float) (dist * meterConversion);
}

Again, use the well known LatLngBounds to determine how much you need to zoom after using one of the filtering algorithms above.



回答3:

Based on some ideas from cYrixmorten, I have simplified the problem because I know the map can accomodate at least 4000km of surface. So here is the function to build the list of ignored webcams (then I simply ignore that webcam for the camera bounds computation but still add the marker so that it is on the map if the user moves).

private List<Webcam> buildIgnoredWebcamsList(List<Webcam> webcams) {
    if (webcams == null || webcams.size() < 2) return Lists.newArrayList();

    int webcamCount = webcams.size();

    // Number of conflicts (distance > 4000 km) for the camera at index #
    float averageConflictCount = 0;
    int[] conflictCount = new int[webcamCount];
    Arrays.fill(conflictCount, 0);

    // Find number of conflicts between camera pairs
    float[] distance = new float[1];

    for (int i = 0; i < webcamCount - 1; ++i) {
        Webcam a = webcams.get(i);

                    // We don't have to start from 0, compare a and b only once
        for (int j = i + 1; j < webcamCount; ++j) {
            Webcam b = webcams.get(j);
            Location.distanceBetween(a.getLatitude(), a.getLongitude(), b.getLatitude(), b.getLongitude(), distance);

            // We have a conflict between a and b if they are more than 4000km away
            if (distance[0] > 4000 * 1000) {
                conflictCount[i] += 1;
                conflictCount[j] += 1;
                averageConflictCount += 2;
            }
        }
    }
    averageConflictCount /= webcamCount;

    // Exclude all webcams with a number of conflicts greater than the average
    List<Webcam> ignoredCamerasForBounds = Lists.newArrayList();

    for (int i = 0; i < webcamCount; ++i) {
        if (conflictCount[i] > averageConflictCount) {
            ignoredCamerasForBounds.add(webcams.get(i));
        }
    }

    return ignoredCamerasForBounds;
}


回答4:

Display display = getWindowManager().getDefaultDisplay(); 
        Point size = new Point();
        display.getSize(size);
        int ancho = size.x;
        int alto =size.y;
List<LatLng> copiedPoints = new ArrayList<LatLng>();
        copiedPoints.add(origin);
        copiedPoints.add(dest);

centerIncidentRouteOnMap(copiedPoints, ancho, alto);

....

public void centerIncidentRouteOnMap(List<LatLng> copiedPoints, int ancho, int alto) {
    double minLat = Integer.MAX_VALUE;
    double maxLat = Integer.MIN_VALUE;
    double minLon = Integer.MAX_VALUE;
    double maxLon = Integer.MIN_VALUE;
    for (LatLng point : copiedPoints) {
        maxLat = Math.max(point.latitude, maxLat);
        minLat = Math.min(point.latitude, minLat);
        maxLon = Math.max(point.longitude, maxLon);
        minLon = Math.min(point.longitude, minLon);
    }
    final LatLngBounds bounds = new LatLngBounds.Builder().include(new LatLng(maxLat, maxLon)).include(new LatLng(minLat, minLon)).build();
    map.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds,ancho, alto, 50));
}