Пользователям не хочется разбираться с особенностями координат, часовых поясов. Некоторые даже не знают, как эти координаты выражаются, и что такое часовые пояса.
Как сделать, чтобы было хорошо пользователю?
В данной статье будет разобрано, как работать с координатами и часовыми поясами:
А именно,
- Установка Google Maps, рассмотрен небольшой функционал с примером.
- Поиск часового пояса с по координатам (Geonames.org).
- Поиск координат и часового пояса по названию города (Geonames.org).
- Определение названия местности по координатам.
Итак,
Пользователь хочет:
- Мало думать.
- Мало кликать.
Поэтому мы просто обязаны научить телефон:
- Находить координаты и часовой пояс по названию города.
- Находить часовой пояс по координатам, если используются карты.
- Узнавать текущее положение пользователя.
- Узнавать точный адрес по координатам.
Для торопливых: нажмите ссылку, чтобы перейти сразу к приложению и описанию кода с комментариями.
Для остальных. Давайте по-порядку.
Введение
Например, для ясности, сейчас на Европейской России истинный полдень, которому положено быть в 12:00, появляется почти на 2 часа позже. В день написания статьи истинный полдень — Солнце в зените в Москве — было в 13:38. На некоторых территориях России получается сдвиг прохождения Солнца по зениту аж на 3 часа.
Вернёмся к цифрам. Мировым
Кстати, бывает, что класс Calendar
в Java
выдаёт ошибку со сдвигом на час, если инициализировать, например, с текущем временем и часовым поясом, а затем установить время до того, как мы перешли исключительно на летнее время, например, 1988г., то календарь будет выводить часы со сдвигом на 1 час. Лечится это, если постоянно при установке новых миллисекунд заново инициализировать календарь. SimpleDateFormat
всегда выдаёт подобную ошибку, потому не советую им пользоваться для старых дат.
Ссылка на статью «часовой пояс» в Википедии
Geonames.org — это великолепный ресурс. Там можно как скачать базы данных, так и использовать для этого ресурса родной API. В данной статье я буду использовать как раз API.
Для работы с Geonames нужно регистрировать пользователя, при помощи которого будет доступ к API. А также не забыть его активировать. Для этого зайдите в аккаунт и нажмите там на ссылку: Click here to enable.
Тут документация
Есть даже библиотека для доступа, но библиотеку лично я не использую, потому что иногда возникают ошибки. Напрямую с API через http-запрос работать понятнее.
Лицензия. Можно бесплатно пользоваться этим продуктом.
О Google Maps все знают.
Можно общую справку по использованию Google Maps можно посмотреть тут.
Также есть такая фишка, как GeoCoder. Можно узнавать адрес по координатам. Но часовых поясов, почему-то там нет.
Для поиска часовых поясов по клику я также буду использовать Geonames.org.
Настройка Google Maps
Для новичков кратко алгоритм редактирования AndroidManifest.xml
, что откуда брать, чтобы работали карты Google Maps и определение положения телефона.
Кому это не интересно — кликайте сюда, чтобы пролистать сразу к описанию особенностей приложения.
1.
внутри
<manifest>
:
<permission
android:name="com.example.android.permission.MAPS_RECEIVE"
android:protectionLevel="signature" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
<!-- The following two permissions are not required to use
Google Maps Android API v2, but are recommended. -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-feature
android:glEsVersion="0x00020000"
android:required="true"/>
2.
внутри
<application>
:
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="API_KEY"/>
Чтобы получить android:value=«API_KEY» (ссылка, где это в коде), нужно:
- сюда
APIs & auth > Credentials > Create new KEY > Android key
- в поле записываем следующее SHA-1-KEY;com.example.android.mapexample
Картинки для android:value=«API_KEY»



особенности получения SHA-1-KEY
- он бывает или debug или release. Получается из файла-сертификата.
- файл-сертификат для debug — берётся из %USERPROFILE%.androiddebug.keystore. Он привязан к компьютеру и Eclipse.
- файл-сертификат для release — my-release-key.keystore, который используется для цифровой подписи приложения. Об этом каждый разработчик знает. Получить его можно так: developer.android.com/tools/publishing/app-signing.html#cert
- для того, чтобы получить debug-key. открываем документацию, а там раздел Displaying the debug certificate fingerprint и смотрим, как это сделать
- чтобы получить release-key, нужно открыть документацию, там раздел Displaying the release certificate fingerprint
- В текущей версии api-console можно сделать несколько сочетаний SHA-1-KEY;com.example.android.mapexample для одного выходного API_KEY. Удобно с одним ключом на нескольких компьютерах.
Для работы Google Maps добавить в проект Android-библиотеку. путь такой: <Android-SDK-Path>extrasgooglegoogle_play_services
. Добавлять нужно так: File>New>Project>Android>Android Prject from Existing Code>Next
Не забудьте отметить в настройках основного проекта habrtimezone
добавить проект google_play_services
как библиотеку. Это надо сделать в разделе Project>Properties>Android>Library>Add...
Собирать проект нужно для Google APIs 4.4.2 (можно > 4.0). Для этого поставьте галочку напротив соответствующего пункта списка в Project>Properties>Android>Project Build Target
. Если там такого пункта нет, то необходимо через Android SDK Manager установить Google APIs.


