Вычисление времени заката и рассвета по координатам Android устройства с помощью earthtools

в 8:55, , рубрики: android, location, sunrise, sunset, Разработка под android

Привет!

image

Как-то раз была поставлена задача разработать движок для вычисления времени начала заката и рассвета, основаной на местоположении телефона.

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

Покопавшись в Google, я нашел интересный сайт: http://www.earthtools.org/. Он предоставляет несколько веб-сервисов, связанных для работы с картами. Одним из них был сервис, которому на вход подаешь широту, долготу и дату, а как response получаешь xml следующего содержания:

Вычисление времени заката и рассвета по координатам Android устройства с помощью earthtools - 2

Исходя из этого, задача намного упрощается:

1. Получить координаты устройства;
2. Сформировать GET запрос;
3. Отправить запрос;
4. Получить response(наш XML);
5. Распарсить XML, чтоб получить необходимые данные;
6. Отобразить результат.

Я сразу предупрежу, что здесь не будет хардкорного кода, а только простой движок.

Приступим, для начала разрешения в AndroidManifest.xml

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.INTERNET" />

Далее получаем координаты устройства. Замечу сразу, что точные координаты на практике для задач такого рода совершенно не нужны, так что можно даже использовать координаты, которые дает нам сотовая сеть. Так как здесь нет супер UI, а исключительно маленький движок, поэтому на Activity лежит только кнопка Update, а под ней TextView для отображения результатов. Так же я убрал названия package'й и import'ы.

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<String> allProviders = locationManager.getAllProviders();
        for (String provider : allProviders) {
            helloWorld.append(provider + "n");
        }
        String bestProvider = locationManager.getBestProvider(createGPSCriteria(), true);
        helloWorld.append("Best: " + bestProvider + "n");
        locationManager.requestSingleUpdate(bestProvider, locationListener, null);
    }

    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<Void, Void, HttpResponse> {

    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/<latitude>/<longitude>/<day>/<month>/<timezone>/<dst>
        StringBuilder stringBuilder = new StringBuilder("http://www.earthtools.org/sun/");
        stringBuilder.append(String.valueOf(latitude)).append("/");
        stringBuilder.append(String.valueOf(longitude)).append("/");
        stringBuilder.append(String.valueOf(day)).append("/");
        stringBuilder.append(String.valueOf(month)).append("/");
        stringBuilder.append("99").append("/");
        stringBuilder.append("0");
        Log.d("URL", stringBuilder.toString());
        return stringBuilder.toString();
    }

    @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";
    }
}

Вот, собственно, и все. Задача решена, результат на экран:

Вычисление времени заката и рассвета по координатам Android устройства с помощью earthtools - 3

Автор: lazexe

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js