Как стать автором
Обновить

Определяем местоположение телефона… без GPS

Время на прочтение5 мин
Количество просмотров153K
Перелистывая хаб «Разработка под Java ME» наткнулся на тему Spb Transport J2ME, где автор использует картографические сервисы, и одним из TODO является поддержка GPS (для улучшения юзабилити). Проблема в том что телефонов с встроенным GPS-приемником относительно небольшое количество. Надеюсь данным постом помогу не только автору той темы, но и кому то еще, сам в свое время набил немало шишек. Итак, приступим.

Чтобы определить местоположение пользователя (телефона, как вам угодно), можно использовать несколько способов:
— по GPS. Способ наиболее точный. Из недостатков: относительно долгий старт, потребляет много энергии, не так уж много аппаратов с встроенным приемником.
— по вышкам оператора. Средний по точности. Энергии кушает немного. Из минусов: не на всех телефонах доступны данные.
— по IP. Наименее точный. Собственно это самый большой минус.
— по CB-сообщениям оператора

Итак, нам нужна более-менее приемлемая точность при определении местоположения (для города это примерно 150-300 метров (это где то 1,5-2 минуты пешего хода), за городом соответственно 2-5 км и более, как повезет), так же нам необходимо охватить как можно большее количество аппаратов, и неплохо было бы оперативно обновлять координаты.
Наиболее подходящим будет определение местоположения через данные сотового оператора.

Сервисы:
Чтобы сконвертировать данные сети и получить координаты, нам понадобятся базы данных вышек сотовых операторов. В сети существует немало ресурсов, я использовал проверенные временем GoogleMaps, Yandex.Locator, location-api.com и opencellid.org (для большей точности и надежности используем все сразу). плюс как бонус у Яндекса в API есть метод определения местоположения с учетом силы сигнала, но о этом позже.
У каждого из сервисов есть неплохо документированное API (кроме GoogleMaps). Параметры, принимаемые API: MCC (код страны), MNC (код оператора сети), LAC (код соты), CellID (идентификатор вышки).
API возвратит координаты для данного набора данных.

Данные:
Получить вышеназванные данные задача нетривиальная, ведь каждый из производителей посчитал делом чести изобрести свой велосипед. В результате для каждой марки существует свой набор ключей для вызова System.getProperty(key), отыскать которые не так то легко.
Существует так же ряд других неприятных моментов. К примеру на Siemens'ах данные сети без патчинга прошивки получить не получиться. SonyEricsson возвращает данные в HEX-представлении. Nokia отказывается выдавать LAC несертифицированным (то-есть почти всем) мидлетам.

Решение:
Я написал класс, перебирающий известные ключи и получающий по этим ключам данные о сети. Потом ключи отправлялись в API сервисов, получались координаты и выводилось среднее значение, которое я считаю наиболее правдоподобным (за год использования не припомню случаев очень больших ошибок). Если телефон позволяет получить силу сигнала, мы используем бонус Яндекса: получаем координаты С учетом силы сигнала и БЕЗ, получаем дельту этих значений, применяем ее ко всем результатам от API, выводим средний результат. Как ни странно, последнее решение оказалось палкой о двух концах. При равномерном затухании сигнала точность по сравнению с обычным способом увеличивается раза в два, но если это плотная городская застройка или холмистая местность, где сигнал распространяется неравномерно, в этом случае точность падает достаточно сильно.

В итоге есть возможность определить местоположение на телефонах Siemens, SonyEricsson, Samsung (к примеру s5230), Huawei и прочих. Время загрузки координат и адресов примерно секунд 10-15.
Пример из демо (используется надстройка класс Location, в котором происходит определение координат и загрузка для них адреса)
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Form;
import javax.microedition.midlet.MIDlet;
import loc.*;

public class HelloWorld extends MIDlet implements Runnable {

    Display display;
    Form form;

    public void startApp() {
        display = Display.getDisplay(this);
        form = new Form("NetMonitor");

        display.setCurrent(form);
        new Thread(this).start();
    }

    public void run() {
//проверяем не Нокиа ли
        if (!Location.reallyNull(Location.lac)) {
            //обновляем данные сети
            Location.getData();
            //получаем координаты
            Location.getCoordinates();
            //если есть доступ к силе сигнала
            if (!Location.reallyNull(SystemUtil.signal())) {
                form.append(("Нетмонитор: ") + "\nКод страны: " + String.valueOf(Location.mcc) + " \nКод сети: " + String.valueOf(Location.mnc) + " \nКод соты: " + String.valueOf(Location.lac) + " \nКод БC: " + String.valueOf(Location.cid) + " \nСила сигнала: " + SystemUtil.signal() + " \n");
            } //иначе
            else {
                form.append("Нетмонитор: " + "\nКод страны: " + String.valueOf(Location.mcc) + " \nКод сети: " + String.valueOf(Location.mnc) + " \nКод соты: " + String.valueOf(Location.lac) + " \nКод БС: " + String.valueOf(Location.cid) + " \n");
            }
        }
//прочие данные от телефона
        String txt = SystemUtil.nativeDigitSupport();

        if (!Location.reallyNull(txt)) {
            form.append(txt + "\n");
        }
        txt = SystemUtil.operatorName();

        if (!Location.reallyNull(txt)) {
            form.append(txt + "\n");
        }
        txt = SystemUtil.serviceProvider();

        if (!Location.reallyNull(txt)) {
            form.append(txt + "\n");
        }
        txt = SystemUtil.traffic();

        if (!Location.reallyNull(txt)) {
            form.append(txt + "\n");
        }
        txt = SystemUtil.gid1();

        if (!Location.reallyNull(txt)) {
            form.append(txt + "\n");
        }
        txt = SystemUtil.gid2();

        if (!Location.reallyNull(txt)) {
            form.append(txt + "\n");
        }
         //геоданные
        form.append("Улица: ".concat(String.valueOf(Location.getStreet().concat(" \n"))));
        form.append("Город: ".concat(String.valueOf(Location.getCity().trim().concat(" \n"))));
        form.append("Область: ".concat(String.valueOf(Location.getArea().concat(" \n"))));
        form.append("Страна: ".concat(String.valueOf(Location.getCountry().concat(" \n"))));
        form.append("Долгота: ".concat(String.valueOf(Location.getLongitude().concat(" \n"))));
        form.append("Широта: ".concat(String.valueOf(Location.getLatitude().concat(" \n"))));
        form.append("Высота над у.м.: ".concat(String.valueOf(Location.getElevation().concat(" м \n"))));
        //для спотсменов бонус
        String sens = System.getProperty("microedition.sensor.version");
        if (sens != null && sens.length() != 0 && !sens.equals("null")) {
            sens = SensorApi.getSensor(3);

            if (sens != null && sens.length() != 0 && !sens.equals("null") && !sens.equals("0")) {
                form.append("Сегодня пеших шагов: " + String.valueOf(sens) + " \n");
            }
        }


    }

    public void pauseApp() {
    }

    public void destroyApp(boolean flag) {
    }
}



Ну и исходники с демо
goo.gl/lPkON
Теги:
Хабы:
Всего голосов 36: ↑35 и ↓1+34
Комментарии7

Публикации