Код приложения
Ниже приведён пример приложения, которое выложено на Google Play, исходные коды есть на GitHub.
Будут приведены комментарии к особенностям работы приложения.


Итак, что умеет приложение:
1. Определять текущие координаты
2. Работать с картой
3. Определять положение города и его часовой пояс по его названию.
Не буду объяснять все аспекты работы приложения, а лишь покажу основные детали и особенности. При наличии кода с мелочами разобраться не составит труда.
Класс AMain. Первый экран
Начинается работа приложения с AMain
Activity
Класс AMain.java
. Код класса.
public class AMain extends Activity implements OnClickListener
(ссылка)
OnClickListener — для обработки кликов. На мой взгляд, обрабатывать клики лучше таким образом, так как память будет потребляться в меньших количествах, если использовать
button.setOnClickListener(this);
, а не
button.setOnClickListener(<новый Listener>);
Этот эффект особенно заметен, когда используется большое количество кликабельных объектов.
(ссылка для кнопки и ссылка для обработчика)
Что тут происходит:
Происходит поиск координат телефона в пространстве при помощи вышек GSM, а также GPS.
Через это окно можно открыть:
1. Карту. Для маркеров на этой карте будут переданы текущее положение телефона и положение (широта/долгота), которое записано в текстовых полях AGMap
2. Поиск городов через интернет. ACityListOnline
Поиск координат сделан при помощи следующих основных объектов:
private LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
private LocationListener gpsLocationListener;
private LocationListener networkLocationListener;
(ссылка)
Как только изменятся координаты и/или время, событие будет обработано при помощи LocListener implements LocationListener
(ссылка) в
@Override
public void onLocationChanged(Location location) {}
(ссылка)
Работа с картой
Класс AGMap. Экран для работы с картой
Для того, чтобы карта была в приложении, нужно в ресурсах в файле agmap.xml
(ссылка) создать следующий блок:
<fragment
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.SupportMapFragment"/>
, который и является картой.(ссылка)
Класс Activity: AGMap.java
Код класса.
Что тут происходит.
1. Открывается карта,
2. В неё передаются координаты с предыдущего экрана.
3. Есть 2 маркера — текущие координаты, и маркер по центру экрана.
4. Можно двигать карту, при нажатии на кнопку Сохранить будут применены координаты центра карты, будет определено название региона, будет определён часовой пояс при помощи Geonames.org.
Особенности настройки и использования карты.
public class AGMap extends FragmentActivity implements OnCameraChangeListener, OnClickListener
(ссылка)
FragmentActivity
— так как включена карта в это Activity, от родительским нужно использовать именно этот класс.
OnCameraChangeListener
— нужен для обработки передвижения карты
private GoogleMap mMap; //основной объект (карта),
который инициализирован следующим образом:
mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
(ссылка)
Положения объектов (указателей на карте) хранятся в
LatLng HEREIAM = null; // текущее положение
LatLng pos = null; // текущее положение центра карты
(ссылка)
Для того чтобы нарисовать маркер, нужно сделать объект с настройками маркера
mo = new markerOptions(... //настроить его текст, цвет, и т.п.
(ссылка)
и применить его к карте:
mar = mMap.addMarker(mo);
на выходе получится Marker mar
(ссылка)
Заметил, что на некоторых устройствах при инициализации карты возникает ошибка в onCreate()
, поэтому приходится ошибку оборачивать в
try{}catch(NullPointerException e){}
(ссылка)
Все настройки карты находятся
GoogleMapOptions options = new GoogleMapOptions();// настройки карты
(ссылка)
Центрирование маркера по карте:
@Override
public void onCameraChange(CameraPosition cp) {
// TODO Auto-generated method stub
pos = cp.target;
mar1.setPosition(pos);
}
(ссылка)
Изначально в эту функцию были добавлены функции обработки результата
uniqueExec(); //поиск названия положения
uniqueExecTZ(pos); //поиск часового пояса
Но в случае плохого соединения будет всё притормаживать. Использовать это неудобно.
Поиск название положения происходит таким образом:
Geocoder geoCoder = new Geocoder(getBaseContext(), Locale.getDefault());
try {
List<Address> addresses = geoCoder.getFromLocation(point.latitude, point.longitude, 1);// тут и будет список адресов
}
catch (IOException e) {
e.printStackTrace();
}
return address;
}
(ссылка)
Справедливости ради стоит добавить, что иногда возникает ошибка «Service not Available». В этом случае лучше дополнить эту функцию проверкой вида
if (Geocoder.isPresent()){}
else{}
и в else
сделать прямой запрос по URL
:
String googleMapUrl = "http://maps.googleapis.com/maps/api/geocode/json?latlng=" + point.latitude + ","
+ point.longitude + "&sensor=false&language=" + Locale.getDefault().getLanguage();
В данном случае добавлять условие if-else
надо будет тут.
На выходе будет JSON-объект, который также разбирается на кусочки как в случае с GeoNames.(см. чуть ниже)
К сожалению, есть ограничение на 2500 запросов в день в случае бесплатного варианта.
При нажатии на кнопку сохранить (ссылка) происходит следующее:
1. поиск названия местности по координатам в центре экрана. Выполнено при помощи geocodertask extends AsyncTask<Void, Integer, Void>
(ссылка) в функции uniqueExec();
(ссылка)
2. поиск часового пояса по координатам в центре экрана. Выполнено при помощи TimeZoneTask extends AsyncTask<Double, Integer, Void>
(ссылка) в функции uniqueExecTZ(pos);
(ссылка)
Запросы по http для Geocoder
и Time Zone
обвёрнуты в AsyncTask
(ссылка 1 и ссылка 2), потому что доступ в интернет должен происходить асинхронно, не забивая основной поток.
По сути, главная часть приложения и данной статьи — это несколько запросов на Geonames.org. Вот первый из них.
String[] urlString = {"http://api.geonames.org/findNearbyJSON?lat=","&lng=", "&radius=50&username=","&style=full&maxRows=1"};
outURL = urlString[0] + f.format(lat).replace(",", ".") + urlString[1] + f.format(lon).replace(",", ".") + urlString[2] + _.names1[r.nextInt(_.names1.length)] + urlString[3];
(ссылка 1)
(ссылка 2)
В запросе происходит поиск любого одного объекта на расстоянии ближе 50 км., если этот объект имеет часовой пояс, то это является результатом. Если нет или такие объекты не обнаружены (например, расположение где-то в океане), то производятся вычисление часового пояса по текущей широте и долготе с обычным делением глобуса на 24 части (ссылка). Возможно объект выбирается не произвольным образом, а берётся ближайший, но я это детально не проверял.
Результат выводится в формате JSON. Поэтому параллельно можно вытянуть и другую информацию.
Массив names1
(ссылка) из класса _.java
хранит в себе логины на Geonames.org. Можно хранить один логин, но лучше несколько, так как есть ограничение на количество запросов в час. В этом случае, увеличив количество пользователей и выбирая их случайным образом, можно будет таким искусственным способом пропорционально увеличить и количество запросов. Выбор пользователя, через которого происходит соединение, возникает случайным образом при помощи r.nextInt() (что такое r). В лицензии я не нашёл ограничений для подобной уловке.
Ещё раз напишу. Внимание, не забудьте активировать пользователя. Для этого зайдите в аккаунт и нажмите там на ссылку: Click here to enable.
Метод, конечно, не абсолютно точный, то зато рабочий. И обычно такого метода вполне достаточно. От конечных пользователей претензий пока не поступало.
Класс ACityListOnline. На этом экране происходит поиск городов по названию.
Activity ACityListOnline.java
Код класса.
Также используется Geonames.org в
CityTask extends AsyncTask<String, Integer, Void>
(ссылка на класс)
(тут выполняется запуск объекта класса CityTask)
Здесь запросы аналогичны AGMap.java
, но происходит поиск по названию.
Вот второй из тех запросов, которые работают с GeoNames:
String[] urlString = {"http://api.geonames.org/searchJSON?q=", "&username=","&style=full"};
outURL = urlString[0] + URLEncoder.encode(str, "UTF-8") + urlString[1] + _.names1[r.nextInt(_.names1.length)] + urlString[2];
(ссылка 1)
(ссылка 2)
Если результат найден, то сохраняется в объект класса _Info
(ссылка на класс)
(ссылка на список объектов класса, которые хранят результаты поиска)
Далее детальную информацию можно просмотреть в другом Activity, кликнув на элемент ListView
.
Сейчас в объекте класса _Info
хранятся широта, долгота, часовой пояс и международное название региона. Если хотите вытащить другую информацию (она также доступна), можете её взять из JSON-объекта самостоятельно. Всё просто и нудно.
Результаты
По основному функционалу всё.
Ещё раз ссылка на Google Play.

Перед тем как использовать код на GitHub обратите ВНИМАНИЕ:
В этом коде нет пользователей от Geonames.org
А именно, необходимо будет вставить имена пользователей в переменную
public static final String[] names1 = {"your_account_1","your_account_2"};
в классе _.java
(ссылка), а также вставить свой API_KEY
. в AndroidManifest.xml
(ссылка)
Как получить эти значения, я объяснял выше.
Чтобы проект с GitHub собрался, нужно добавить в Project>Properties>Java Build Path>Libraries>Add External JARs
вот этот файл: Android_SDK_Pathextrasandroidsupportv4android-support-v4.jar
. Затем перейти в Project>Properties>Java Build Path>Order and Export
и включить этот файл.
Если есть интерес к подобной статье, могу в следующий раз могу рассказать, как это может быть сделано локально без подключения к интернету.
Автор: Yurevich1