Android Maps v2 - анимированная камера для включения большинства маркеров
У меня есть набор точек, поступающих из веб-сервиса, которые необходимо отобразить на карте.
У меня есть текущее решение, работающее хорошо для большинства случаев, используя хорошо известные LatLngBounds.Builder
, CameraUpdateFactory.newLatLngBounds
а также map.animateCamera
,
У меня есть некоторые случаи, которые создают проблемы: когда точки расположены слишком далеко, карта центрируется на максимальном уровне масштабирования на барицентре этих точек. Например: у меня 10 очков во Франции и 2 очка на Гавайях. Карты в большей или меньшей степени сосредоточены на карибах с минимальным уровнем масштабирования. Следовательно, на экране я ничего не показывал, пользователь должен прокрутить, чтобы увидеть, что что-то есть.
Итак, мой вопрос:
Есть ли способ заставить карту уменьшать достаточно далеко, чтобы я мог видеть все точки (это было бы предпочтительнее)
Или: что было бы лучшим способом отфильтровать те случаи, когда всего несколько точек находятся очень далеко от большинства, и выбрать набор точек для увеличения (в моем примере я бы выбрал увеличение на 10 точек во Франции). и забыть о тех, кто на Гавайях).
4 ответа
Основываясь на некоторых идеях cYrixmorten, я упростил задачу, потому что я знаю, что карта может занимать как минимум 4000 км поверхности. Итак, вот функция для построения списка игнорируемых веб-камер (тогда я просто игнорирую эту веб-камеру для вычисления границ камеры, но все же добавляю маркер, чтобы он был на карте, если пользователь перемещается).
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;
}
Поместите все LatLng маркеров в список и передайте их этому методу и в последней строке newLatLngBounds(bounds, 50))
50 представляют отступ между краем карты и самым внешним маркером в каждой стороне
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));
}
Обнаружил ошибку в моем предыдущем коде и решил сесть и переписать ее.
Я делал нечто подобное раньше, где у меня было ~4500 маркеров, и я хотел выбрать те, которые находятся на определенном расстоянии от определенного места. Взял этот код и обобщил его для использования с любым видом маркеров.
Код, который я опубликую ниже, содержит два метода, которые вы можете использовать:
selectLowDistanceMarkers
Измеряет расстояние между каждым маркером и выбирает только те, которые не имеют большого расстояния до других маркеров. Это требует времени выполнения O(n+n^2) из-за сравнения между каждым маркером и последующим исследованием.
getSurroundingMarkers
Если вы уже знаете сообщение, которое хотите увеличить, этот метод выполняет те же действия, что и выше. Этот метод менее загружен процессором, так как ему нужно только выполнить O(n) через все маркеры и сравнить их с заданной позицией.
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);
}
Опять же, используйте хорошо известные LatLngBounds, чтобы определить, сколько вам нужно увеличить после использования одного из алгоритмов фильтрации выше.
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));
}