Проект OpenSpace3D, основанный на языке Scol 3D приложений режима реального времени и многопользовательских приложений и 3D-движке SO3Engine, предоставляет набор свободного программного обеспечения для разработки проектов приложений виртуальной и дополненной реальности.
В OpenSpace3D отсутствует возможность монетизации готового приложения и возможность экспорта сцены в распространенные форматы.
В OpenSpace3D есть возможность регистрации маркера динамически, на лету, также имеется Face Tracking.
Скачаем и установим OpenSpace3D, а также дополнительные пакеты (http://www.openspace3d.com/lang/en/support/download/). При этом будет создан каталог для OpenSpace3D проектов C:\Users\user\Documents\OpenSpace3D.
После скачивания и установки дистрибутива OpenSpace3D, откроем OpenSpace3D редактор.
В правом верхнем углу, в меню Preferences выберем язык интерфейса редактора.
В качестве примера откроем готовую сцену tutorial01_quickstart. xos папки OpenSpace3D\demos\tutorial с помощью меню Open scene или с помощью меню Import scene.
Приблизим ящик сцены – это будет узел cardboard сцены и нажмем правой кнопкой мышки.
Выберем Set physic.
Увидим, что для этого объекта выбран Body type shape и установлена масса 0.1 кг.
Выберем узел cave сцены, нажмем правой кнопкой мышки и выберем Set physic.
Увидим, что для этого объекта выбран Body type collisionTree.
Нажмем правой кнопкой мышки на узле Scene и выберем Set physic setting.
Увидим, что физика включена при проигрывании сцены, а значение ускорения свободного падения установлено как -9.8. Если минус поменять на плюс, тогда ящики окажутся на потолке.
В зоне редактирования функций щелкнем два раза на сцене и во вкладке Scene увидим, что подключен компонент FPS Like Controller, обеспечивающий перемещение в сцене от первого лица.
FPS like Controller PlugIT можно подключить, нажав правой кнопкой мышки в зоне редактирования функций вкладки Scene и выбрав navigation/FPS like controller. Удалить компонент можно кнопкой Delete клавиатуры.
Щелкнув два раза на компоненте FPS like controller, можно установить его свойства, такие как угол камеры, скорость передвижения и др.
Нажав кнопку Play/Stop панели инструментов, походим по сцене и разобьем стопку ящиков.
В 3D представлении сцены можно добавлять, удалять и перемещать 3D объекты, а также редактировать их свойства.
Для выбора объекта сцены кликнем на нем левой кнопкой мышки. После этого для редактирования его свойств или его удаления кликнем правой кнопкой мышки.
Remove удаляет объект. Clone дублирует объект, после чего нужно потянуть за ось Х, чтобы разъединить объекты. Set orientation позволяет положить объект.
Перемещать объекты по сцене можно, нажав кнопку Move панели инструментов и перетаскивая объект за оси.
Кнопка Rotate панели инструментов позволяет вращать объект, а кнопка Scale панели инструментов позволяет масштабировать объект.
Чтобы придать реальность объекту, в его свойствах выберем Set physic, выберем объекту форму shape и установим его массу, при этом в узле cave сцены должна быть установлена форма collisionTree. Если отмечен флажок Freezed, тогда физика объекта будет ожидать столкновения, перед тем как заработать.
Чтобы объединить объекты в группу, на узле Scene нажмем правой кнопкой мышки и выберем Add dummy, затем в дереве сцены перетащим объекты в созданный узел dummy.
Теперь можно манипулировать сразу группой объектов.
Компоненты PlugIT обеспечивают взаимодействие пользователя со сценой. Связываясь между собой, компоненты PlugIT образуют систему событий и действий.
В зоне редактирования функций вкладки Scene удалим PlugIT FPS like Controller с помощью клавиши Delete клавиатуры.
Нажмем правой кнопкой мышки на зоне редактирования функций и выберем object/object click. Присвоим имя switch экземпляру PlugIT. В поле Object name свяжем экземпляр PlugIT с объектом switch дерева сцены. Отметим флажок Enable hand cursor. В поле Bubble label введем click to light и нажмем Ok.
Нажмем правой кнопкой мышки на зоне редактирования функций и выберем object/light, свяжем этот PlugIT экземпляр с объектом Omnidir.
Нажмем правой кнопкой мышки на экземпляре switch и выберем LeftClick, свяжем его с экземпляром light_room, выбрав Off.
Теперь при проигрывании сцены, при нажатии левой кнопкой мышки на выключателе у двери, лампочка будет выключаться.
С помощью клавиши Delete удалим связь между экземпярами.
Нажмем правой кнопкой мышки на зоне редактирования функций и выберем misc/sequence.
Нажмем правой кнопкой мышки на экземпляре switch и выберем LeftClick, свяжем его с экземпляром On/Off sequence, выбрав Input. Нажмем правой кнопкой мышки на экземпляре On/Off и выберем Out1, свяжем его с экземпляром light_room, выбрав Off. Нажмем правой кнопкой мышки на экземпляре On/Off и выберем Out2, свяжем его с экземпляром light_room, выбрав On.
Теперь при проигрывании сцены, при нажатии левой кнопкой мышки на выключателе у двери, лампочка будет выключаться и включаться.
Однако при выключении цвет лампочки остается желтым. Чтобы это исправить нажмем правой кнопкой мышки на зоне редактирования функций и выберем material/material color. Свяжем этот экземпляр с объектом lamp. Установим цвет материала.
Нажмем правой кнопкой мышки на экземпляре On/Off и выберем Out1, свяжем его с экземпляром material color, выбрав Enable. Нажмем правой кнопкой мышки на экземпляре On/Off и выберем Out2, свяжем его с экземпляром material color, выбрав Disable.
Теперь при проигрывании сцены, при нажатии левой кнопкой мышки на выключателе у двери, лампочка будет выключаться и включаться, а ее цвет будет меняться.
Сделаем так, чтобы на телевизоре сцены проигрывалось видео. Для этого нажмем правой кнопкой мышки на зоне редактирования функций и выберем media/Video. Отметим флажок Apply on texture и свяжем этот экземпляр с объектом oldscreen. В поле Url введем ссылку. Отметим флажок Auto play и Play in loop, в поле Volume поставим 0, уберем флажок Transparency.
Теперь по телевизору можно смотреть видео.
Выберем объект lense и нажмем Zoom on selected object в панели инструментов.
Нажмем правой кнопкой на объекте и выберем Add dynamic reflection map. Выберем материал Lense, текстуру reflection и размер 512, отметим флажок Enable.
Нажмем правой кнопкой на объекте и выберем Add dynamic reflection map. Выберем материал Lense, текстуру refraction и размер 512, отметим флажок Enable и Revert clip plane. В результате получим нормальную линзу.
Импортируем в сцену робота. Для этого выберем меню Import scene/Standard file formats/OpenSpace3D\demos\tutorial\andy.scene/As a new group/Do you want to import this environment setting/No.
Нажмем правой кнопкой мышки на узле walk_inplace и уберем флажок Enable.
Для этого нажмем правой кнопкой мышки на зоне редактирования функций и выберем object/object click. Свяжем этот экземпляр с объектом andy_robot.
Нажмем правой кнопкой мышки на узле pose_init и отметим флажок Enable.
Для этого нажмем правой кнопкой мышки на зоне редактирования функций и выберем object/animation transition. First animation name свяжем с posed_init, а Second animation name свяжем с posed2.
Нажмем правой кнопкой мышки на wake up и выберем LeftClick и свяжем с animation transition, выбрав Fade in.
Теперь, при проигрывании сцены, при нажатии на роботе, он будет выпрямляться.
Нажмем правой кнопкой мышки на зоне редактирования функций и выберем object/animation transition. First animation name свяжем с posed2, а Second animation name свяжем с idle1.
Нажмем правой кнопкой мышки на animation transition и выберем Transition ended и свяжем с animation transition1, выбрав Fade in.
Нажмем правой кнопкой мышки на узле idle и выберем Edit setting. Отметим флажок Loop.
Таким образом, получим двухступенчатую анимацию.
Приступим к созданию дополненной реальности.
Создадим новую сцену, в которую импортируем модель OpenSpace3D\assets\models\library\Primitive_World\Edaphosaurus\Edafossauro.mesh.
Нажмем правой кнопкой мышки на узле default_skl, выберем Edit setting и отметим флажок Loop.
Нажмем правой кнопкой мышки на узле Scene и выберем Add dummy.
Перетащим модель в узел dummy.
Нажмем правой кнопкой мышки на зоне редактирования функций и выберем input/AR capture.
Нажмем правой кнопкой мышки на узле Scene и выберем Add camera.
Нажмем кнопку Move панели инструментов и перетащим камеру по оси Z.
Нажмем правой кнопкой мышки на зоне редактирования функций и выберем object/set active camera. Свяжем этот экземпляр с объектом сцены AR_camera. Отметим флажок Enable on init.
Нажмем правой кнопкой мышки на зоне редактирования функций и выберем input/AR marker. В поле Bitmap path выберем произвольное изображение, которое поместим в каталог OpenSpace3D и распечатаем. Подождем пока изображение не будет обработано в качестве маркера. Изменим размер маркера и свяжем его с объектом dummy.
Нажмем правой кнопкой мышки на зоне редактирования функций и выберем oblect/hide. Свяжем этот экземпляр с объектом dummy и отметим флажок Hide on start.
Нажмем правой кнопкой мышки на экземпляре AR marker и выберем Found, соединим его с экземпляром hide и выберем Show.
Нажмем правой кнопкой мышки на экземпляре AR marker и выберем Lost, соединим его с экземпляром hide и выберем Hide.
Нажмем правой кнопкой мышки на зоне редактирования функций и выберем input/keyboard. Выберем клавишу.
Нажмем правой кнопкой мышки на экземпляре keyboard и выберем Key down, соединим его с экземпляром AR marker, выбрав Register current frame.
Теперь маркер будет регистрироваться динамически, при нажатии соответствующей клавиши – поднесем к камере изображение и нажмем клавишу – должна появиться 3D модель.
В свойствах экземпляра AR marker, в качестве маркера можно установить реперный маркер, сохранив его для распечатывания.
Нажмем правой кнопкой мышки на зоне редактирования функций и выберем input/AR face tracker. Изменим размер маркера и свяжем его с объектом dummy.
Нажмем правой кнопкой мышки на экземпляре AR face tracker и выберем Found, соединим его с экземпляром hide и выберем Show. Нажмем правой кнопкой мышки на экземпляре AR face tracker и выберем Lost, соединим его с экземпляром hide и выберем Hide.
Теперь, если посмотреть прямо в камеру, появится 3D модель.
Соберем проект в Android приложение. Для этого в меню Export to Openspace3D player выберем As an Android Application.
Добавим или выберем хранилище ключей и в результате получим Android проект с готовым APK файлом в папке bin.
BeyondAR это фреймворк, обеспечивающий ресурсы для разработки приложений с дополненной реальностью, основанной на георасположении на смартфонах и планшетах.
Для начала работы скачаем BeyondAR фреймворк с Github https://github.com/BeyondAR/beyondar.
Импортируем проект BeyondAR_Examples в среду разработки Android Studio.
Для запуска этого приложения на Android устройстве требуется наличие датчика ориентации.
Для сборки APK файла с большим количеством методов в коде, в Gradle файл добавим:
defaultConfig {
multiDexEnabled true
}
dependencies {
compile 'com.android.support: multidex:1.0.0»
}
android {
dexOptions {
javaMaxHeapSize «4g»
}
}
В файл манифеста:
<application
android:name="android.support.multidex.MultiDexApplication»>
Для двух классов, возможно, придется реализовать OnMapReadyCallback.
package com.beyondar. example;
import android.content.Context;
import android. location. LocationManager;
import android. os. Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.view.View. OnClickListener;
import android. widget. Button;
import android.widget.Toast;
import com.beyondar.android.plugin. googlemap. GoogleMapWorldPlugin;
import com.beyondar.android.util.location.BeyondarLocationManager;
import com.beyondar.android.world.GeoObject;
import com.beyondar.android. world. World;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps. GoogleMap;
import com.google.android.gms.maps. GoogleMap. OnMarkerClickListener;
import com.google.android.gms.maps. OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
public class BeyondarLocationManagerMapActivity extends FragmentActivity implements OnMarkerClickListener, OnClickListener, OnMapReadyCallback {
private GoogleMap mMap;
private GoogleMapWorldPlugin mGoogleMapPlugin;
private World mWorld;
@Override
protected void onCreate (Bundle savedInstanceState) {
super. onCreate (savedInstanceState);
setContentView(R.layout.map_google);
Button myLocationButton = (Button) findViewById(R.id.myLocationButton);
myLocationButton.setVisibility(View.VISIBLE);
myLocationButton.setOnClickListener (this);
((SupportMapFragment) getSupportFragmentManager () .findFragmentById(R.id.map)).getMapAsync (this);
}
@Override
public boolean onMarkerClick (Marker marker) {
// To get the GeoObject that owns the marker we use the following
// method:
GeoObject geoObject = mGoogleMapPlugin.getGeoObjectOwner (marker);
if (geoObject!= null) {
Toast.makeText (this, «Click on a marker owned by a GeoOject with the name: " + geoObject.getName (),
Toast.LENGTH_SHORT).show ();
}
return false;
}
@Override
protected void onResume () {
super. onResume ();
// When the activity is resumed it is time to enable the
// BeyondarLocationManager
BeyondarLocationManager. enable ();
}
@Override
protected void onPause () {
super. onPause ();
// To avoid unnecessary battery usage disable BeyondarLocationManager
// when the activity goes on pause.
BeyondarLocationManager. disable ();
}
@Override
public void onClick (View v) {
// When the user clicks on the button we animate the map to the user
// location
LatLng userLocation = new LatLng(mWorld.getLatitude (), mWorld.getLongitude ());
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom (userLocation, 15));
mMap.animateCamera (CameraUpdateFactory. zoomTo (19), 2000, null);
}
@Override
public void onMapReady (GoogleMap googleMap) {
mMap=googleMap;
// We create the world and fill the world
mWorld = CustomWorldHelper.generateObjects (this);
// As we want to use GoogleMaps, we are going to create the plugin and
// attach it to the World
mGoogleMapPlugin = new GoogleMapWorldPlugin (this);
// Then we need to set the map in to the GoogleMapPlugin
mGoogleMapPlugin.setGoogleMap (mMap);
// Now that we have the plugin created let’s add it to our world.
// NOTE: It is better to load the plugins before start adding object in
// to the world.
mWorld.addPlugin (mGoogleMapPlugin);
mMap.setOnMarkerClickListener (this);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mGoogleMapPlugin.getLatLng (), 15));
mMap.animateCamera (CameraUpdateFactory. zoomTo (19), 2000, null);
// Lets add the user position to the map
GeoObject user = new GeoObject (1000l);
user.setGeoPosition(mWorld.getLatitude (), mWorld.getLongitude ());
user.setImageResource (R. drawable. flag);
user.setName («User position»);
mWorld.addBeyondarObject (user);
BeyondarLocationManager.addWorldLocationUpdate (mWorld);
BeyondarLocationManager.addGeoObjectLocationUpdate (user);
// We need to set the LocationManager to the BeyondarLocationManager.
BeyondarLocationManager
.setLocationManager ((LocationManager) getSystemService (Context. LOCATION_SERVICE));
}
}
package com.beyondar. example;
import android. os. Bundle;
import android.support.v4.app.FragmentActivity;
import android.widget.Toast;
import com.beyondar.android.plugin. googlemap. GoogleMapWorldPlugin;
import com.beyondar.android.world.GeoObject;
import com.beyondar.android. world. World;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps. GoogleMap;
import com.google.android.gms.maps. GoogleMap. OnMarkerClickListener;
import com.google.android.gms.maps. OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.Marker;
public class GoogleMapActivity extends FragmentActivity implements OnMarkerClickListener, OnMapReadyCallback {
private GoogleMap mMap;
private GoogleMapWorldPlugin mGoogleMapPlugin;
private World mWorld;
@Override
protected void onCreate (Bundle savedInstanceState) {
super. onCreate (savedInstanceState);
setContentView(R.layout.map_google);
((SupportMapFragment) getSupportFragmentManager () .findFragmentById(R.id.map)).getMapAsync (this);
}
@Override
public boolean onMarkerClick (Marker marker) {
// To get the GeoObject that owns the marker we use the following
// method:
GeoObject geoObject = mGoogleMapPlugin.getGeoObjectOwner (marker);
if (geoObject!= null) {
Toast.makeText (this, «Click on a marker owned by a GeoOject with the name: " + geoObject.getName (),
Toast.LENGTH_SHORT).show ();
}
return false;
}
@Override
public void onMapReady (GoogleMap googleMap) {
mMap=googleMap;
// We create the world and fill the world
mWorld = CustomWorldHelper.generateObjects (this);
// As we want to use GoogleMaps, we are going to create the plugin and
// attach it to the World
mGoogleMapPlugin = new GoogleMapWorldPlugin (this);
// Then we need to set the map in to the GoogleMapPlugin
mGoogleMapPlugin.setGoogleMap (mMap);
// Now that we have the plugin created let’s add it to our world.
// NOTE: It is better to load the plugins before start adding object in to the world.
mWorld.addPlugin (mGoogleMapPlugin);
mMap.setOnMarkerClickListener (this);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mGoogleMapPlugin.getLatLng (), 15));
mMap.animateCamera (CameraUpdateFactory. zoomTo (19), 2000, null);
// Lets add the user position
GeoObject user = new GeoObject (1000l);
user.setGeoPosition(mWorld.getLatitude (), mWorld.getLongitude ());
user.setImageResource (R. drawable. flag);
user.setName («User position»);
mWorld.addBeyondarObject (user);
}
}
После запуска приложения на Android устройстве появится список с примерами.
Simple AR camera – показывает набор изображений на фоне камеры. При этом изображения расположены в пространстве вокруг устройства.
Simple camera with a max/min distance far for rendering – показывает набор изображений на фоне камеры с возможностью регулировки расстояния до изображений.
BeyondAR World in Google maps – показывает набор изображений на карте.
AR camera with Google maps – показывает набор изображений на фоне камеры с кнопкой переключения на карту.
Camera with touch events – показывает набор изображений на фоне камеры, а также сообщение при нажатии на одном из изображений.
Camera with screenshot – показывает набор изображений на фоне камеры с кнопкой скриншота.
Change GeoObject images on touch – показывает набор изображений на фоне камеры, которые заменяются на другие изображения при нажатии.
Attach view to GeoObject – показывает набор изображений на фоне камеры с добавлением вида к изображению при нажатии.
Set static view to geoObject – вместо изображений показывает виды на фоне камеры, а также сообщение при нажатии на одном из видов.
Customize sensor filter – показывает набор изображений на фоне камеры с возможностью регулировки чувствительности датчика ориентации.
Simple AR camera with a radar view – показывает набор изображений на фоне камеры, а также расположение изображений вокруг устройства.
Using BeyondarLocationManager – показывает набор изображений на карте с кнопкой обновления местоположения.
Для работы BeyondAR фреймворка в файле манифеста приложения декларируются необходимые разрешения и наличие сенсоров устройства.
<! – Minimum permissions for Beyondar – >
<uses-permission android:name="android.permission.CAMERA» />
<! – For Beyondar this is not mandatory unless you want to load something from Internet (for instance images) – >
<uses-permission android:name="android.permission.INTERNET» />
<! – BeyondAR needs the following features – >
<uses-feature android:name="android.hardware.camera» />
<uses-feature android:name="android.hardware.sensor.accelerometer» />
<uses-feature android:name="android.hardware.sensor.compass» />
Активность SimpleCameraActivity, отображающая набор изображений на фоне камеры, имеет достаточно простой код.
package com.beyondar. example;
import android. os. Bundle;
import android.support.v4.app.FragmentActivity;
import android.view. Window;
import com.beyondar.android.fragment.BeyondarFragmentSupport;
import com.beyondar.android. world. World;
public class SimpleCameraActivity extends FragmentActivity {
private BeyondarFragmentSupport mBeyondarFragment;
private World mWorld;
/** Called when the activity is first created. */
@Override
public void onCreate (Bundle savedInstanceState) {
super. onCreate (savedInstanceState);
// Hide the window title.
requestWindowFeature (Window. FEATURE_NO_TITLE);
setContentView(R.layout.simple_camera);
mBeyondarFragment = (BeyondarFragmentSupport) getSupportFragmentManager().findFragmentById(R.id.beyondarFragment);
// We create the world and fill it…
mWorld = CustomWorldHelper.generateObjects (this);
// … and send it to the fragment
mBeyondarFragment.setWorld (mWorld);
// We also can see the Frames per seconds
mBeyondarFragment.showFPS (true);
}
}
В методе onCreate создается фрагмент BeyondarFragmentSupport, отвечающий за отображение вида камеры и вида BeyondarGLSurfaceView, рисующего дополненную реальность.
Для этого используется файл компоновки.
<?xml version=«1.0» encoding=«utf-8»? >
<FrameLayout xmlns: android="http://schemas.android.com/apk/res/android"
android: layout_width=«match_parent»
android: layout_height=«match_parent»
android: id="@+id/parentFrameLayout»>
<fragment
android: id="@+id/beyondarFragment»
android:name="com.beyondar.android.fragment.BeyondarFragmentSupport»
android: layout_width=«match_parent»
android: layout_height=«match_parent» />
</FrameLayout>
Далее создается объект World – контейнер объектов дополненной реальности, который затем добавляется во фрагмент BeyondarFragmentSupport.
Метод mBeyondarFragment.showFPS (true) показывает количество кадров в секунду в левом верхнем углу экрана.
Вся магия по созданию объектов дополненной реальности осуществляется в классе CustomWorldHelper.
Здесь создается новый контейнер World, устанавливается его местоположение в реальном мире, а также на основе изображений создаются объекты GeoObject, которые добавляются в контейнер World.
public static World sharedWorld;
sharedWorld = new World (context);
sharedWorld.setGeoPosition (41.90533734214473d, 2.565848038959814d);
GeoObject go4 = new GeoObject (4l);
go4.setGeoPosition (41.90518862002349d, 2.565662767707665d);
go4.setImageUri("assets://creature_7.png»);
go4.setName («Image from assets»);
sharedWorld.addBeyondarObject (go4);
По умолчанию для контейнера World и для его объектов, в классе CustomWorldHelper, задаются фиксированные координаты в реальном мире. Исправим это, привязав координаты контейнера World к местоположению устройства.
Для определения местоположения устройства используем Fused location provider API (Android API Level> v9, Android Build Tools> v21).
Изменим код классов CustomWorldHelper, GoogleMapActivity и SimpleCameraActivity.
import android.annotation.SuppressLint;
import android.content.Context;
import android. location. Location;
import android.widget.Toast;
import com.beyondar.android.world.GeoObject;
import com.beyondar.android. world. World;
@SuppressLint («SdCardPath»)
public class CustomWorldHelper {
public static final int LIST_TYPE_EXAMPLE_1 = 1;
public static World sharedWorld;
public static World generateObjects (Context context, Location mCurrentLocation) {
sharedWorld = new World (context);
// The user can set the default bitmap. This is useful if you are
// loading images form Internet and the connection get lost
sharedWorld.setDefaultImage(R.drawable.beyondar_default_unknow_icon);
// User position (you can change it using the GPS listeners form Android
// API)
if (mCurrentLocation== null) {
mCurrentLocation=new Location (»»);
mCurrentLocation.setLatitude (41.90533734214473d);
mCurrentLocation.setLongitude (2.565848038959814d);
}
sharedWorld.setGeoPosition(mCurrentLocation.getLatitude(),mCurrentLocation.getLongitude ());
// Create an object with an image in the app resources.
// And the same goes for the app assets
GeoObject go = new GeoObject (1l);
go.setGeoPosition(mCurrentLocation.getLatitude()+0.00005,mCurrentLocation.getLongitude () -0.0001);
go.setImageUri("assets://creature_7.png»);
go.setName («Image from assets»);
// Add the GeoObjects to the world
sharedWorld.addBeyondarObject (go);
return sharedWorld;
}
}
import android.content.pm.PackageManager;
import android. location. Location;
import android. os. Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentActivity;
import android.widget.Toast;
import com.beyondar.android.plugin. googlemap. GoogleMapWorldPlugin;
import com.beyondar.android.world.GeoObject;
import com.beyondar.android. world. World;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common. api. GoogleApiClient;
import com.google.android.gms. location. LocationListener;
import com.google.android.gms. location. LocationRequest;
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. GoogleMap. OnMarkerClickListener;
import com.google.android.gms.maps. OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.Marker;
public class GoogleMapActivity extends FragmentActivity implements OnMarkerClickListener, OnMapReadyCallback, LocationListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient. OnConnectionFailedListener {
private GoogleMap mMap;
private GoogleMapWorldPlugin mGoogleMapPlugin;
private World mWorld;
GoogleApiClient mGoogleApiClient;
Location mCurrentLocation;
LocationRequest mLocationRequest;
@Override
protected void onCreate (Bundle savedInstanceState) {
super. onCreate (savedInstanceState);
setContentView(R.layout.map_google);
((SupportMapFragment) getSupportFragmentManager () .findFragmentById(R.id.map)).getMapAsync (this);
buildGoogleApiClient ();
}
/**
* Builds a GoogleApiClient. Uses the {@code #addApi} method to request the
* LocationServices API.
*/
protected synchronized void buildGoogleApiClient () {
mGoogleApiClient = new GoogleApiClient. Builder (this)
.addConnectionCallbacks (this)
.addOnConnectionFailedListener (this)
.addApi (LocationServices. API)
.build ();
createLocationRequest ();
}
protected void createLocationRequest () {
mLocationRequest = LocationRequest.create ();
// Sets the desired interval for active location updates. This interval is
// inexact. You may not receive updates at all if no location sources are available, or
// you may receive them slower than requested. You may also receive updates faster than
// requested if other applications are requesting location at a faster interval.
mLocationRequest.setInterval (10000);
// Sets the fastest rate for active location updates. This interval is exact, and your
// application will never receive updates faster than this value.
mLocationRequest.setFastestInterval (5000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
@Override
public void onStart () {
super. onStart ();
mGoogleApiClient.connect ();
}
@Override
public void onStop () {
super. onStop ();
mGoogleApiClient. disconnect ();
}
@Override
public void onResume () {
super. onResume ();
// Within {@code onPause ()}, we pause location updates, but leave the
// connection to GoogleApiClient intact. Here, we resume receiving
// location updates if the user has requested them.
if (mGoogleApiClient.isConnected ()) {
startLocationUpdates ();
}
}
@Override
protected void onPause () {
super. onPause ();
// Stop location updates to save battery, but don’t disconnect the GoogleApiClient object.
if (mGoogleApiClient.isConnected ()) {
stopLocationUpdates ();
}
}
protected void startLocationUpdates () {
if (ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission (this, android.Manifest.permission.ACCESS_COARSE_LOCATION)!= PackageManager.PERMISSION_GRANTED) {
return;
}
LocationServices.FusedLocationApi.requestLocationUpdates (
mGoogleApiClient, mLocationRequest, this);
}
/**
* Removes location updates from the FusedLocationApi.
*/
protected void stopLocationUpdates () {
// It is a good practice to remove location requests when the activity is in a paused or
// stopped state. Doing so helps battery performance and is especially
// recommended in applications that request frequent location updates.
// The final argument to {@code requestLocationUpdates ()} is a LocationListener
// (http://developer.android.com/reference/com/google/android/gms/location/LocationListener.html).
LocationServices.FusedLocationApi.removeLocationUpdates (mGoogleApiClient, this);
}
@Override
public boolean onMarkerClick (Marker marker) {
// To get the GeoObject that owns the marker we use the following
// method:
GeoObject geoObject = mGoogleMapPlugin.getGeoObjectOwner (marker);
if (geoObject!= null) {
Toast.makeText (this, «Click on a marker owned by a GeoOject with the name: " + geoObject.getName (),
Toast.LENGTH_SHORT).show ();
}
return false;
}
@Override
public void onMapReady (GoogleMap googleMap) {
mMap=googleMap;
// We create the world and fill the world
mWorld = CustomWorldHelper.generateObjects (this, mCurrentLocation);
// As we want to use GoogleMaps, we are going to create the plugin and
// attach it to the World
mGoogleMapPlugin = new GoogleMapWorldPlugin (this);
// Then we need to set the map in to the GoogleMapPlugin
mGoogleMapPlugin.setGoogleMap (mMap);
// Now that we have the plugin created let’s add it to our world.
// NOTE: It is better to load the plugins before start adding object in to the world.
mWorld.addPlugin (mGoogleMapPlugin);
mMap.setOnMarkerClickListener (this);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mGoogleMapPlugin.getLatLng (), 15));
mMap.animateCamera (CameraUpdateFactory. zoomTo (19), 2000, null);