[Из песочницы] Вычисление времени заката и рассвета по координатам Android устройства с помощью earthtools
Привет Хабр!
Как-то раз была поставлена задача разработать движок для вычисления времени начала заката и рассвета, основаной на местоположении телефона.
В дополнение, дабы облегчить работу, меня нагрузили аномальными математическими формулами, от которых в голове ничего не укладывалось. Итак, мною было принято решение искать альтернативу этим адским формулам.Покопавшись в Google, я нашел интересный сайт: http://www.earthtools.org/. Он предоставляет несколько веб-сервисов, связанных для работы с картами. Одним из них был сервис, которому на вход подаешь широту, долготу и дату, а как response получаешь xml следующего содержания:
Исходя из этого, задача намного упрощается:
1. Получить координаты устройства;2. Сформировать GET запрос;3. Отправить запрос;4. Получить response (наш XML);5. Распарсить XML, чтоб получить необходимые данные;6. Отобразить результат.
Я сразу предупрежу, что здесь не будет хардкорного кода, а только простой движок.
Приступим, для начала разрешения в AndroidManifest.xml
public class MainActivity extends ActionBarActivity implements View.OnClickListener {
private TextView helloWorld; private Button update; private LocationListener locationListener;
@Override protected void onCreate (Bundle savedInstanceState) { super.onCreate (savedInstanceState); setContentView (R.layout.activity_main); initUI (); initLocationListener (); }
private void requestLocationUpdate () {
LocationManager locationManager = (LocationManager) this.getSystemService (Context.LOCATION_SERVICE);
List
private void initLocationListener () { locationListener = new LocationListener () { @Override public void onLocationChanged (Location location) { Calendar calendar = Calendar.getInstance (); int day = calendar.get (Calendar.DATE); int month = calendar.get (Calendar.MONTH) + 1; SunAsyncTask sunAsyncTask = new SunAsyncTask (location.getLatitude (), location.getLongitude (), day, month); try { HttpResponse response = sunAsyncTask.execute ().get (); String stringEntity = EntityUtils.toString (response.getEntity ()); SunXmlParser sunXmlParser = new SunXmlParser (stringEntity); helloWorld.append («Sunset:» + sunXmlParser.parse (SunXmlParser.SUNSET) + »\n»); helloWorld.append («Sunrise:» + sunXmlParser.parse (SunXmlParser.SUNRISE) + »\n»); } catch (Exception e) { e.printStackTrace (); } }
@Override public void onStatusChanged (String provider, int status, Bundle extras) {
}
@Override public void onProviderEnabled (String provider) {
}
@Override public void onProviderDisabled (String provider) {
} }; }
private void initUI () { helloWorld = (TextView) findViewById (R.id.hello_world); update = (Button) findViewById (R.id.update); update.setOnClickListener (this); }
private Criteria createGPSCriteria () { Criteria criteria = new Criteria (); criteria.setAccuracy (Criteria.ACCURACY_FINE); criteria.setPowerRequirement (Criteria.POWER_LOW); criteria.setCostAllowed (false); return criteria; }
@Override public void onClick (View clickedView) { int id = clickedView.getId (); if (id == R.id.update) { requestLocationUpdate (); } } } Здесь мы сначала инициализируем UI, далее инициализируем LocationListener, который должен будет отреагировать на изменение координат, а именно при изменении положения он запустит AsyncTask, который сформирует запрос, отправит его и получит response.После инициализации нам необходимо выбрать провайдера, у которого мы будем брать широту и долготу нашего местоположения.
Здесь ничего сложного, есть уже готовый метод getBestProvider, который делает это по критериям, которые мы передаем ему в первом аргументе. Criteria — это класс, в который мы вкладываем параметры провайдера, который мы хотим получить. В данном примере я задал критерий для отбора бесплатного провайдера, который бы работал даже с плохим сигналом, и с «нормальной» точностью определения местоположения.
Ну и, соответственно, по нажатию на клавишу Update у провайдера мы запрашиваем обновление местоположения, на которое реагирует LocationListener, вызывая AsynkTask про который я вам сейчас и расскажу.
public class SunAsyncTask extends AsyncTask
private double latitude; private double longitude; private int day; private int month;
public SunAsyncTask (double latitude, double longitude, int day, int month) { this.latitude = latitude; this.longitude = longitude; this.day = day; this.month = month; }
private String createUrl () {
// http://www.earthtools.org/sun/
@Override protected HttpResponse doInBackground (Void… params) { HttpResponse response = null; HttpClient client = new DefaultHttpClient (); HttpGet get = new HttpGet (createUrl ()); try { response = client.execute (get); } catch (IOException e) { e.printStackTrace (); } return response; } } Здесь тоже все просто. Напомню, как я уже и говорил, для получения времени необходимы координаты и дата дня заката и рассвета, которые хотим получить.Метод createUrl формирует URL для GET запроса, но там немножко больше параметров, чем я писал раньше. А именно: timezone и dst. Так вот, с timezone все понятно, можно указать временную зону, в которой хочешь вычислить, но если подставить »99» как временную зону, то она автоматически определится исходя из координат, которые передаешь. Соответственно я и поставил 99. Ну и dst — это учет летнего времени (0 — нет, 1 — да).
Итак, предполагается, что мы получим XML с результатом. Теперь его нужно распарсить. Пользоваться будем как обычно XmlPullParser, кто не знаком, советую ознакомиться, так как он имеет ряд «особенностей». Ну и, собственно, класс, который парсит sunset и sunrise.
public class SunXmlParser {
private String xmlString; private XmlPullParser parser;
public static final String SUNRISE = «sunrise»; public static final String SUNSET = «sunset»;
public SunXmlParser (String xmlString) throws XmlPullParserException { this.xmlString = xmlString; parser = Xml.newPullParser (); parser.setInput (new StringReader (xmlString)); }
public void setXmlString (String xmlString) throws XmlPullParserException { this.xmlString = xmlString; }
public String parse (String tag) throws XmlPullParserException, IOException { parser.setInput (new StringReader (xmlString)); int eventType = parser.getEventType (); while (eventType!= XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_TAG && parser.getName ().equalsIgnoreCase (tag)) { parser.next (); return parser.getText (); }
eventType = parser.next (); } return «Not found»; } } Вот, собственно, и все. Задача решена, результат на экран: