Статьи

Начало работы с Google Maps для Android: расширенный

В то время как стандартные функции Карт Google невероятно полезны, иногда вам захочется сделать что-то еще. К счастью, Google создал библиотеку с открытым исходным кодом, содержащую набор утилит, которые разработчики Android могут использовать для улучшения своих приложений с помощью улучшенных карт.

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

Исходные файлы для этого урока можно найти на GitHub .

В первом уроке из этой серии я рассказал, как настроить проект с помощью консоли разработчика Google и добавить ключ API в свой манифест. Для этого урока вам нужно получить ключ API и настроить ваш проект с манифестом, как описано там.

Затем откройте build.gradle и добавьте две новые зависимости: одну для Play Services, чтобы использовать Google Maps, а другую — для библиотеки Google Maps Utils.

1
2
compile ‘com.google.android.gms:play-services-maps:7.8.0’
compile ‘com.google.maps.android:android-maps-utils:0.4’

Следует отметить, что технически библиотека Google Maps Utils все еще находится на стадии бета-тестирования, хотя она была доступна в течение последних двух лет. После того, как вы импортировали эти библиотеки и синхронизировали проект, вам необходимо обновить файл макета для MainActivity.java, чтобы он использовал пользовательский фрагмент, показанный ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<RelativeLayout xmlns:android=»http://schemas.android.com/apk/res/android»
    xmlns:tools=»http://schemas.android.com/tools» android:layout_width=»match_parent»
    android:layout_height=»match_parent» android:paddingLeft=»@dimen/activity_horizontal_margin»
    android:paddingRight=»@dimen/activity_horizontal_margin»
    android:paddingTop=»@dimen/activity_vertical_margin»
    android:paddingBottom=»@dimen/activity_vertical_margin» tools:context=».MainActivity»>
 
    <fragment
        android:id=»@+id/list_fragment»
        android:layout_width=»match_parent»
        android:layout_height=»match_parent»
        android:name=»com.tutsplus.mapsdemo.fragment.UtilsListFragment» />
 
</RelativeLayout>

Затем создайте класс UtilsListFragment который используется выше, чтобы он отображал простой список элементов, представляющих различные части библиотеки, о которых вы узнаете в этом руководстве.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class UtilsListFragment extends ListFragment {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ArrayAdapter<String> adapter = new ArrayAdapter<String>( getActivity(), android.R.layout.simple_list_item_1 );
        String[] items = getResources().getStringArray( R.array.list_items );
        adapter.addAll( new ArrayList( Arrays.asList(items) ) );
        setListAdapter( adapter );
    }
 
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);
 
        String item = ( (TextView) v ).getText().toString();
        if( getString( R.string.item_clustering ).equalsIgnoreCase( item ) ) {
            startActivity( new Intent( getActivity(), ClusterMarkerActivity.class ) );
        } else if( getString( R.string.item_heat_map ).equalsIgnoreCase( item ) ) {
            startActivity( new Intent( getActivity(), HeatMapActivity.class ) );
        } else if( getString( R.string.item_polylines ).equalsIgnoreCase( item ) ) {
            startActivity( new Intent( getActivity(), PolylineActivity.class ) );
        } else if( getString( R.string.item_spherical_geometry ).equalsIgnoreCase( item ) ) {
            startActivity( new Intent( getActivity(), SphericalGeometryActivity.class ) );
        }
    }
}

Каждая из строк определяется и помещается в string-array для единообразия.

01
02
03
04
05
06
07
08
09
10
11
<string name=»item_clustering»>Clustering</string>
<string name=»item_heat_map»>Heat Map</string>
<string name=»item_polylines»>Polyline Decoding</string>
<string name=»item_spherical_geometry»>Spherical Geometry Utils</string>
 
<string-array name=»list_items»>
    <item>@string/item_clustering</item>
    <item>@string/item_heat_map</item>
    <item>@string/item_polylines</item>
    <item>@string/item_spherical_geometry</item>
</string-array>
Начальный список утилит, используемых в этом руководстве

