Android - Фильтрация Точек На Карте В Зависимости От Расстояния Друг От Друга

Недавно мне представилась возможность реализовать проект с использованием карт Google. В какой-то момент я увидел вот эту картинку:

Android - фильтрация точек на карте в зависимости от расстояния друг от друга



Android - фильтрация точек на карте в зависимости от расстояния друг от друга

Это происходило, как вы уже догадались, когда поблизости было слишком много мест, которые нужно было отобразить.

Ну ничего - проблема вроде стандартная - многие должны были с этим столкнуться, разработчики наверняка предусмотрели группировку и фильтрацию.

Да, это не так! Полистав документацию (сразу признаюсь, что я был лишь беглым - возможно, я пропустил то, что мне было нужно) и задав вопрос Гуглу - к своему удивлению, я быстро не смог найти ничего нужного.

Ну да ладно - не боги горшки обжигают - так что сами напишем.

Сначала давайте создадим проект и подключим к нему API Google Maps. Тут думаю объяснять ничего не надо - об этом уже много написано, кстати вот ссылки на хабе - один раз , два , три .

Для начала давайте определимся, что и как должно работать.

Идти… Очевидно, нам нужно придумать какой-то алгоритм, согласно которому на экране будут отображаться не все пины, а только те, которые расположены на определенном расстоянии друг от друга.

А еще вам нужно научиться объединять их в группы.

Перерасчет всей этой экономики должен произойти: 1) При первой загрузке пинов на карту.

2) При изменении масштаба В принципе, с первым пунктом все понятно – давайте определимся со вторым.

Объявим интерфейс:

  
  
  
  
  
  
  
   

public interface IOnZoomListener { void onZoomChanged(); }

И мы модифицируем наш MapView следующим образом:

public class MyMapView extends MapView { int oldZoomLevel = -1; IOnZoomListener onZoomListener; public MyMapView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public MyMapView(Context context, String apiKey) { super(context, apiKey); } public MyMapView(Context context, AttributeSet attrs) { super(context, attrs); } public void setOnZoomListener(IOnZoomListener onZoomListener) { this.onZoomListener = onZoomListener; } @Override public void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); int newZoom = this.getZoomLevel(); if (newZoom != oldZoomLevel) { if (oldZoomLevel != -1 && onZoomListener != null) { onZoomListener.onZoomChanged(); } oldZoomLevel = getZoomLevel(); } } }

Здесь мы просто добавили возможность прописывать наш интерфейс и проверять при рисовании изменение ZoomLevel (если ZoomLevel изменился, мы подтягиваем метод нашего интерфейса).

Теперь определимся, как мы будем отображать пины на карте и как объединять их в группы.

Для этого создадим класс MyOverlayItem, унаследованный от OverlayItem со следующими дополнениями:

public class MyOverlayItem extends OverlayItem { private String name; private ArrayList<MyOverlayItem> list = new ArrayList<MyOverlayItem>(); public MyOverlayItem(GeoPoint point, String name) { super(point, "", ""); this.name = name; } public String getName() { if (list.size() > 0) { return "There are " + (list.size() + 1) + " places."; } else { return name; } } public void addList(MyOverlayItem item) { list.add(item); } public ArrayList<MyOverlayItem> getList() { return list; } }

В списке ArrayList будет храниться список сгруппированных пинов, а метод getName вернет либо имя объекта, либо их количество в группе.

Теперь опишем, для чего, собственно, все это и затевалось — нашего модифицированного ItemizedOverlay. Суть алгоритма фильтрации довольно проста: мы просто пробегаем в цикле по всем существующим элементам и проверяем каждый элемент на предмет близости к уже существующей группе элементов.

Если мы находим такую группу, элемент добавляется в нее; если нет, с этим элементом создается новая группа:

boolean isImposition; for (MyOverlayItem itemFromAll : myOverlaysAll) { isImposition = false; for (MyOverlayItem item : myOverlays) { if (itemFromAll == item) { isImposition = true; break; } if (isImposition(itemFromAll, item)) { item.addList(itemFromAll); isImposition = true; break; } } if (!isImposition) { myOverlays.add(itemFromAll); } }

