Вступление

Я 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 сервера в клиентском приложении. И словно магию можно увидеть очертания старинных улиц и домов с документов вековой давности.

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

Заключение

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

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

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


  1. yukon39
    03.03.2022 17:50

    Очень даже интересный проект. А о каких платных картах Восточной Пруссии идет речь?

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


    1. born2fish Автор
      03.03.2022 17:53
      +1

      Я ставил себе приложение Vetus Map, там бесплатная только одна карта, остальные платные. И т.к. я не любитель ставить софт на телефон, а навигатор у меня украли - лучшее решение для меня стало веб приложение.


      1. yukon39
        03.03.2022 18:11

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


  1. yukon39
    03.03.2022 17:52

    Что касается интересных мест, есть проект prussia39 — там довольно много точек и интересной информации.


    1. born2fish Автор
      03.03.2022 17:55

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


      1. fotobred
        04.03.2022 13:49

        Здравствуйте!
        Может заинтересуют мои наработки по похожему проекту история - география ??
        (извините что не по теме... )
        http://walks.ru/about.html


  1. Gonzales451F
    03.03.2022 18:03
    +1

    Когда-то, когда деревья были еще маленькими, а на телефонах крутились ява апплеты, был похожий проект для натягивания совы на глобус загрузки бумажных карт в телефон с калибровкой и возможностью навигации. Назывался он Mapnav. Сам проект (mapnav.spb.ru) уже давно приставился, но какие-то следы в сети еще остались. Даже фанклуб https://vk.com/mapnav Ж-)


  1. Kotman
    03.03.2022 20:36

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


  1. VladimirKalachikhin
    03.03.2022 21:41
    +2

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

    https://vladimirkalachikhin.github.io/Galadriel-map/README.ru-RU

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


    1. born2fish Автор
      03.03.2022 21:43

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


  1. Tzimie
    04.03.2022 08:00

    OruxMaps


  1. Moskus
    06.03.2022 02:39

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