Русский
Русский
English
Статистика
Реклама

Openstreetmap

Роман Шувалов Мне пришла в голову идея сделать трехмерный рендер карты OpenStreetMap

21.07.2020 00:08:15 | Автор: admin

Роман Шувалов разработчик инди-игр из Тольятти, который в начале этого года выпустил игру Generation Streets, основанную на данных OpenStreetMap. Не так давно он открыл часть кода своего проекта. Зачем он это сделал, как появилась игра и почему выбор пал на OSM обо всем этом Роман рассказал в интервью.

Как и когда вы узнали о проекте OpenStreetMap?

Это случилось около 5 лет назад в 2014-2015 годах, когда я искал решения, которые бы позволяли пользователям создавать карты для собственных нужд. На тот момент у меня уже был небольшой веб-проект карта дорог и тропинок тольяттинского леса. Он был выполнен на сервисе Яндекс.Карты, поверх стандартной подложки которого накладывался векторный слой с нужными мне объектами. Кстати, этот слой был сделан на основе GPS-треков, записанных мною и участниками нашего велоклуба. Это было нечто похожее на тепловую карту Strava. В какой-то момент я понял, что мне хочется чего-то большего и стал думать, как модернизировать свою карту.



В итоге узнал про OpenStreetMap. Когда впервые увидел его, очень удивился: меня поразила невероятная открытость и гибкость проекта. Я мог не только свободно использовать его исходные данные, но и принять участие в отрисовке карты, что для меня было достаточно важным.

Постепенно, по имеющимся у меня GPS-трекам, отрисовал в OSM тольяттинский лес и связанную с ним инфраструктуру. После сделал свой собственный рендер, его можно увидеть на Велосипедной карте Самарской области. Его особенность состоит в том, что даже на маленьких масштабах (начиная с 10) отображаются тропинки и иные объекты, которые могут быть полезны велосипедисту. В то время как на стандартных рендерах на 10-м и даже 12-м масштабе карта имеет довольно низкую детализацию, что особенно заметно при попытке построить веломаршрут по полям и лесам.


Велосипедная карта Самарской области. Мелкие тропы и населенные пункты видны даже крупных масштабах.

Почему в итоге остались в проекте? Ведь могли отрисовать только нужные вам дорожки и всё.

Рисовать карту в OSM опасное занятие, так как оно невероятно затягивает, потом сложно остановиться. Как обычно это происходит? Отметил тропинку, но увидел, что рядом есть здание, а на карте его нет. Добавил. Затем понял, что тут еще должен быть тротуар, а за ним дорога и так далее. Тобою движет чувство прекрасного: хочется, чтобы всё было красиво, причем не только в том месте, где ты живешь, а всюду. Поэтому я и остался в проекте. Правда, сейчас уже не так много рисую как раньше, но все равно потихоньку продолжаю делать OSM точнее. В основном, исправляю мелкие неточности или актуализирую карту добавляю новые объекты, которые постепенно появляются в интересующей меня локации.

Чем вам интересна картография? Всё-таки достаточно необычное хобби.

Наверное, это исходит из внутреннего желания знать, что находится вокруг тебя, причем гораздо дальше, чем видят твои глаза. И это чувство у меня было всегда. Помню, что уже в старших классах школы я экспериментировал с ГИСами, например, написал на Delphi (тогда я умел программировать только на нем) приложение, которое строило трехмерную карту Самарской области. Причем тогда еще не было открытых наборов данных, или я о них просто ничего не знал, а потому данные о высотах я взял из обычного бумажного атласа. Отсканировал его и с помощью какого-то плагина построил 3D-модель. Так что интерес к картографии, а вернее к визуализации окружающего мира, у меня давно.

Давайте поговорим о вашей 3D-игре Generation Streets, в основе которой данные OSM. Как она появилась на свет?

Отмечу сразу, что разработкой игр я занимаюсь с 2010 года. Что же касается конкретно этой игры, несколько лет назад совершенно случайно мне пришла в голову идея сделать трехмерный рендер карты OSM. Причем всей планеты сразу. Я посмотрел уже имеющиеся аналогичные проекты (F4map, OSM Buildings), но, к сожалению, ни один из них меня не удовлетворил они слишком схематичные. Мне же хотелось более реалистичный рендер: с текстурами, визуальным мусором в виде деревьев, фонарей и пр. Одним словом с той ерундой, которую мы обычно не замечаем, но без которой мир не выглядит живым.



И с чего вы начали разработку?

С поиска метода массовой обработки данных, так как мне хотелось сделать всю планету разом. На это у меня ушло достаточно много времени. Поначалу я пытался это сделать через базу данных на основе osm2vectortiles. Но ничего не вышло, потому что у меня был слабый компьютер.

В итоге я остановился на двух маленьких программках: Osmconvert и Osmfilter. Для их работы не требуется поднятие БД. Они берут данные OSM в формате PBF и позволяют через параметры командной строки вырезать нужные кусочки. Таким образом я получил возможность без лишних проблем нарезать всю планету на тайлы.

Но тут же столкнулся со следующей: формат PBF не совсем удобен для генерации трехмерного рендера, а потому нужно было его преобразовать в подходящий для этого формат. Для этого мне пришлось написать несколько вспомогательных утилит, исходный код которых я уже выложил под свободной лицензией на GitHub. Отчасти они основаны на библиотеке ogr2ogr.

В результате у меня получилось создать собственный формат векторных тайлов, который, как мне кажется, весьма удобен для чтения внутри рендерера и генерирования трехмерных моделей. Кроме того, он позволяет объединять тайлы в большие архивы, что упрощает их хранение. Благодаря этой функции у меня не миллиарды файлов, а всего несколько сотен.



Почему решили придумать свой собственный формат? Предполагаю, что уже есть готовые решения.

Да, вы правы: готовые форматы уже есть, в том числе и векторные, например, у Mapbox, но всё дело в том, что они ориентированы на плоское двухмерное изображение. И у них есть одна неприятная особенность: если здание попадает на стык тайлов, то оно разрезается. Для меня это было абсолютно неприемлемо, так как для того, чтобы я потом смог сделать 3D-модель здания, оно должно было быть целым. Готового решения этой проблемы не было. Думаю, что просто ранее никто с этим не сталкивался. Потому я решил сделать что-то свое.



Поделитесь статистикой по игре? Какие отзывы?

С момента публикации игры на Steam (ранний доступ октябрь 2018 года, полноценный релиз февраль 2020 года) ее скачало около 3 тысяч человек. Для такого типа игры инди это достаточно неплохо. Важно еще учесть следующий момент: у игры слабый геймплей. И я сразу понимал, что в рамках этого проекта у меня никак не получится сделать его увлекательнее. Поэтому он такой, какой он есть.

Главный интерес в этой игре состоит в том, что у пользователя есть возможность скачать интересную для него территорию на планете Земля и полетать там. У большинства игр такой возможности нет. Например, те, кто живет в Нью-Йорке, регулярно радуются тому, что вышла очередная игра, в которой этот город воссоздан достаточно детально. А те, кто живет в Тольятти, вряд ли когда-нибудь дождутся игры созданной в локациях их города. Вот именно на это я и делал ставку.

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



Ваша игра использует данные OSM. Можно ли через неё править его?

Изначально я хотел встроить редактор OSM, но после небольшого эксперимента решил этого не делать. Какое-то время у пользователей была возможность вносить правки. Эти правки не шли напрямую в OSM, они оседали в локальной БД. Оказалось, что игроки далеки от мира OSM, а потому к карте они отнеслись весьма безответственно. Сейчас думаю о том, чтобы отключить даже и этот экспериментальный редактор, а всех желающих направлять в JOSM.

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

Я открыл не всю игру целиком, а только код, отвечающий за генерирование трехмерных моделей. Во-первых, я это сделал для того, чтобы поделиться своими наработками с сообществом, так как я ощущал некий долг перед ним, ведь я сам использовал открытый код, написанный другими программистами. Во-вторых, скорее всего, я уже более не буду вносить в этот продукт серьезных изменений, но при этом предполагаю, что он может быть кому-то интересен и даже кто-то возьмется его развивать. В-третьих, мне хочется, чтобы моим кодом пользовались и он помогал людям в решении их задач. Глядишь, так станет больше проектов, связанных с 3D-рендерингом, и может быть в них будет мой родной город Тольятти.



Что вам нравится в OSM?

Нравится открытая лицензия и возможность получить данные, а не только готовые тайлы. Я, например, сделал видеоигру. Ни один другой картосервис не позволил бы мне так просто это сделать. Как минимум, пришлось бы связываться с разработчиками и просить предоставить исходные данные. И я не уверен, что гиганты отрасли пошли бы мне навстречу. Тут же я взял и скачал всю планету и делал с ней всё то, что мне было нужно.
Еще мне нравится, что любой может принять участие в проекте и сделать карту точнее. Редактор JOSM не такой уж и сложный. И при этом не нужно ждать пока твои правки примут или одобрят как только ты их отправил, они тут же находятся в основной базе, а это значит, что ты можешь их немедленно использовать для своих нужд. Например, обновить карту в своем навигаторе.

Что не нравится?

Изобилие и несистемность тегов. Не всегда это хорошо. Помимо этого, пора уже что-то придумать со смещениями. Иногда открываешь город и понимаешь, что человек отрисовал его без учета этого нюанса. И ты не знаешь как поступить: переносить всё или продолжить рисовать в неправильном смещении? Конечно, я понимаю, что мир несовершенен, приходится переступать через какие-то проблемы. Если же говорить в целом, то у OSM больше плюсов, чем минусов.

Что бы сделали лучше?

Хотел бы увидеть хороший веб-редактор. Да, уже есть iD, но он ориентирован на новичков. Хочется что-то поудобнее. Также, мне кажется, что было бы намного лучше, если бы объекты внутри OSM были как-то связаны между собой. Сейчас если вносятся какие-то изменения в форму здания или меняется положение дороги, которая проходит рядом, то это изменение никак не влияет на соседние объекты. Даже если после изменения, например, дорога стала проходить поверх дома. Пока не знаю каким именно образом подобное взаимодействие можно реализовать, но вот хотелось бы, чтобы информация о зависимостях была. Тогда, например, не ломались бы маршруты общественного транспорта в случае редактирования дороги. Предполагаю, что это сделает данные OSM еще более удобными для редактирования и их дальнейшего использования.

Используете OSM в личной жизни? По работе? Кроме игры.

В повседневной жизни использую мобильные навигаторы OruxMaps и Maps.Me. Оба оффлайновые, то есть позволяют работать с картой без интернета, в основе их карт OSM. Чем мне нравится OruxMaps, так это тем, что он еще позволяет использовать собственные карты в векторном формате mapsforge.

Что посоветуете новичкам? Или тем, кто только думает: связываться с проектом или нет.

Если вы используете какой-либо мобильный навигатор, в основе которого OSM, подумайте о том, чтобы начать делать карту, которую, кстати, вы используете, еще точнее. Ведь многие просто не знают, что карту можно редактировать, причем прямо с телефона.
Тем же, кто уже знаком с OSM и даже немного картирует, первый и главный совет прежде чем редактировать карту, почитайте WikiOSM. Там почти всё есть. Это спасет вас от многих ошибок, а также позволит перенять культуру картирования и в большей степени осознать свою ответственность перед теми, кто после будет пользоваться картой, которую вы редактируете. Никогда не забывайте, что данные OSM используются в огромном количестве проектов, а потому всегда старайтесь поддерживать высокую планку качества вносимых данных.

Что скажете в завершении беседы?

Как бы это громко не звучало, но на данный момент OSM это единственный открытый и свободный картографический проект в мире. Это независимая альтернатива коммерческим картосервисам. Именно поэтому в нем нужно принимать участие и всячески его развивать. Ведь если OpenStreetMap перестанет существовать, мы все будем вынуждены пользоваться коммерческими картами на тех условиях, которые нам будут кем-то навязаны. А о свободном использовании геоданных можно вообще будет забыть.


Общение российских участников OpenStreetMap идёт в чатике Telegram и на форуме.
Также есть группы в социальных сетях ВКонтакте, Facebook, но в них, в основном, публикуются новости.

Присоединяйтесь к OSM!



Подробнее..

JavaScript за 60 секунд работаем с картой (Geolocation API, Leaflet.js, Nominatim)

14.12.2020 12:16:00 | Автор: admin


Доброго времени суток, друзья!

В этом небольшом туториале мы вместе с вами выполним три простых задания:

  • С помощью Geolocation API и Leaflet.js определим текущее местоположение пользователя и отобразим его на карте
  • Реализуем анимированный переход между городами
  • Реализуем переключение между адресами с предварительным получением названия объекта и его координат

Код проекта находится здесь.

Поиграть с кодом можно здесь:


Определяем текущее местоположение пользователя


Geolocation API позволяет пользователю предоставлять веб-приложению данные о своем местоположении. В приложении для запроса этих данных используется метод Geolocation.getCurrentPosition(). Данный метод принимает один обязательный и два опциональных параметра: success функция обратного вызова, получающая объект Position при предоставлении разрешения, error функция обратного вызова, получающая объект PositionError при отказе в доступе, и options объект с настройками. Вот как это выглядит в коде:

navigator.geolocation.getCurrentPosition(success, error, {  // высокая точность  enableHighAccuracy: true})function success({ coords }) {  // получаем широту и долготу  const { latitude, longitude } = coords  const position = [latitude, longitude]  console.log(position) // [широта, долгота]}function error({ message }) {  console.log(message) // при отказе в доступе получаем PositionError: User denied Geolocation}

Отображаем местоположение пользователя на карте


В качестве карты мы будем использовать Leaflet.js. Данный сервис является альтернативой Google Maps и OpenStreetMap, уступает им по функционалу, но подкупает простотой интерфейса. Создаем разметку, в которой подключаем стили и скрипт карты:

<head>  <!-- стили карты -->  <link      rel="stylesheet"      href="http://personeltest.ru/aways/unpkg.com/leaflet@1.7.1/dist/leaflet.css"      integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="      crossorigin=""    />    <!-- скрипт карты -->    <script      src="http://personeltest.ru/aways/unpkg.com/leaflet@1.7.1/dist/leaflet.js"      integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="      crossorigin=""    ></script>    <!-- наши стили -->    <link rel="stylesheet" href="style.css" /></head><body>  <!-- контейнер для карты -->  <div id="map"></div>  <!-- кнопка для вызова функции -->  <button id="my_position">My Position</button>  <!-- наш скрипт-модуль -->  <script src="script.js" type="module"></script></body>

Добавляем минимальные стили (style.css):

* {  margin: 0;  padding: 0;  box-sizing: border-box;}body {  min-height: 100vh;  display: grid;  place-content: center;  place-items: center;  background-color: rgb(241, 241, 241);}#map {  width: 480px;  height: 320px;  border-radius: 4px;  box-shadow: 0 0 1px #222;}button {  padding: 0.25em 0.75em;  margin: 1em 0.5em;  cursor: pointer;  user-select: none;}

Создаем модуль map.js следующего содержания:

// создаем локальные переменные для карты и маркера// каждый модуль имеет собственное пространство именlet map = nulllet marker = null// функция принимает позицию - массив с широтой и долготой// и сообщение, отображаемое над маркером (tooltip)export function getMap(position, tooltip) {  // если карта не была инициализирована  if (map === null) {    // второй аргумент, принимаемый методом setView - это масштаб (zoom)    map = L.map('map').setView(position, 15)  } else return  // что-то типа рекламы  // без этого карта работать не будет  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {    attribution:      ' <a href="http://personeltest.ru/aways/www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'  }).addTo(map)  // добавляем маркер с сообщением  L.marker(position).addTo(map).bindPopup(tooltip).openPopup()}

Наконец, создаем script.js:

// импортируем функциюimport { getMap } from './map.js'// находим кнопку и добавляем к ней обработчикdocument.getElementById('my_position').onclick = () => {  navigator.geolocation.getCurrentPosition(success, error, {    enableHighAccuracy: true  })}function success({ coords }) {  const { latitude, longitude } = coords  const currentPosition = [latitude, longitude]  // вызываем функцию, передавая ей текущую позицию и сообщение  getMap(currentPosition, 'You are here')}function error({ message }) {  console.log(message)}

Открываем index.html в браузере, нажимаем на кнопку, предоставляем разрешение на получение данных о местоположении, видим нашу позицию на карте.



Отлично. Двигаемся дальше.

Анимированный переход между городами


Предположим, что у нас имеется объект с тремя городами (Москва, Санкт-Петербург, Екатеринбург) и их координатами (db/cities.json):

{  "Moscow": {    "lat": "55.7522200",    "lon": "37.6155600"  },  "Saint-Petersburg": {    "lat": "59.9386300",    "lon": "30.3141300"  },  "Ekaterinburg": {    "lat": "56.8519000",    "lon": "60.6122000"  }}

Нам необходимо реализовать плавное переключение между этими городами на карте.

Добавляем в разметку контейнер для городов:

<div id="cities"></div>

Переписываем script.js:

import { getMap } from './map.js'// получаем контейнер для городовconst $cities = document.getElementById('cities');(async () => {  // получаем объект с городами  const response = await fetch('./db/cities.json')  const cities = await response.json()  // перебираем города  for (const city in cities) {    // создаем кнопку    const $button = document.createElement('button')    // текстовое содержимое кнопки - название города    $button.textContent = city    // получаем широту и долготу    const { lat, lon } = cities[city]    // записываем название города, широту и долготу    // в соответствующие data-атрибуты    $button.dataset.city = city    $button.dataset.lat = lat    $button.dataset.lon = lon    // добавляем кнопку в контейнер    $cities.append($button)  }})()// обрабатываем нажатие кнопки$cities.addEventListener('click', ({ target }) => {  // нас интересует только нажатие кнопки  if (target.tagName !== 'BUTTON') return  // получаем название города, широту и долготу из data-атрибутов  const { city, lat, lon } = target.dataset  const position = [lat, lon]  // вызываем функцию, передавая ей позицию и название города  getMap(position, city)})

Также немного изменим map.js:

let map = nulllet marker = nullexport function getMap(position, tooltip) {  if (map === null) {    map = L.map('map').setView(position, 15)  } else {    // перемещение к следующей позиции    map.flyTo(position)  }  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {    attribution:      ' <a href="http://personeltest.ru/aways/www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'  }).addTo(map)  // удаление предыдущего маркера  if (marker) {    map.removeLayer(marker)  }  marker = new L.Marker(position).addTo(map).bindPopup(tooltip).openPopup()}

Открываем index.html. При нажатии первой кнопки сразу получаем позицию и название города. При нажатии второй и последующих кнопок плавно перемещаемся между городами.



Плавное переключением между адресами


Предположим, что у нас имеются три объекта с названиями и адресами (db/addresses.json):

{  "Театр драмы": "Октябрьская площадь, 2",  "Театр оперы и балета": "Проспект Ленина, 46А",  "Коляда-Театр": "Проспект Ленина, 97"}

Нам необходимо реализовать переключение между этими объектами на карте. Но как нам это сделать без координат? Никак. Следовательно, нам каким-то образом нужно эти координаты получить. Для этого воспользуемся сервисом Nominatim от OpenStreetMap. О том, как правильно сформировать строку запроса, смотрите здесь. Я продемонстрирую лишь один из возможных вариантов.

Итак, создаем в разметке контейнер для адресов:

<div id="addresses"></div>

Переписываем script.js:

// получаем контейнер для адресовconst $addresses = document.getElementById('addresses');(async () => {  // названия и адреса объектов  const response = await fetch('./db/addresses.json')  const addresses = await response.json()  // для каждого места  for (const place in addresses) {    // создаем кнопку    const $button = document.createElement('button')    $button.textContent = place    // получаем адрес    const address = addresses[place]    // формируем строку запроса    const query = address.replace(      /([А-ЯЁа-яё]+)\s([А-ЯЁа-яё]+),\s([0-9А-ЯЁа-яё]+)/,      '$3+$1+$2,+Екатеринбург'    )    // получаем, например, 2+Октябрьская+площадь,+Екатеринбург    // записываем данные в соответствующие data-атрибуты    $button.dataset.address = address    $button.dataset.query = query    $addresses.append($button)  }})()// обрабатываем нажатие кнопки$addresses.addEventListener('click', async ({ target }) => {  if (target.tagName !== 'BUTTON') return  // получаем данные из data-атрибутов  const { address, query } = target.dataset  // получаем ответ от сервиса  const response = await fetch(    `https://nominatim.openstreetmap.org/search?q=${query}&format=json&limit=1`  )  // format - формат данных, limit - количество объектов с данными  // парсим ответ, извлекая нужные сведения  const { display_name, lat, lon } = (await response.json())[0]  // редактриуем название объекта  const name = display_name.match(/[А-ЯЁа-яё\s(\-)]+/)[0]  const position = [lat, lon]  // формируем сообщение  const tooltip = `${name}<br>${address}`  // вызываем функцию  getMap(position, tooltip)})

Открываем index.html. При нажатии первой кнопки сразу получаем позицию и название театра. При нажатии второй и последующих кнопок плавно перемещаемся между театрами.



Круто. Все работает, как ожидается.

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

Перевод На благо нашего общего будущего. Creative Commons возглавила Кэтрин Стилер, бывший евродепутат и CEO OKF

10.09.2020 18:21:56 | Автор: admin
Бывший евродепутат, ректор Сент-Эндрюсского университета и глава Open Knowledge Foundation Кэтрин Стилер заняла должность CEO в организации Creative Commons. Как MEP она занималась вопросами цифровой политики, цифрового рынка, защитой данных пользователей и реформой авторских прав.


Для меня большая честь присоединиться к CC в канун 20-летия организации.

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

Когда CC начинала свою деятельность в 2001 году, я тогда только недавно была избрана членом Европарламента. Это было время, когда вопросы авторского права и доступа к информации начинали привлекать внимание общественности.

На протяжении 20 лет работы депутатом я непосредственно представляла более 5 миллионов человек в Шотландии и добивалась изменений для более, чем 500 миллионов жителей ЕС, занимаясь решением вопросов цифровой политики, таких как реформа авторского права, защита неприкосновенности частной жизни граждан, защита их данных, а также обеспечение доступа широкой публики к цифровым инструментам.

Сегодня мы оказались в совершенно ином мире. И когда я пытаюсь представить будущее, я чувствую, что работа CC никогда не была так важна.

У нас есть возможность играть ведущую роль в глобальной борьбе за устранение препятствий на пути распространения знаний и творчества.

Особенно это важно из-за возникших перед нами проблем, ведь пандемия коронавируса по-прежнему приводит к человеческим и экономическим потерям по всему земному шару.

Усиливается неравенство, и несправедливость становится ещё более очевидной.

Трагическое убийство Джорджа Флойда вызвало глобальное движение Black Lives Matter, а в ряде стран прошли продемократические акции протеста, в том числе недавно протесты состоялись в Беларуси.

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

Испытания и кризисы, свидетелями которых мы стали в этом необычном году, вызывают законные вопросы о власти и привилегиях.

Кто имеет доступ к знаниям в нашем неравноправном обществе?

Мы понимаем, что слишком часто доступ к знаниям находится в руках узкого круга лиц, а не большинства, и зачастую его лишены женщины, люди с другим цветом кожи, сообщества LGBTQI и люди из стран Глобального Юга.

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

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

Был открыт доступ к ранее платным публикациям, а результаты исследований распространялись по всему миру. Гонка по разработке вакцины против COVID-19 демонстрирует, почему быстрый и неограниченный доступ к научным исследованиям и образовательным материалам так важен.

Досадно, что для этого потребовалась глобальная пандемия, но я надеюсь, что теперь этот урок усвоен.

Однако, мы стали свидетелями не только шагов вперёд, но и шагов назад.

Некоторые страны ввели ограничения права на информацию, и не все восстановили это право.

И по-прежнему многие знания остаются недоступными людям: во многих странах двери музеев и библиотек по-прежнему закрыты, а цифровой доступ всё ещё невозможен для большого числа людей.

Разрушать препятствия нелегко.

В качестве примера можно привести National Emergency Library, созданную Архивом Интернета, которая во время пандемии бесплатно предоставила пользователям свыше 1,3 млн. электронных книг.

Консорциум из четырёх издателей обратился в суд, и библиотека была вынуждена закрыться. Это свидетельствует о сохраняющихся препятствиях.

Но есть и надежда.

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

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

Работа СС уже доказала свою важность во время этой губительной пандемии. Инициатива Open COVID Pledge упростила университетам, компаниям и другим обладателям прав интеллектуальной собственности работу по разработке лекарств, тестовых наборов, вакцин и других научных открытий.

И мы работали ради того, чтобы образовательные ресурсы, финансируемые государством, были доступны по открытой лицензии, чтобы дать общественности доступ к достоверной, практической информации.

Предстоит сделать намного больше.

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

Технологические достижения сблизили многих людей, но в то же время слишком многих отодвинули на второй план.

Наша миссия построить общее будущее для всех, и мне не терпится приступить к этой работе.

Кэтрин Стилер (Catherine Stihler),
исполнительный директор Creative Commons Corporation.

Creative Commons License
Этот текст распространяется на условиях лицензии Creative Commons Attribution 4.0 International.
Вы можете копировать, редактировать и использовать в коммерческих целях этот текст при обязательном указании авторства.


Photo by DAVID ILIFF. License: CC BY-SA 3.0
https://commons.wikimedia.org/wiki/File:Catherine_Stihler_MEP,_Strasbourg_-_Diliff.jpg?uselang=ru
Подробнее..

Как найти количество всех букв на всех знаках вида въезд в город Х в стране? Точный способ ответить на такие вопросы

24.08.2020 18:21:28 | Автор: admin
Недавно в рамках одного собеседования мне понадобилось решить задачу, условие которой приведено ниже:
У лучшего в мире управляющего по имени Пенультимо родилась очередная гениальнейшая идея, peализовать которую вам и предстоит. Он верит, что поток туристов на Исла-де-Эдукадос повысится, если он сможет рассказать всему миру, как же много замечательных дорожных знаков с длинными надписями eсть у них на острове. Вам предлагается придумать алгоритм, позволяющий подсчитать суммарное количество букв на всех знаках Въезд в город Х на острове, а затем применить полученные знания для подсчёта аналогичной метрики для Республики Беларусь. Обратите внимание язык, используемый для обозначения населённых пунктов, а также тот факт, что въездов в город может быть несколько. Пенультимо также приветствует инициативность, так что можете исследовать этот вопрос для отдельных областей, провести сравнение с количеством людей, проживающих в области, а также провести любые другие исследования, которые покажутся Вам интересными.

Под катом покажу точное решение этой и других похожих задач, например: Сколько АЗС находится в пределах Москвы?

Общий метод решения


Если взглянуть на карту OpenStreetMap, то на ум сразу приходит следующая идея: а давайте для каждого города получим его границы и дороги, находящиеся внутри его границ, а потом найдем их пересечения, на которых будут стоять знаки! Как будем искать пересечения: берем отрезок границы, потом отрезок дороги и смотрим, пересекаются ли они (типичная геометрическая задача). И так пока не кончатся все отрезки и города.
Про архитектуру данных OSM
Вообще, есть три главных компонента: узлы, линии и отношения
Каждый элемент имеет свой ID, а также свои теги.
  • Узел это просто точка на карте, имеющая кроме ID также широту и долготу
  • Линия это отсортированный список узлов, который представляет контур здания или отдельную улицу
  • Отношение это список, состоящий из узлов, линий или других отношений, представляющий логические или географические связи между объектами на карте


OverPass


OverPass Это API для получения данных из OpenStreetMap. Оно имеет свой язык составления запросов, про него подробно можно почитать В этой статье.
Для того чтобы было проще и удобнее составлять запросы, есть инструмент Overpass-turbo, где результат выполнения запроса можно посмотреть в удобном и интерактивном виде.

Использование OverPass API в Python


Для обработки данных из OSM в Питоне можно использовать пакет Overpy в качестве оболочки.
Для отправки запросов и получения данных нужно проделать следующее:
import overpyapi = overpy.Overpass()Data = api.query("""*ваш запрос*""")

где в переменной(?) Data и содержится все, что выдал нам сервер.
Как обработать эти данные? Предположим, что мы ввели следующий запрос на получение границ Минска:
relation["type"="boundary"]["boundary"="administrative"]["name:be"="Мнск"];//Дословно: отношение вида административная граница города Минска>; out skel qt;

На выходе имеем файл XML (можно выбрать Json), имеющий следующую структуру:
<*шапка файла*><далее идет подобное перечисление всех узлов>  <node id="277218521" lat="53.8605688" lon="27.3946601"/>  <node id="4623647835" lat="53.8603938" lon="27.3966685"/>  <node id="4713906615" lat="53.8605343" lon="27.3998220"/>  <node id="4713906616" lat="53.8605398" lon="27.3966820"/>  <node id="4713906617" lat="53.8605986" lon="27.3947987"/>  <node id="277050633" lat="53.8463790" lon="27.4431241"/>  <node id="277050634" lat="53.8455797" lon="27.4452681"/>  <node id="4713906607" lat="53.8460017" lon="27.4439797"/><далее указаны пути и ID узлов, из которых они состоят><way id="572768148">    <nd ref="5502433452"/>    <nd ref="277218520"/>    <nd ref="4713906620"/>    <nd ref="277218521"/>    <nd ref="4713906617"/>    <nd ref="4623647835"/>    <nd ref="4713906616"/></way><way id="29079842">    <nd ref="277212682"/>    <nd ref="277051005"/>    <nd ref="4739822889"/>    <nd ref="4739822888"/>    <nd ref="4739845423"/>    <nd ref="4739845422"/>    <nd ref="4739845421"/></way>

Давайте получим некоторые данные:
import overpyapi = overpy.Overpass()Data = api.query("""relation["type"="boundary"]["boundary"="administrative"]["name:be"="Мнск"];>; out skel qt;""")Xa=Data.ways[0].nodes[0].lon #получаем долготу первого узла первой линииYa=Data.ways[0].nodes[0].lat #получаем широтуXb=Data.ways[0].nodes[1].lonYb=Data.ways[0].nodes[1].latNodeID=Data.ways[0]._node_ids[0] #получаем ID первого узла первой линииprint(len(Data.nodes)) #получаем общее количество узловprint(NodeID)print(Xa,Ya)print(Xb,Yb)

С точки зрения работы с OpenStreetMap в питоне, это всё, что понадобится, чтобы достать данные.

Перейдем непосредственно к задаче


Для ее решения написан код на Питоне, увидеть его можно под спойлером. Просьба сильно не ругать за качество кода, это первый такой большой проект на нем.
Заголовок спойлера
import overpy###########################def line_intersection(line1, line2): #функция поиска пересечений    ax1 = line1[0][0]    ay1 = line1[0][1]    ax2 = line1[1][0]    ay2 = line1[1][1]    bx1 = line2[0][0]    by1 = line2[0][1]    bx2 = line2[1][0]    by2 = line2[1][1]    v1 = (bx2 - bx1) * (ay1 - by1) - (by2 - by1) * (ax1 - bx1)    v2 = (bx2 - bx1) * (ay2 - by1) - (by2 - by1) * (ax2 - bx1)    v3 = (ax2 - ax1) * (by1 - ay1) - (ay2 - ay1) * (bx1 - ax1)    v4 = (ax2 - ax1) * (by2 - ay1) - (ay2 - ay1) * (bx2 - ax1)    return (v1 * v2 < 0) & (v3 * v4 < 0)#######################################citytmp = []city = []Borderway = []Roadway = []Total = 0A = [0, 0]B = [0, 0]C = [0, 0]D = [0, 0]amount = 0progressbar = 0 ReadyData = open('Готовые данные.txt','w')with open("Города Беларуси.txt", "r", encoding='utf8') as file:    for i in range(115):        citytmp.append(file.readline())citytmp = [line.rstrip() for line in citytmp]for i in range(115):    city.append('"' + citytmp[i] + '"')city[0]='"Дзсна"'api = overpy.Overpass()for number in range(0,115):#главный цикл обработки, перебирает города    borderstring = """(relation["type"="boundary"]["boundary"="administrative"]["name:be"=""" + city[number] + """][place=town]; relation["type"="boundary"]["boundary"="administrative"]["name:be"=""" + city[number] + """][place=city];);>; out skel qt;"""    roadstring = """(area[place=town]["name:be"=""" + city[number] + """]; way["highway"][highway!=service]["highway"!="footway"]["highway"!="track"]["highway"!="path"]    ["highway"!="cycleway"]["highway"!="pedestrian"]["highway"!="steps"]["highway"!="residential"](area);area[place=city]["name:be"=""" + city[number] + """]; way["highway"][highway!=service]["highway"!="footway"]["highway"!="track"]["highway"!="path"]    ["highway"!="cycleway"]["highway"!="pedestrian"]["highway"!="steps"]["highway"!="residential"](area););out body; >; out skel qt;"""    print('Getting data about', city[number],'...')        road = api.query(roadstring)        border = api.query(borderstring)    print('got data!, city:', city[number]) #получаем данные    for w in range(len(border.ways)): #перебирает линии границ        for i in range(len(border.ways[w]._node_ids)):#перебирает узлы линий границ             progressbar = i / len(border.ways[w]._node_ids) * 100            print(progressbar, "%;", w, "of", len(border.ways), "parts ready; city-", city[number])            A[0] = border.ways[w].nodes[i].lon            A[1] = border.ways[w].nodes[i].lat            if i == len(border.ways[w]._node_ids) - 1:                break            B[0] = border.ways[w].nodes[i+1].lon            B[1] = border.ways[w].nodes[i+1].lat            for j in range(len(road.ways)):                for k in range(len(road.ways[j]._node_ids)):                    C[0] = road.ways[j].nodes[k].lon                    C[1] = road.ways[j].nodes[k].lat                    if k == len(road.ways[j]._node_ids) - 1:                        break                    D[0] = road.ways[j].nodes[k+1].lon                    D[1] = road.ways[j].nodes[k+1].lat                    if line_intersection((A, B), (C, D)) == 1:                        amount += 1                        print(road.ways[j]._node_ids[k])    print(amount)    Total += amount * len(city[number])    ReadyData.write(str(city[number]))    ReadyData.write(str(amount))    ReadyData.write('\n')    amount = 0print('Total', Total) #и вывод всего


Заметки по коду


Я достаточно долго составлял запрос, подбирая разные типы дорог чтобы было меньше считать и чтобы не пропустить знаки. В итоговом запросе просто убраны те дороги, на которых нет знаков, например residential, service, footway, track и т. п.
Список городов я спарсил с википедии и сохранил в формате.тхт
Код выполняется достаточно долго, у меня даже один раз возникло желание переписать его на С++, но решил оставить так как есть. У меня он выполнялся два дня, все из-за проблем с диктатурой белорусским интернетом и перегруженностью сервера OverPass. Чтобы решить вторую проблему, нужно составить один запрос ко всем городам, но я еще не придумал как это нормально сделать.

Мой ответ на задачу

18981 буква



Что хочу сказать насчет правильности цифры: все упирается в качество данных самой OSM, то есть там есть места где, например, одну дорогу пересекает две линии границы, или где-нибудь на развязке граница проведена чуть-чуть не так, и в результате имеем лишнее/недостающее пересечение. Но это особенность конкретно этой не имеющей практического смысла задачи, в остальном OSM сила.

Вторая задача


Сейчас давайте посчитаем количество заправок в пределах Москвы:
area[name="Москва"];(  node["amenity"="fuel"](area);  way["amenity"="fuel"](area);  relation["amenity"="fuel"](area););out body;>;out skel qt;

Код
import overpyapi = overpy.Overpass()Data = api.query("""area[name="Москва"];(  node["amenity"="fuel"](area);  way["amenity"="fuel"](area);  relation["amenity"="fuel"](area););out body;>;out skel qt;""")print(len(Data.nodes)) #получаем общее количество узлов


Результат 489 заправок:
Подробнее..

Добавляем CMDB и географическую карту к Zabbix

19.10.2020 10:13:52 | Автор: admin
Хабр, конечно, не очень-то подходящая для романтики площадка, но мы не можем не признаться в любви к Zabbix. В очень многих наших проектах по мониторингу мы использовали Zabbix и очень ценим стройность и логичность этой системы. Да, здесь нет модной кластеризации событий и машинного обучения (и некоторых других фичей, доступных из коробки в коммерческих системах), но уже того что есть, определенно достаточно для внутреннего спокойствия за продуктивные системы.



В этой статье расскажем о паре инструментов для расширения функционала Zabbix: CMDB на базе бесплатного решения iTop и карте объектов на базе OpenStreetMap (OSM). А в конце статьи ваш ждет ссылка на репозиторий с кодом фронтовой части для OSM.

Общий концепт разберем на примере условного проекта по мониторингу розничной сети аптек. На скриншоте ниже наш демо-стенд, но похожую концепцию мы используем в боевой среде. Переход с объекта возможен как на вложенную карту так и на карточку объекта в CMDB.



Каждая аптека это набор следующего оборудования: рабочая станция (или несколько рабочих станций), роутер, IP-камеры, принтер и другая периферия. На рабочих станциях установлены агенты Zabbix. С рабочей станции выполняется проверка через ping периферийного оборудования. Аналогичным образом, на карте объектов, с принтера можно перейти на его карточку в CMDB и посмотреть инвентаризационные данные: модель, дату поставки, ответственного и т.д. Так выглядит вложенная карта.



Здесь нужно сделать небольшое отступление. Вы можете спросить, а почему бы не использовать внутренний инвентарь Zabbix? В некоторых случаях его бывает достаточно, но мы рекомендуем клиентам всё-таки использовать внешнюю CMDB (iTop не единственный вариант, но эта система достаточно функциональна при своей бесплатности). Это удобное централизованное хранилище, где можно формировать отчеты и следить за актуальностью данных (на самом деле не только это).



На скриншоте ниже пример шаблона для наполнения инвентаря Zabbix из iTop. Все эти данные потом, разумеется, потом можно использовать в тексте уведомлений, что позволит иметь актуальную информацию сразу же при возникновении аварийной ситуации.



На скриншоте ниже карточка расположения. Здесь мы можем увидеть перечень всего ИТ-оборудования, которое находится в аптеке. На вкладке История можно отслеживать изменения в составе оборудования.



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



На этой странице наш общий подход к интеграции Zabbix с iTop.

Теперь перейдем к картографическому сервису. Мы считаем его удобным инструментом для просмотра статусов распределенных объектов на телевизоре в кабинете с большим кожаным креслом.



При нажатии на аварийную метку появляется всплывающая подсказка. С нее можно перейти на карточку объекта в CMDB либо в Zabbix. При приближении и отдалении метки объединяются в кластеры с цветом наихудшего статуса.

Географическая карта реализована с использованием js-библиотеки leaflet и плагина для кластеризации объектов. На каждую метку добавляются события из системы мониторинга и ссылка на соответствующий объект в CMDB. Статус кластеров определяется по наихудшему событию для вложенных меток. При необходимости, можно интегрировать карту с любой системой мониторинга с открытым API.

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

Если вам интересен наш подход, на этой странице можно оставить заявку на демонстрацию. Расскажем подробнее и покажем.
Подробнее..

Делаем маршрутизацию (роутинг) на OpenStreetMap. Введение

15.07.2020 20:18:51 | Автор: admin

Хотелось бы поделиться опытом создания систем маршрутизации PostgreSQL/PgRouting на карте OpenStreetMap. Речь пойдет о разработке [коммерческих] решений со сложными требованиями, для более простых проектов, вероятно, достаточно обратиться к документации. Насколько мне известно, такие вещи, как полная поддержка односторонних дорог и направлений движения, быстрый роутинг на тысячах адресов (порядка секунд на обычном лаптопе, к примеру, Macbook Pro 13" 2013 года), создание дорожного графа с заданными свойствами, мета-оптимизация маршрутов вообще нигде и никак не рассматриваются. Как обычно, все данные и результаты доступны в моем GitHub репозитории OSM Routing Tricks, который я буду пополнять по мере публикаций.



Небольшой маршрут из 330 адресов на карте OpenStreetMap (время построения около 5 секунд на вышеупомянутом лаптопе). Можно ли за это же время построить маршрут, скажем, из 5000 точек? Да, можно, и об этом мы тоже поговорим (в следующих частях статьи).


Что такое маршрутизация и зачем она нужна


Пожалуй, легче сказать, где маршрутизация не используется, чем перечислить все ее применения. Например: маршрутизация нужна для доступа к этой статье в сети интернет, для доставки почты или посылок, для путешествий, и даже для спутниковой интерферометрии, о которой я писал ранее, тоже используется маршрутизация (вычисление суммарного сдвига фазы по замкнутым траекториям на растре)! Далее мы будем говорить только о применении расширения PgRouting для СУБД PostgreSQL и карте OpenStreetMap.


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


Очень рекомендую также ознакомиться с соответствующей статьей википедии Задача коммивояжёра, поскольку многие старые подходы к задаче и сейчас успешно используются в качестве оптимизаций и решений для частных случаев.


Версии программ и операционные системы


Для новых проектов рекомендую актуальную версию PgRouting 3.0 и СУБД PostgreSQL 12, хотя тот же код обычно работает с PgRouting 2.6 и СУБД PostgreSQL 9 и более давними. Стоит отметить, что на MacOS (любой версии) и Debian/Ubuntu (любой версии) результаты работы функции PgRouting для поиска оптимального маршрута pgr_TSP() сильно отличаются, при этом, как правило, на линуксах результаты при параметрах по умолчанию получаются значительно лучше, при этом используемый алгоритм "отжига" в компиляции на MacOS работает нестабильно, то есть небольшие изменения параметров приводят к непредсказуемым изменениям результатов при недостаточно продуманном дорожном графе, что, кстати сказать, очень помогает при тестировании. Сам я использую и MacOS и Debian/Ubuntu.


Как загрузить OpenStreetMap в базу данных PostgreSQL


Существует много разных утилит, из которых я предпочитаю GDAL, на мой взгляд, наиболее мощную и предсказуемую. Эта утилита позволяет, в частности, преобразовать дамп OpenStreetMap в дамп PostgreSQL и потом загрузить его как обычный SQL скрипт, см. GDAL: PostgreSQL SQL Dump. Можно загрузить и напрямую в базу, см. документацию GDAL. Пример команды загрузки данных из дампа OSM для Германии (germany-latest.osm.pbf) в базу данных PostgreSQL (osmrouting):


ogr2ogr \    -f PGDUMP \    /vsistdout/ "germany-latest.osm.pbf" \    -nln "osm_lines" \    -nlt LINESTRING \    -progress \    --config PG_USE_COPY YES \    --config GEOMETRY_NAME the_geom \    --config OSM_COMPRESS_NODES YES \    --config OSM_CONFIG_FILE "osmconf.ini" \    -sql "select * from lines where highway is not null" \    -lco FID=id \    | psql osmrouting

Дампы OpenStreetMap по странам предоставляет сервис OpenStreetMap Data Extracts.


Как превратить карту дорог OpenStreetMap в дорожную сеть для роутинга


Простейший путь заключается в поиске и нумерации всех пересечений улиц и разбиении всех дорог на участки между найденными точками пересечения. Точки пересечения будут узлами дорожного графа (сети), а участки ребрами этого графа. Узлы необходимы в процессе построения дорожной сети, а для роутинга они не нужны, хотя их удобно использовать для поиска ближайших узлов графа для заданных домов (адресов). В чем минусы такого простого подхода, хорошо описанного в документации? В первую очередь тем, что мы никак не можем ограничить пересечения дорог и полученные маршруты будут в шахматном порядке обходить дома по обе стороны каждой дороги, что выглядит странно, в случае широких улиц такой маршрут очень далек от оптимального и, вдобавок, вовсе не все дороги можно пересечь в произвольном месте. Кроме того, по умолчанию односторонние улицы или полностью исключаются из дорожной сети, или доступны в обоих направлениях. Зато такой вариант очень прост в реализациии для него существуют и специальные утилиты и функции PgRouting.


В предложенной в репозитории дорожной сети каждое ребро разделено на два односторонних (два направления движения транспорта или два тротуара для пешеходов) и добавлены соединяющие кратчайшие пути между дорогами и домами так, чтобы добавленные сегменты соответствовали принятому направлению движения (напомню, в разных странах бывает как левостороннее, так и правостороннее движение) и необходимые узлы сети. Таким образом, сменить направление движения можно только на перекрестках и мы избегаем лишних пересечений дорог и обеспечиваем непрерывную нумерацию домов по каждую сторону каждого участка дорог. При необходимости, протяженные участки можно разбить на более короткие для возможности смены направления движения. Заметим, что такой вариант дорожной сети оптимизирован для автомобильных маршрутов.



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


Для пешеходных маршрутов лучше для каждого добавленного узла связи с домами также добавить узел к парному ребру (обратного направления) и определить короткие сегменты между этими узлами, так как во многих случаях пешеходу удобнее пересекать небольшие улицы, нежели двигаться по одной стороне от перекрестка до перекрестка. Для автотранспортных маршрутов такой вариант тоже подходит, только нужно увеличить расстояние между виртуальными узлами. Скажем, 10 м для пешеходного маршрута и 100 м для автомобильного разумная оценка для каждой смены направления вне перекрестка.


Возможно, вас интересует вопрос, почему мы тратим время на создание сложной дорожной сети вместо использования более продвинутых методов построения оптимального маршрута? Все очень просто по хорошей дорожной сети даже простой алгоритм быстро (секунды) построит отличные маршруты, в то время как по посредственной дорожной сети даже лучшие из существующих алгоритмов за разумное время (минуты, часы или дни, в зависимости от задачи) построят плохие маршруты или вообще не смогут завершить вычисления. Это следствие невероятной вычислительной сложности задачи. К примеру, если мы желаем посетить 10 или 100 адресов по обе стороны одной улицы и при этом у нас определены два направления движения и только два перехода между ними, скажем, в начале и в конце улицы задача имеет единственное решение и любой алгоритм его найдет почти мгновенно! В случае же, когда у нас нет заданных направлений движения и разрешено пересечение улицы около каждого заданного адреса задача становится не решаемой вычислительно уже для нескольких десятков адресов и разные алгоритмы и для разного числа адресов вернут разные и весьма не оптимальные маршруты. Таким образом, критически важно именно построить дорожную сеть с такими ограничениями, которые запрещают большинство (неоптимальных) маршрутов, так что пространство перебора становится несравнимо меньше и достаточно оптимальный результат гарантирован любым из методов. Именно ограничения на направление движения и допустимые повороты являются самыми эффективными.


Таблицы базы данных PostgreSQL


Мы будем работать с базой "osmrouting", содержащей несколько таблиц, содержимое которых доступно в виде PostgreSQL SQL дампов в репозитории, см. также скрипт инициализации базы данных и загрузки дампов load.sh (также загружает необходимые расширения PgRouting и PostGIS).


Первая таблица является единственной необходимой и содержит данные маршрутной сети и, по желанию, информацию для визуализации (сам алгоритм роутинга работает с идентификаторами ребер графа start_id и end_id и некоторой стоимостью каждого сегмента, задаваемой, например, его длиной length):


-- таблица с ребрами маршрутного графа (сегментами дорожной сети)-- id - идентификатор для отладки-- the_geom - геометрия дорожного сегмента для визуализации-- oneway - флаг односторонней дороги-- highway - флаг скоростного шоссе (можно запретить на нем повороты и пересечения)-- pedestrian - флаг пешеходной улицы (можно запретить движение транспорта)-- start_id - идетификатор стартового узла сегмента для роутинга-- end_id - идетификатор конечного узла сегмента для роутинга-- length - длина сегмента (может переопределяться в зависимости от типа дороги и проч.)osmrouting=# \d osm_network                       Table "public.osm_network"   Column   |           Type            | Collation | Nullable | Default ------------+---------------------------+-----------+----------+--------- id         | integer                   |           |          |  the_geom   | geometry(LineString,4326) |           |          |  type       | text                      |           |          |  oneway     | boolean                   |           |          |  highway    | boolean                   |           |          |  pedestrian | boolean                   |           |          |  start_id   | bigint                    |           |          |  end_id     | bigint                    |           |          |  length     | double precision          |           |          | 

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


-- таблица с узлами маршрутного графа-- id - идентификатор для отладки-- the_geom - геометрия дорожного узла для визуализацииosmrouting=# \d osm_nodes                     Table "public.osm_nodes"  Column  |         Type         | Collation | Nullable | Default ----------+----------------------+-----------+----------+--------- id       | bigint               |           |          |  the_geom | geometry(Point,4326) |           |          | 

Третья таблица также не является необходимой и используется только для хранения адресов и координат домов, которые мы используем в тестовых скриптах:


osmrouting=# \d osm_buildings                   Table "public.osm_buildings"  Column  |         Type         | Collation | Nullable | Default ----------+----------------------+-----------+----------+--------- id       | character varying    |           |          |  the_geom | geometry(Point,4326) |           |          | ...

Также тестовые скрипты создают дополнительные таблицы.


Поиск оптимального маршрута


Скрипт репозитория route.sql содержит необходимые команды для выбора случайных 330 адресов домов и построения маршрута по ним с помощью функции PgRouting pgr_TSP(). На самом деле, указанная функция работает не с координатами, а с матрицей расстояний (есть функции и для работы с координатами, см. документацию PgRouting). Матрица расстояний может быть создана функцией pgr_dijkstraCostMatrix(). Заметим, что в скрипте для генерации этой матрицы указан флаг directed=false, так как по умолчанию pgr_TSP() не работает с односторонними дорогами (точнее, не работает с несимметричной матрицей, которая получается при наличии односторонних дорог). С помощью моих функций pgr_dijkstraSymmetrizeCostMatrix() и pgr_dijkstraValidateCostMatrix() это ограничение можно обойти, как мы увидим далее. Что интересно, маршрут возвращается в виде списка идентификаторов узлов дорожного графа (в нашем случае все узлы результата соответствуют добавленным нами виртуальным узлам для домов) и для получения маршрута в виде линии нужно полученный список идентификаторов передать в функцию pgr_dijkstraVia() для нахождения всех посещенных ребер дорожной сети в нужном порядке.


Параметр randomize=false обеспечивает выполнение нескольких запусков алгоритма роутинга и возвращение лучшего результата, для целей отладки и ускорения вычислений можно указать randomize=true, но возвращаемый результат в таком случае непредсказуем. В результате выполнения указанного SQL скрипта создается таблица "route" с сегментами маршрута для каждого заданного адреса, которую можно визуализировать с помощью программы QGIS. Файл проекта QGIS также представлен в репозитории, см. route.qgs Полученная карта с маршрутом представлена на картинке до хабраката.


Смотрите на следующем рисунке участок дорожной сети с узлами и построенного маршрута с порядковыми номерами посещенных домов:



Кроме уже означенной проблемы с направлениями движения, местами можно заметить странный порядок нумерации например, смотрите номера 245,246,247 и другие. Вы можете захотеть покрутить доступные параметры для алгоритма отжига но, даже если это удастся сделать, найденные подходящие параметры для конкретного маршрута не помогут с любым другим маршрутом. Вместо попыток подбора параметров и увеличения времени вычисления следует заняться улучшением дорожной сети (и матрицы расстояний), например, за счет жесткого указания направлений движения.


Заключение


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

Подробнее..

Делаем маршрутизацию (роутинг) на OpenStreetMap. Добавляем поддержку односторонних дорог

27.07.2020 10:17:57 | Автор: admin

Продолжаем цикл статей про построение систем роутинга со сложными требованиями на основе Open Source базы данных PostgreSQL и расширения PgRouting на карте OpenStreetMap. Сегодня мы поговорим о том, как добавить поддержку односторонних дорог (направлений движения). Зачастую, именно отсутствие этой поддержки является основной причиной смены PgRouting на другой "движок" маршрутизации. Как обычно, все данные и результаты доступны в моем GitHub репозитории OSM Routing Tricks, который я пополняю по мере публикаций.



Небольшой маршрут из 330 адресов на карте OpenStreetMap.


Что такое односторонние дороги и зачем они нужны


В первую очередь, мы говорим об односторонних дорогах на карте, то есть таких, по которым движение разрешено только в одну сторону. Разумеется, двигаться по ним в другую сторону запрещено. Особенно часто такое ограничение встречается на мостах, в туннелях и на высокоскоростных шоссе, где движение в противоположную сторону опасно для жизни. Разумеется, нам необходимо соблюдать направление движения для автотранспортных средств, здесь не может быть компромиссов. В то же время, для пешеходного роутинга это не является обязательным, хотя и удобно двигаться по маршруту по направлению движения автомобилей проще ориентироваться, можно воспользоваться общественным транспортом или такси и т.п. Замечу, что некоторые (многие, на самом деле) открытые системы роутинга игнорируют это правило, то есть они подразумевают пешеходный роутинг (по пешеходным дорогам и по окружающим не пешеходные дороги тротуарам, которые могут и отсутствовать в действительности), независимо от длины маршрута и наличия тротуаров в туннелях и на скоростных шоссе.
Кроме того, поддержка односторонних дорог позволяет улучшить маршрутную сеть заданные направления движения заметно упрощают поиск оптимального маршрута за счет очень значительного снижения количества возможных вариантов, можно учесть рельеф местности (довольно трудно с грузом или без подняться по множеству ступенек крутой лестницы, особенно, если можно вместо того спускаться по ней) и принудительно сделать некоторые участки односторонними, можно сделать односторонними виртуальные соединения между домами и улицами (так что маршрут будет построен с последовательной нумерацией, даже если он несколько раз проходит по одной улице поскольку длина маршрута в таком случае не меняется, сам PgRouting никак не гарантирует, что все адреса на такой улице будут посещены в один и тот же проход по ней). И так далее, есть еще много возможностей, доступных для роутинга с поддержкой направлений движения.


Добавляем поддержку односторонних дорог в PgRouting


Официальная документация PgRouting для функции pgr_TSP Using Simulated Annealing approximation algorithm сообщает нам:


If using directed := true, the resulting non symmetric matrix must be converted to symmetric by fixing the non symmetric values according to your application needs.

Отлично, но в документации нет ни слова о том, как это сделать. Нам придется начать с теории и разобраться, возможно ли это и как именно это сделать. Англоязычная страница википедии, посвященная проблеме коммивояжера, содержит нужный нам раздел Travelling salesman problem: Asymmetric:


Solving an asymmetric TSP graph can be somewhat complex. The following is a 33 matrix containing all possible path weights between the nodes A, B and C. One option is to turn an asymmetric matrix of size N into a symmetric matrix of size 2N.

Здесь сказано, что решение задачи коммивояжера на асимметричной матрице расстояний усложняется, поэтому лучше превратить асимметричную матрицу в симметричную (ценой удвоения размера матрицы) и решать более простую симметричную задачу на симметричной матрице расстояний. Почти то же самое, что и в документации PgRouting, зато здесь объяснено, зачем же нужна именно симметричная матрица. Далее на этой же викистранице приводится алгоритм конвертации асимметричной матрицы в симметричную и пример конвертации простой матрицы. Идея простая каждый узел и каждое ребро заменить на два и задать такую матрицу расстояний между ними, чтобы полученный на такой матрице путь соответствовал искомому. Ниже мы рассмотрим этот пример из вики. К сожалению, графическое представление соответствующего графа я рисовал на листочках от руки и показать здесь не могу (почерк у меня как у физика, так что извините).


Пусть у нас задана асимметричная матрица весов:


A B C
A 1 2
B 6 3
C 5 4

Ей соответствует следующая симметричная матрица весов:


A B C A' B' C'
A -w 6 5
B 1 -w 4
C 2 3 -w
A' -w 1 2
B' 6 -w 3
C' 5 4 -w

где -w это виртуальные соединения для виртуальных узлов, которые рекомендуется задать некоторым отрицательным числом, потому что


w=0 is not always low enough

Теперь мы можем написать соответствующую функцию-обертку для PgRouting. Поскольку PgRouting отбрасывает все отрицательные значения в матрице весов, мы используем нулевое значение (мы это компенсируем заданием очень высоких весов для запрещенных направлений) и добавляем еще [избыточные] ребра с очень высоким весом, поскольку PgRouting требует указания весов между всеми узлами матрицы (в теории этого не требуется, это просто техническое ограничение, которому нам приходится соответствовать).


CREATE OR REPLACE FUNCTION pgr_dijkstraSymmetrizeCostMatrix(matrix_cell_sql text,    OUT start_vid BIGINT, OUT end_vid BIGINT, OUT agg_cost float)RETURNS SETOF RECORD AS$BODY$DECLARE    sql text;    r record;BEGIN    sql := 'with edges as (' || matrix_cell_sql || ')        select 3e9+start_vid as start_vid, end_vid as end_vid, agg_cost from edges        union        select end_vid, 3e9+start_vid, agg_cost from edges        union        select 3e9+start_vid, start_vid, 0 from edges        union        select start_vid, 3e9+start_vid, 0 from edges        union        select start_vid, end_vid, 1e6 from edges        union        select 3e9+start_vid, 3e9+end_vid, 1e6 from edges        ';    FOR r IN EXECUTE sql LOOP        start_vid := r.start_vid;        end_vid   := r.end_vid;        agg_cost  := r.agg_cost;        RETURN NEXT;    END LOOP;END;$BODY$LANGUAGE plpgsql VOLATILE STRICT;

Однако, этого недостаточно. Дело в том, что PgRouting требует указания всех весов, а у нас в исходной матрице весов часть значений может быть пропущено, что приводит к ошибке роутинга


An Infinity value was found on the Matrix

Заполним все значения матрицы весов с помощью еще одной функции-обертки (можно было бы упростить pgr_dijkstraSymmetrizeCostMatrix() и не добавлять в ней избыточные ребра, но в таком случае при полной исходной матрице мы получим не полную итоговую матрицу, что, на мой взгляд, не есть правильно):


CREATE OR REPLACE FUNCTION pgr_dijkstraValidateCostMatrix(matrix_cell_sql text,    OUT start_vid BIGINT, OUT end_vid BIGINT, OUT agg_cost float)RETURNS SETOF RECORD AS$BODY$DECLARE    sql text;    r record;BEGIN    sql := 'WITH RECURSIVE src AS (' || matrix_cell_sql || '),    dst AS (        select            *        from src where start_vid =            (select                start_vid            from src            group by start_vid            order by count(*) desc            limit 1)        union        select            src.*        from src, dst        where src.start_vid=dst.end_vid    )    select * from dst';    FOR r IN EXECUTE sql LOOP        start_vid := r.start_vid;        end_vid   := r.end_vid;        agg_cost  := r.agg_cost;        RETURN NEXT;    END LOOP;END;$BODY$LANGUAGE plpgsql VOLATILE STRICT;

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


Исходные данные


Мы используем все те же данные, что и в предыдущей части статьи плюс определенные выше дополнительные функции. Несколько расширенный скрипт load.sh из репозитория загрузит в базу данных PostgreSQL все необходимое.


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


Поскольку для пешехода тротуары доступны в любом направлении, а для автомобиля одностонние дороги доступны лишь в одном направлении, необходимо указывать разные весовые функции для роутинга пешеходного и автомобильного. Как уже упоминалось ранее, наша дорожная сеть оптимизирована для автомобильного роутинга, о нем и поговорим. Итак, запретим (укажем очень большую длину миллион метров) движение в противоположном направлении. Ранее при подготовке дорожной сети мы разделили каждую дорогу на две, одна из которых (type='osm') совпадает с исходной и вторая (type='osmreverse') инвертирована. Соответственно, для односторонней дороги добавленная нами ее инвертированная копия должна быть запрещена для любого движения (вообще говоря, можно ее и вовсе не создавать, но тогда будет немного сложнее объяснить построение дорожной сети). Кроме того, для прямого движения должна быть доступна только исходная дорога (type='osm') и для обратного инвертированная (type='osmreverse'). В таком случае, итоговые прямая и обратная весовые функции имеют вид:


    case when (type='osmreverse' and oneway) then 1000000 else length end as cost,    case when type ilike 'osm%' then 1000000 else length end as reverse_cost,

где length геометрическая длина соответствующего сегмента дороги. Усложнив дорожную сеть (заранее исключив ненужные сегменты), можно взамен упростить весовые функции.


Построим оптимальный маршрут все для тех же случайных 330 адресов домов и с помощью функции PgRouting pgr_TSP() и добавленных выше функций pgr_dijkstraSymmetrizeCostMatrix() и pgr_dijkstraValidateCostMatrix(). Теперь мы можем использовать флаг directed=true, так как теперь мы добавили для pgr_TSP() поддержку односторонних дорог (точнее, симметризацию и заполнение пропущенных значений в несимметричной матрице, которая получается при наличии односторонних дорог). Как и ранее, в результате выполнения немного модифицированного SQL скрипта route.sql создается таблица "route" с сегментами маршрута для каждого заданного адреса, которую можно визуализировать с помощью программы QGIS. Файл проекта QGIS тоже обновлен, см. в репозитории route.qgs Полученная карта с маршрутом представлена на картинке до хабраката.


Смотрите на следующем рисунке участок построенного маршрута с порядковыми номерами посещенных домов (здесь черным цветом обведены односторонние дороги):



Проверим, что теперь по односторонним улицам движение выполняется только одну сторону и, притом, в требуемом направлении. Вот карта OpenStreetMap, на которой стрелками указаны направления движения по односторонним дорогам:



Как и указано на карте OpenStreetMap, в построенном маршруте движение выполняется слева направо для участка дороги с пунктами маршрута 329,330,331 на левой стороне дороги:



и в том же направлении (слева направо) для участка дороги с пунктами маршрута 72,73,74 на правой стороне дороги (второй проход по этому участку дороги):



Нумерация домов по сторонам улиц между перекрестками последовательная. Зачастую нумерация последовательная и после перекрестков (см. картинки выше). Таким образом, мы добились заметного улучшения качества маршрута, не меняя дорожную сеть и не занимаясь оптимизацией параметров функции pgr_TSP().


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


Заключение


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


Если рассмотреть весь маршрут детально, то его еще можно улучшить, в том числе, с помощью оптимизации параметров функции pgr_TSP(). Но дело в том, что только путем оптимизации этих параметров подобного полученному выше результату добиться не удастся в самом деле, алгоритм оптимизации маршрутов PgRouting ничего "не знает" про наши предпочтения о последовательной нумерации и другие. Так что поддержка односторонних дорог, на самом деле, дает нам намного больше, чем можно было бы ожидать.


Можно ли добиться аналогичного результата с последовательной нумерацией зданий между перекрестками иначе, не увеличивая матрицу весов с соответствующим замедлением построения маршрута? Да, можно. Более того, можно еще и значительно ускорить построение маршрута (например, на один-два десятичных порядка), в том числе, с учетом односторонних дорог. Об этом мы и поговорим в следующий раз.

Подробнее..

Перевод Как сделать интерактивную карту с помощью Python и open source библиотек

06.10.2020 18:23:53 | Автор: admin

Сегодня делимся с вами пошаговым руководством создания интерактивных карт для веб-приложения или блога. Просто сохраните эту статью в закладках. Хоть и существует, например, библиотека d3.js, которая может создавать пользовательские карты, есть несколько инструментов еще проще. В этом посте посмотрим на три простые в обращении, но мощные библиотеки Python с открытым исходным кодом и поработаем с ними.



Когда документация вводит в ступор


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

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

  1. Altair простая и быстрая реализация с легкодоступным набором функций.
  2. Plotly обладает богатой функциональностью. Включает в себя Mapbox, пользовательские настройки и стилизацию.
  3. Folium Leaflet полностью настраиваемая и интерактивная. Включает в себя подсказки, всплывающие окна и многое другое.

Предварительные требования


Хороплетная карта требует двух видов данных в фоновом режиме, один из которых это геопространственные данные, географические границы для заполнения карты (обычно это векторный файл .shp (Shapefile) или GeoJSON), и две точки данных на каждом квадрате карты для цветового кодирования карты в зависимости от самих данных.

Библиотека Geo Pandas полезна, когда нужно получить данные в требуемом формате. В примере используется файл GeoJSON индийских штатов. Вы можете начать с любого общедоступного Shapefile или GeoJSON.

Altair



Altair библиотека визуализации для Python, основанная на Vega. Хороплет реализуется минимальными усилиями и включает интерактивные элементы выделение, всплывающие подсказки и т.д.

Altair совместима с fastpages. Вы можете создавать простые записи блога в считанные минуты, просто конвертируя файлы Jupyter Notebook с помощью минимального количества кода. Ознакомьтесь с Readme на GitHub.

Фрагмент кода:

# Importing required Librariesimport geopandas as gpdimport jsonimport altair as altimport pandas as pd

Читаем Shapefile как фрейм GeoPandas:

gdf = gpd.read_file('states_india.shp')

Фрейм выглядит так:



Создаем базовый слой и слой хороплета:

# Creating configs for color,selection,hoveringmulti = alt.selection_multi(fields=['count','state'], bind='legend')color = alt.condition(multi,                  alt.Color('count', type='ordinal',                  scale=alt.Scale(scheme='yellowgreenblue')),                  alt.value('lightgray'))hover = alt.selection(type='single', on='mouseover', nearest=True,                      fields=['x', 'y'])#Creating an altair map layerchoro = alt.Chart(gdf).mark_geoshape(    stroke='black').encode(     color=color,     tooltip=['state','count']).add_selection(        multi    ).properties(     width=650,    height=800)# Legendc1 = alt.layer(choro).configure_legend(    orient = 'bottom-right',    direction = 'horizontal',    padding = 10,    rowPadding = 15)#Adding Labelslabels = alt.Chart(gdf).mark_text().encode(    longitude='x',    latitude='y',    text='count',    size=alt.value(8),    opacity=alt.value(0.6))c2 = alt.Chart(gdf).mark_geoshape(    stroke='black').encode(     color=color,     tooltip=['state','count']).add_selection(        hover    ).project(    scale=100, )(c1+labels).configure_view(strokeWidth=0)

Код выше должен визуализировать интерактивную карту с функцией отображения подсказки и подсветки при выборе (клике).

Плюсы:

  • Простая и быстрая реализация. Включает предопределенный набор функций, чтобы ускорить работу.
  • Совместимость с fastpages

Минусы:

  • Мало вариантов настройки и ограниченная интерактивность.
  • Нет возможности использовать внешние стилизованные части карты, такие как OSM, Mapbox и т.п.
  • API плохо документирован.

Реализация c помощью Plotly



Библиотека Plotly для построения графиков на Python отрисовывает готовые к публикации карты с большим количеством интерактивных и настраиваемых функций.

Доступны пользовательские конфигураций базовой карты из Mapbox, OSM и другие стилевые опции, а также простая реализация с помощью Plotly Express и обширная документация. Это делает Plotly одним из предпочтительных вариантов для создания интерактивных карт.

Фрагмент кода:

# Importing required librariesfrom plotly.graph_objs import Scatter, Figure, Layoutimport plotlyimport plotly.graph_objs as goimport jsonimport numpy as npimport geopandas as gpd

Импортирование Shapefile:

gdf = gpd.read_file('states_india.shp')with open('states_india_1.json') as response: india = json.load(response)

Создание базового слоя и добавление частей карты:

fig = go.Figure(go.Choroplethmapbox(geojson=india, locations=gdf['st_nm'], z=gdf['state_code'],featureidkey="properties.st_nm",colorscale="Viridis", zmin=0, zmax=25,marker_opacity=0.5, marker_line_width=1))fig.update_layout(mapbox_style="carto-positron",                  mapbox_zoom=3.5,mapbox_center = {"lat":23.537876 , "lon": 78.292142} ) fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})fig.show()

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

Плюсы:

  • Очень простая реализация с помощью библиотек диаграмм и Plotly Express. Имеется обширная документация.
  • Множество настроек и настраиваемых опций стилизации.
  • Совместимость с Dash и другими вариантами для встраивания фрагмента во внешние веб-приложения.

Минусы:

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

Реализация с помощью Folium



Folium сочетает в себе простоту использования экосистемы Python и сильные стороны картографирования библиотеки leaflet.js. Это позволяет визуализировать настраиваемые, отзывчивые и интерактивные хороплетные карты, а также передавать богатые векторные, растровые, HTML-визуализации в виде маркеров на карте.

Библиотека имеет ряд встроенных наборов частей карт из OpenStreetMap, Mapbox и Stamen, а также поддерживает пользовательские наборы через API Mapbox или Cloudmade. Поддерживаются изображения, видео, GeoJSON и TopoJSON.

Фрагмент кода:

# Importing required Librariesimport geopandas as gpdimport pandas as pdimport foliumimport brancaimport requestsimport jsonfrom folium.features import GeoJson, GeoJsonTooltip, GeoJsonPopup

Импортирование Shapefile:

gdf = gpd.read_file('states_india.shp')with open('states_india_1.json') as response:    india = json.load(response)#Creating a custom tile (optional)import branca# Create a white image of 4 pixels, and embed it in a url.white_tile = branca.utilities.image_to_url([[1, 1], [1, 1]])

Добавление базовых слоев и слоев Choropleth:

#Base layerf = folium.Figure(width=680, height=750)m = folium.Map([23.53, 78.3], maxZoom=6,minZoom=4.8,zoom_control=True,zoom_start=5,               scrollWheelZoom=True,maxBounds=[[40, 68],[6, 97]],tiles=white_tile,attr='white tile',               dragging=True).add_to(f)#Add layers for Popup and Tooltipspopup = GeoJsonPopup(    fields=['st_nm','cartodb_id'],    aliases=['State',"Data points"],     localize=True,    labels=True,    style="background-color: yellow;",)tooltip = GeoJsonTooltip(    fields=['st_nm','cartodb_id'],    aliases=['State',"Data points"],    localize=True,    sticky=False,    labels=True,    style="""        background-color: #F0EFEF;        border: 1px solid black;        border-radius: 3px;        box-shadow: 3px;    """,    max_width=800,)# Add choropleth layerg = folium.Choropleth(    geo_data=india,    data=gdf,    columns=['st_nm', 'cartodb_id'],    key_on='properties.st_nm',    fill_color='YlGn',    fill_opacity=0.7,    line_opacity=0.4,    legend_name='Data Points',    highlight=True,    ).add_to(m)folium.GeoJson(    india,    style_function=lambda feature: {        'fillColor': '#ffff00',        'color': 'black',        'weight': 0.2,        'dashArray': '5, 5'    },    tooltip=tooltip,    popup=popup).add_to(g)f

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

Плюсы:

  • Количество настроек и настраиваемых опций стиля наряду с уникальными интерактивными функциями, такими как пользовательские всплывающие окна/части карты/фон, а также увеличение по клику.
  • Опция передачи векторных, растровых, HTML визуализаций в виде маркеров на карте.
  • Опция отображения карты в виде HTML и другие опции встраивания фрагмента во внешние веб-приложения.
  • Достойный объем документации для изучения всех доступных функций.

Минусы:

  • Зависит от нескольких библиотек.

Заключение


Эти три инструмента позволяют без особых хлопот реализовать индивидуально настроенные интерактивные карты для веб-сайтов.

Уважаемые читатели, а вам приходилось делать такие интерактивные карты для своих проектов?

image

Получить востребованную профессию с нуля или Level Up по навыкам и зарплате, можно пройдя онлайн-курсы SkillFactory:



Подробнее..

Из песочницы Создание тайлов из растровых карт

07.10.2020 18:18:13 | Автор: admin
Как-то я озадачился вопросом создания карт, пригодных для использования в OsmAnd и OpenLayers. О ГИС я тогда вообще не имел ни малейшего понятия, поэтому разбирался со всем с нуля.

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

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

Для описания эллипсоида достаточно только двух независимых значений: экваториального радиуса (обычно обозначается a) и полярного радиуса (b), но вместо второго независимого значения обычно пользуются полярным сжатием f=(a-b)/a. Это первое, что нам понадобится в нашем алгоритме как объект эллипсоид. Для разных участков Земли в разные годы разными исследователями было вычислено множество референц-эллипсоидов, информация о них приводится в виде данных: a (в метрах) и 1/f (безразмерная). Как это ни странно, для общеземного эллипсоида также существует множество отличающихся вариантов (разные a,f), но отличие не очень сильное, связано оно в основном с различием в методиках определения a и f.

struct Ellipsoid {    char *name;    double a;  /* Большая (экваториальная) полуось      */    double b;  /* Малая (полярная) полуось              */    double al; /* Сжатие (a-b)/a                        */    double e2; /* Квадрат эксцентриситета (a^2-b^2)/a^2 */};struct Ellipsoid Ellipsoid_WGS84 = {    .name = "WGS84",    .a  = 6378137.0,    .al = 1.0 / 298.257223563,};struct Ellipsoid Ellipsoid_Krasovsky = {    .name = "Krasovsky",    .a  = 6378245.0,    .al = 1.0 / 298.3,};

В примере приведены два эллипсодида: общеземной WGS84, используемой в спутниковой навигации, и референц-эллипсоид Красовского, применимый для территории Европы и Азии.

Рассмотрим еще один интересный момент, дело в том, что форма Земли медлено, но меняется, поэтому эллипсоид, который сегодня хорошо описывает поверхнось, через сотню лет может быть далек от реальности. Это мало касается общеземного эллипсоида, т.к. отклонения в пределах его же погрешности, но актуально для референц-эллипсоида. Тут мы подошли еще к одному понятию датум. Датум это совокупность параметров эллипсоида (a,f), его смещения внутри Земли (для референц-эллипсоида), зафиксированные в определенный момент времени. Если говорить более точно, то датум может описывать не обязательно эллипсоид, это могут быть параметры более сложной фигуры, например, квазигеоида.

Наверняка уже возник вопрос: как переходить от одного эллипсоида или датума к другому? Для этого на каждом эллипсоиде должна быть система геодезических координат: широта и долгота (фи, лямбда), переход осуществляется переводом координат из одной системы координат в другую.
Для преобразования координат существуют различные формулы. Можно сначала геодезичесике координаты в одной системе координат переводить в трехмерные координаты X,Y,Z, с ними выполнять сдвиги и повороты и затем полученные трехмерные координаты переводить в геодезические в другой системе координат. Можно это делать и напрямую. Т.к. все формулы это бесконечные сходящиеся ряды, то обычно ограничиваются несколькими членами рядов для достижения требуемой точности. В качестве примера воспользуемся преобразованиями Гельмерта (Helmert), это преобразования с использование перехода в трехмерные координаты, состоят из трех этапов описанных выше. Для преобразований кроме двух эллипсоидов понадобятся еще 7 параметров: три сдвига по трем осям, три угла поворота и масштабный коэффициент. Как можно догадаться, все параметры можно извлечь из датумов. Но в алгоритме мы не будем пользоваться таким объектом как датум, а вместо этого введем объект перехода из одной системы координат в другую, который будет содержать: ссылки на два эллипсоида и 7 параметров преобразования. Это будет вторым объектом нашего алгоритма.

struct HelmertParam {    char *src, *dst;    struct Ellipsoid *esp;    struct Ellipsoid *edp;    struct {        double dx, dy, dz;        double wx, wy, wz;        double ms;    } p;    // Вспомогательные величины    double a,  da;    double e2, de2;    double de2__2, dxe2__2;    double n, n__2e2;    double wx_2e2__ro, wy_2e2__ro;    double wx_n__ro, wy_n__ro;    double wz__ro, ms_e2;};struct HelmertParam Helmert_SK42_WGS84 = {    .src = "SK42",    .dst = "WGS84",    .esp = &Ellipsoid_Krasovsky,    .edp = &Ellipsoid_WGS84,    // SK42->PZ90->WGS84 (ГОСТ Р 51794-2001)    .p = {23.92, -141.27, -80.9, 0, -0.35, -0.82, -0.12e-6},};

В примере приведены параметры для преобразования из системы координат Пулково 1942 в систему координат WGS84. Сами параметры преобразования это отдельная тема, для некоторых систем координат они открыты, для других подобраны опытным путем, поэтому в разных источниках их значения могут незначительно отличаться.

Кроме параметров необходима и функция преобразования, она может быть прямая и для преобразования в обратном направлении, нам понадобится только преобразование в обратном направлении. Пропущу тонны матана и приведу свою оптимизированную функцию.

void setupHelmert(struct HelmertParam *pp) {    pp->a = (pp->edp->a + pp->esp->a) / 2;    pp->da = pp->edp->a - pp->esp->a;    pp->e2 = (pp->edp->e2 + pp->esp->e2) / 2;    pp->de2 = pp->edp->e2 - pp->esp->e2;    pp->de2__2 = pp->de2 / 2;    pp->dxe2__2 = pp->de2__2 + pp->e2 * pp->da / pp->a;    pp->n = 1 - pp->e2;    pp->n__2e2 = pp->n / pp->e2 / 2;    pp->wx_2e2__ro = pp->p.wx * pp->e2 * 2 * rad(0,0,1);    pp->wy_2e2__ro = pp->p.wy * pp->e2 * 2 * rad(0,0,1);    pp->wx_n__ro = pp->p.wx * pp->n * rad(0,0,1);    pp->wy_n__ro = pp->p.wy * pp->n * rad(0,0,1);    pp->wz__ro = pp->p.wz * rad(0,0,1);    pp->ms_e2 = pp->p.ms * pp->e2;}void translateHelmertInv(struct HelmertParam *pp,        double lat, double lon, double h, double *latp, double *lonp) {    double sin_lat, cos_lat;    double sin_lon, cos_lon;    double q, n;    if (unlikely(!pp)) {        *latp = lat;        *lonp = lon;        return;    }        sin_lat = sin(lat);    cos_lat = cos(lat);    sin_lon = sin(lon);    cos_lon = cos(lon);    q = 1 / (1 - pp->e2 * sin_lat * sin_lat);    n = pp->a * sqrt(q);   *latp = lat        - ((n * (q * pp->de2__2 + pp->dxe2__2) * sin_lat + pp->p.dz) * cos_lat           - (pp->p.dx * cos_lon + pp->p.dy * sin_lon) * sin_lat          ) / (n * q * pp->n + h)        + (pp->wx_2e2__ro * sin_lon - pp->wy_2e2__ro * cos_lon)          * (cos_lat * cos_lat + pp->n__2e2)        + pp->ms_e2 * sin_lat * cos_lat;    *lonp = lon        + ((pp->p.dx * sin_lon - pp->p.dy * cos_lon) / (n + h)           - (pp->wx_n__ro * cos_lon + pp->wy_n__ro * sin_lon) * sin_lat          ) / cos_lat        + pp->wz__ro;}

Откуда все это берется? На более понятном языке формулы можно найти в проекте proj4, но т.к. мне была необходима оптимизация по скорости выполнения, то всякие вычисления синуса суммы углов были преобразованы по формулам, возведения в степень оптимизированы венесениями за скобки, а константы посчитаны отдельно.

Теперь, чтобы приблизиться к выполнению изначальной задачи создать тайлы, необходимо рассмотреть систему координат под названием WebMercator. Эта система координат используется в приложении OsmAnd и в web, например в картах от Google и в OpenStreetMap. WebMercator это проекция Меркатора, построенная на сфере. Координаты в этой проекции это координаты пикселя X,Y и они зависят от масштаба Z, для нулевого масштаба вся земная поверхность (примерно до 85 градуса широты) помещается на одном тайле 256x256 пикселей, координаты X,Y меняются от 0 до 255, начиная с левого верхнего угла, для масштаба 1 уже 4 тайла, X,Y от 0 до 511 и так далее.

Для преобразования из WebMercator в WGS84 используются такие функции:

void XYZ_WGS84(unsigned x, unsigned y, int z, double *latp, double *lonp) {    double s = M_PI / ((1UL << 7) << z);    *lonp = s * x - M_PI;    *latp = asin(tanh(M_PI - s * y));}void WGS84_XYZ(int z, double lat, double lon, unsigned *xp, unsigned *yp) {    double s = ((1UL << 7) << z) / M_PI;    *xp = uint_round((lon + M_PI) * s);    *yp = uint_round((M_PI - atanh(sin(lat))) * s);}

И под конец первой части статьи мы уже сможем набросать начало нашего алгоритма создания тайла. Каждый тайл 256x256 пикселей адресуется тремя значениями: x,y,z, соотношение с координатами X,Y,Z очень простое: x = (int)(X / 256); y = (int)(Y /256); z = Z;

void renderTile(int z, unsigned long x, unsigned long y) {    int i, j;    double wlat, wlon;    double lat, lon;    for (i = 0; i < 255; ++i) {        for (j = 0; j < 255; ++j) {            XYZ_WGS84(x * 256 + j, y * 256 + i, z, &wlat, &wlon);            translateHelmertInv(&Helmert_SK42_WGS84, wlat, wlon, 0, &lat, &lon);            /* lat,lon - координаты в СК42 */        }    }}

Координаты в СК42 это уже преобразованные координаты в систему координат карты, теперь осталось найти на карте пиксель по этим координатам и занести его цвет в пиксель тайла по координатам j,i. Об этом будет следующая статья, в которой мы поговорим о геодезических проекциях и афинных преобразованиях.
Подробнее..

Создание тайлов из растровых карт (ч.2)

05.11.2020 18:21:17 | Автор: admin
В этой части статьи мы завершим наш алгоритм создания тайла, узнаем, как использовать полученные тайлы в OpenLayers и в OsmAnd. Попутно продолжим знакомство с ГИС и узнаем про картографические проекции, а также узнаем в чем заключается привязка растровой карты и зачем она нужна.


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

Геодезические координаты заданы на поверхности эллипсоида, а координаты пикселя на плоскости, т.е. необходим способ перехода от выпуклой поверхности к плоской. Проблема состоит в том, что выпуклую поверхность (представим что на ней нанесен какой-то рисунок или координатная сетка), невозможно перенести на плоскую без каких-либо искажений. Искажаться может: форма (углы), площадь, линейные размеры. Есть, конечно, возможность, причем не единственная, переноса на плоскую поверхность не искажая только что-то одно, но неизбежно будет искажаться остальное.



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

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

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

Картографических проекций изобретено огромное множество, все они находят применение для своих целей: равновеликие (в кторых сохраняется площадь) для политических карт, карт грунтов, равноугольные (в которых сохраняется форма) для навигации, равнопромежуточные (сохранение масштаба по выбранному направлению) для компьютерной обработки и хранения массивов геоданных. Есть также проекции, которые сочетают в себе в определенных пропорциях вышеперечисленные особенности, есть совсем эзотерические проекции. Примеры проекций можно найти в Википедии на странице Список картографических проекций.

Для каждой проекции выведены либо точные формулы, либо в виде суммы бесконечных сходящихся рядов, причем в последнем случае может существовать даже несколько вариантов. Формулы для проекций переводят широту и долготу в декартовы координаты, как правило единицей измерения в таких координатах используется метр. Такая метровая прямоугольная координатная сетка иногда может быть нанесена и на карту (в виде километровой сетки), но в большинстве случаев ее не наносят. Но мы теперь знаем, что в неявном виде она все-таки есть. Масштаб карты, который указан на карте, какраз и вычислен относительно размера этой сетки. Следует четко понимать, что 1 метр на такой координатной сетке соответствует 1 метру на местности не на всей карте, а лишь в некоторых точках, по определенной линии, либо по линиям в определенном направлении, в остальных же точках или направлениях проявляются искажения и 1 метр на местности не соответствет 1 метру координатной сетки.

Небольшое отступление. Функция из первой части статьи WGS84_XYZ как раз и является преобразованием координат из СК WGS84 в прямоугольные координаты, но выраженные не в метрах, а в пикселях. Как видно, формула там предельно простая, если бы использовалась проекция Меркатора не на сфере, а на эллипсоиде, то формула была бы посложнее. Именно поэтому, чтобы облегчить жизнь браузерам, была выбрана сфера, в последствии проекция WebMercator так и прижилась со своей сферой, за что ее часто ругают.

Для своих нужд я использовал документ под названием Map projections used by the U.S. Geological Survey в формате pdf, который можно найти в Интернет. В документе для каждой проекции приведены подробные инструкции, по которым легко написать функцию преобразования на языке программирования.

Продолжим писать наш алгоритм. Реализуем одну из популярных проекций под названием поперечная проекция Меркатора (Transverse Mercator) и один из его вариантов под названием проекция Гаусса-Крюгера (Gauss-Kruger).

struct TransverseMercatorParam {    struct Ellipsoid *ep;    double k;           /* Масштабный коэффициент                                 */    double lat0;        /* Начальная параллель  (в радианах)                      */    double lon0;        /* Центральный меридиан (в радианах)                      */    double n0;          /* Условное северное смещение для начальной параллели     */    double e0;          /* Условное восточное смещение для центрального меридиана */    double zw;          /* Ширина зоны (в радианах)                               */    double zs;          /* Условное восточное смещение между зонами               */    // Вспомогательные величины    double e2__a2k2, ie2__a2k2, m0, mp, imp;    double f0, f2, f4, f6;    double m1, m2, m4, m6;    double q, q1, q2, q3, q4, q6, q7, q8;    double q11, q12, q13, q14, q15, q16, q17, q18;    // Вспомогательные величины - 2    double apk, n, b, c, d;    double b1, b2, b3, b4;};struct TransverseMercatorParam TM_GaussKruger = {    .ep   = &Ellipsoid_Krasovsky,    .zw   = rad(6,0,0),    .lon0 = -rad(3,0,0),    .e0   = 5e5,    .zs   = 1e6,};


Особенностью поперечной проекции Меркатора является то, что она конформна, т.е сохраняется форма объектов на карте и углы, а также то, что сохраняется масштаб вдоль одного центрального меридиана. Проекция пригодна для всего земного шара, но при отдалении от центрального меридиана растут искажения, поэтому всю земную поверхность нарезают на узкие полоски по меридианам, которые называются зонами, для каждой из которых используется свой центральный меридиан. Примерами реализации таких проекций являются проекция Гаусса-Крюгера и UTM, в которых также определен способ распределения координат по зонам, т.е. определена и одноименная СК.



И, собственно, код функций инициализации и преобразования координат. В функции инициализации одноразово вычисляются константы, которые будут многократно использоваться функцией преобразования.

void setupTransverseMercator(struct TransverseMercatorParam *pp) {    double sin_lat, cos_lat, cos2_lat;    double q, n, rk, ak;    if (!pp->k)        pp->k = 1.0;    sin_lat = sin(pp->lat0);    cos_lat = cos(pp->lat0);    cos2_lat = cos_lat * cos_lat;    q = pp->ep->e2 / (1 - pp->ep->e2);    // Приплюснутость n = (a-b)/(a+b)    n = (pp->ep->a - pp->ep->b) / (pp->ep->a + pp->ep->b);    rk = (pp->ep->a + pp->ep->b) * pp->k / 2;    ak = pp->ep->a * pp->k;    pp->e2__a2k2  = pp->ep->e2 / (ak * ak);    pp->ie2__a2k2 = (1 - pp->ep->e2) / (ak * ak);    pp->f6 = 1097.0/4 * n*n*n*n;    pp->f4 = (151.0/3 - 3291.0/8 * n) * n*n*n;    pp->f2 = (21.0/2 + (-151.0/3 + 5045.0/32 * n) * n) * n*n;    pp->f0 = (3.0 + (-21.0/4 + (31.0/4 - 657.0/64 * n) * n) * n) * n;    pp->m6 = rk * 315.0/4 * n*n*n*n;    pp->m4 = rk * (-70.0/3 - 945.0/8 * n) * n*n*n;    pp->m2 = rk * (15.0/2 + (70.0/3 + 1515.0/32 * n) * n) * n*n;    pp->m1 = rk * (-3.0 + (-15.0/4 + (-4.0 - 255.0/64 * n) * n) * n) * n;    // polar distance    pp->mp = rk * (1.0 + (1.0/4 + 1.0/64 * n*n) * n*n);    pp->imp = 1 / pp->mp;    pp->m0 = pp->n0 - pp->mp * pp->lat0 - sin_lat * cos_lat *        (pp->m1 + (pp->m2 + (pp->m4 + pp->m6 * cos2_lat) * cos2_lat) * cos2_lat);    pp->q   =                        q;    pp->q1  =                            1.0/6    * q*q;    pp->q2  =            3.0/8     * q;    pp->q3  =            5.0/6     * q;    pp->q4  =  1.0/6   - 11.0/24   * q;    pp->q6  =            1.0/6     * q;    pp->q7  =            3.0/5     * q;    pp->q8  =  1.0/5   - 29.0/60   * q;    pp->q11 =          - 5.0/12    * q;    pp->q12 = -5.0/24  + 3.0/8     * q;    pp->q13 =                          - 1.0/240  * q*q;    pp->q14 =            149.0/360 * q;    pp->q15 = 61.0/720 - 63.0/180  * q;    pp->q16 =                          - 1.0/40   * q*q;    pp->q17 =          - 1.0/60    * q;    pp->q18 = 1.0/24   + 1.0/15    * q;    // Вспомогательные величины - 2    double e2 = pp->ep->e2;    pp->apk = ak * (1 + n*n / 4 + n*n*n*n / 64) / (1 + n);    pp->n = n;    pp->b = (5 - e2) * e2 * e2 / 6;    pp->c = (104 - 45 * e2) * e2 * e2 * e2 / 120;    pp->d = 1237.0/1260 * e2 * e2 * e2 * e2;    pp->b1 = (1.0/2 + (-2.0/3 + (5.0/16 + 41.0/180 * n) * n) * n) * n;    pp->b2 = (13.0/48 + (-3.0/5 + 557.0/1440 * n) * n) * n*n;    pp->b3 = (61.0/240 - 103.0/140 * n) * n*n*n;    pp->b3 = 49561.0/161280 * n*n*n*n;}void translateTransverseMercator(struct TransverseMercatorParam *pp, int zone,                double lat, double lon, double *ep, double *np) {    double lon2, v, m;    double k4, k6, h3, h5;    double sin_lat = sin(lat);    double cos_lat = cos(lat);    double cos2_lat = cos_lat * cos_lat;    lon -= zone * pp->zw + pp->lon0;    while (unlikely(lon <= -M_PI))        lon += 2*M_PI;    lon2 = lon * lon;    // Вычисление переменных для преобразования    v  = 1 / sqrt(pp->e2__a2k2 * cos2_lat + pp->ie2__a2k2);    m  = ((pp->m6 * cos2_lat + pp->m4) * cos2_lat + pp->m2) * cos2_lat + pp->m1;    k4 = ((pp->q1 * cos2_lat + pp->q2) * cos2_lat + 1.0/4 ) * cos2_lat - 1.0/24;    k6 = ((pp->q3 * cos2_lat + pp->q4) * cos2_lat - 1.0/12) * cos2_lat + 1.0/720;    h3 = ((                    pp->q6) * cos2_lat + 1.0/3 ) * cos2_lat - 1.0/6;    h5 = ((pp->q7 * cos2_lat + pp->q8) * cos2_lat - 1.0/6 ) * cos2_lat + 1.0/120;    // Вычисление северного и восточного смещения (в метрах)    *np = pp->m0 + pp->mp * lat        + (m + v * ((k6 * lon2 + k4) * lon2 + 0.5) * lon2) * cos_lat * sin_lat;    *ep = pp->e0 + pp->zs * zone        + (    v * ((h5 * lon2 + h3) * lon2 + 1.0) * lon ) * cos_lat;}


На выходе функции преобразования будем иметь координаты: восточное и северное смещение (e,n) это прямоугольные декартовы координаты в метрах.



Мы уже очень близки к завершению нашего алгоритма. Нам осталось только найти координаты пикселя (x,y) в файле карты. Т.к. координаты пикселей тоже декартовы, то мы можем найти их афинным преобразованием (e,n) к (x,y). К нахождению параметров самого афинного преобразования мы вернемся чуть позже.

struct AffineParam {    double c00, c01, c02;    double c10, c11, c12;};void translateAffine(struct AffineParam *app, double e, double n,                                double *xp, double *yp) {    *xp = app->c00 + app->c01 * e + app->c02 * n;    *yp = app->c10 + app->c11 * e + app->c12 * n;}


И, наконец, полный алгоритм создания тайла:

void renderTile(ImagePtr tile, int z, unsigned long x, unsigned long y) {    int i, j;    double wlat, wlon;    ImagePtr srcimg;    double lat, lon;    double e, n;    double x, y;    for (i = 0; i < 256; ++i) {        for (j = 0; j < 256; ++j) {            XYZ_WGS84(x * 256 + j, y * 256 + i, z, &wlat, &wlon);            translateHelmertInv(&Helmert_SK42_WGS84, wlat, wlon, 0, &lat, &lon);            findSrcImg(&srcimg, lat, lon);            translateTransverseMercator(&TM_GaussKruger, srcimg->zone, lat, lon, &e, &n);            translateAffine(&srcimg->affine, e, n, &x, &y);            setPixelColor(tile, j, i, interpolatePixelColor(srcimg, x, y));        }    }}


Результат работы для z=12, y=1377, x=2391:



В алгоритме осталась не описанной функция нахождения исходного изображения карты srcimg по заданным в СК карты геодезическим координатам lat, lon. Думаю, с ней и номером зоны srcimg->zone проблем не возникнет, а вот на нахождении параметров афинного преобразования srcimg->affine остановимся более подробно.

Где-то на просторах Интернета очень давно была найдена вот такая функция для нахождения параметров афинного преобразования, привожу ее даже с оригинальными комментариями:

struct TiePoint {    struct TiePoint       *next;    double                lon, lat;    double                e, n;    double                x, y;};void setupAffine(struct AffineParam *app, struct TiePoint *plist) {    /*     * Преобразование задается формулами:     *   x = c00 + c01 * e + c02 * n     *   y = c10 + c11 * e + c12 * n     */    struct TiePoint *pp;     /* Контрольная точка */    double a11, a12, a13;    /*             */    double a21, a22, a23;    /* Матрица 3*3 */    double a31, a32, a33;    /*             */    double b1, b2, b3;       /* Свободный член */    int    m;                /* Индекс цикла для z: m=0 -> z=x, m=1 -> z=y */    double z;                /* Либо x, либо y */    double t;                /* Рабочая величина при вычислении коэффициентов */    /* Нормальная система состоит из 2-х подсистем по 3 уравнения,       отличающихся свободными членами. */    /* Подсчет общих коэффициентов системы */    a11 = a12 = a13 = a22 = a23 = a33 = 0;    for (pp = plist; pp; pp = pp->next) {        a11 += 1;        a12 += pp->e;        a13 += pp->n;        a22 += pp->e * pp->e;        a23 += pp->e * pp->n;        a33 += pp->n * pp->n;    }    /* Преобразование коэффициентов (треугольное разложение матрицы) */    a21 = a12;    a31 = a13;    a12 /= a11;    a13 /= a11;    a22 -= a21 * a12;    a32 = a23 - a31 * a12;    a23 = a32 / a22;    a33 -= a31 * a13 + a32 * a23;    /* Теперь вещи, различные для подсистем X и Y */    for (m = 0; m < 2; m++) { /* m=0 -> X, m=1 -> Y */        /* Подсчет свободных членов подсистемы */        b1 = b2 = b3 = 0;        for (pp = plist; pp; pp = pp->next) {            z = !m ? pp->x : pp->y;            b1 += z;            b2 += pp->e * z;            b3 += pp->n * z;        }        /* Преобразование свободных членов */        b1 /= a11;        b2 = (b2 - a21 * b1) / a22;        b3 = (b3 - a31 * b1 - a32 * b2) / a33;        /* Решение подсистемы */        t = b2 - a23 * b3;        *(!m ? &app->c00 : &app->c10) = b1 - a12 * t - a13 * b3;        *(!m ? &app->c01 : &app->c11) = t;        *(!m ? &app->c02 : &app->c12) = b3;    }}


На вход необходимо подать не менее трех точек привязки, на выходе получаем готовые параметры. Точки привязки это точки, для которых одновременно известны координаты пикселя (x,y) и координаты восточного и северного смещения (e,n). В качестве таких точек можно использовать точки пересечения километровой сетки на исходной карте. А что если на карте нет километровой сетки? Тогда можно задать пары (x,y) и (lon,lat), в качестве таких точек взять точки пересечения параллелей и меридианов, они на карте есть всегда. Осталось только преобразовать (lon,lat) в (e,n), это делается функцией преобразования для проекции, в нашем случае это translateTransverseMercator().

Как видно, точки привязки необходимы, чтобы сообщить алгоритму, какой участок земной поверхности описывает файл с изображением карты. Поскольку обе координатные системы были декартовыми, то сколько бы точек привязки мы не задавали и как далеко бы они не находились друг от друга, расхождение по всей плоскости карты будет в пределах погрешности определения точек привязки. Большинство ошибок состоит в том, что применяется не правильная (с не точно заданными параметрами) проекция, датум или эллипсоид, в результате координаты (e,n) на выходе получаются не в декартовой системе координат, а в немного искривленной относительно декартовой. Визуально это можно представить как мятую простыню. Естественно, увеличение количества точек привязки эту проблему не решает. Проблему может решить тюнинг параметров проекции, датума и эллипсоида, в данном случае большое количество точек привязки позволит разгладить простыню точнее и не пропустить неразглаженных участков.

И в завершение статьи расскажу, как готовые тайлы использовать в OpenLayers и в каком виде их приготовить для программы OsmAnd.

Для OpenLayers тайлы необходимо просто выложить в web и назвать так, чтобы в названии файла или каталога можно было выделить (z,y,x), например так:
/tiles/topo/12_1377_2391.jpg
или, еще лучше так:
/tiles/topo/12/1377/2391.jpg
Тогда их использовать можно таким образом:

map = new OpenLayers.Map (...);map.addLayer(new OpenLayers.Layer.XYZ("Topo Maps", "/tiles/topo/${z}_${y}_${x}.jpg", {      isBaseLayer: false, visibility: false,  }));


Для программы OsmAnd легко определить формат по каким-либо уже имеющимся файлам с набором тайлов. Расскажу сразу про результаты. Тайлы упаковываются в файл базы данных sqlite, который создается таким образом:

CREATE TABLE info AS SELECT 99 AS minzoom, 0 AS maxzoom;CREATE TABLE tiles (x int, y int, z int, s int, image blob, PRIMARY KEY (x,y,z,s));CREATE INDEX IND on tiles (x,y,z,s);UPDATE info SET minzoom=$minzoom, maxzoom=$maxzoom


Колонка s всегда заполняется нулем, для чего она, я не разбирался, в image заносится картинка в исходном бинарном виде, формат (jpg, png, gif) теряется, но OsmAnd определяет его сам по содержимому. В одной базе могут быть упакованы тайлы в разных форматах. Вместо $minzoom и $maxzoom необходимо подставить пределы масштаба занесенных в базу тайлов. Заполненный файл базы данных, например, Topo.sqlitedb переносим на смартфон или планшет в каталог osmand/tiles. Перезапускаем OsmAnd, в Меню -> Configure Map -> Верхний слой появится новая опция Topo это карта с нашими новыми тайлами.
Подробнее..

Из песочницы Рассказ об этапах работы над картой возраста домов Владимира

12.10.2020 14:18:52 | Автор: admin

Ниже я картограф Александр Пронин расскажу, как совместно с моим коллегой Никитой Славиным была создана карта возраста домов города Владимира.


Данные


Для слоя со зданиями использовал выгрузку c сайта OpenStreetMap. На этом слое есть адрес, который разбит по колонкам, название объекта и этажность. Данные о годе постройки, адресе, названии объекта и фотографии собираю с нескольких источников.


Первый мой сайт владимирдом.рф карта застройки. Ее я создал для того, чтобы показать жителям, как застраивался наш город. Для нее данные о годе постройки брал с сайта Мин.ЖКХ, для детских садов и школ с их официальных сайтов. Много данных по году прислали пользователи карты. На карте есть данные только о годе постройки.


Второй кадастровые данные. Меня интересуют объекты капитального строительства. У многих указан год постройки и адрес.


Третий сайт открытых данных Министерства культуры. Нужны объекты культурного наследия. С него собираю данные по названию объекта, адресу и фотографию.


Четвертый сайт wikimapia.org. Есть название объекта, адрес и фотография. После сбора данных приступаем к их обработке.


Геопроцессинг


Для обработки данных использовал программу MapInfo. Для преобразования данных Excel. Конечная цель собрать все данные на одном слое. Начинаю со слоя со зданиями из выгрузки с сайта OpenStreetMap. Выгружаю данные в таблицу Excel, чтобы соединить данные с адресом из разных колонок в одну. Использую функцию сцепить. Загружаю файл из Excel в MapInfo и по уникальному значению проставляю получившийся адрес. Получаем слой, в котором адреса корректны и указана этажность.



Слой с OpenStreetMap


Далее обработку проводил по одному алгоритму. В MapInfo в основном слое со зданиями создавал столбец с указанием источника и типом данных. При помощи процесса Обновить колонку записывались те данные с точек, которые попадали в полигоны со зданиями.



Слой с OpenStreetMap + данные с владимирдом.рф



Слой с OpenStreetMap + данные кадастра



Слой с OpenStreetMap + открытые данные Министерства культуры



Слой с OpenStreetMap + данные с wilimapia.org


Выгрузил в Excel полученную таблицу и начал соединять данные из разных источников в один столбец по типу. Данные, которые были в слое со зданиями, не трогаю, а начинаю заполнять пустоты из других источников. По году постройки приоритет у данных с сайта владимирдом.рф, так как они проверены и просмотрены большим количеством людей.


Для адреса и названия приоритет у данных с сайта wikimapia.org, так как они более полные. Приоритет по фото у открытых данных Министерства культуры, так как качество фотографий лучше, чем у wikimapia.org. После обработки данные из таблицы выгружаю в MapInfo и по уникальному значению подтягиваю данные. В итоге получаю слой со зданиями и данными:


  • Название
  • Адрес
  • Год постройки
  • Этажность
  • Фото
  • Источник фото

Всего на карте получил 21429 зданий, у 8017 год постройки известен.



Красные полигоны год неизвестен, зеленые известен


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



Могу предположить, что многие дома дореволюционной постройки обозначали одним годом. Скорее всего это не повлияет на общую картину, так как большинство построек расположено в центральной части города. Переходим к визуализации.


Палитра цветов


Заниматься визуализацией данных буду в QGIS. Палитра цветов, которая зависит от года постройки, будет представлена в непрерывной палитре от горячих цветов к холодным. Беру темную базовую карту от CartoDB и цветовую палитру Spectral, предустановленную в QGIS.



Для красного и синего цветов делаю фотографии здания банка на Соборной площади и панельного дома по улице Василисина. Остальные фотографии беру у фотографа Ивана Медведева. Оранжевый цвет водонапорная башня, зеленый кукольный театр, жёлтый дом 3 по улице Луначарского (бывш. Совнархоз), голубой Городской дворец культуры и серый Политехнический колледж.









Тут же в QGIS делаю базовую карту. На ней будет 3 слоя: сплошной цвет, дороги и водные объекты.



Собираю все вместе и настраиваю распределение временной шкалы по палитре. До 1900 года делаю большие временные отрезки, так как домов из них осталось не так много. С 1900 по 2000 годы делю по десятилетиям и немного корректирую, опираясь на даты правления глав государства. В итоге получаем вот такую карту.



Публикация карты


Для того, чтобы карту можно было посмотреть в интернете, используется хаб пространственных данных Геосемантика. На него загружаются базовый слой и слой со зданиями. При нажатии на здание открывается вся информация, которую удалось по нему собрать.



Любой пользователь может нажать на кнопку Редактировать и предложить свои правки, а так же загрузить к зданию его фотографию.

Подробнее..

OpenStreetMap часть заключительная наполняем иерархию адреса

20.11.2020 16:08:20 | Автор: admin

В самой первой части, мы из большого набора данных вырезали условный город и оставили в нём только данные с адресом. Адреса интерпретировались, как принадлежащие этому городу. Т.е. точно знали в какой стране они находятся, в каком регионе и так далее. Но что, если нам нужны адреса не одного населённого пункта, а целого региона или может быть даже нескольких стран? Как узнать откуда он?

И хотя в OpenStreetMap существует возможность на каждом доме указывать в какой он стране, области и далее ниже по иерархии, в России используется сокращённый способ т.е. только улица и номер дома. Весь мартышкин труд по структурировании адреса будет делать за нас компьютер. Он это сделает быстрее и правильней, если конечно, все необходимые данные будут в его распоряжении.

Подготовка

Экспериментировать буду на Саранске, вернее, на его городском округе вырезав его прямоугольником, с таким охватом: нижняя граница (45 54), верхняя (45.5 54.3). Вырезку из дампа сохраняю в формате pbf, потому, что следующий инструмент работают именно с ним:

osmconvert -b=45,54,45.5,54.3 RU-local.o5m -o=SaranskGO.pbf

Теперь вся идея в том, чтобы всем зданиям с адресом дописать в теги в каком населённом пункте они находятся. Вычислено это будет по вхождению геометрии дома в контур населённого пункта. Для это нам понадобится плагин OsmAreaTag для osmosis (более детальное описание плагина от автора). Скомпилированную версию плагина автор выложил тут. Сам osmosis можно забрать с гитхаба. Это Java приложение, так что понятно, без чего оно не будет работать.

Установка плагина

Чтобы osmosis увидел плагин osmareatag он должен располагаться в папке plugins текущего каталога, что несколько не удобно. Поэтому его можно разместить в домашнем каталоге пользователя, для windows это будет c:\Users\<Пользователь>\.openstreetmap\osmosis\plugins либо в c:\Users\<Пользователь>\AppData\Roaming\openstreetmap\osmosis\plugins. Туда и распаковываем содержимое архива плагина, папка osmareatag-1.3.zip должна лежать в папке plugins.

Настройка правил

Тут расскажу немного теории о том как с этим плагином работать. Вот пример базового файла конфига:

<?xml version="1.0" encoding="UTF-8"?><tag-processing>    <area id="national-boundary" cache-file="national-boundary.idx">    <match type="relation">      <tag k="boundary" v="administrative"/>      <tag k="admin_level" v="2"/>    </match>  </area>  <transform>    <name>Country</name>    <match>      <tag k="building" v=".*"/>      <tag k="addr:housenumber" v=".*"/>      <inside area="national-boundary"/>    </match>    <output>      <add-tag k="addr:country" v="${ISO3166-1}" context-area="national-boundary"/>    </output>  </transform></tag-processing>

Первое это задаём области с которыми будем работать. У тега area есть атрибут id, где задаётся имя, чтобы в дальнейшем взаимодействовать с этим контуром. Далее в match указываем, какую геометрию выбрать из OSM, чтобы построить контур. В данном примере это отношения границ второго уровня, т.е. границы государств. Атрибут cache-file позволяет сохранить контур в файл и в дальнейшем использовать его не строя его из данных OSM заново. Во-первых построить контур страны это долго, а во-вторых в данных его может и не быть вовсе, если у нас вырезан например только отдельный регион. Если файл уже был создан, контур будет доступен для проверки вхождения в него объектов.

Второе это трансформация объекта, тег transform. В теге match описываем какие объекты нас интересуют, а именно: здания с адресом и тег inside для проверки на вхождение в контур, где в атрибуте area указываем с каким контуром осуществляется проверка.

И если все условия выполняются, то в output описываем, что нужно делать, а именно добавим к объекту, прошедшему проверку, тег с адресом страны, значение которого возьмём из контура national-boundary ключа ISO3166-1. Если добавляемый тег уже задан, то он заменяться не будет.

Так же стоит иметь ввиду, что объекты, не попавшие под выше указанный фильтр, никуда не пропадают. Они так же остаются в результирующем файле. Поэтому если нужны только адреса, то логично предварительно отфильтровать всё не нужное, это ускорит обработку.

Наш код для дополнения адреса названием населённого пункта будет выглядеть так:

<?xml version="1.0" encoding="UTF-8"?><tag-processing>  <area id="place">    <match>      <tag k="place" v="city|town|village|hamlet|isolated_dwelling|allotments"/>    </match>  </area>  <transform>    <name>Place</name>    <match>      <tag k="building" v=".*"/>      <tag k="addr:housenumber" v=".*"/>      <inside area="place"/>    </match>    <output>      <add-tag k="addr:city-auto" v="${name}" context-area="place"/>    </output>  </transform></tag-processing>

Я специально назвал добавляемый тег addr:city-auto, чтобы посмотреть его отличия с тем, как он вручную внесён в OSM. Так же я будут сохранять в формате osm-xml, чтобы глазами увидеть добавленный тег. Команда будет выглядит так:

call osmosis-0.48.3\bin\osmosis.bat --read-pbf SaranskGO.pbf --lp --tag-area-content file=tag-building-addr-place.xml --write-xml SaranskGO.place.osm

tag-building-addr-place.xml - это как раз тот файл с правилами преобразования данных, представленный выше.

Анализ результатов

Т.к. файл сохранён в человеко-читаемом формате, можно в него заглянуть. И увидеть что новый тег появился в данных, значит всё должно было отработать правильно. А ещё можно посмотреть на разные ошибки допускаемые людьми. Вот вам новый город саранак например.

  <way id="103738775" version="2" timestamp="2019-09-20T18:28:15Z" uid="10124028" user="MarinaAR" changeset="74731679">    <nd ref="1197639591"/>    <nd ref="1197639690"/>    <nd ref="1197639206"/>    <nd ref="1197639237"/>    <nd ref="1197639591"/>    <tag k="building" v="yes"/>    <tag k="addr:city" v="саранак"/>    <tag k="addr:street" v="улица Лодыгина"/>    <tag k="addr:housenumber" v="5"/>    <tag k="addr:city-auto" v="Саранск"/>  </way>

Но давайте воспользуемся мощностями ГИС, а не текстового редактора. Чтобы увидеть это на карте, как и в первой части, конвертируем всё в точки, фильтруем только здания с адресами и сохраняем в CSV, чтобы затем добавить эти данные в QGIS. И хотя он понимает нативные форматы OSM, для этого нужно создавать файл привязки тегов OSM к атрибутам в ГИС, т.к. по-умолчанию адреса там не отображаются. Поэтому мне всё же удобней с текстовым CSV.

Рис.1 addr:city не совпадает с addr:city-autoРис.1 addr:city не совпадает с addr:city-auto

Видно, что целые посёлки обозначают не верно. Это и просто мусор в названии населённого пункта. Это и путаница города Саранск и одноимённого муниципального образования, в которое входят несколько населённых пунктов. Или наоборот в место названия посёлка вписывают туда название сельского поселения. На территории самого города видно несколько десятков точек, где допущены опечатки в названии. Как я и говорил раньше: оставьте это дело машинам, там где можно ошибиться, человек обязательно ошибётся.

Сейчас присвоили название только населённого пункта. Так же по аналогии можно сделать и для привязки к поселениям и регионам стран.

Подробнее..
Категории: Openstreetmap , Геоданные

Владельцы MAPS.ME отменили изменения и вернули старое приложение. Надолго ли?

01.02.2021 12:14:39 | Автор: admin

Слева старое приложение, справа декабрьская версия от южнокорейцев. Источник: Смерть MAPS.ME?

В ноябре 2020 года Mail.Ru Group продала MAPS.ME южнокорейской компании Daegu Limited (входит в состав платёжной системы Parity.com), и уже 20 декабря 2020 года новые владельцы выпустили обновление, которое практически убило приложение.

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

К счастью, новые владельцы осознали глубину своей ошибки и откатили изменения. В апдейте от 30 декабря 2020 года восстановлена вся функциональность. Казалось бы, победа? Справедливость восторжествовала? Нет, в это слабо верится.

Продажа MAPS.ME


Приложение MAPS.ME (первоначально MapsWithMe) создал белорусский стартап MapsWithMe в апреле 2011 года. Разработчиков было четверо Юрий Мельничек (автор проекта, бывший сотрудник Google), Александр Золотарёв, Виктор Говако и Сергей Речицкий.


Юрий Мельничек, Александр Золотарёв, Виктор Говако и Сергей Речицкий в 2011 году. Источник: 42.tut.by

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

За основу сразу взяли OpenStreetMap, но в приложении изменили стили, чтобы карты OSM стали удобнее и симпатичнее. Впоследствии ссылку на OpenStreetMap спрятали, что можно считать нарушением лицензии OSM.

Команда работала над проектом примерно полтора года. Было тяжело, инвестиции из собственного кармана достигли $250 тыс. Двум основателям пришлось продать свои квартиры. Первой вышла версия для iOS, потом для Android. Затем платная версия за 169 рублей, которая позволяла загружать карты в высоком разрешении. Приложение нашло аудиторию по всему миру: на первом месте по количеству пользователей были США, потом Россия.

В 2014 году компанию купила корпорация Mail.Ru Group за 542 млн рублей ($11,48 млн по курсу на 14 ноября 2014 года) и основатели получили свои доли. Приложение стало бесплатным.

Сразу после покупки был закрыт допотопный фирменный сервис Карты@Mail.ru. Вместо него планировалось везде использовать MAPS.ME, но не получилось. Он остался независимым мобильным приложением. В остальных продуктах (доставка, такси) Мейл по-прежнему использует карты Google Maps, которые стоят огромных денег.

Нужно заметить, что автономия проекта MAPS.ME в составе Mail.Ru была одним из условий его продажи. И его не получилось никуда интегрировать.

Может, оно и к лучшему, потому что тогда Mail.Ru мог бы и прибить отдельное мобильное приложение.

К 2019 году приложение преодолело отметку в 140 млн скачиваний и 10 млн месячных активных пользователей. Выручка составила около $2,5 млн, чистый убыток около $385 тыс. Команда разрослась, набрали менеджеров и т. д.


Команда MAPS.ME в Mail.Ru, источник

По мнению пользователей, с 2014 года приложение не стало хуже, что является заслугой разработчиков Mail.Ru. Они его не испортили. Хотя пытались. Например, в 2019 году сервис запустил платную подписку на каталог путеводителей, которые бесплатно доступны в других местах это не очень красиво.

В ноябре 2020 года MAPS.ME решили продать корейской компании Daegu Limited (входит в состав платёжной системы Parity.com) за 1,557 млрд рублей, это $19,33 млн по курсу на 2 ноября 2020 года. К тому времени компанию давно покинули основатели, а проект до самого конца оставался убыточным.

Возможно, финансовые показатели стали одной из причин продажи. К тому же, у совместного предприятия Сбера и Mail.ru есть доля в картографическом сервисе 2ГИС, который Сбербанк целиком купил в июне 2020 года. Кроме того, и OpenStreetMap, и MAPS.ME опенсорсные проекты, и кто угодно может создавать продукты на их основе. Сейчас Mail.Ru начала работать над собственными сервисами картографии на основе OpenStreetMap.

Что изменилось в декабре 2020 года


Для начала вспомним этот ужас, который появился в декабрьском обновлении. Создалось ощущение, что новые владельцы из Южной Кореи понятия не имеют, что за программа попала им в руки, как она работает и за что её любят миллионы человек.

Небольшой список сделанных изменений:

  • Для получения карт внедрили Mapbox SDK. В результате карты практически перестали работать в офлайне
  • Фоновое скачивание работало плохо
  • Пропали карты метро и навигация по метро
  • Испортилась пешеходная навигация, исчезла диаграмма с перепадом высот
  • Пропали личные данные: подписки на отключение рекламы, отзывы, записи пройденных маршрутов
  • Интерфейс стал монструозным, сильно напоминая Google Maps. Размер карт увеличился в четыре раза, при этом ухудшилась детализация. Например, размер карт Москвы вырос с 50 до 200 МБ
  • Исчез ландшафтный режим
  • Приложение стало тормозить

Зато появился кошелёк. Ведь новый владелец это платёжная система.

Для разработчиков MAPS.ME выход новой версии оказался сюрпризом.

Разразился большой скандал, поэтому вскоре изменения откатили и вернули оригинальную версию.

Но есть основания полагать, что корейцы не просто так переделали приложение и поэтому могут со временем вернуться к изначальному плану. Ведь нужно понимать, что проект убыточный. Если приложение бесплатное, то остаётся монетизация через рекламу. Кафе, рестораны и магазины платят за баннеры прямо на картах. Чтобы реализовать такую модель, разработчикам нужна удобная платформа, куда будет интегрироваться эта реклама. Вероятно, с этой целью они и переделали приложение.

Поэтому существует ненулевая вероятность, что ситуация повторится. Изменения просто ненадолго откатили, а потом Daegu Limited снова вернётся к первоначальному плану. Деньги есть деньги.

Форки MAPS.ME


К счастью, в 2015 году Mail.Ru сделала хорошее дело и открыла исходный код на GitHub под лицензией Apache 2.0 для свободного использования (официальный репозиторий). Его можно использовать частично (например, для добавления отображения офлайн-карт) или целиком, для создания нового сервиса с помощью модификации Maps.me.

Репозиторий форкнули более 1100 пользователей GitHub. Это гарантирует сохранность исходного кода.

Благодаря открытым исходникам, вскоре после продажи проекта был запущен проект по восстановлению оригинального облика MAPS.ME. В телеграм-канал Original MAPS.ME Project добавилось более 250 человек.

И уже много лет поддерживается свободный форк F-Droid.

Хотя инициатива с форками сейчас не так актуальна, потому что корейцы откатили изменения и вернули оригинальную версию MAPS.ME, но какая-то работа всё равно идёт, судя по активности в телеграм-канале и репозиториях. Например, разработчики заменяют иконки, на которые может распространяться копирайт. Они заменили уже 100 из 400 иконок.



Но если владельцы MAPS.ME опять испортят приложение, можно какое-то время не устанавливать новую версию. Просто скачать все нужные карты и пользоваться старым приложением. Форк исходного кода гарантирует, что эта версия приложения никуда не исчезнет.

Альтернативы MAPS.ME


На самом деле MAPS.ME это просто одна из многих оболочек для рендеринга OpenStreetMap, и вся суть именно в свободных картах, которые редактируются силами сообщества. Туда люди вносят тропинки и другие объекты, которые вы никогда не найдёте на Google Maps. Лучшая информативность OSM по сравнению с Google Maps особенно хорошо заметна в труднодоступных местах и не очень популярных туристических маршрутах.

Вообще, карты Google Maps предназначены в первую очередь для автомобилистов и обслуживания бизнесов. А для прогулок по городу, туризма, велосипедного спорта и любого другого активного отдыха OpenStreetMap просто без конкуренции.


Для сравнения, одна и та же лесная зона в черте города. Слева OpenStreetMap (MAPS.ME), справа Google Maps. Источник

Чтобы карты отображались в офлайне, нам нужны векторные тайлы и программа для их рендеринга.

OpenStreetMap главная причина популярности MAPS.ME. Но OSM это по сути база геоинформационных данных, а не векторные тайлы. Чтобы сгенерировать тайлы, требуется специальный движок, так как MapTiler, Mapbox или опенсорсный OpenMapTiles (на базе MapTiler), который можно установить на своём сервере и пользоваться им бесплатно. Этот сервер подготавливает векторные тайлы для загрузки в мобильное приложение.

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

  • OsmAnd, есть бесплатная и платная версии (треки и тропинки, поддерживаются тепловые карты Strava и тайлы OpenTopo), не требует создания аккаунта, поддерживает закладки, работает в офлайне, отличается насыщенным UI, iOS, Android. В последних версиях под Android исправлен баг с поиском, из-за которого раньше поиск на карте практически не работал. Если кому-то интересно, вот телеграм-канал с разработчиками.


    OsmAnd для Android
  • Mapy.cz (есть популярные трейлы, горизонтали высот, хорошо подходит для велоспорта), работает в офлайне, Android-версия Mapy.cz Cycling & Hiking offline maps, также версии под iOS, Windows и AppGallery. Это приложение фокусируется на Центральной Европе, а международная версия называется Windy Maps
  • Guru Maps
  • Bike Citizens
  • Naviki
  • MapFactor Navigator
  • Pocket Earth, только для iOS
  • MapOut, только для iOS


    3D-эффект в MapOut
  • Gaia GPS, походы и путешествия

Что касается альтернатив, то одной из лучших считают OsmAnd. Здесь поддерживаются GPS-навигатор, предупреждения о камерах контроля скорости (POI) и другие необходимые функции. Правда, некоторые функции платные, хотя при желании можно сэкономить. Например, вот бесплатная версия от F-Droid (3.8.5) и бесплатные карты. Карты скачиваются заранее и хранятся на устройстве, как и положено. Есть мнение, что карты OsmAnd даже полнее и информативнее, чем у MAPS.ME.



Итак, старое приложение MAPS.ME пока остаётся с нами. Но если что-то пойдёт не так мы знаем, что делать.

P. S. Кстати, в картографических магазинах ещё продаются бумажные карты разных масштабов. Иногда в походе такая карта удобнее, чем экран мобильного телефона, хотя в 21 веке это уже какой-то экстремальный дауншифтинг.



На правах рекламы


Наша компания предлагает серверы не только с CPU от Intel, но и серверы с процессорами AMD EPYC. Как и для других типов серверов, огромный выбор операционных систем для автоматической установки, есть возможность установить любую ОС с собственного образа. Попробуйте прямо сейчас!

Подробнее..

Перевод Как создавать красивые карты с помощью Python

03.02.2021 20:08:13 | Автор: admin
Мне всегда нравились карты городов, и несколько недель назад я решил создать свою собственную, художественную версию. Немного погуглив, я обнаружил крутое руководство, написанное Фрэнком Себальосом. Оно увлекательно и полезно, но я предпочитаю более подробные/реалистичные карты-схемы. Из-за этого я решил создать свою собственную версию карт. Итак, давайте посмотрим, как мы можем создавать красивые карты с помощью Python и данных OpenStreetMap.



Установка OSMnx


Прежде всего нам нужно установить Python. Я рекомендую использовать Conda и виртуальные среды (venv) для создания рабочего пространства. Также мы собираемся использовать пакет Python OSMnx, который позволит нам загружать пространственные данные из OpenStreetMap. Чтобы развернуть venv и установить OSMnx, нужно выполнить две команды Conda:

conda config --prepend channels conda-forgeconda create -n ox --strict-channel-priority osmnx

Скачивание дорожно-уличных сетей


После успешной установки OSMnx мы можем начинать программировать. Первое, что нам нужно сделать, это загрузить данные. Сделать это можно разными способами, один из самых простых использовать метод graph_from_place().


graph_from_place() принимает несколько параметров. place это запрос, который будет использоваться в OpenStreetMaps для извлечения данных указанного места, retain_all вернёт нам все улицы, даже если они не связаны с другими элементами, simplify немного очистит предоставленный граф, а network_type укажет, какой тип уличной сети нужно получить.

Я хочу получить все возможные данные (network_type=all), но вы можете загружать только проезжие дороги, используя drive, или пешеходные дорожки, используя walk.

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


Вам необходимо учитывать, что, если вы загружаете данные из крупных мест, таких как Мадрид или Берлин, нужно немного подождать, чтобы получить всю информацию.

Распаковка и раскраска наших данных


И graph_from_place(), и graph_from_point() вернут MultiDiGraph, который мы можем распаковать и положить в список, как показано в руководстве Фрэнка Себальоса.


Теперь нам остаётся перебрать данные и раскрасить их, а также отрегулировать ширину линий в зависимости от длины улицы.


Есть даже возможность идентифицировать определённые дороги и как-то иначе раскрасить только эти дороги.


В моём примере colourMap.py я использую следующие цвета:


color="#a6a6a6"color="#676767"color="#454545"color="#bdbdbd"color="#d5d5d5"color="#ffff"

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

Чертим и сохраняем карту


Наконец, нам нужно только одно сформировать карту. Во-первых, нам нужно определить центр нашей карты. Выберите GPS-координаты того места, которое вы хотите сделать центром карты. Затем мы добавим границы и цвет фона. bgcolor, north, south, east и west будут новыми границами нашей карты. Сформированная карта будет обрезана по введённым координатам. Если вам нужна карта побольше, просто увеличьте границы. Учтите, что, если вы используете метод graph_from_point(), вам нужно будет увеличить значение dist в соответствии с вашими потребностями. Для bgcolor я выбрал тёмно-синий цвет #061529, чтобы прикинуть чертёж, но вы опять же можете настроить его по своему вкусу.


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


Результаты


Используя этот код, мы можем создать карты, приведённые ниже, но я рекомендую вам настраивать такие параметры, как ширина линий или ограничение границ для каждого города.


Одно различие, которое следует учитывать между graph_from_place() и graph_from_point(), заключается в том, что graph_from_point() получает данные об улицах из окрестности на основе установленного вами расстояния (dist). В зависимости от того, нужна ли вам простая карта или более подробная, вы можете использовать любой из этих методов. Карта Мадрида была создана с помощью graph_from_place(), а карта Берлина с помощью graph_from_point().


Вдобавок к этому, возможно, вам понадобится изображение размером с плакат. Самый простой способ сделать это установить атрибут figsize внутри ox.plot_graph(). figsize может регулировать ширину и высоту в дюймах. Обычно я выбираю размер побольше, например figsize=(27,40).


Бонус: добавляем воду


OpenStreetMap также содержит данные о реках и других природных источниках воды, таких как озёра или водные каналы. Снова используя OSmnx, мы можем загрузить эти данные.


Как и раньше, мы можем перебирать данные и раскрашивать карту. В данном случае я предпочитаю синие цвета, например #72b1b1 или #5dc1b9.




Наконец, нам нужно сохранить рисунок. В этом случае я не использую границы с bbox внутри plot_graph. Это ещё одна вещь, с которой можно поэкспериментировать.


После успешной загрузки водоёмов нам нужно соединить два изображения. Немного GIMPa или Photoshopa сделает своё дело; не забудьте, что эти два изображения должны быть с одним и тем же fig_size или границами, bbox, для упрощения интерполяции.


Последние штрихи


Мне нравится добавлять текст с названием города, GPS-координатами и названием страны. GIMP или Photoshop делают своё дело.


Также, если вы добавите воду, вам нужно будет заполнить некоторые места или покрасить другие водоёмы, например море. Линия рек изменяется, но с помощью инструмента Ведро с краской вы можете заполнить эти пробелы.


Заключение и код


Код для создания этих карт доступен в моем GitHub. Не стесняйтесь использовать его и покажите мне свои результаты! Надеюсь, вам понравился этот пост. Благодарю, что прочитали, и призываю вас делиться знаниями, которые приведут нас к лучшим, невероятным результатам!

Отвечая на призыв автора, делимся знаниями на нашем курсе Python, который будет еще выгоднее с промокодом HABR, добавляющим 10% к скидке на баннере.



image



Подробнее..

Человеку надо мало чтоб искал и находил

16.02.2021 16:13:42 | Автор: admin


Нет, это не очередная реклама одного известного автомобильного бренда. И даже не попытка литературного разбора стихотворения Рождественского. Речь пойдет о картах. Мы в Mail.ru Group продолжаем развивать собственные геосервисы как для нужд внутренних проектов, так и для запуска новых продуктов для широкого круга пользователей. Ядром географических данных для наших сервисов выступают данные картографического проекта OpenStreetMap. Но мы не хотим быть просто ленивыми потребителями, которые, получая обратную связь от пользователей, хранят исправления в локальной базе данных и не делятся ими с общественностью. Мы активно вносим свой вклад в проект OpenStreetMap. В цикле статей мы решили рассказать, как корректно вносить правки о наиболее насущных для нас объектах географической действительности.

О проекте


OpenStreetMap работает по принципу вики, то есть каждый зарегистрированный пользователь может вносить свои правки в карту. После регистрации становится доступным богатый мир сообщества OpenStreetMap.



Прежде чем рисовать что-то на карте, необходимо ознакомиться со спецификой представления информации в OpenStreetMap объекты здесь представлены в виде точек или узлов (node), линий или путей (way), и отношений (relation). Атрибутивная информация описывается в виде тегов, представляющих собой пару ключ-значение.

О редакторах


Для редактирования данных в OpenStreetMap существует множество редакторов для различных платформ. Наиболее распространенными из немобильных редакторов в OSM-сообществе являются веб-редактор iD, который сейчас рассмотрим подробно, и настольный редактор JOSM, который рассмотрим в следующей статье.

Новичкам, не имеющим опыта работы в ГИС-редакторах и немного знающих теги OSM, подойдет редактор iD. В нем удобно оцифровывать белые пятна и редактировать теги объектов. При создании и редактировании объекта в левой части экрана появляется его карточка, хранящая информацию о тегах.


Набор тегов для карточки сформирован в соответствии с типом объекта, указанным при его создании. Например, при добавлении бара в карточке появятся поля для указания его названия, адреса, времени работы (отдельно поле для часов работы во время пандемии), минимальном возрасте посетителей. А при добавлении болота в карточке появятся поля для указания его типа, солености воды и приливных явлений.

Более продвинутым пользователям может понравиться редактор JOSM, который мы рассмотрим подробнее в следующей статье. В JOSM тоже есть шаблоны для рисования объектов определенных категорий, однако нужно быть знакомым со схемой тегирования OpenStreetMap. Она описана на вики-портале, где также есть ссылки на форумы и Telegram-каналы активного русскоязычного OSM-сообщества.

Об источниках данных


Поскольку данные OpenStreetMap могут быть использованы в коммерческих целях, то и к источникам данных применяются строгие требования: их использование должно быть явно разрешено правообладателем. Так, например, не допускается использование данных панорам Google Maps, но разрешено использование панорам Яндекс.Карт. Недавно сообщество договорилось об использовании данных об адресах из слоев группы Общедоступные сведения, содержащиеся в ЕГРН публичной кадастровой карты. Также к правомерным источникам относятся фотографии сервиса Mapillary.

Самым очевидным способом получить данные, легитимные для использования в OpenStreetMap, является обход/объезд местности. Тогда обходчик становится правообладателем данных, которые он собрал, и со спокойной совестью может нанести их на карту. Один из глобальных источников данных спутниковые снимки. При их использовании стоит помнить, что из-за особенностей съемки и рельефа земной поверхности, снимок может быть смещен относительно реального положения объектов на местности. Тут на помощь придут GPS-треки, загружаемые пользователями проекта. Возможность подгрузить треки есть как в редакторе iD, так и в редакторе JOSM. Подгрузив треки, можно настроить корректное смещение снимка; проще всего это делать по перекресткам дорог, выбирая перекрестки с большим количеством треков и сопоставляя центральную ось пучка треков с осью дороги на снимке.



Также про смещение на снимке стоит помнить при оцифровке многоэтажных зданий: очень часто крыша здания может быть смещена относительно его основания. Именно положение основания здания на снимке соответствует положению здания на местности.


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

Редактирование OpenStreetMap средствами iD


iD является редактором по умолчанию. Именно он открывается при клике на кнопку Правка на странице карты OpenStreetMap. Перед началом редактирования пользователю предлагается пройти обучение.


Обучение интерактивное, а обучающие материалы позволяют пользователю любого уровня достаточно быстро освоить базовые операции в iD. Пожалуй, большинство новичков начинает свой вклад в OpenStreetMap с добавления информации о собственном доме. Подробно рассмотрим этот процесс с помощью редактора iD.

Для начала настроим среду для работы. У правого края экрана расположены кнопки управления редактором. Наиболее используемые две верхние кнопки в нижнем блоке: Настройки фонового изображения и Данные карты.


По клику на Настройки фонового изображения открывается панель с настройками. В этом же разделе настраивается смещение спутниковых снимков, о важности которого написано выше. Панель Данные карты позволяет настроить стиль отображения объектов OpenStreetMap. Например, объекты можно скрыть, сняв флаг Данные OpenStreetMap, чтобы подробнее рассмотреть фоновое изображение. Настроим смещения снимков, активировав флаг GPS-треки OpenStreetMap в разделе Накладываемые изображения панели Фоновое изображение. Прокрутив вниз панель Фоновое изображение, найдем раздел Смещение снимка, где, перетаскивая серую область, манипулируем спутниковым изображением, подстраивая его под GPS-треки.


Теперь рабочая среда настроена. Можно начинать рисовать. Здания являются полигональными объектами, поэтому для начала рисования нажимаем кнопку Полигон. Обводим здание по контуру, учитывая наклон строения на снимке. Двойной щелчок левой кнопки мыши завершает редактирование. Укажем тип объекта, и в обновившейся карточке добавим необходимую информацию.

Стоит обратить внимание на раздел Адресная информация в карточке дома. При указании номера дома, состоящего не только из цифр, следует руководствоваться следующими правилами: литеры пишутся слитно с номером дома, а номера корпусов, строений, сооружений, владений и т.п. указываются через пробел.


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


Но как быть, если наименования нет в списке? Для начала осмотрите окружающие объекты карты. Если нужная улица уже нарисована, то стоит проверить ее теги, возможно, ей присвоено некорректное название, либо оно и вовсе не заполнено. А если улица не нарисована, необходимо ее нарисовать, присвоив корректный тип объекта. Скорее всего, это будет тип Улица, так как большинство основных дорог уже нарисовано. Теперь необходимо добавить название улицы в соответствии с принятым в русскоязычном сообществе OpenStreetMap соглашением.

Коротко об основных правилах соглашения: наименование состоит из статусной части, определяющей элемент улично-дорожной сети, например, улица, проспект, переулок, бульвар, и основной части, характеризующей название. Статусная часть должна быть указана полностью, без сокращений, со строчной буквы. Статусная часть указывается перед основной частью, если основная часть представляет собой существительное или словосочетание, управляемое существительным (улица Клин, улица Морские Дубки), а также группу слов в родительном падеже (проспект Ленина, улица Подольских Курсантов), или группу слов, управляемую предлогом (дорога в Каменку). В случае, если основная часть представляет собой прилагательное в именительном падеже, статусная часть указывается после основной (Невский проспект, Рябиновая улица).

Итак, с адресной информацией разобрались. Также в карточке объекта можно указать данные об этажности поле Число этажей, высоте поле Высота (метры), а в поле Units указывается количество жилых помещений (квартир) в здании. Вернемся к редактированию формы нашего дома. Выйти из режима редактирования тегов можно с помощью клавиши Esc, либо нажатием на кнопку с крестиком в карточке объекта.

Скорректируем форму только что нарисованного дома, нажав на него правой кнопкой мыши, и в контекстном меню выбрав кнопку Выпрямить углы.


Следует обратить внимание, что тип здания указывается в соответствии с его назначением, а не текущим использованием. Например, здание, построенное как склад, в настоящее время используется как жилой дом. В этом случае тип здания Склад, так как предназначалось оно именно под складские помещения, а текущее использование отображается дополнительным тегом building:use=residential.


Если есть сомнения в том, какой тип для здания нужно указать, то лучше использовать тип Здание без конкретного типа.

Итак, первый этап пройден, дом нарисован. Теперь хорошо бы нарисовать окружающую инфраструктуру и уточнить адресную информацию, добавив подъезды. Они обозначаются дополнительными точками на полигоне здания с указанием типа узла Вход/выход и типом входа staircase (за таким входом обычно скрывается лестница). Номер подъезда указывается в поле Номер/идентификатор. Если необходимо указать вход в частный дом, то значение типа входа должно быть home.


Как определить, где рисовать дополнительную точку входа в дом? Довольно часто для жилых домов можно определить количество и расположение подъездов по спутниковым снимкам: на крышах зданий видны лифтовые шахты и выходы вентиляционных труб. Однако, следует помнить, в большинстве случаев, особенно для высотных домов, крыша здания смещена относительно реального положения подъезда, поэтому необходимо также ориентироваться и по другим, косвенным признакам козырькам подъездов, входным дорожкам.


Так, на рисунке выше видно, что дома 14 и 18 имеют два подъезда, а в доме 16А их не менее пяти.

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

Когда с оцифровкой закончено, наступает время отправить свои правки на сервер OpenStreetMap. Для этого в верхнем правом углу экрана есть кнопка Сохранить.


При сохранении пакета правок необходимо оставить комментарий, описывающий сделанные изменения. Нажимаем кнопку Сохранить и наслаждаемся своими изменениями на карте OSM.

В заключение


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

День открытых данных 2021. Онлайн

01.03.2021 20:22:05 | Автор: admin

image


1-6 марта приглашаем на мероприятия, приуроченные к Международному Дню открытых данных 2021.


Это крупнейшее ежегодное международное событие, которое помогает продвигать концепцию открытых данных среди органов государственной власти, бизнес-корпораций, некоммерческих организаций и гражданского общества. Организатором Дня открытых данных в России выступает АНО Информационная культура.


Рассказываем, какие мероприятия мы приготовили для участников в этом году.
Накануне Дня открытых данных, с 1 по 5 марта, проведем серию практических онлайн мастер-классов по работе с открытыми данными.


  • 1 марта, мастер-класс Вскрываем декларации. Как при помощи регулярных выражений привести Wordовскую табличку к пригодной для анализа форме. Доступна видеозапись.
  • 2 марта, мастер-класс О чем говорят депутаты Госдумы? Анализ текстовых данных на Python.
  • 3 марта, мастер-классы по работе с геопространственными данными и картами для новичков и профи.
  • 4 марта, мастер-класс по поиску открытых данных от DataMasters.
  • 5 марта, мастер-класс Российская официальная статистика: как сделать работу с данными удобнее, а данные понятнее?.
  • 5 марта, мастер-класс Визуализация данных в ObservableHQ.

6 марта пройдет онлайн-конференция День открытых данных.


В центре внимания вопросы о том, что происходит с открытостью в России и мире и как использовать данные для эффективного решения конкретных проблем и задач общества. В дискуссиях примут участие не только российские эксперты, но и представители крупнейших международных проектов, продвигающих ценности и идеологию открытых данных: Global Data Barometer, The Humanitarian Data Exchange.


В программе дискуссии и выступления:


  • Дискуссия. Бизнес на открытости: зачем заниматься открытым кодом и открытыми данными
  • Дискуссия. Как инструменты оценки влияют на открытость государства?
  • Дискуссия. Доступность данных о госфинансах
  • Дискуссия. Данные переписи населения 2021: приватность vs. польза для общества
  • Выступления. Что происходит с тематикой открытости в мире?

Программа и регистрация: opendataday.ru/msk. Трансляция будет доступна и бесплатна для всех желающих.


Подробнее..

Как использовать GraphHopper для построения пешеходных маршрутов по собственным правилам

06.03.2021 00:17:06 | Автор: admin

Построение маршрутов..., люди регулярно этим пользуются, особенно для автомобильных маршрутов, в навигаторах.

Решений, для построения маршрута тоже немало, в том числе существует GraphHopper, который умеет строить маршруты, и для автомобилей, и для пешеходов, и даже для пешего туризма, - подойдёт, наверно, в 99% случаев.

Далее речь пойдёт том, что делать в остальных ситуациях, точнее о моём опыте использования GraphHopper, когда существующее решение не подходило. Требовалось учитывать дополнительные ограничения: строить пешеходные маршруты для людей с ограниченными возможностями. Не будет ни каких значимых особенностей реализации именно этой задачи. Обобщённо.

Будет описано, как создать на основе библиотеки GraphHopper свой вебсервис, который, по координатам начала и окончания пути, вернёт массив координат маршрута.

Пример приложения, со всеми необходимыми для запуска заглушками, можно найти в моём репозитории на GitHub.

GraphHopper - механизм маршрутизации, написанный на Java. Выпущен под лицензией Apache, и может быть встроен в продукты с закрытым исходным кодом.

Статьи подобного толка на хабре встречаются, например, Гуляем по городу с умом, но в ней не приводится деталей реализации, к сожалению, и ну и всё.

Также в публикации Новости из мира OpenStreetMap 512 (05.05.2020-11.05.2020), была новость следующего содержания:

Разработчики GraphHopper ждут наших с вами комментариев, так как они ввели новую функцию, позволяющую даже людям без знания программирования или Java изменять модель построения маршрутов.

Наверно, эта новая функция покроет ещё 0.99% возможных ситуаций, вероятно подойдёт и для Вашей задачи, знания Java не потребуются, и вообще проблем не возникнет. Я расскажу, а своём опыте создания правил построения маршрутов, когда этой функции не было, а до её создания оставалось 2 года.

Понадобятся знания Java.

Считаю, что публикация всё ещё актуальна, ибо:

  • ничто не может сравниться по гибкости и податливости с возможностью изменения исходного кода

  • GraphHopper работает на данных OSM, а Вам могут потребоваться правила, не предусмотренные OSM. Например, вы можете строить маршруты по закрытым дорогам, их закрытость очевидна из OSM. Вот только надо учесть цветовую дифференциации штанов. А ездить по зимникам в летнее время года я крайне не рекомендую, здесь может потребоваться проверка даты.

Решение

В статье используется версия библиотеки GraphHopper 0.10.0, актуальная на момент создания приложения.

Для начала подключаем библиотеку.

Maven:

<dependency><groupId>com.graphhopper</groupId><artifactId>graphhopper-reader-osm</artifactId><version>0.10.0</version></dependency> 

Исходный код GraphHopper, в том числе этой библиотеки, выложен на github. Так же там есть некоторая документация, например How to create new routing profile aka a new FlagEncoder? которая, как бы намекает, что нам необходимо создавать совой FlagEncoder. Уже существующие FlagEncoder, находятся в пакете com.graphhopper.routing.util, нас особо интересуют FootFlagEncoder, т.к. он занимается построением именно пешеходных маршрутов, и AbstractFlagEncoder, как его родительский класс.

Отправной точкой для постижения GraphHopper (актуальной версии) может стать вот эта страница GraphHopper Documentation и пример RoutingExample.java.

Создаём FlagEncoder

Имеет смысл, либо унаследовать свой FlagEncoder от AbstractFlagEncoder, частично повторив FootFlagEncoder и внеся изменения куда следует, либо сразу от FootFlagEncoder, что избавит от дублирования кода. Мне больше подходит наследование от AbstractFlagEncoder и копирование кода FootFlagEncoder, ибо требуется доступ к полям, которые в FootFlagEncoder приватны.

Магия построения графа путей сосредоточена в методе acceptWay, который принимает поочерёдно объекты дорог - ReaderWay и решает пригодна эта дорога для прохода/проезда или нет. Определение пригодности это уже прерогатива FlagEncoder. Я передаю во FlagEncoder список дорог, по которым ходить нельзя. Необходимо чтобы метод acceptWay, натолкнувшись на эту дорогу сказал своё твёрдое нет вернув 0.

Список назовём restricted, и хранить он будет id объекта way из OSM.

public class MyFlagEncoder {private List<Long> restricted;@Overridepublic long acceptWay(ReaderWay way) {        if (restricted.contains(way.getId()))            return 0;        }}

У нас запретительный подход, если объект оказался в списке, то выполнение прерываем, вернув 0.

Предварительная подготовка данных

Написав FlagEncoder, и переделав в нём всё что хотели, можно приступать к построению графа маршрутов.

Я черпал вдохновение в документации Routing via Java API.

GraphHopper closableInstance = new GraphHopperOSM().setOSMFile(osmFilePath).forServer();closableInstance.setStoreOnFlush(true);closableInstance.setGraphHopperLocation(graphFolder);closableInstance.setEncodingManager(new EncodingManager(encoder));closableInstance.setCHEnabled(false);GraphHopper hopper = closableInstance.importOrLoad();

Здесь

  • osmFilePath - путь к pbf-файлу региона, pbf можно взять например на geofabrik, или других порталов, это срез данных из OSM;

  • encoder объект интересующего нас FlagEncoder, например того, который мы сами и создали на предыдущем шаге;

  • graphFolder директория куда будут сохранены результаты построения.

Метод importOrLoad проведёт построение графа, в соответствии с правилами из FlagEncoder, и сохранит результат в указанную папку.

Строим маршрут

Нужно обратиться к следующей части документации GraphHopper: Low level API.

Предварительно созданные графы можно загрузить всё тем же методом importOrLoad.

GraphHopper closableInstance = new GraphHopperOSM().setOSMFile(pbfFile).forServer().setStoreOnFlush(true).setGraphHopperLocation(graphFolder).setEncodingManager(new EncodingManager(encoder)).setCHEnabled(false);GraphHopper hopper = closableInstance.importOrLoad();

Затем создать объект класса LocationIndex:

GraphHopperStorage graph = hopper.getGraphHopperStorage();LocationIndex index = new LocationIndexTree(graph, new RAMDirectory());index.prepareIndex();

Для построения маршрута нам потребуются объекты трёх классов: GraphHopperStorage, FlagEncoder, LocationIndex.

Используем их следующим образом, результатом будет List<Double[]>:

QueryResult fromQR = index.findClosest(fromLon, fromLat, EdgeFilter.ALL_EDGES);QueryResult toQR = index.findClosest(toLon, toLat, EdgeFilter.ALL_EDGES);QueryGraph queryGraph = new QueryGraph(graph);// Получить координаты путиqueryGraph.lookup(fromQR, toQR);Dijkstra dij = new Dijkstra(queryGraph, new FastestWeighting(encoder), TraversalMode.NODE_BASED);Path path = dij.calcPath(fromQR.getClosestNode(), toQR.getClosestNode());PointList pl = path.calcPoints();return pl.toGeoJson();

Заключение

Реализация получилась примитивной т.к. основана на проверке (в методе acceptWay) попадания объекта в заранее составленный (или полученный всеми правдами и неправдами) список:

if (restricted.contains(way.getId()))return 0;

Гораздо правильнее было сделать что-то подобное коду, основанному на проверке значений тегов из OSM, как здесь:

if (way.hasTag("foot", intendedValues)) {return acceptBit;}

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

Удачи!

Подробнее..

Как начать работу в JOSM

26.03.2021 12:06:50 | Автор: admin


В предыдущей статье были рассмотрены основополагающие принципы картографирования для OpenStreetMap, а также веб-редактор iD. А здесь мы рассмотрим настольный редактор JOSM. Его выбирают продвинутые пользователи OpenStreetMap. Причем выбирают осознанно. При запуске приложения не запускается процесс обучения, но доступны хорошие обучающие материалы, в том числе на русском языке. Ниже рассмотрим, как начать работать в JOSM.

Чтобы начать редактирование в JOSM, необходимо скачать jar-файл с сайта josm.openstreetmap.de и установить его. В окне настроек можно указать язык, настроить авторизацию и скачать необходимые модули. Одними из наиболее удобных и широко используемых модулей являются:

  • Buildings_tools инструмент для прорисовки зданий. Позволяет рисовать прямоугольники или круги, нарисованной фигуре проставляется по умолчанию тег building=yes;
  • Utilsplugin2 добавляет несколько утилит, упрощающих редактирование геометрических фигур, а также расширяющих возможности выборки;
  • Reltoolbox упрощает процесс работы с отношениями;
  • Imagery_offset_db позволяет получить/сохранить ближайшее к месту редактирования смещение снимка;
  • HouseNumberTaggingTool позволяет достаточно быстро тегировать адресную информацию для зданий, номера которых идут с определенным шагом.

Подробную информацию о настройке и начале работы с JOSM можно почерпнуть из видео на сайте josm.ru.

Итак, мы сделали первоначальную настройку редактора. Теперь подготовим рабочую область для рисования. Сперва надо в левом верхнем углу интерфейса найти кнопку с зеленой стрелкой вниз она позволит выбрать область для редактирования. В открывшемся модальном окне ставим галочки в разделе Источники и типы данных, чтобы скачать данные OpenStreetMap, GPS-треки и заметки. Прямоугольником на карте выделяем область, которую хотим редактировать (перемещение по карте в JOSM осуществляется с зажатой правой клавишей мыши) и кликаем на кнопку Скачать.


Если была выделена слишком большая область, JOSM выдаст сообщение о неудачном запросе (но GPS-треки и заметки будут загружены).


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


В правой части экрана располагается панель слоёв, в которой можно управлять их видимостью и порядком отображения. Добавим слой со спутниковыми изображениями: в главном меню развернем пункт Слои и выберем слой, например, Снимки Maxar Premium (Beta). На экране и в списке слоев появится слой со спутниковыми изображениями.


Настроим смещение снимка, кликнув в верхней панели инструментов на иконку с двумя красными стрелками.


Теперь рабочая область готова к рисованию. Начнем, как и в редакторе iD, с добавления здания. В JOSM есть два основных режима редактирования: режим выбора горячая клавиша S или верхняя кнопка Выделять, перемещать, масштабировать и вращать объекты на панели инструментов слева; и режим рисования горячая клавиша A или кнопка Рисовать точки на панели инструментов слева.

Здание часто представляет собой замкнутый многоугольник. В большинстве случаев жилой дом имеет форму, близкую к прямоугольной. Чтобы нарисовать ровный прямоугольник при установленном модуле Building_tools, достаточно нажать горячую клавишу B (от слова Building) и щелчками мыши на экране растянуть длину и ширину прямоугольника. Если же этот плагин не установлен, то можно начать редактирование горячей клавишей A (от слова Add), нарисовать многоугольник (двойной щелчок заканчивает рисование) и нажать горячую клавишу Q, которая спрямит углы. То же самое можно сделать, не пользуясь горячими клавишами, а нажимая на соответствующие кнопки в интерфейсе.


Более сложные формы можно рисовать несколькими прямоугольниками, выделить их все, сменив клавишей S режим редактирования на режим выделения, и горячими клавишами Shift+J объединить в один полигон.


Другой способ нарисовать ограничивающий прямоугольник, клавишей A переключиться в рисование узлов, в местах изменения формы добавить узлы, а затем горячей клавишей X выдавить или вытянуть необходимую форму.


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


Основной тег для многоугольника здания building. В соответствии с назначением здания необходимо проставить корректное значение этого тега. В простых случаях назначение здания совпадает с его использованием. Так, для многоквартирных домов используется значение apartments, для частных жилых домов detached, школы обозначаются school, а детские сады kindergarten. Чуть более замысловатым является тегирование зданий, использование которых не совпадает с их назначением: пример, рассмотренный в предыдущей статье здание, построенное как склад, в настоящее время используется как жилой дом. В этом случае здание тегируется как склад (building=warehouse), текущее использование задается тегом building:use=residential. Как быть, если, например, бывшее здание больницы используется теперь как библиотека? Ответ прост: здание должно иметь теги building=hospital и amenity=library. В сложных случаях, когда назначение дома определить не удается, применяется тегирование building=yes.

Итак, тип объекта был установлен. Теперь нужно указать адресную информацию. Она обозначается тегами с префиксом addr:. Минимально необходимая адресная информация для дома это его номер и название улицы, обозначающиеся тегами addr:housenumber и addr:street соответственно. При указании номера дома, состоящего не только из цифр, следует руководствоваться следующими правилами: литеры пишутся слитно с номером дома, а номера корпусов, строений, сооружений, владений и т.п. указываются через пробел. Например, номер дома 8А, корпус 1, строение 3 должен быть указан как 8А к1 с3.


Наиболее распространенным типом адресации является адресация по улицам, но как быть в случае, когда адрес относится к определенной территории, как, например, в Ангарске, Набережных Челнах или Троицке? В таких случаях необходимо использовать тег addr:place, который заменяет тег addr:street (то есть недопустимо одновременное использование тегов addr:street и addr:place для одного объекта).

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

Хорошим тоном будет указать почтовый индекс, задав тег addr:postcode.

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


На рисунке выше видно, что дома 14 и 18 имеют два подъезда, а в доме 16А их не менее пяти.

Вход в подъезд обозначается как entrance=staircase (за таким входом обычно скрывается лестница), черный вход entrance=service, пожарный выход обозначается как entrance=emergency, вход в частный дом или квартиру с улицы entrance=home. Если есть сомнения, как обозначать конкретный вход, допускается использование общего значения тега yes. Порядковый номер входа указывается с помощью тега ref. Для указания номеров квартир используется тег addr:flats. Указание диапазона квартир производится с помощью дефиса: addr:flats=1-28.



В нестандартных ситуациях, когда нумерация прерывается, диапазоны квартир в подъезде указываются через точку с запятой: addr:flats=1-12;14;16-20.

Дополнительно можно указать инженерную информацию о здании этажность (тег building:levels), форму крыши (roof:shape), материал фасадов здания и его крыши (теги building:material и roof:material соответственно), цвет внешних стен и крыши здания (теги building:colour и roof:colour). Однако новичкам следует с осторожностью указывать такую информацию для сложносоставных зданий, имеющих различную этажность и меняющуюся форму крыши. Для таких зданий в первом приближении можно отрисовать общий контур здания, проставить максимальную этажность. Для внесения более точных характеристик здания необходимо разделять его на элементарные части building:part, из которых собирается финальное отношение c типом multipolygon с тегом building. Вот, например, как выглядит собор Василия Блаженного в Москве:


Возможно, кому-то покажется, что отношения это очень сложно. Это и впрямь непросто, но стоит их освоить, и станет понятно, насколько они удобны и упрощают жизнь картографам. Разберем один случай, не такой изощренный, как представленный выше собор. Для работы понадобятся модули utilsplugin2 и reltoolbox. Удостоверьтесь, что они установлены. Наш случай это многоквартирный дом переменной этажности в жилом квартале Влюблино в Москве. Находится он по адресу улица Цимлянская, дом 3 корпус 2. Дом состоит из двух отдельно стоящих зданий, которые имеют секции с разной этажностью. Начнем с того, что нарисуем контуры отдельных зданий.


Теперь прорисуем линии, разделяющие разноэтажные секции дома. Выделив полигон здания и входящие в него перемычки и нажав горячую клавишу Q, можно скорректировать прямые углы, если это необходимо. Следующий шаг разбить цельные полигоны теми перемычками, которые мы нарисовали. Для этого выделяем полигон и входящую в него перемычку (важно делать это по одной) и нажимаем горячие клавиши Alt+X, либо на вкладке Еще инструменты находим пункт Разрезать объект.


Выделим получившиеся 5 полигонов и проставим им тег building:part=yes. Добавим также тег, описывающий форму крыши roof:shape=flat. Теперь, выделяя каждый отдельный полигон, проставим ему количество этажей building:levels, используя фотографические изображения зданий. Для трехсекционного здания это будут 1, 9 и 17; для двухсекционного 9 и 17.



Теги с адресной информацией добавляются к общему зданию, а не к его частям, на данном этапе адрес указывать не нужно. Также для общего здания должна быть указана максимальная из указанных в building:part этажность.

Выделим снова все пять полигонов. В панели Relation Toolbox, расположенной в правой части экрана, нажмем на кнопку Multi. Эта операция разбила полигоны на отдельные сегменты и создала отношения на месте бывших полигонов building:part. При этом все заданные теги сохранились в соответствующих отношениях. Соберем итоговый мультиполигон для всего здания, он должен соответствовать тем контурам, которые мы нарисовали в самом начале. И поскольку два отдельных здания относятся к одному адресу, они должны быть участниками одного отношения (в нашем случае одного мультиполигона). Выделив линии, образующие два внешних контура здания, снова нажимаем кнопку Multi в панели Relation Toolbox и видим появившуюся у контуров зданий желтую обводку.



В панели Теги добавляем к уже имеющемуся тегу type=multipolygon теги building=apartments, building:levels=17, addr:street=Цимлянская улица, addr:housenumber=3 к2.

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

Итак, первый этап пройден, дом нарисован. Теперь хорошo бы нарисовать окружающую инфраструктуру. Проезды, обозначенные знаками Жилая зона, имеющие название, тегируются как highway=residential + living_street=yes, неименованные внутридворовые проезды обозначаются как highway=service + living_street= yes. Пешеходные дорожки тегируются highway=footway. Детские площадки обозначаются leisure=playground, площадки для выгула собак leisure=dog_park, спортивные площадки leisure=pitch, желательно с указанием вида спорта (тег sport), парковки amenity=parking.

Когда с оцифровкой закончено, наступает время отправить свои правки на сервер OpenStreetMap. Для этого на верхней панели инструментов находим кнопку с зеленой стрелкой вверх. В этот момент происходит валидация сделанных правок, и если что-то сделано некорректно, JOSM выведет об этом сообщение.


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


В новом окне необходимо указать комментарий к своей правке, описывающий проделанные изменения. Также нужно указать источник данных. Активировав флаг Автоматически брать источник из текущих слоёв, можно автоматически заполнить эту строку названием слоёв спутниковых снимков, использованных при оцифровке. Если данные были получены путем обхода, то указываем survey или дописываем через точку с запятой, например, Bing; survey. Нажимаем кнопку Отправить изменения на сервер и наслаждаемся своими изменениями на карте OSM.

Если на одном участке работало несколько картографов, в момент валидации может появиться окно с конфликтом правок, но это уже совсем другая история.

В заключение


Редактирование в JOSM требует некоторого опыта работы с данными OpenStreetMap. Но в большинстве случаев, когда дело доходит до обрисовки большого района, массового исправления тегов, работы с отношениями, JOSM оказывается удобнее редактора iD.

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

Использование данных OSM для анализа

25.04.2021 10:05:42 | Автор: admin

Постановка задачи

В рамках проекта Фото-Географического Атласа России (photogeomap.ru) мы собрали ряд фотографий различных ландшафтов страны. Многие из них сделаны в достаточно труднодоступных местах. Именно эту труднодоступность на качественном уровне мы и хотим оценить для каждой точки (фотографии)/

Индекс недоступности

Характеристика, позволяющая косвенно, на качественном уровне оценить сложность попасть к точку съемки данного кадра. Поскольку не нашлось готовых решений по расчету такой характеристики. Решено было создать свою. Назовем ее индекс недоступности (ИН).

От чего он зависит? Очевидно, что от:

  • удаленности от дорог доступных для транспорта

  • удаленности от пеших троп и дорог пригодных только для пешего передвижения

  • удаленности от водных путей сообщения

Источник данных для анализа

Единственным доступным источником векторных данных для такого анализа близости нам видится OSM.

Спорные моменты и допущения

Сразу опишу все допущения принятые нами

  1. Все расчеты проведены с использованием данных OSM со всеми содержащимися в них огрехами и неточностями.
    На карте могут быть отображены НЕ ВСЕ тропы и не все дороги. Степень валидности существующих объектов также не обсуждалась.

  2. Все расчеты расстояний проведены не в лучшей для этого проекции но с учетом что нас интересуют довольно небольшие расстояния искажениями пренебрежём. Тем более что в финале все количественные характеристики все равно переводятся в качественные.

  3. При анализе близости от водных объектов не учитывалась их связность и судоходность. Что в общем случае конечно неверно, но мы сознательно вводим это допущение.

Подготовка данных OSM

1. Данные OSM на территорию РФ, полученные через https://download.geofabrik.de загружены в СУБД Postgres (c ext. PostGIS).
Основные нужные нам для анализа объекты расположены в таблице planet_osm_line.
Не забываем индексировать нужные нам поля (в случае дорог это поле highway)

2. Готовим дороги и тропы. Созданы materialize view для автодороги и тропинок из класса planet_osm_line.

Дороги - select * from planet_osm_line where highway is not null and highway != track (выбраны все типы дорог из данных OSM вне зависимости от типа и качества покрытия) ошибки неверного назначения тегов проигнорированы..

Тропы - select * from planet_osm_line where highway is not null and highway = track (выбраны тропинки)

На полученных m.view - тоже создаем индексы на нужное поле. Работать будет легче.

3. Готовим реки. Создаем materialize view для линейных рек и площадных водных объектов
Теги по которым можно выбрать реки смотрим ТУТ

Краткий анализ что у нас есть по рекам вообще -

--------------------------------
SELECT t.waterway , count (t.waterway) as cnt FROM public.osm_rivers_l as t where t.waterway is not null group by t.waterway order by cnt desc
---------------------------------

Реки (линейные)
select * from planet_osm_line where waterway is not null

Реки (площадные)
select * from planet_osm_polygon where water is not null

Расчет удаления от точек съемки

На этом этапе мы собственно считаем расстояния от наших точек (фото) до дорог, троп и рек.
Выполнить эту процедуру можно в любом настольном ГИС приложении , например в QGIS . В принципе, такой расчет можно провести в самом PostGIS, не вылезая из БД. Но я не программист и мне лень изучать и делать с нуля то, что я могу быстро за 10 мин сделать в той среде где работаю ежедневно (в GIS)

Определение расстояний от точек съемки до дорог - пишем в поле Road_dist и троп - Track_dist считаем все сразу в километрах! Определяем расстояние от линейных и площадных рек. Берем минимальное из пары (ближайший водные объект, неважно какой геометрии) и пишем в поле River_dist

Итак у нас есть поля с записанными в них расточениями. Собственно на этом использование данных OSM завершается, но не весь процесс..

Методика расчета

Теперь у нас все готово, и мы начинаем считать сам ИН.
Сначала мы переводим количественные показатели в качественные характеристики.

1. Введены градации расстояний (поля Road_cat и Track_cat) и присвоены значения весового коэффициента для удаленности от автодорог и троп (Road_cst и Track_cst)

Track_cst считается только для объектов с удаление от дорог более 5 км иначе принимается 0

до 1

до 0,5 часа пешком

1

от 1 до 5

до 2х часа

2

от 5 до 10

до 3х часов

4

от 10 до 25

до дня ходьбы

6

более 25

более 1 дня

10

2. Введены градации расстояний (River_cat) и присвоены значения весового коэффициента для удаленности от рек (River_cst) для объектов с удаление более 10 км от любых дорог и троп , иначе принимается 0

до 1 км

до 0,5 часа пешком

2

от 1до 5

до 2х часа

4

от 5 до 10

до 3х часов

6

более 10

до дня ходьбы

10

3. Вводим характеристику отражающую доступ только с акватории северных морей.
Поле Sea_cst весовой коэффициент по доступу с северных морей для объектов, расположенных в Арктике и на побережье северных морей (выбраны по карте визуальное их не много). В основном все объекты приняты за 0, кроме пары десятков.

с побережья северных морей

5

арктические острова

10

4. Все 4 поля *_cst суммированы в INDEX_IMP
смысл у него примерно такой - чем он выше --тем тяжелее добраться к точке.

менее 3 - относительно легко доступная точка (можно ии приехать на машине или относительно недалеко прийти пешком

в районое первого десятка приехать на машине и хорошо прогуляться.

второй десяток уровень автономной экспедиции.

более 30 очень сложно доступная точка - только с морских судов и пр пр радости

Собственно - все. Мы посчитали искомую характеристику и теперь можем расклассифицировать точки на карте по ней (для наглядности) или просто указать ее в атрибутах.

Подробнее..

OrganicMaps релиз форка Maps.me с открытым кодом

19.06.2021 00:09:06 | Автор: admin
На днях состоялся первый релиз OrganicMaps open-source форка Maps.me, который команда добровольцев готовила больше чем полгода. В этой небольшой заметке мне бы хотелось поделиться краткой историей проекта, за которой я следил в Telegram-чатах и на GitHub и обозначить основные отличия форка от текущей версии Maps.me. Если вы являетесь текущим или бывшим пользователем Maps.me или интересуетесь проектами с открытым кодом с фокусом на приватности без рекламы и сбора данных, то добро пожаловать.



Предыстория


В декабре 2020 года как и многие посетители Хабра я был обеспокоен ситуацией с Maps.me моей основной картографической программой на тот момент. Продажа криптовалютчикам, провальная первая новая версия (по которой казалось, что новоиспечённые владельцы вообще не понимают почему пользователи выбирают Maps.me), позже откат на старую версию, но состояние неопределенности и непонимание чего ожидать в дальнейшем. Так что для начала я просто отключил обновления для старой версии Maps.me, но было понятно, что это временное решение. К счастью, нашёлся доброволец, который создал телеграм-чат для желающих поучаствовать в развитии форка, но дальше чата дело не пошло. Действительно, развитие и поддержка такого сложного приложения на нескольких платформах требует значительного опыта и усилий, по сравнению с какими-то несложными веб-проектами или мобильными программами, которые дают только интерфейс к веб-сервису. Тут и накопленная годами кодовая база на C++/Java/ObjectiveC и нетривиальные алгоритмы поиска/роутинга.

OMaps


Через некоторое время я увидел объявление о начале работы над форком под названием OMaps. Мне сразу приглянулся мощный старт проекта десятки коммитов в день (хоть и от одного разработчика), налаженный на GitHub CI/CD с публикацией на всех платформах, возможность пообщаться с авторами напрямую в чате. Через некоторое время команда расширилась и началась подготовка к релизу, оценить масштаб работ можно по списку задач на GitHub.

OrganicMaps


В ходе подготовки к релизу программа была переименована в OrganicMaps и на днях вышел релиз для Android и iOS.



Интерфейс программы кажется знакомым, программа сохранила все основные функции, из-за которых ее выбирают пользователи:

  • Работа в offline-режиме для всех основных функций: просмотр карт, поиск, навигация
  • Прокладка маршрутов не только для автомобилей, но и для пешеходов и велосипедистов, в некоторых городах есть ограниченная поддержка общественного транспорта
  • Компактные файлы карт
  • Карты на основе OpenStreetMap со всеми его достоинствами (и недостатками)
  • Простой редактор карт, позволяющий добавлять POI


Посмотрим на что сделали упор разработчики в течение почти что полугода готовившие релиз:

  • Убрана вся реклама
  • Убраны трекеры, рекламные SDK и тому подобное
  • Приложение проверено через Exodus Privacy Report и загружено в F-Droid


Как видно, основной фокус разработки был в сторону избавления от отслеживающе-рекламных компонентов. Функции, для работы которых требовался сервер, аккуратно отключены т.к. эта часть не являлась open-source. Удалены и коммерческие данные. Так программа лишилась:

  • POI отелей от Booking.com
  • Отзывов
  • Путеводителей
  • Пробок

Впрочем, многое из этого пропало и из Maps.me.

Выводы


Для меня этот форк оказался предпочтительнее обновлённого Maps.me прежде всего из-за понимания разработчиками своей аудитории и (по крайней мере на данный момент) ориентирования на удобство пользователей, а не на доход с рекламы. Быстрая связь с командой через чат или issue-tracker тоже удобна. Пока непонятно, насколько долго будет продолжаться интенсивная разработка на энтузиазме нескольких авторов, но даже небольших улучшений в комбинации со свежими картами и поддержкой актуальных версий мобильных ОС будет достаточно для удовлетворения потребностей значительного числа пользователей.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru