Впервые за весь свой опыт работы 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 (на сколько я помню).

Всем мира и добра!???

Комментарии (14)


  1. Borjomy
    11.06.2024 11:45
    +1

    Наиболее этичным способом будет спрашивать напрямую у пользователя о его местоположении, вообще-то. А так это просто слежка, о которой вас не просили


    1. AmalDoskhoev Автор
      11.06.2024 11:45

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


      1. tuxi
        11.06.2024 11:45
        +1

        данные о своем местоположении в сессии файлов куки

        Это как? Расшифруйте если не сложно.