Как только ваш список станет доступен, вам нужно создать BaseMapActivity.java , который обрабатывает все настройки, относящиеся к общей карте, для каждого из примеров действий, которые вы будете строить. Это действие инициализирует GoogleMap и увеличивает камеру в указанной области. В данном случае этот район — город Денвер в штате Колорадо, США. Все в этом классе должно выглядеть знакомым из последних двух статей этой серии.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public abstract class BaseMapActivity extends AppCompatActivity {
 
    protected LatLng mCenterLocation = new LatLng( 39.7392, -104.9903 );
 
    protected GoogleMap mGoogleMap;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView( getMapLayoutId() );
        initMapIfNecessary();
    }
 
    @Override
    protected void onResume() {
        super.onResume();
        initMapIfNecessary();
    }
 
    protected void initMapIfNecessary() {
        if( mGoogleMap != null ) {
            return;
        }
 
        mGoogleMap = ( (MapFragment) getFragmentManager().findFragmentById( R.id.map ) ).getMap();
 
        initMapSettings();
        initCamera();
    }
 
    protected void initCamera() {
        CameraPosition position = CameraPosition.builder()
                .target( mCenterLocation )
                .zoom( getInitialMapZoomLevel() )
                .build();
 
        mGoogleMap.animateCamera(CameraUpdateFactory.newCameraPosition(position), null);
    }
 
    protected int getMapLayoutId() {
        return R.layout.activity_map;
    }
 
    protected float getInitialMapZoomLevel() {
        return 12.0f;
    }
 
    protected abstract void initMapSettings();
}

Теперь, когда у вас есть начальный проект, вы можете перейти к следующему разделу, в котором вы создадите новое действие для каждой утилиты, которую мы рассмотрим в этом руководстве.

Тепловые карты являются отличным способом визуального представления концентрации точек данных на карте. Библиотека Google Maps Utils позволяет легко добавлять их в приложение. Для начала создайте новый BaseMapActivity именем HeatMapActivity и добавьте его в файл AndroidManifest.xml . В верхней части этого класса объявите HeatmapTileProvider который мы будем использовать для создания наложения карты.

1
private HeatmapTileProvider mProvider;

В BaseMapActivity метод с именем initMapSettings который позволяет добавлять настройки на карту. Для этого Activity вам нужно переопределить этот метод, чтобы получить ArrayList объектов LatLng который затем используется для создания объекта HeatmapTileProvider .

У провайдера есть различные методы, которые можно использовать для изменения внешнего вида вашей тепловой карты, такие как цвета градиента, радиус для каждой точки и вес каждой точки. После того, как ваш провайдер построен, вы можете создать тепловую карту TileOverlay и применить ее к своей карте.

1
2
3
4
5
6
7
@Override
protected void initMapSettings() {
    ArrayList<LatLng> locations = generateLocations();
    mProvider = new HeatmapTileProvider.Builder().data( locations ).build();
    mProvider.setRadius( HeatmapTileProvider.DEFAULT_RADIUS );
    mGoogleMap.addTileOverlay(new TileOverlayOptions().tileProvider(mProvider));
}

В приведенной выше реализации initMapSettings generateLocations является вспомогательным методом, который генерирует 1000 позиций LatLng вокруг LatLng центральной карты.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private ArrayList<LatLng> generateLocations() {
    ArrayList<LatLng> locations = new ArrayList<LatLng>();
    double lat;
    double lng;
    Random generator = new Random();
    for( int i = 0; i < 1000; i++ ) {
        lat = generator.nextDouble() / 3;
        lng = generator.nextDouble() / 3;
        if( generator.nextBoolean() ) {
            lat = -lat;
        }
        if( generator.nextBoolean() ) {
            lng = -lng;
        }
        locations.add(new LatLng(mCenterLocation.latitude + lat, mCenterLocation.longitude + lng));
    }
 
    return locations;
}

initMapSettings реализацию initMapSettings и generateLocations , вы можете запустить свое приложение и щелкнуть по разделу тепловой карты, чтобы увидеть его в действии.

Тепловая карта точек на карте

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

