You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
IoT-For-Beginners/translations/ru/3-transport/lessons/4-geofences
co-op-translator[bot] 7b6699135a
🌐 Update translations via Co-op Translator (#544)
4 weeks ago
..
README.md 🌐 Update translations via Co-op Translator (#544) 4 weeks ago
assignment.md 🌐 Update translations via Co-op Translator (#544) 4 weeks ago

README.md

Геозоны

Скетчноут с обзором этого урока

Скетчноут от Nitya Narasimhan. Нажмите на изображение, чтобы увидеть его в большем размере.

Это видео дает обзор геозон и их использования в Azure Maps, темы, которые будут рассмотрены в этом уроке:

Геозоны с Azure Maps из шоу Microsoft Developer IoT

🎥 Нажмите на изображение выше, чтобы посмотреть видео

Викторина перед лекцией

Викторина перед лекцией

Введение

В последних трех уроках вы использовали IoT для отслеживания грузовиков, перевозящих вашу продукцию с фермы на перерабатывающий центр. Вы собирали данные GPS, отправляли их в облако для хранения и визуализировали их на карте. Следующий шаг в повышении эффективности вашей цепочки поставок — это получение уведомления, когда грузовик приближается к перерабатывающему центру, чтобы команда, необходимая для разгрузки, могла быть готова с погрузчиками и другим оборудованием сразу по прибытии транспортного средства. Это позволит быстро разгрузить грузовик, и вы не будете платить за простой грузовика и водителя.

В этом уроке вы узнаете о геозонах — определенных геопространственных областях, таких как зона в радиусе 2 км от перерабатывающего центра, и о том, как проверять, находятся ли GPS-координаты внутри или вне геозоны, чтобы определить, прибыл ли ваш GPS-датчик в зону или покинул ее.

В этом уроке мы рассмотрим:

🗑 Это последний урок в этом проекте, поэтому после завершения урока и задания не забудьте очистить свои облачные сервисы. Вам понадобятся эти сервисы для выполнения задания, поэтому сначала убедитесь, что вы его завершили.

При необходимости обратитесь к руководству по очистке проекта для получения инструкций.

Что такое геозоны

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

Примеры геозон: круговая геозона вокруг магазина Microsoft и многоугольная геозона вокруг западного кампуса Microsoft

💁 Возможно, вы уже использовали геозоны, даже не зная об этом. Если вы устанавливали напоминание в приложении iOS Reminders или Google Keep на основе местоположения, вы использовали геозону. Эти приложения создают геозону на основе указанного местоположения и уведомляют вас, когда ваш телефон входит в эту зону.

Есть множество причин, по которым важно знать, находится ли транспортное средство внутри или вне геозоны:

  • Подготовка к разгрузке — уведомление о прибытии транспортного средства на объект позволяет команде подготовиться к разгрузке, сокращая время ожидания. Это может позволить водителю выполнять больше доставок за день с меньшим временем простоя.
  • Налоговое соответствие — в некоторых странах, таких как Новая Зеландия, дорожные налоги для дизельных транспортных средств рассчитываются на основе веса транспортного средства при движении только по общественным дорогам. Использование геозон позволяет отслеживать пробег на общественных дорогах в отличие от частных дорог, например, на фермах или в лесозаготовительных районах.
  • Мониторинг краж — если транспортное средство должно оставаться в определенной зоне, например, на ферме, и покидает геозону, это может означать, что его украли.
  • Соблюдение правил местоположения — некоторые части рабочего объекта, фермы или завода могут быть недоступны для определенных транспортных средств, например, для тех, которые перевозят искусственные удобрения и пестициды, чтобы избежать их попадания на поля с органической продукцией. Если транспортное средство входит в такую геозону, это нарушение, и водителя можно уведомить.

Можете ли вы придумать другие способы использования геозон?

Azure Maps, сервис, который вы использовали в прошлом уроке для визуализации GPS-данных, позволяет определять геозоны и проверять, находится ли точка внутри или вне геозоны.

Определение геозоны

Геозоны определяются с использованием GeoJSON, так же как и точки, добавленные на карту в предыдущем уроке. В данном случае вместо FeatureCollection с Point значениями используется FeatureCollection, содержащий Polygon.

{
   "type": "FeatureCollection",
   "features": [
     {
       "type": "Feature",
       "geometry": {
         "type": "Polygon",
         "coordinates": [
           [
             [
               -122.13393688201903,
               47.63829579223815
             ],
             [
               -122.13389128446579,
               47.63782047131512
             ],
             [
               -122.13240802288054,
               47.63783312249837
             ],
             [
               -122.13238388299942,
               47.63829037035086
             ],
             [
               -122.13393688201903,
               47.63829579223815
             ]
           ]
         ]
       },
       "properties": {
         "geometryId": "1"
       }
     }
   ]
}

Каждая точка многоугольника определяется как пара долгота, широта в массиве, и эти точки находятся в массиве, который задается как coordinates. В Point из прошлого урока coordinates был массивом из двух значений: широты и долготы. Для Polygon это массив массивов, содержащих два значения: долгота, широта.

💁 Помните, GeoJSON использует долгота, широта для точек, а не широта, долгота.

Массив координат многоугольника всегда имеет на одну запись больше, чем количество точек многоугольника, так как последняя запись совпадает с первой, замыкая многоугольник. Например, для прямоугольника будет 5 точек.

Прямоугольник с координатами

На изображении выше показан прямоугольник. Координаты многоугольника начинаются в верхнем левом углу на 47,-122, затем перемещаются вправо к 47,-121, затем вниз к 46,-121, затем влево к 46,-122, а затем возвращаются к начальной точке на 47,-122. Это дает многоугольнику 5 точек: верхний левый угол, верхний правый угол, нижний правый угол, нижний левый угол и снова верхний левый угол для замыкания.

Попробуйте создать GeoJSON многоугольник вокруг вашего дома или школы. Используйте инструмент, например, GeoJSON.io.

Задание — определение геозоны

Чтобы использовать геозону в Azure Maps, сначала ее нужно загрузить в ваш аккаунт Azure Maps. После загрузки вы получите уникальный идентификатор, который можно использовать для проверки точки относительно геозоны. Для загрузки геозон в Azure Maps необходимо использовать веб-API карт. Вы можете вызывать веб-API Azure Maps с помощью инструмента curl.

🎓 Curl — это инструмент командной строки для выполнения запросов к веб-эндпоинтам.

  1. Если вы используете Linux, macOS или последнюю версию Windows 10, curl, скорее всего, уже установлен. Выполните следующую команду в терминале или командной строке, чтобы проверить:

    curl --version
    

    Если вы не видите информацию о версии curl, вам нужно установить его с страницы загрузки curl.

    💁 Если вы опытный пользователь Postman, вы можете использовать его вместо curl, если предпочитаете.

  2. Создайте GeoJSON файл, содержащий многоугольник. Вы будете тестировать его с помощью вашего GPS-датчика, поэтому создайте многоугольник вокруг вашего текущего местоположения. Вы можете создать его вручную, отредактировав приведенный выше пример GeoJSON, или использовать инструмент, например, GeoJSON.io.

    GeoJSON должен содержать FeatureCollection, включающий Feature с geometry типа Polygon.

    Вы ОБЯЗАНЫ также добавить элемент properties на том же уровне, что и элемент geometry, и он должен содержать geometryId:

    "properties": {
        "geometryId": "1"
    }
    

    Если вы используете GeoJSON.io, вам нужно будет вручную добавить этот элемент в пустой properties, либо после загрузки JSON-файла, либо в редакторе JSON в приложении.

    Этот geometryId должен быть уникальным в этом файле. Вы можете загружать несколько геозон как несколько Features в FeatureCollection в одном GeoJSON-файле, если у каждой из них разный geometryId. Многоугольники могут иметь одинаковый geometryId, если они загружаются из разных файлов в разное время.

  3. Сохраните этот файл как geofence.json и перейдите в папку, где он сохранен, в вашем терминале или консоли.

  4. Выполните следующую команду curl, чтобы создать геозону:

    curl --request POST 'https://atlas.microsoft.com/mapData/upload?api-version=1.0&dataFormat=geojson&subscription-key=<subscription_key>' \
         --header 'Content-Type: application/json' \
         --include \
         --data @geofence.json
    

    Замените <subscription_key> в URL на API-ключ вашего аккаунта Azure Maps.

    URL используется для загрузки данных карты через API https://atlas.microsoft.com/mapData/upload. Вызов включает параметр api-version, чтобы указать, какую версию API Azure Maps использовать. Это позволяет API изменяться со временем, сохраняя обратную совместимость. Формат загружаемых данных установлен как geojson.

    Эта команда выполнит POST-запрос к API загрузки и вернет список заголовков ответа, включая заголовок location.

    content-type: application/json
    location: https://us.atlas.microsoft.com/mapData/operations/1560ced6-3a80-46f2-84b2-5b1531820eab?api-version=1.0
    x-ms-azuremaps-region: West US 2
    x-content-type-options: nosniff
    strict-transport-security: max-age=31536000; includeSubDomains
    x-cache: CONFIG_NOCACHE
    date: Sat, 22 May 2021 21:34:57 GMT
    content-length: 0
    

    🎓 При вызове веб-эндпоинта вы можете передавать параметры вызова, добавляя ?, за которым следуют пары ключ-значение в формате key=value, разделенные символом &.

  5. Azure Maps не обрабатывает запрос мгновенно, поэтому вам нужно будет проверить, завершился ли процесс загрузки, используя URL, указанный в заголовке location. Выполните GET-запрос к этому URL, чтобы узнать статус. Вам нужно будет добавить ваш ключ подписки в конец URL location, добавив &subscription-key=<subscription_key>, заменив <subscription_key> на API-ключ вашего аккаунта Azure Maps. Выполните следующую команду:

    curl --request GET '<location>&subscription-key=<subscription_key>'
    

    Замените <location> на значение заголовка location, а <subscription_key> на API-ключ вашего аккаунта Azure Maps.

  6. Проверьте значение status в ответе. Если оно не Succeeded, подождите минуту и попробуйте снова.

  7. Как только статус станет Succeeded, посмотрите на resourceLocation в ответе. Этот параметр содержит детали уникального идентификатора (UDID) для объекта GeoJSON. UDID — это значение после metadata/, не включая api-version. Например, если resourceLocation был:

    {
      "resourceLocation": "https://us.atlas.microsoft.com/mapData/metadata/7c3776eb-da87-4c52-ae83-caadf980323a?api-version=1.0"
    }
    

    Тогда UDID будет 7c3776eb-da87-4c52-ae83-caadf980323a.

    Сохраните этот UDID, так как он понадобится вам для проверки геозоны.

Проверка точек относительно геозоны

После загрузки многоугольника в Azure Maps вы можете проверить, находится ли точка внутри или вне геозоны. Для этого выполните запрос к веб-API, передав UDID геозоны, а также широту и долготу проверяемой точки.

При выполнении этого запроса вы также можете передать значение, называемое searchBuffer. Оно указывает API карт, насколько точно возвращать результаты. Это важно, так как GPS не является абсолютно точным, и иногда местоположение может быть смещено на несколько метров или больше. По умолчанию searchBuffer равен 50 м, но вы можете установить значения от 0 м до 500 м.

Когда результаты возвращаются из API, одна из частей результата — это distance, измеряемое до ближайшей точки на краю геозоны. Значение будет положительным, если точка находится вне геозоны, и отрицательным, если она внутри. Если это расстояние меньше searchBuffer, возвращается фактическое расстояние в метрах, иначе значение будет 999 или -999. 999 означает, что точка находится вне геозоны на расстоянии, превышающем searchBuffer, -999 означает, что точка находится внутри геозоны на расстоянии, превышающем searchBuffer.

Геозона с буфером поиска 50 м вокруг нее

На изображении выше геозона имеет буфер поиска 50 м.

  • Точка в центре геозоны, далеко внутри буфера поиска, имеет расстояние -999.
  • Точка далеко за пределами буфера поиска имеет расстояние 999.
  • Точка внутри геозоны и внутри буфера поиска, на расстоянии 6 м от геозоны, имеет расстояние 6 м.
  • Точка вне геозоны и внутри буфера поиска, на расстоянии 39 м от геозоны, имеет расстояние 39 м.

Важно знать расстояние до края геозоны и комбинировать эту информацию с другими данными, такими как другие GPS-замеры, скорость и данные о дорогах, при принятии решений на основе местоположения транспортного средства.

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

Траектория GPS, показывающая, что транспортное средство проезжает мимо кампуса Microsoft по трассе 520, с GPS-замерами вдоль дороги, за исключением одного внутри геозоны на кампусе На изображении показана геозона, охватывающая часть кампуса Microsoft. Красная линия обозначает движение грузовика по трассе 520, а круги показывают GPS-замеры. Большинство из них точны и находятся вдоль трассы 520, но один из замеров оказался внутри геозоны. Этот замер не может быть правильным — на территории кампуса нет дорог, по которым грузовик мог бы внезапно свернуть с трассы 520, а затем вернуться обратно. Код, проверяющий эту геозону, должен учитывать предыдущие замеры перед тем, как действовать на основе результатов теста геозоны.

Какие дополнительные данные вам понадобятся, чтобы проверить, можно ли считать GPS-замер корректным?

Задача — тестирование точек относительно геозоны

  1. Начните с создания URL для запроса к веб-API. Формат:

    https://atlas.microsoft.com/spatial/geofence/json?api-version=1.0&deviceId=gps-sensor&subscription-key=<subscription-key>&udid=<UDID>&lat=<lat>&lon=<lon>
    

    Замените <subscription_key> на ключ API для вашей учетной записи Azure Maps.

    Замените <UDID> на UDID геозоны из предыдущего задания.

    Замените <lat> и <lon> на широту и долготу, которые вы хотите протестировать.

    Этот URL использует API https://atlas.microsoft.com/spatial/geofence/json для запроса геозоны, определенной с помощью GeoJSON. Он нацелен на версию API 1.0. Параметр deviceId обязателен и должен быть именем устройства, от которого поступают широта и долгота.

    По умолчанию радиус поиска составляет 50 м, и вы можете изменить его, добавив дополнительный параметр searchBuffer=<distance>, где <distance> — радиус поиска в метрах, от 0 до 500.

  2. Используйте curl для выполнения GET-запроса к этому URL:

    curl --request GET '<URL>'
    

    💁 Если вы получите код ответа BadRequest с ошибкой:

    Invalid GeoJSON: All feature properties should contain a geometryId, which is used for identifying the geofence.
    

    это означает, что в вашем GeoJSON отсутствует раздел properties с geometryId. Вам нужно исправить GeoJSON, затем повторить шаги выше, чтобы повторно загрузить файл и получить новый UDID.

  3. Ответ будет содержать список geometries, по одному для каждого полигона, определенного в GeoJSON, использованном для создания геозоны. Каждый полигон имеет 3 интересующих поля: distance, nearestLat и nearestLon.

    {
        "geometries": [
            {
                "deviceId": "gps-sensor",
                "udId": "7c3776eb-da87-4c52-ae83-caadf980323a",
                "geometryId": "1",
                "distance": 999.0,
                "nearestLat": 47.645875,
                "nearestLon": -122.142713
            }
        ],
        "expiredGeofenceGeometryId": [],
        "invalidPeriodGeofenceGeometryId": []
    }
    
    • nearestLat и nearestLon — это широта и долгота точки на границе геозоны, ближайшей к тестируемому местоположению.

    • distance — расстояние от тестируемого местоположения до ближайшей точки на границе геозоны. Отрицательные числа означают, что точка находится внутри геозоны, положительные — снаружи. Это значение будет меньше 50 (по умолчанию радиус поиска) или равно 999.

  4. Повторите это несколько раз с местоположениями внутри и вне геозоны.

Использование геозон в серверless-коде

Теперь вы можете добавить новый триггер в ваше приложение Functions для проверки данных GPS событий IoT Hub относительно геозоны.

Группы потребителей

Как вы помните из предыдущих уроков, IoT Hub позволяет воспроизводить события, которые были получены хабом, но не обработаны. Но что произойдет, если подключится несколько триггеров? Как хаб узнает, какой из них обработал какие события?

Ответ: никак! Вместо этого вы можете определить несколько отдельных подключений для чтения событий, и каждое из них может управлять воспроизведением непрочитанных сообщений. Эти подключения называются группами потребителей. При подключении к конечной точке вы можете указать, к какой группе потребителей вы хотите подключиться. Каждый компонент вашего приложения будет подключаться к отдельной группе потребителей.

Один IoT Hub с 3 группами потребителей, распределяющими одни и те же сообщения между 3 различными приложениями Functions

Теоретически до 5 приложений могут подключаться к каждой группе потребителей, и все они будут получать сообщения по мере их поступления. Наилучшей практикой является подключение только одного приложения к каждой группе потребителей, чтобы избежать дублирования обработки сообщений и обеспечить правильную обработку всех очередных сообщений при перезапуске. Например, если вы запустите ваше приложение Functions локально, а также в облаке, оба будут обрабатывать сообщения, что приведет к дублированию blob-объектов, сохраненных в учетной записи хранилища.

Если вы откроете файл function.json для триггера IoT Hub, созданного в предыдущем уроке, вы увидите группу потребителей в разделе привязки триггера Event Hub:

"consumerGroup": "$Default"

При создании IoT Hub по умолчанию создается группа потребителей $Default. Если вы хотите добавить дополнительный триггер, вы можете сделать это, используя новую группу потребителей.

💁 В этом уроке вы будете использовать другую функцию для проверки геозоны, отличную от той, которая используется для хранения данных GPS. Это сделано для демонстрации использования групп потребителей и разделения кода, чтобы сделать его более читаемым и понятным. В производственном приложении существует множество способов архитектурного решения — объединение обеих функций в одну, использование триггера на учетной записи хранилища для запуска функции проверки геозоны или использование нескольких функций. Нет "правильного" способа, все зависит от остальной части вашего приложения и ваших потребностей.

Задача — создание новой группы потребителей

  1. Выполните следующую команду для создания новой группы потребителей с именем geofence для вашего IoT Hub:

    az iot hub consumer-group create --name geofence \
                                     --hub-name <hub_name>
    

    Замените <hub_name> на имя, которое вы использовали для вашего IoT Hub.

  2. Если вы хотите увидеть все группы потребителей для IoT Hub, выполните следующую команду:

    az iot hub consumer-group list --output table \
                                   --hub-name <hub_name>
    

    Замените <hub_name> на имя, которое вы использовали для вашего IoT Hub. Это отобразит список всех групп потребителей.

    Name      ResourceGroup
    --------  ---------------
    $Default  gps-sensor
    geofence  gps-sensor
    

💁 Когда вы запускали монитор событий IoT Hub в предыдущем уроке, он подключался к группе потребителей $Default. Именно поэтому вы не можете одновременно запускать монитор событий и триггер событий. Если вы хотите запустить оба, вы можете использовать другие группы потребителей для всех ваших приложений Functions, а $Default оставить для монитора событий.

Задача — создание нового триггера IoT Hub

  1. Добавьте новый триггер событий IoT Hub в ваше приложение gps-trigger, созданное в предыдущем уроке. Назовите эту функцию geofence-trigger.

    ⚠️ Вы можете обратиться к инструкциям по созданию триггера событий IoT Hub из проекта 2, урока 5, если это необходимо.

  2. Настройте строку подключения IoT Hub в файле function.json. Файл local.settings.json используется всеми триггерами в приложении Functions.

  3. Обновите значение consumerGroup в файле function.json, чтобы оно ссылалось на новую группу потребителей geofence:

    "consumerGroup": "geofence"
    
  4. Вам понадобится ключ подписки для вашей учетной записи Azure Maps в этом триггере, поэтому добавьте новую запись в файл local.settings.json с именем MAPS_KEY.

  5. Запустите приложение Functions, чтобы убедиться, что оно подключается и обрабатывает сообщения. Триггер iot-hub-trigger из предыдущего урока также будет работать и загружать blob-объекты в хранилище.

    Чтобы избежать дублирования GPS-замеров в blob-хранилище, вы можете остановить приложение Functions, которое у вас работает в облаке. Для этого используйте следующую команду:

    az functionapp stop --resource-group gps-sensor \
                        --name <functions_app_name>
    

    Замените <functions_app_name> на имя, которое вы использовали для вашего приложения Functions.

    Вы можете перезапустить его позже с помощью следующей команды:

    az functionapp start --resource-group gps-sensor \
                        --name <functions_app_name>
    

    Замените <functions_app_name> на имя, которое вы использовали для вашего приложения Functions.

Задача — тестирование геозоны из триггера

Ранее в этом уроке вы использовали curl для запроса геозоны, чтобы определить, находится ли точка внутри или снаружи. Вы можете выполнить аналогичный веб-запрос из вашего триггера.

  1. Чтобы запросить геозону, вам нужен ее UDID. Добавьте новую запись в файл local.settings.json с именем GEOFENCE_UDID и этим значением.

  2. Откройте файл __init__.py из нового триггера geofence-trigger.

  3. Добавьте следующий импорт в начало файла:

    import json
    import os
    import requests
    

    Пакет requests позволяет выполнять вызовы веб-API. У Azure Maps нет Python SDK, поэтому для использования его из Python-кода необходимо выполнять вызовы веб-API.

  4. Добавьте следующие 2 строки в начало метода main, чтобы получить ключ подписки Maps:

    maps_key = os.environ['MAPS_KEY']
    geofence_udid = os.environ['GEOFENCE_UDID']    
    
  5. Внутри цикла for event in events добавьте следующий код для получения широты и долготы из каждого события:

    event_body = json.loads(event.get_body().decode('utf-8'))
    lat = event_body['gps']['lat']
    lon = event_body['gps']['lon']
    

    Этот код преобразует JSON из тела события в словарь, затем извлекает lat и lon из поля gps.

  6. При использовании requests, вместо создания длинного URL, как вы делали с curl, вы можете использовать только часть URL и передать параметры в виде словаря. Добавьте следующий код для определения URL и настройки параметров:

    url = 'https://atlas.microsoft.com/spatial/geofence/json'
    
    params = {
        'api-version': 1.0,
        'deviceId': 'gps-sensor',
        'subscription-key': maps_key,
        'udid' : geofence_udid,
        'lat' : lat,
        'lon' : lon
    }
    

    Элементы в словаре params будут соответствовать ключевым значениям, которые вы использовали при вызове веб-API через curl.

  7. Добавьте следующие строки кода для вызова веб-API:

    response = requests.get(url, params=params)
    response_body = json.loads(response.text)
    

    Это вызывает URL с параметрами и возвращает объект ответа.

  8. Добавьте следующий код ниже этого:

    distance = response_body['geometries'][0]['distance']
    
    if distance == 999:
        logging.info('Point is outside geofence')
    elif distance > 0:
        logging.info(f'Point is just outside geofence by a distance of {distance}m')
    elif distance == -999:
        logging.info(f'Point is inside geofence')
    else:
        logging.info(f'Point is just inside geofence by a distance of {distance}m')
    

    Этот код предполагает наличие одного полигона и извлекает расстояние от этого единственного полигона. Затем он записывает разные сообщения в журнал в зависимости от расстояния.

  9. Запустите этот код. Вы увидите в выводе журнала, находятся ли GPS-координаты внутри или вне геозоны, с указанием расстояния, если точка находится в пределах 50 м. Попробуйте этот код с различными геозонами, основываясь на местоположении вашего GPS-датчика, попробуйте переместить датчик (например, подключив его к Wi-Fi через мобильный телефон или с другими координатами на виртуальном IoT-устройстве), чтобы увидеть изменения.

  10. Когда будете готовы, разверните этот код в вашем приложении Functions в облаке. Не забудьте развернуть новые настройки приложения.

    ⚠️ Вы можете обратиться к инструкциям по загрузке настроек приложения из проекта 2, урока 5, если это необходимо.

    ⚠️ Вы можете обратиться к инструкциям по развертыванию вашего приложения Functions из проекта 2, урока 5, если это необходимо.

💁 Вы можете найти этот код в папке code/functions.


🚀 Челлендж

В этом уроке вы добавили одну геозону, используя GeoJSON-файл с одним полигоном. Вы можете загрузить несколько полигонов одновременно, если они имеют разные значения geometryId в разделе properties.

Попробуйте загрузить GeoJSON-файл с несколькими полигонами и измените ваш код, чтобы определить, к какому полигону GPS-координаты ближе или в каком они находятся.

Пост-лекционный тест

Пост-лекционный тест

Обзор и самостоятельное изучение

Задание

Отправка уведомлений с помощью Twilio


Отказ от ответственности:
Этот документ был переведен с помощью сервиса автоматического перевода Co-op Translator. Хотя мы стремимся к точности, пожалуйста, учитывайте, что автоматические переводы могут содержать ошибки или неточности. Оригинальный документ на его исходном языке следует считать авторитетным источником. Для получения критически важной информации рекомендуется профессиональный перевод человеком. Мы не несем ответственности за любые недоразумения или неправильные интерпретации, возникшие в результате использования данного перевода.