Впервые за весь свой опыт работы frontend разработчиком я столкнулся с задачей определения города (решение применимо не только для города, но и страны, улицы и т.д.) текущего пользователя и, порыскав в интернете, находил лишь кусочки того, что мне нужно, поэтому, чтобы сэкономить время тех, кто столкнулся с похожей задачей решил опубликовать данную статью.
Прежде чем писать код, хотелось бы отметить, что моё решение не претендует на "чистое" и единственно-верное, поэтому если есть более гармоничное и красивое решение - используйте его (буду только рад если поделитесь им).
Если кому интересно, задача была реализована на Options API, фреймворка Vue, но сам код написан на чистом JS. Это может быть полезно и тем, кто пишет на React и т.д.
1. С чего начать
Начать нужно с выбора, с помощью чего мы будет определять местоположение, с помощью IP-адреса или же координат latitude и longitude (широта и долгота)? В случае с IP-адресом диапазон удачного нахождения составляет около 60-80%, что в целом тоже не плохо, но хотелось бы поточней. Поэтому я выбрал второй вариант с координатами.
2. Как получить координаты пользователя (lat и lon)
Для того, чтобы получить координаты пользователя, нам потребуется разрешение на получение его геоданных, с этим нам поможет браузерное api - Geolocation API, с помощью метода getCurrentPosition()
этого api мы инициируем всплытие разрешение на получение геоданных, после чего получаем информацию, в случае, если пользователь даст разрешение. Выглядит сама функция следующим образом:
const getCurrentGeolocation = () =>
{
return new Promise((resolve, reject) =>
{
if (navigator.geolocation)
{
navigator.geolocation.getCurrentPosition((position) =>
{
const lat = position.coords.latitude;
const lon = position.coords.longitude;
resolve({lat, lon});
return {lat, lon};
}, (error) =>
{
console.error(`Я гнусная ошибка ${error} ?`);
resolve({lat: null, lon: null});
return {lat: null, lon: null};
});
} else
{
console.warn('Я слишком стар для этого ?');
resolve({lat: null, lon: null});
return {lat: null, lon: null};
}
});
}
Не забываем, что функция getCurrentPosition()
вызывает асинхронный запрос, поэтому возвращаем Promise. Конкретно в моем случае, вызов reject мне был не столь важен, поэтому его я благополучно вырезал (чего делать вам не советую).
Условие if (navigator.geolocation)
проверяет поддерживается ли Geolocation
браузером или нет, а последующий вызов Callback
функции - на самом деле вызов errorCallback
- функции для обработки ошибок (например пользователь запретил получение геоданных и т.д.).
Вот как выглядит вызов самой функции:const {lat, lon} = await getCurrentGeolocation();
, где lat и lon
- ширина и долгота.
3. Что делать с полученными координатами
К сожалению, способы как получить название страны, города, улицы и т.д. на основе полученных координат с помощью встроенных API я не нашел, поэтому остается прибегнуть к сторонним сервисам, их на самом деле куча, но я отмечу всего парочку, это сервис DaData и всеми известное YandexAPI, что из них лучше зависит от конкретного случая, но yandex конечно будет помасштабнее. В нашей задаче от этих сервисов нас интересует графа "Обратное геокодирование" - получение адреса по координатам.
4. Сервис DaData
В моем случае, я пробовал оба и к сожалению dadata не хотел возвращать мне название города, хотя делал всё по инструкции (но это не точно, мои ошибки никто не отменял ?), выглядело это следующим образом:
const getCityByLatAndLon = async ({lat, lon}) =>
{
if (!lat || !lon) return '';
const query = {lat, lon};
const url = "https://suggestions.dadata.ru/suggestions/api/4_1/rs/geolocate/address";
const options = {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": `Token ${process.env.VUE_APP_DADATA_API_KEY}`
},
body: JSON.stringify(query)
};
try
{
const response = await fetch(url, options);
if (!response.ok) return '';
const data = await response.json();
console.log(data, 'Массив найденых адресов');
} catch (error)
{
console.error('Ошибка при получении города от DaData:', error);
}
},
*P.s. на момент написания статьи, как и говорилось ранее, вся проблема оказалось в моей ошибке ?, так что с сервисом dadata всё прекрасно, как оказалось я просто обернул query
в body: JSON.stringify(query)
лишними фигурными скобками, конечно сервис возвращал мне пустой массив...? выглядело это следующим образом body: JSON.stringify({query})
, убрал их и всё прекрасно заработало ?!
5. Сервис YandexApi
С сервисом yandex будет чуть посложней, а конкретнее с его документацией, но как говорится нужно только захотеть (или же должны гореть дедлайны?). В общем для успешной отправки запроса нам потребуется функцияgeocode
, которая обрабатывает запросы геокодирования, подробнее читать тут (чтобы облегчить рысканья в доке). После чего в моем случае используем метод getLocalities()
для возврата населённого пункта и, опционально, образование внутри населённого пункта, которым принадлежит топоним (в простонародье город), подробнее про методы читать тут. Выглядит код следующим образом:
const getCityByLatAndLon = async ({lat, lon}) =>
{
if (!lat || !lon) return '';
const {geoObjects} = await ymaps.geocode([lat, lon], {kind: "locality"});
return geoObjects.get(0).getLocalities()?.[0] || ''
},
*P.s. в случае текущего проекта для инициализацииymaps
используется старая версия библиотеки vue-yandex-maps и выглядит она следующим образом:
await loadYmap({
apiKey: process.env.VUE_APP_YA_API_KEY,
lang: 'ru_RU',
coordorder: 'latlong',
version: '2.1'
});
Но для инициализации ymaps
в обычном JS проекте достаточно вставить в тег head
следующую строку с вашим API ключем (подробнее тут):
<head>
<script src="https://api-maps.yandex.ru/2.1/?apikey=ваш API-ключ&lang=ru_RU" type="text/javascript">
</script>
</head>
В конце мы получаем следующую картину:
const {lat, lon} = await getCurrentGeolocation();
const myCityName = await getCityByLatAndLon({lat, lon});
console.log(`Мы находимся в городе под названием ${myCityName} ?`);
Подведем итоги
В целом данная задача не является сложной, но к сожалению нету актуальной информации по этой теме, поэтому приходилось искать все по кусочкам, и чтобы сэкономить ваше время была создана данная статья, за ясность которой я не отвечаю, т.к. она была моей первой и надеюсь не последней ?.
❗️ Имейте ввиду, что бесплатная версия сервиса DaData предоставляет 10000 запросов в сутки, а Yandex в свою очередь всего 2000 (на сколько я помню).
Всем мира и добра!???
Borjomy
Наиболее этичным способом будет спрашивать напрямую у пользователя о его местоположении, вообще-то. А так это просто слежка, о которой вас не просили
AmalDoskhoev Автор
Я не согласен с вами в этом вопросе, начнем с того, что уже изначально, хотите вы того или нет, вы отправляете данные о своем местоположении в сессии файлов куки, просто это не видно вашему глазу, а мое решение создано для того, чтобы пользователю наоборот облегчить опыт работы с сайтом и вместо навязчивого модального окна с вводом адреса, подставлять его автоматически (что в любом случае нужно, как минимум для служб доставок, карт и т.д.)
tuxi
Это как? Расшифруйте если не сложно.