Чтобы облегчить некоторые проблемы, вызванные этими проблемами, вы можете использовать библиотеку Google Maps Utils, чтобы анимировать маркеры в кластеры. Первое, что вам нужно сделать, это создать новый объект модели, который реализует интерфейс ClusterItem . Эта модель должна реализовать метод getPosition из интерфейса ClusterItem , чтобы вернуть действительный объект LatLng .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class ClusterMarkerLocation implements ClusterItem {
 
    private LatLng position;
 
    public ClusterMarkerLocation( LatLng latLng ) {
        position = latLng;
    }
 
    @Override
    public LatLng getPosition() {
        return position;
    }
 
    public void setPosition( LatLng position ) {
        this.position = position;
    }
}

ClusterMarkerActivity модель, вы можете создать новый Activity под названием ClusterMarkerActivity и добавить его в свой манифест. Когда вы инициализируете свою карту, вам нужно создать ClusterManager , связать его с GoogleMap и добавить свои позиции LatLng как ClusterMarkerLocations в ClusterManager чтобы утилита ClusterManager знать, что кластеризовать. Посмотрите на реализацию initMarkers чтобы лучше понять, как это работает.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private void initMarkers() {
    ClusterManager<ClusterMarkerLocation> clusterManager = new ClusterManager<ClusterMarkerLocation>( this, mGoogleMap );
    mGoogleMap.setOnCameraChangeListener(clusterManager);
 
    double lat;
    double lng;
    Random generator = new Random();
    for( int i = 0; i < 1000; i++ ) {
        lat = generator.nextDouble() / 3;
        lng = generator.nextDouble() / 3;
        if( generator.nextBoolean() ) {
            lat = -lat;
        }
        if( generator.nextBoolean() ) {
            lng = -lng;
        }
        clusterManager.addItem( new ClusterMarkerLocation( new LatLng( mCenterLocation.latitude + lat, mCenterLocation.longitude + lng ) ) );
    }
}

В этом примере мы создаем 1000 случайных точек для отображения и добавляем их на карту. Библиотека Google Maps Utils обрабатывает все остальное для нас.

Увеличенный вид маркеров, когда они не сгруппированы
Уменьшенный вид 1000 маркеров, сгруппированных вместе

В дополнение к двум последним элементам библиотека Google Maps Utils содержит множество полезных утилит. Если у вас есть много разных точек, которые составляют маршрут, вы можете закодировать их как полилинию, а затем добавить эту полилинию к вашей карте, используя PolyUtil . Это покажет путь между каждой из точек на карте.

01
02
03
04
05
06
07
08
09
10
11
public class PolylineActivity extends BaseMapActivity {
 
    private static final String polyline = «gsqqFxxu_SyRlTys@npAkhAzY{MsVc`AuHwbB}Lil@}[goCqGe|BnUa`A~MkbG?eq@hRq}@_N}vKdB»;
 
    @Override
    protected void initMapSettings() {
        List<LatLng> decodedPath = PolyUtil.decode(polyline);
 
        mGoogleMap.addPolyline(new PolylineOptions().addAll(decodedPath));
    }
}
Отображается маршрут с использованием PolyUtil для декодирования полилинии

В дополнение к PolyUtil , Google добавил SphericalUtil который можно использовать для измерения расстояний или определения геометрии вдоль поверхности сферы. Если вы хотите найти расстояние между двумя точками на карте, вы можете вызвать SphericalUtil.computeDistanceBetween( LatLng position1, LatLng position2 ) чтобы получить double расстояние в метрах. Если вы хотите найти заголовок между двумя точками, вы можете вызвать SphericalUtil.computeHeading( LatLng point1, LatLng point2 ) .

Расстояние между двумя точками от SphericalUtil

В связи с этим еще один служебный метод в классе SpericalUtil позволяет вам находить точку в определенном направлении и на расстоянии. Я рекомендую просмотреть документацию, чтобы узнать больше о классе SpericalUtil .

В этом руководстве вы только что коснулись библиотеки Google Maps Utils и всего, что она может предложить. Другие функции, которые он может добавить в ваше приложение, включают добавление наложений для данных KML, создание пользовательских маркеров и вспомогательных методов для работы с GeoJSON.

К счастью, Google открыл все библиотеки, поэтому вы можете найти исходный код и демонстрационный код библиотеки на GitHub . Изучив последние три части этой серии, вы должны чувствовать себя достаточно комфортно с Картами Google, чтобы добавить их в свои собственные приложения, чтобы обогатить пользовательский опыт и создавать отличные приложения.