Tile server на коленке: навигация по старинным картам

от автора

Вступление

Я full stack разработчик на культурно-историческом IT портале Königsland, который успешно начал свою работу примерно месяц назад. Этот ресурс посвящается культуре и истории Восточной Пруссии и является своеобразной летописью времен, которая больше всего напоминает вирутальный музей, где можно получить довольно полную информацию об истории этого великого края, а эта информация пополняется по мере возникновения у меня свободного времени.

Страницы этой летописи приоткрывают завесу тайны и позволяют получить пользу от современных технологий тем, кто увлекается стариной.

Как у меня украли GPS навигатор

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

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

И вот этот навигатор пропал. Его конечно можно отследить по GPS, но все доступы к аккаунтам утеряны, и вообще я уже смирился с тем, что спустя 8 лет нашей крепкой с ним дружбы, его у меня украли. Это приносило мне душевные страдания на фоне повышенного стресса — я даже поставил себе мобильное приложение с платными картами, чтобы вновь получить возможность контролировать мое местонахождение в прострастве и времени. Но это было не очень удобно, да и деньги платить — не те времена сейчас.

Tile server

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

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

Развернуть свой apache2 tile server труда никакого не составило, но возникли проблемы с картами — я никак не мог найти способа конвертировать карты в нужный формат. В примере используется утилита osm2pgsql, которая позволяет конвертировать карты из формата pbf и положить их в postgres. И все отлично работало с картами из докумментации osm. Но где мне найти нужные старинные карты (восточная пруссия) в таком формате? Это оказалось весьма трудно.

Однако оказалось просто найти карты в формате готовой базы данных sqlite3 — этих карт много в сети и они есть почти для любого региона. Карты всей нужной мне области были разбиты на две базы данных — центральная часть и восточная часть. В базах по сути была одна нужная мне табличка tiles. Эта табличка в которой не было id, но были индексированные колонки: x, y, z. В последней четвертой колонке хранился мой заветный tile с кусочком старой карты в формате Blob. Если x (долгота) и y (широта) были понятны , то с z (zoom) осознание пришло чуть позже.

Давайте теперь посмотрим, как выглядит экземпляр компонента TileLayer из react-leaflet библиотеки:

import {TileLayer} from "react-leaflet";  <TileLayer   minZoom={2}   maxZoom={20}   attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'   url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />

В этом фрагменте мы видим, что компонент использует в запросе к серверу тайлов те же самые параметры, что у нас находятся в базе, а занчит обработав их, мы сможем получить нужный нам тайл и вернуть его по запросу клиенту. Для этого нам даже не нужен apache2 — достаточно просто обработать параметры http запроса и сделать по ним правильное обращение в базу данных. Ух, теперь это не кажется таким сложным, ведь дело за малым.

Чтобы не тратить время на настройку и подготовку, используем django:

urls.py

from django.contrib import admin from django.urls import path from app.views import IndexView  urlpatterns = [     path('admin/', admin.site.urls),     path(       'tile/<int:x>/<int:y>/<int:z>.jpeg', IndexView.as_view(), name='endpoint'     ), ]        

views.py

import io from PIL import Image  from django.http import HttpResponse from django.views import generic from app.models import Tiles  class IndexView(generic.View):     def get(self, request, x, y):         if Tiles.objects.filter(x=x, y=y).first():             response = HttpResponse(content_type="image/jpeg")             Image.open(io.BytesIO(tile.image)).save(response, "JPEG")         else:             response = HttpResponse(200)             return response 

models.py

import base64 from django.db import models  class BlobField(models.Field):     description = "Blob"     def db_type(self, connection):         return 'blob'  class Tiles(models.Model):     x = models.IntegerField(primary_key=True)     y = models.IntegerField()     z = models.IntegerField()     s = models.IntegerField()     image = BlobField(db_column='image', blank=True)      def set_data(self, data):         self._data = base64.encodestring(data)      def get_data(self):         return base64.decodestring(self._data)      data = property(get_data, set_data)          class Meta:         db_table = 'tiles'         unique_together = (('x', 'y', 'z', 's'),)

Если человеческим языком, то берем широту и долготу из запроса, находим по ним байткод картинки, конвертируем его в HttpResponse с {content-type: image/jpeg} и отдаем нашему клиенту. Тут нам пригодится любимая библиотека для раьбты с картинками: Pillow

Для того чтобы две sqlite3 базы объединить в одну, достаточно добавить настройки для обеих, и написать простенький скрипт по миграции данных из одной базы в другую:

settings.py

DATABASES = {     'default': {         'ENGINE': 'django.db.backends.sqlite3',         'NAME': BASE_DIR / 'db.sqlite3',     },     'second_db_name': {         'ENGINE': 'django.db.backends.sqlite3',         'NAME': BASE_DIR / 'db2.sqlite3',     }, }
from app.models import *  second_db_tiles = Tiles.objects.using('second_db_name').all() i=0 for sdt in second_db_tiles:     try:         sdt.save(using='default', force_insert=True)     except Exception as e:         print(e.__class__.__name__)     i+=1 

nginx.conf

location /tile/ {     proxy_set_header Host $host;     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;     proxy_set_header X-Real-IP $remote_addr;     proxy_pass http://127.0.0.1:8080/tiles/; } 

Следующий шаг — настрйока диррективы ALLOWED_HOSTS, чтобы ограничить доступ к нашему серверу только нашимм доменом. Потом достаточно просто запустить gunicorn --daemon и указать правильный url (https://yourdomain/tile/{x}/{y}/{z}.jpeg) для своего новенького tile сервера в клиентском приложении. И словно магию можно увидеть очертания старинных улиц и домов с документов вековой давности.

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

Заключение

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

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


ссылка на оригинал статьи https://habr.com/ru/post/654281/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *