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

    Перелистывая хаб «Разработка под 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
    • +34
    • 115k
    • 7
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 7
    • +1
      Большущее спасибо!
      К слову, todo с GPS попытался решить через JSR, но не уверен что работает.
      Воспользуюсь сим топиком и комментарием, чтобы указать, где теперь новости по тому проекту с транспортом:
      plus.google.com/b/100553821359869060330/100553821359869060330/posts
      vk.com/spb_transport_j2me
      • +1
        Два вопроса пока ещё не начал ковырять исходники:
        1. по мотивам топика http://habrahabr.ru/post/133427/, телефон может не иметь соответвующий JSR и класса Location. Это корректное утверждение?
        2. Этот способ будет работать за рубежом? (в смысле нет ли где в коде привязки только к РФ, местным операторам и т.п.?)
        • +1
          Способ совершенно не привязан к какой либо стране. Работает корректно как на Украине, так и в России, а также и в Малайзии или Пакистане. Проверено.
          Что касается первого вопроса, не совсем понял. Многие телефоны не имеют на борту JSR175 (Location API), к примеру SonyEricsson w610. На большинстве телефонов АПИ есть, но без GPS-приемника определение местоположения не работает (или работает, но я не знаю способа как его заставить работать в наших краях). И есть телефоны и с АПИ и с приемником, там проблемы не возникают.
          • 0
            Многие телефоны не имеют на борту JSR175 (Location API), к примеру SonyEricsson w610.
            Собственно вопрос был про то, сработает ли решение из статьи на этом телефоне?
            p.s. под комментариями есть ссылка «ответить». Так намного понятнее, кому и на что адресован ответ. Спасибо.
          • +1
            А телефоны позволяют получить список доступных приемнику вышек? или можно только текущую активную получить? Если можно получить список, то точность можно повысить вычисляя пересечение зон действия каждой из них…
            • +1
              К сожалению только данные текущей вышки.
              • +4
                Давно прокуренная тема (для старых Моторол, типа E398, дак точно)

                1. Чтоб освободить от обязательности доступа в инет, базу можно предварительно спарсить. А потом периодически обновлять. CellId Grabber
                2. Теоретически (опять же в курсе как на моторах), можно получить инфу о соседних вышках, тогда точность возрастёт. В своё время не дошли руки до этого. Но, к сожалению, о кросплатформенности можно забыть.
                3. Есть готовые базы вышек, где точное положение вышек определено людьми, а не усреднением как у яндекса и гугла netmonitor.ru
                4. И наполседок перечень софта для старых моторол. Может кому пригодится
                Informer — нативное приложение, показывает местоположение на рабочем столе
                MediaViewer -корелет, плагин для него Informator
                Базы для вышеперечисленного

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.