Онлайн-курс:"OTUS.NoSQL".
Проект: https://github.com/BorisPlus/mongodb_geo
Введение
Геоинформация- это любые сведения, отражающие расположение, форму и размеры объекта (далее - геообъект). Ее учет ведется в картографии, геологии, метеорологии, землеустройстве, экологии, муниципальном управлении, транспорте, экономике, обороне и многих других областях. Геоинформация является неотъемлемой частью так называемых Больших данных, что приводит к необходимости разработки средств ее анализа и визуализации.
Академический подход написания статей подразумевал "сведения, отражающие свойства ... объектов материального мира". Однако на практике имелся факт осуществления энтузиастом накладки поверх Гугл-карт через штатное API рисунков с топографией Средиземья и построение маршрутов героев Дж. Толкина, что не совсем "материально". Другим стыком с нематериальным может служить пример наборов данных по типуGeoIP,E.164,ABC.
Результат исследования представляет собой инструмент отображения хранящихся в MongoDB сведений о геообъектах на карте посредством web-доступа. Клиентская часть реализована с использованиемLeaflet(JavaScript-библиотека с открытым исходным кодом для мобильных интерактивных карт) и набора соответствующих процедур асинхронного получения сведений от серверной части. Сервис разработан на базе созданного ранее на курсе"OTUS.Web-python"конструктора программного обеспечения"Dummy WSGI Framework"(репозиторий) на языке программирования Python3 с задействованием WSGI.
В настоящем тексте основное внимание уделено простоте работы с геоинформацией средствами MongoDB. Особенности реализации процедур сбора демонстрационных сведений отражены по мере изложения.
Необходимо заметить, что документацияMongoDBявляется достаточно проработанной и удобной по навигации. Исчерпывающие сведения в разделе погеообъектамподтолкнули к изучению изложенных в данной работе возможностей. Кроме того, после осуществленной разработки и достигнутых результатов в ходе написания настоящего текста в документации MongoDB обнаруженпримерработы с геообъектами, что еще раз подтверждает ее высокую проработку.
Гео-объекты MongoDB
В данной статье продемонстрированы базовые возможности оперирования в MongoDB такимигеообъектамикак:точка,линия,полигон.
Более сложные структуры, такие как:набор точек,набор линий,набор полигоновиколлекция геообъектовне рассматриваются.
Представляемое в рамках настоящей работы решение предполагает
возможность хранения в обособленных коллекциях одной базы данных
сведений о геообъектах различных типов. Тем не менее хранение также
возможно в рамках одной коллекции. Названия полей, описывающих
геообъекты, могут быть произвольными, за исключением уникально
идентифицирующего атрибута документа -ident
. Также
необходимо соблюдать установленную MongoDBструктуру сведенийо
геообъекте:
<field>: { type: <GeoJSON type> , coordinates: <coordinates> }
В рамках получения сведений клиентской стороной от серверной геообъекты искусственно поделены на два вида - статичные и динамичные.
Под статичными понимаются те, свойства которых фиксированы, и, таким образом, актуализация сведений в отношении которых не требуется, в том числе при изменении позиции наблюдения. В раннейстатьек данной категории относились метеориты. С целью демонстрации возможностей работы с полигонами к ним добавлены - здания и сооружения (собраны для отдельного района г. Санкт-Петербурга).
Динамичные геообъекты - это те, положение, форма или размер которых изменяются с течением времени даже при неизменной позиции наблюдателя. В качестве демонстрации возможности работы с таковыми осуществляется фоновый сбор перемещений таксопарка компании "Яндекс", представляемых на карте в виде части пути (линия из нескольких крайних точек пройденного маршрута) и текущих мест пребывания (точка).
Предполагается, что инфраструктура MongoDB развернута.
mongo 192.168.102.99 --port 49326 ---> use otus switched to db otus > db.dropDatabase() { "dropped" : "otus", "ok" : 1 } > use otus switched to db otus > db otus > show collections
Точки (статичные)
В качестветочекиспользуютсяметеориты. Необходимые коллекция, поля и индексы:
> db.meteorites.createIndex( { "ident": 1 }, { unique: true } )> db.meteorites.createIndex( { "location" : "2dsphere" } )
Изначально сведения о местоположении хранятся в
атрибутеgeolocation
. Однако оно не имеет необходимой
структуры для геообъекта типа "точка". Поэтому в качестве атрибута
местоположения метеоритов выступает дополнительное, отсутствующее в
демонстрационном наборе, полеlocation
, куда перенесены
сведения в необходимом формате:
location: { type: 'Point' , coordinates: [ LON, LAT ] }
Загрузка исходных данных о метеоритах:
mongoimport --host 192.168.102.99 --port 49326 \--db otus --collection meteorites --jsonArray \--file ./foreign/meteorites/data.json2021-03-28T10:28:09.443+0300 connected to: mongodb://192.168.102.99:49326/2021-03-28T10:28:12.443+0300 [<span class="pl-c" style="box-sizing: border-box; color: var(--color-prettylights-syntax-comment);"><span class="pl-c" style="box-sizing: border-box; color: var(--color-prettylights-syntax-comment);">#</span>##.....................] otus.meteorites 1.62MB/10.1MB (16.0%)</span>2021-03-28T10:28:15.443+0300 [<span class="pl-c" style="box-sizing: border-box; color: var(--color-prettylights-syntax-comment);"><span class="pl-c" style="box-sizing: border-box; color: var(--color-prettylights-syntax-comment);">#</span>########...............] otus.meteorites 3.97MB/10.1MB (39.4%)</span>2021-03-28T10:28:18.443+0300 [<span class="pl-c" style="box-sizing: border-box; color: var(--color-prettylights-syntax-comment);"><span class="pl-c" style="box-sizing: border-box; color: var(--color-prettylights-syntax-comment);">#</span>###########............] otus.meteorites 5.39MB/10.1MB (53.4%)</span>2021-03-28T10:28:21.443+0300 [<span class="pl-c" style="box-sizing: border-box; color: var(--color-prettylights-syntax-comment);"><span class="pl-c" style="box-sizing: border-box; color: var(--color-prettylights-syntax-comment);">#</span>################.......] otus.meteorites 7.23MB/10.1MB (71.6%)</span>2021-03-28T10:28:24.443+0300 [<span class="pl-c" style="box-sizing: border-box; color: var(--color-prettylights-syntax-comment);"><span class="pl-c" style="box-sizing: border-box; color: var(--color-prettylights-syntax-comment);">#</span>####################...] otus.meteorites 8.83MB/10.1MB (87.5%)</span>2021-03-28T10:28:27.443+0300 [<span class="pl-c" style="box-sizing: border-box; color: var(--color-prettylights-syntax-comment);"><span class="pl-c" style="box-sizing: border-box; color: var(--color-prettylights-syntax-comment);">#</span>######################.] otus.meteorites 9.71MB/10.1MB (96.3%)</span>2021-03-28T10:28:28.453+0300 [<span class="pl-c" style="box-sizing: border-box; color: var(--color-prettylights-syntax-comment);"><span class="pl-c" style="box-sizing: border-box; color: var(--color-prettylights-syntax-comment);">#</span>#######################] otus.meteorites 10.1MB/10.1MB (100.0%)</span>2021-03-28T10:28:28.454+0300 45716 document(s) imported successfully. 0 document(s) failed to import.
Исходя из уже имеющегося опыта, из набора 45716 объектов
необходимо удалить метеорит, который не относится к земной
поверхности (марсианский метеоритMeridiani Planum), так как
его координаты не соответствуют стандарту земного
геопозиционирования и не могут быть помещены в индекс
(индексирование приводит к ошибкеCan't extract geo keys: ...
longitude/latitude is out of bounds, ...
, равно как и
вставка таких данных в индекс).
db.meteorites.remove({"ident" : "32789"});
Кроме этого в наборе имеется 7315 метеоритов, чье местоположение не известно. Данный факт также не позволит включить их в гео-индекс и приведет к ошибке. Поэтому в индексе учтены только те метеориты, чье местоположение известно.
db.meteorites.updateMany( {"geolocation":{$exists:true}}, [{ $set: { "location" : { "type": "Point", "coordinates" : [ { $toDouble: "$geolocation.longitude" } , { $toDouble: "$geolocation.latitude" } ] } } }]);
В результате в MongoDB в коллекцииmeteorites
в
атрибутахlocation
содержится информация о
местоположении 38400 метеоритах из их общего числа 45716.
Важное замечание:согласнодокументацииданный порядок
следования координат{ долгота, широта }
является
единственно верным с точки зрения MongoDB (If specifying
latitude and longitude coordinates, list the longitude first and
then latitude
). Необходимо заострить внимание на этом
обстоятельстве, так как в последующем при отображении информации на
карте Leaflet нужен будет другойпорядокабсолютно для всех
координат любых геообъектов -{ широта, долгота }
.
Указанное приводит к тому, что после получения сведений из MongoDB
требуется произвести перестановку в парах координат или представить
их в виде "словаря"{ lon: долгота, lat: широта }
. Если
для точки это выражается в одной перестановке, то для полигона это
происходит в рамках итерации по точкам границы. Было бы хорошо,
если бы MongoDB поддерживала хранение и в формате{ широта,
долгота }
.
Полигоны (статичные)
В качествеполигоновиспользуются здания и сооружения, сведения о которых единоразово получены для отдельного района г. Санкт-Петербурга с использованием API сервисаWikiMapia.
WikiMapia имеет лимит по числу запросов, при превышении которого сведения информационным ресурсом не предоставляются (при этом указанное не приводит к возникновению ошибки, что затрудняет понимание в фактическом наличии подобных данных). Данное обстоятельство нивелируется возможностью (предположительно неограниченного) получения дополнительных API-ключей доступа.
Необходимые коллекция, поля и индексы:
db.geo_wikimapia_polygons.createIndex( { "ident": 1 }, { unique: true } )db.geo_wikimapia_polygons.createIndex( { "area" : "2dsphere" } )
Сбордемонстрационных данных
реализован на языке программирования Python3 с использованием
библиотекиpymongo
.
Вниманию Python-разработчиков: в исходном коде данной процедуры исключено исполнение набора инструкций вставки-обновления (
UPSERT
) за раз (bulk_write(instructions)
), так как при наличии ошибки (о чем сказано ниже) соответственно он отвергался полностью. Вставка-обновление происходит последовательно по одной инструкции.
Изначально сведения о местоположении полигона разнесены по полям
записи, то есть фактически отсутствует атрибут необходимой для
MongoDB структуры для геообъекта типа "полигон". Поэтому в его
качестве выступает дополнительное полеarea
, куда
перенесены сведения в необходимом формате (происходит сразу
привставкесведений в рамках их
онлайн-получения). Структура геообъекта
типаполигон
имеет вид:
area: { type: 'Polygon' , coordinates: [[ [LON_1, LAT_1], [LON_2, LAT_2], ..., [LON_1, LAT_1] ]] }
В результате запросов к WikiMapia:
python3 ./foreign/onetime_static_load_polygons_wikimapia.py Page 1 has docs count 50Page 2 has docs count 50...Page 37 has docs count 35Max page 37 with some data
в MongoDB накоплена информация в отношении:
> db.geo_wikimapia_polygons.count()
1832 зданий и сооружений.
Важное замечание: в MongoDB сведения о полигоне
должны удовлетворять спецификации (пункт 3.1.6 RFC 7946 "GeoJSON"
August 2016). В частности, полигон, имеющий пересечение граней,
не может быть добавлен (иначе в MongoDB возникает ошибкаEdges
<number K> and <number M> cross. Edge locations in
degrees: [Kx1, Ky1]-[Kx2, Ky2] and [Mx1, My1]-[Mx2,My2]
).
Кроме того, важно, чтобы полигон был "замкнут", то есть крайняя
точка должна совпадать и первоначальной (иначе в MongoDB возникает
ошибкаLoop is not closed
). WikiMapia же иным образом
подходит к требованиям достоверности координат. Поэтому из 1835
полигонов, полученных в описанном ранее абзаце (36 страниц * 50
полигонов + 35 полигонов = 1835 полигонов), сохранены 1832
объекта.
Линии (динамичные)
В качестве демонстрационных динамичных геообъектов выбранылиниии точки, отражающие крайние части маршрутов и фактическое пребывание автомобилей таксопарка компании "Яндекс" соответственно.
Важное замечание: сведения о линии должны удовлетворять спецификации (пункт 3.1.4 RFC 7946 "GeoJSON" August 2016), то есть линия должна содержать две разные точки (при этом она может также содержать и одинаковые).
Сбор демонстрационных данныхреализованна языке
программирования Python3 с использованием
библиотекrequests
,pymongo
и
задействованием пакета многопроцессорной
обработкиmultiprocessing
. Необходимость крайнего
обусловлена требованиями увеличения скорости получения актуальных
сведений о местоположении и пути следования автомобилей с целью
повышения эффективности частоты прорисовки маршрутов на карте
(максимизации интерактивности). Сведения получаются в отношении
заранее определенных точек района г. Санкт-Петербурга. Точки сбора
данной информации располагаются на определенном коротком расстоянии
друг от друга и образуют заранее рассчитанную в проекте "ячеистую"
структуру. Данный подход отличается оталгоритма "заливки",
применявшегося иным разработчиком, исследовавшим подобную
информацию ранее.
Вниманию Python-разработчиков: невозможно организовать пул процессов, к которым применены декораторы. Необходимо переписать код таких функций с условием внесения в их содержание "оберточной" логики.
Изначально сведения о маршруте не имеют необходимой MongoDB
структуры для геообъекта типа "линия". Поэтому в качестве данного
атрибута выступает дополнительное полеpath
, куда
перенесены сведения в необходимом формате. Структура геообъекта
типалиния
имеет вид:
path: { type: 'LineString' , coordinates: [ [LON_1, LAT_1], [LON_2, LAT_2], ..., [LON_N, LAT_N] ] }
Операции с геообъектами
В рамках работы рассматриваются такиеоперациикак:$geoIntersects
и$nearSphere
.
Операция$geoIntersectsявляется основной реализованной в проекте и используется для нахождения всех геообъектов, имеющих пересечение по георасположению с текущей областью карты. Например, если в область карты (соответствующий полигон, описываемый двумя крайними диаметрально расположенными координатами) попадет часть маршрута (линии) или часть здания (полигона), то они будут получены из базы данных и отображены на карте. Точки, соответственно, появятся при их попадании в данную область.
Важное замечание: при строгом подходе и оценке данного исследования можно утверждать, что оно фактически основано на единственном запросе "найти все объекты в области на карте". Однако, в рамках защиты необходимо заметить, что конечной целью являлась демонстрация именно простоты работы с геоинформацией в MongoDB.
Использование иной операции$nearSphereпродемонстрировано на примере выборки из MongoDb полигонов и метеоритов с целью отображения их на карте только при условии присутствия их в "круговой" окрестности наблюдения (для полигона - пересечение с окружностью).
Операции$geoWithin(выборка геообъектов, имеющих полное включение в заданную область) и$near(выборка геообъектов в окрестности точки) в рамках настоящей работы не рассматриваются.
Операции
$near
и$nearSphere
шире по возможности, чем просто "нахождение в круговой окрестности", так как описывают не только максимальное удаление ($maxDistance
) от точки наблюдения, но и минимальное ($minDistance
). Данное обстоятельство может быть использовано при работе с секторами, учитывающими "примерное" удаление объектов от исходной точки наблюдения: секторное поле зрения на панораме "Яндекс.Карты", "классические" области действия сотовых вышек, углы обзора видеокамер городского наружного наблюдения и иное.
Сервис демонстрации геообъектов
Сервис реализован на базе авторского конструктора программного Web-обеспечения"Dummy WSGI Framework"(репозиторий), является легковесным (около 45 килобайт кода).
pip3 install -r ./service/requirements.txtuwsgi --http 127.0.0.1:8080 --wsgi-file ./service/application.py
Обстановка в области карты
По адресуhttp://127.0.0.1:8080вниманию представлен район г. Санкт-Петербурга, где отображены полигоны зданий. По мере перемещения по карте или изменении масштаба карты в асинхронном режиме подгружаются иные полигоны.
Разработчикам на заметку: для записи GIF-анимации использовалось программное обеспечениеPeek.
При перемещении в определенные районы (достаточно в исходной точке г. Санкт-Петербурга уменьшить масштаб до размера "7") подгружаются указатели на конкретные места падения метеоритов.
С целью демонстрации в текущем приложении нет ограничения на степень масштабирования, при которой запросы к серверной части не происходят. Тем самым представляется возможным увидеть скопления метеоритов на карте достаточно малого масштаба. Однако в качестве рекомендаций при работе с данными, имеющими значительный "мировой" объем, можно посоветовать использование имеющегося в Leaflet параметра минимально допустимого уровня масштабирования карты.
Очередным открытием в данных NASA явилось наличие 64 метеоритов, местоположения падения которых имеют абсолютно одинаковые координаты. Указанное обнаружено в результате визуального изучения метеоритов на карте (выделяющаяся темная тень).
Представляется возможным кластеризовать метеориты по местоположению и выявить подобные случаи.
> db.meteorites.find({"location.coordinates": [13.43333,58.58333] }).count() 64> db.meteorites.find({"location.coordinates": [13.43333,58.58333] }, {name: 1, _id: 0}) { "name" : "Osterplana" } { "name" : "sterplana 002" } { "name" : "sterplana 003" } ... { "name" : "sterplana 064" }
Данные "необычные" сведения соответствуют метеориту "sterplana", имеющему удивительнуюисторию(рус.).
Для интерактивной демонстрации динамичных геообъектов необходима следующая коллекция:
db.geo_yandex_taxi.deleteMany({})db.geo_yandex_taxi.createIndex( { "ident": 1 }, { unique: true } )db.geo_yandex_taxi.createIndex( { "last_point" : "2dsphere" } )db.geo_yandex_taxi.createIndex( { "path" : "2dsphere" } )
и фоновый сбор актуальных данных:
python3 ./foreign/upsert_yandex_taxi_loop.py 9 2.61409401893615729 2.4818162918090829 2.5282382965087899 2.3746058940887459 2.53371548652648939 2.72976160049438489 2.605773925781259 2.5869448184967049 2.5660433769226074
Исходные сведения о перемещении таксопарка "Яндекс" обезличены и содержат крайние точки пройденных маршрутов:
{'id': 'bcc095db8e3b56e057caebdb97af5694', 'display_tariff': 'business', 'free': True, 'static_icon': False, 'positions': [{'lon': 30.326291, 'lat': 59.974395, 'direction': 50.0, 'timestamp': '2021-03-24T23:49:01.000000+0000'}, {'lon': 30.326291, 'lat': 59.974395, 'direction': 50.0, 'timestamp': '2021-03-24T23:48:52.000000+0000'}, {'lon': 30.326291, 'lat': 59.974395, 'direction': 50.0, 'timestamp': '2021-03-24T23:48:43.000000+0000'}, {'lon': 30.326291, 'lat': 59.974395, 'direction': 50.0, 'timestamp': '2021-03-24T23:48:34.000000+0000'}]}
Формат данных предположительно свидетельствует о том, что с целью повышения скорости предоставления крайних позиций автомобилей система "Яндекс" ориентирована на хранение ненормализованного вида соответствующих сведений.
На их основании сформированы линия перемещения и точка крайнего местоположения.
Помимо мониторинга на предмет появления нового динамичного объекта в текущей области карты системой осуществляется проверка покидания объектами ее границ. В ином бы случае движущиеся объекты останавливались и скапливались по краям карты (в случае работы подобного сервиса с "подконтрольными" сведениями необходимость в данной особенности полностью отсутствует).
Обстановка в области круговой окрестности
По адресуhttp://localhost:8080/circle/продемонстрирована выборка только тех геообъектов, которые попадают в круговую окрестность, располагаемую по центру карты.
Важное замечание: с целью
использованияnear
иnearSphere
требуется
создать индекс2d
или2dsphere
, иначе их
вызов приведет к ошибке исполнения:
error processing query: ns=otus.geo_wikimapia_polygonsTree: GEONEAR field=area maxdist=500 isNearSphere=0Sort: {}Proj: { _id: 0 } planner returned error :: caused by :: unable to find index for $geoNear query, full error: {'ok': 0.0, 'errmsg': 'error processing query: ns=otus.geo_wikimapia_polygonsTree: GEONEAR field=area maxdist=500 isNearSphere=0\nSort: {}\nProj: { _id: 0 }\n planner returned error :: caused by :: unable to find index for $geoNear query', 'code': 291, 'codeName': 'NoQueryExecutionPlans'}
Технические особенности реализации
При использовании данных наработок помимо перечисленных аспектов необходимо учитывать следующее.
Конфигурационный файл
Параметр конфигурации сервисаbase_config.py
содержит
отсылку на вид ("статичный", "динамичный"), название коллекции базы
MongoDB ("meteorites", "geo_wikimapia_polygons", "geo_yandex_taxi")
и атрибуты ("location", "area", "last_point", "path"), содержащие
сведения о геообъектах с указанием их GeoJSON-типа ("Point",
"LineString", "Polygon"), а именно:
...MONGODB_DB_COLLECTIONS = dict( static={ "meteorites": { "location": POINT_OBJECT, }, "geo_wikimapia_polygons": { "area": POLYGON_OBJECT, }, }, dynamic={ "geo_yandex_taxi": { "last_point": POINT_OBJECT, "path": LINE_STRING_OBJECT, }, },)...
Таким образом, при необходимости отображения на карте сведений из иной коллекции необходимо определиться с их видом ("статичный", "динамичный"), типом ("точка", "линия", "полигон") и названием конкретного атрибута, где они сохранены в требуемой MongoDB структуре.
При отсутствии в конфигурации динамичной коллекции, генерируемая сервисом HTML-страница не содержит инструкцию на JavaScript, осуществляющую периодический запрос сведений при неизменной локации. Указанное устраняет излишнюю нагрузку на серверную часть сервиса.
Изменения конфигурации вступают в силу после перезапуска сервиса, работа исключительно с HTML-файлами данных действий не требует.
"Ненагружающий" запоздалый AJAX запрос
Получение сведений клиентской частью от серверной реализовано с использованием асинхронных запросов на JavaScript (AJAX). При этом с целью недопущения исполнения сервисом запросов, являющихся "промежуточными", запросы происходят с некоторой малой задержкой, во время которой данный запрос может быть отменен последующим обращением. То есть результативная выборка данных происходит только в конечном положении наблюдения. Данный подход снижает нагрузку на серверную часть при последовательном переходе от участка карты к участку или при работе с ее масштабированием за счет исключения к ней AJAX-запросов, фактическое исполнение которых не требуется.
function get_data(...){ ... if (xhr && !(xhr.readyState === 4)) { xhr.abort(); console.log('Previous AJAX #' + xhr.__dt + ' was aborted'); } clearTimeout(timer); xhr = new XMLHttpRequest(); xhr.responseType = 'json'; xhr.__dt = Date.now(); console.log('Start AJAX #' + xhr.__dt); timer = setTimeout(function() { // find objects in area. }}
Демонстрация работы по старту и прерыванию AJAX-запросов доступна в консоли web-браузера при включении в нем режима "отладки" (F12).
Направления работы
Соответствие координат
MongoDB использует систему координат WGS84 (MongoDB
geospatial queries on GeoJSON objects calculate on a sphere;
MongoDB uses the WGS84 reference system for geospatial queries on
GeoJSON objects
) (поиск вглоссариислова "wgs84").
При этом Leaflet по-умолчанию использует систему координат EPSG 3857.
Исходя из описания,EPSG 3857допустима для координат
между85.06S
и85.06N
.
То есть в рамках рабочей эксплуатации необходимо в Leaflet установить параметр CRS равным"L.CRS.EPSG4326", посколькуонне имеет таких ограничений и целиком соответствует системе геокодирования MongoDB.
Запредельные координаты
К сожалению в данной работе не решен нюанс запроса сведений в области карты, находящейся за пределами стандартных для MongoDB широты и долготы. Например, после изначальной загрузки карты возможно осуществить значительное ее перетаскивание в сторону, и тем самым Leaflet станет запрашивать сведения, подобные этим:
pymongo.errors.OperationFailure: longitude/latitude is out of bounds, lng: 561.213 lat: 89.9823 ... Valid longitude values are between -180 and 180, both inclusive.Valid latitude values are between -90 and 90, both inclusive.
Данный момент возможно решить, если проводить "нормирование" запрашиваемой области (операции по типу остатка от деления, поиск соответствия "нормальному" двумерному "интервалу"), а при отображении на карте - "денормирование" координат полученных геообъектов (в зависимости от смещения относительно них периметра карты).
-тестирование
В рамках работы не рассматривались вопросы шардирования сведений о геообъектах (операции ихподдерживают в MongoDBу версий выше 4.0) и не исследовалась отдача при нагрузке.
Расширение числа источников
В результате рефакторинга представляется возможной организация получения сведений не только из разных коллекций, как это реализовано в настоящее время, но и от разных баз данных MongoDB. Указанное значительно повысит гибкость использования разработанного инструмента.
Выводы
Необходимо заметить, что Leaflet через браузер позволяет осуществить запрос у пользователя его текущих координат. Таким образом, достигнутый результат позволяет организовать на его базе ядро практически любого геосервиса - от работы с привычными статичными объектами (банкоматы, столовые, места общего пользования, остановки городского транспорта, места подзарядки телефонов) до мониторинга динамичных (друзья рядом, грузо-пассажирскийтранспорт, погодный (температура, осадки) фронт).
Вместо заключения
Лучше один раз увидеть, чем сто раз услышать.
Отдельные участки поражают изобилием метеоритов, особенно в сравнении с "соседними".
Спасибо курсам"OTUS" ("OTUS.Web-python" 2018 и "OTUS.NoSQL" 2020)за приобретенный опыт Fullstack-разработки (в частности интеграции Python, Javascript и MongoDB).