Сначала для проверки расстояния я хотел просто использовать координаты булавок (ошибкой, возникающей в зависимости от широты, можно пренебречь, так как расстояния не большие), но тогда мне пришлось бы управлять еще и ZoomLevel. Для моих задач вполне подошел метод mapView.getLatitudeSpan; он возвращает расстояние видимой ширины экрана в нужной нам системе координат. Остается только разделить это расстояние на определенный коэффициент (сколько максимальных пинов должно «поместиться» в экран по ширине) — это и будет минимальное расстояние между пинами:

private boolean isImposition(MyOverlayItem item1, MyOverlayItem item2) { int latspan = mapView.getLatitudeSpan(); int delta = latspan / KOEFF; int dx = item1.getPoint().

getLatitudeE6() - item2.getPoint().

getLatitudeE6(); int dy = item1.getPoint().

getLongitudeE6() - item2.getPoint().

getLongitudeE6(); double dist = Math.sqrt(dx * dx + dy * dy); if (dist < delta) { return true; } else { return false; } }

На всякий случай вот полный исходный код класса:

public class PlaceOverlay extends ItemizedOverlay<MyOverlayItem> { private static final int KOEFF = 20; private ArrayList<MyOverlayItem> myOverlaysAll = new ArrayList<MyOverlayItem>(); private ArrayList<MyOverlayItem> myOverlays = new ArrayList<MyOverlayItem>(); private MapView mapView; public PlaceOverlay(Drawable defaultMarker, MapView mapView) { super(boundCenterBottom(defaultMarker)); this.mapView = mapView; populate(); } public void addOverlay(MyOverlayItem overlay) { myOverlaysAll.add(overlay); myOverlays.add(overlay); } public void doPopulate() { populate(); setLastFocusedIndex(-1); } @Override protected MyOverlayItem createItem(int i) { return myOverlays.get(i); } @Override public int size() { return myOverlays.size(); } private boolean isImposition(MyOverlayItem item1, MyOverlayItem item2) { int latspan = mapView.getLatitudeSpan(); int delta = latspan / KOEFF; int dx = item1.getPoint().

getLatitudeE6() - item2.getPoint().

getLatitudeE6(); int dy = item1.getPoint().

getLongitudeE6() - item2.getPoint().

getLongitudeE6(); double dist = Math.sqrt(dx * dx + dy * dy); if (dist < delta) { return true; } else { return false; } } public void clear() { myOverlaysAll.clear(); myOverlays.clear(); } public void calculateItems() { myOverlaysClear(); boolean isImposition; for (MyOverlayItem itemFromAll : myOverlaysAll) { isImposition = false; for (MyOverlayItem item : myOverlays) { if (itemFromAll == item) { isImposition = true; break; } if (isImposition(itemFromAll, item)) { item.addList(itemFromAll); isImposition = true; break; } } if (!isImposition) { myOverlays.add(itemFromAll); } } doPopulate(); } private void myOverlaysClear() { for (MyOverlayItem item : myOverlaysAll) { item.getList().

clear(); } myOverlays.clear(); } @Override protected boolean onTap(int index) { Toast.makeText(mapView.getContext(), myOverlays.get(index).

getName(), Toast.LENGTH_SHORT).

show(); return true; } }

Ах да — в методе onTap мы отображаем Toast с названием группы — чтобы наглядно продемонстрировать работу алгоритма.

Хочу добавить, что этот алгоритм не является истиной в последней инстанции — его можно и нужно совершенствовать — например, рисовать булавку не на месте первого элемента группы, а рассчитывать ее расположение в зависимости от ее содержимого.

Но вы уже реализуете это сами в своих проектах.

Теперь давайте разберемся, как все это собрать воедино.

Давайте создадим ManyPinsProjectActivity, который будет наследовать от MapActivity и реализуем следующие интерфейсы: LocationListener, IOnZoomListener. Однако я не буду описывать все подробно — за меня все расскажет исходный код:

public class ManyPinsProjectActivity extends MapActivity implements LocationListener, IOnZoomListener { private static final int DEFAULT_ZOOM = 15; private MyMapView mapView = null; private Drawable myCurrentMarker = null; private Drawable placeMarker = null; private List<Overlay> mapOverlays; private PlaceOverlay placeOverlay; private MyCurrentLocationOverlay myCurrentLocationOverlay; double currentLatitude, currentLongitude; private MapController mapController; private LocationManager locationManager; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); mapView = (MyMapView) findViewById(R.id.mapview); myCurrentMarker = this.getResources().

getDrawable(R.drawable.my_pin_red); placeMarker = this.getResources().

getDrawable(R.drawable.my_pin); myCurrentLocationOverlay = new MyCurrentLocationOverlay(myCurrentMarker, mapView); placeOverlay = new PlaceOverlay(placeMarker, mapView); mapOverlays = mapView.getOverlays(); mapController = mapView.getController(); mapView.setBuiltInZoomControls(true); mapView.setOnZoomListener(this); } private void animateToPlaceOnMap(final GeoPoint geopoint) { mapView.post(new Runnable() { @Override public void run() { mapView.invalidate(); mapController.animateTo(geopoint); mapController.setZoom(DEFAULT_ZOOM); } }); } private void setCurrentGeopoint(double myLatitude, double myLongitude) { currentLatitude = myLatitude; currentLongitude = myLongitude; final GeoPoint myCurrentGeoPoint = new GeoPoint((int) (myLatitude * 1E6), (int) (myLongitude * 1E6)); MyOverlayItem myCurrentItem = new MyOverlayItem(myCurrentGeoPoint, "Current Location"); myCurrentLocationOverlay.addOverlay(myCurrentItem); mapOverlays.add(myCurrentLocationOverlay); animateToPlaceOnMap(myCurrentGeoPoint); } @Override protected void onPause() { super.onPause(); locationManager.removeUpdates(this); } @Override protected void onResume() { super.onResume(); locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 5000, 100, this); } private ArrayList<PlaceInfo> generatePlaces(){ Random random = new Random(); int x, y; ArrayList<PlaceInfo> places = new ArrayList<PlaceInfo>(); PlaceInfo p; for(int i = 0; i < 100; i++){ x = random.nextInt(2000); y = random.nextInt(2000); p = new PlaceInfo(); p.setLatitude(currentLatitude + x/100000f); p.setLongitude(currentLongitude - y/100000f); p.setName("Place № " + i); places.add(p); } return places; } private void displayPlacesOnMap() { ArrayList<PlaceInfo> places = generatePlaces(); mapOverlays.remove(placeOverlay); GeoPoint point = null; MyOverlayItem overlayitem = null; placeOverlay.clear(); for (PlaceInfo place : places) { point = new GeoPoint((int) (place.getLatitude() * 1E6), (int) (place.getLongitude() * 1E6)); overlayitem = new MyOverlayItem(point, place.getName()); placeOverlay.addOverlay(overlayitem); } placeOverlay.calculateItems(); placeOverlay.doPopulate(); if (placeOverlay.size() > 0) { mapOverlays.add(placeOverlay); mapView.postInvalidate(); } } @Override public void onLocationChanged(Location location) { locationManager.removeUpdates(this); double myLatitude = location.getLatitude(); double myLongitude = location.getLongitude(); setCurrentGeopoint(myLatitude, myLongitude); displayPlacesOnMap(); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } @Override protected boolean isRouteDisplayed() { return false; } @Override public void onZoomChanged() { if (placeOverlay != null) { placeOverlay.calculateItems(); } } }

Здесь стоит добавить, что MyCurrentLocationOverlay — это обычный ItemizedOverlay с одним элементом, а PlaceInfo — это обычный класс-обертка, содержащий:

private String name; private double latitude; private double longitude;

После всех этих манипуляций наша карта с булавками стала выглядеть вот так:

Android - фильтрация точек на карте в зависимости от расстояния друг от друга



Android - фильтрация точек на карте в зависимости от расстояния друг от друга

Надеюсь, статья окажется для вас полезной.

Весь проект можно найти по адресу связь .

Теги: #Android #API карт Google #Разработка Android

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.