Определение местоположения с помощью Geolocation API

Open in CodeSandbox

В связи с ростом популярности мобильных устройств часто возникает задача определения местоположения пользователя.

API Яндекс.Карт умеет определять местоположение пользователя на основе его IP-адреса (пример), однако этот способ не такой точный, как при использовании Geolocation API.

В данном примере на карте размещается кнопка, при нажатии на которую производится обращение к Geolocation API устройства и получение его координат. Для полученных координат производится обратное геокодирование и определяется адрес.

Обсуждение этого примера можно посмотреть в клубе.

<!DOCTYPE html>

<html>
    <head>
        <title>
            Примеры. Определение местоположения с помощью Geolocation API
        </title>
        <meta
            http-equiv="Content-Type"
            content="text/html; charset=utf-8"
        />
        <!--
        Укажите свой API-ключ. Тестовый ключ НЕ БУДЕТ работать на других сайтах.
        Получить ключ можно в Кабинете разработчика: https://developer.tech.yandex.ru/keys/
    -->
        <script
            src="https://api-maps.yandex.ru/2.0/?load=package.full&amp;lang=ru-RU&amp;apikey=<ваш API-ключ>"
            type="text/javascript"
        ></script>

        <script src="geolocation_api.js" type="text/javascript"></script>
    </head>

    <body>
        <div id="map" style="width:400px; height:300px"></div>
    </body>
</html>
var baseIconsURL = "icons/";

function init() {
    /**
     * Класс кнопки определения местоположения пользователя.
     * с помощью Geolocation API.
     * @see http://www.w3.org/TR/geolocation-API/
     * @class
     * @name GeolocationButton
     * @param {Object} params Данные для кнопки и параметры к Geolocation API.
     */
    function GeolocationButton(params) {
        GeolocationButton.superclass.constructor.call(this, params);

        // Расширяем опции по умолчанию теми, что передали в конструкторе.
        this.geoLocationOptions = ymaps.util.extend(
            {
                // Не центрировать карту.
                noCentering: false,
                // Не ставить метку.
                noPlacemark: false,
                // Не показывать точность определения местоположения.
                noAccuracy: false,
                // Режим получения наиболее точных данных.
                enableHighAccuracy: true,
                // Максимальное время ожидания ответа (в миллисекундах).
                timeout: 10000,
                // Максимальное время жизни полученных данных (в миллисекундах).
                maximumAge: 1000,
            },
            params.options
        );
    }

    ymaps.util.augment(GeolocationButton, ymaps.control.Button, {
        /**
         * Метод будет вызван при добавлении кнопки на карту.
         * @function
         * @name GeolocationButton.onAddToMap
         * @param {ymaps.Map} map Карта на которую добавляется кнопка.
         */
        onAddToMap: function () {
            GeolocationButton.superclass.onAddToMap.apply(this, arguments);

            ymaps.option.presetStorage.add("geolocation#icon", {
                iconImageHref: "man.svg",
                iconImageSize: [27, 26],
                iconImageOffset: [-10, -24],
            });

            this.hint = new GeolocationButtonHint(this);
            // Обрабатываем клик на кнопке.
            this.events.add("click", this.onGeolocationButtonClick, this);
        },
        /**
         * Метод будет вызван при удалении кнопки с карты.
         * @function
         * @name GeolocationButton.onRemoveFromMap
         * @param {ymaps.Map} map Карта с которой удаляется кнопка.
         */
        onRemoveFromMap: function () {
            this.events.remove(
                "click",
                this.onGeolocationButtonClick,
                this
            );
            this.hint = null;
            ymaps.option.presetStorage.remove("geolocation#icon");

            GeolocationButton.superclass.onRemoveFromMap.apply(
                this,
                arguments
            );
        },
        /**
         * Обработчик клика на кнопке.
         * @function
         * @private
         * @name GeolocationButton.onGeolocationButtonClick
         * @param {ymaps.Event} e Объект события.
         */
        onGeolocationButtonClick: function (e) {
            // Меняем иконку кнопки на прелоадер.
            this.toggleIconImage("loader.svg");

            // Делаем кнопку ненажатой
            if (this.isSelected()) {
                this.deselect();
            }

            if (navigator.geolocation) {
                // Запрашиваем текущие координаты устройства.
                navigator.geolocation.getCurrentPosition(
                    ymaps.util.bind(this._onGeolocationSuccess, this),
                    ymaps.util.bind(this._onGeolocationError, this),
                    this.geoLocationOptions
                );
            } else {
                this.handleGeolocationError(
                    "Ваш броузер не поддерживает GeolocationAPI."
                );
            }
        },
        /**
         * Обработчик успешного завершения геолокации.
         * @function
         * @private
         * @name GeolocationButton._onGeolocationSuccess
         * @param {Object} position Объект, описывающий текущее местоположение.
         */
        _onGeolocationSuccess: function (position) {
            this.handleGeolocationResult(position);
            // Меняем иконку кнопки обратно
            this.toggleIconImage("wifi.svg");
        },
        /**
         * Обработчик ошибки геолокации.
         * @function
         * @name GeolocationButton._onGeolocationError
         * @param {Object} error Описание причины ошибки.
         */
        _onGeolocationError: function (error) {
            this.handleGeolocationError(
                "Точное местоположение определить не удалось."
            );
            // Меняем иконку кнопки обратно.
            this.toggleIconImage("wifi.svg");

            if (console) {
                console.warn(
                    "GeolocationError: " +
                        GeolocationButton.ERRORS[error.code - 1]
                );
            }
        },
        /**
         * Обработка ошибки геолокации.
         * @function
         * @name GeolocationButton.handleGeolocationError
         * @param {Object|String} err Описание ошибки.
         */
        handleGeolocationError: function (err) {
            this.hint.show(err.toString()).hide(2000);
        },
        /**
         * Меняет иконку кнопки.
         * @function
         * @name GeolocationButton.toggleIconImage
         * @param {String} image Путь до изображения.
         */
        toggleIconImage: function (image) {
            this.data.set("image", baseIconsURL + image);
        },
        /**
         * Обработка результата геолокации.
         * @function
         * @name GeolocationButton.handleGeolocationResult
         * @param {Object} position Результат геолокации.
         */
        handleGeolocationResult: function (position) {
            var location = [
                    position.coords.latitude,
                    position.coords.longitude,
                ],
                accuracy = position.coords.accuracy,
                map = this.getMap(),
                options = this.geoLocationOptions,
                placemark = this._placemark,
                circle = this._circle;

            // Смена центра карты (если нужно)
            if (!options.noCentering) {
                map.setCenter(location, 15);
            }

            // Установка метки по координатам местоположения (если нужно).
            if (!options.noPlacemark) {
                // Удаляем старую метку.
                if (placemark) {
                    map.geoObjects.remove(placemark);
                }
                this._placemark = placemark = new ymaps.Placemark(
                    location,
                    {},
                    { preset: "geolocation#icon" }
                );
                map.geoObjects.add(placemark);
                // Показываем адрес местоположения в хинте метки.
                this.getLocationInfo(placemark);
            }

            // Показываем точность определения местоположения (если нужно).
            if (!options.noAccuracy) {
                // Удаляем старую точность.
                if (circle) {
                    map.geoObjects.remove(circle);
                }
                this._circle = circle = new ymaps.Circle(
                    [location, accuracy],
                    {},
                    { opacity: 0.5 }
                );
                map.geoObjects.add(circle);
            }
        },
        /**
         * Получение адреса по координатам метки.
         * @function
         * @name GeolocationButton.getLocationInfo
         * @param {ymaps.Placemark} point Метка для которой ищем адрес.
         */
        getLocationInfo: function (point) {
            ymaps
                .geocode(point.geometry.getCoordinates())
                .then(function (res) {
                    var result = res.geoObjects.get(0);

                    if (result) {
                        point.properties.set(
                            "hintContent",
                            result.properties.get("name")
                        );
                    }
                });
        },
    });

    /**
     * Человекопонятное описание кодов ошибок.
     * @static
     */
    GeolocationButton.ERRORS = [
        "permission denied",
        "position unavailable",
        "timeout",
    ];

    /**
     * Класс хинта кнопки геолокации, будем использовать для отображения ошибок.
     * @class
     * @name GeolocationButtonHint
     * @param {GeolocationButton} btn Экземпляр класса кнопки.
     */
    function GeolocationButtonHint(btn) {
        var map = btn.getMap(),
            // Позиция кнопки.
            position = btn.options.get("position");

        this._map = map;
        // Отодвинем от кнопки на 35px.
        this._position = [position.left + 35, position.top];
    }

    /**
     * Отображает хинт справа от кнопки.
     * @function
     * @name GeolocationButtonHint.show
     * @param {String} text
     * @returns {GeolocationButtonHint}
     */
    GeolocationButtonHint.prototype.show = function (text) {
        var map = this._map,
            globalPixels = map.converter.pageToGlobal(this._position),
            position = map.options
                .get("projection")
                .fromGlobalPixels(globalPixels, map.getZoom());

        this._hint = map.hint.show(position, text);

        return this;
    };
    /**
     * Прячет хинт с нужной задержкой.
     * @function
     * @name GeolocationButtonHint.hide
     * @param {Number} timeout Задержка в миллисекундах.
     * @returns {GeolocationButtonHint}
     */
    GeolocationButtonHint.prototype.hide = function (timeout) {
        var hint = this._hint;

        if (hint) {
            setTimeout(function () {
                hint.hide();
            }, timeout);
        }

        return this;
    };

    var myMap = new ymaps.Map("map", {
            center: [55.755768, 37.617671],
            zoom: 10,
            behaviors: ["default", "scrollZoom"],
        }),
        myButton = new GeolocationButton({
            data: {
                image: baseIconsURL + "wifi.svg",
                title: "Определить местоположение",
            },
            options: {
                // Режим получения наиболее точных данных.
                enableHighAccuracy: true,
            },
        });

    myMap.controls.add(myButton, { top: 5, left: 5 });
}

ymaps.ready(init);