Skip to content

Commit

Permalink
Add support for pmtiles (#14)
Browse files Browse the repository at this point in the history
* Add support for pmtiles

* Generate pmtiles in upload task
  • Loading branch information
dimasciput authored Oct 10, 2024
1 parent ac5bf35 commit 351618c
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 125 deletions.
15 changes: 12 additions & 3 deletions deployment/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@

FROM python:3.12.0-slim-bookworm AS prod

RUN apt-get update -y && \
apt-get install -y --no-install-recommends \
gcc gettext cron \
spatialite-bin libsqlite3-mod-spatialite \
gcc g++ make build-essential gettext cron \
spatialite-bin libsqlite3-mod-spatialite libsqlite3-dev \
python3-dev python3-gdal python3-psycopg2 python3-ldap \
python3-pip python3-pil python3-lxml python3-pylibmc \
uwsgi uwsgi-plugin-python3
uwsgi uwsgi-plugin-python3 \
gdal-bin \
libprotobuf-dev protobuf-compiler zlib1g-dev git && \
echo "Installing Tippecanoe" && \
git clone https://github.com/felt/tippecanoe.git /tmp/tippecanoe && \
cd /tmp/tippecanoe && \
make -j$(nproc) && make install && \
rm -rf /tmp/tippecanoe && \
apt-get clean && rm -rf /var/lib/apt/lists/*

# Install pip packages
ADD deployment/docker/requirements.txt /requirements.txt
Expand Down
53 changes: 53 additions & 0 deletions django_project/cloud_native_gis/api/pmtile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# coding=utf-8
"""Cloud Native GIS."""
import os
import re

from django.http import FileResponse, Http404, HttpResponse
from django.shortcuts import get_object_or_404

from cloud_native_gis.models import Layer


def serve_pmtiles(request, layer_uuid):
"""Serve pmtiles."""
layer = get_object_or_404(Layer, unique_id=layer_uuid)

if not layer.pmtile:
raise Http404("PMTile file not found for this layer.")

full_path = layer.pmtile.path

if not os.path.exists(full_path):
raise Http404("PMTile file does not exist.")

range_header = request.headers.get('Range')
if range_header:
range_match = re.match(
r'bytes=(\d+)-(\d*)', range_header)
if range_match:
start_byte = int(range_match.group(1))
end_byte = int(
range_match.group(2)) if (
range_match.group(2)) else (
os.path.getsize(full_path) - 1)

file_size = os.path.getsize(full_path)
content_length = end_byte - start_byte + 1
content_range = f'bytes {start_byte}-{end_byte}/{file_size}'

file = open(full_path, 'rb')
file.seek(start_byte)

response = HttpResponse(
file.read(content_length),
status=206,
content_type='application/octet-stream')
response['Content-Length'] = content_length
response['Content-Range'] = content_range
response['Accept-Ranges'] = 'bytes'
return response

return FileResponse(
open(full_path, 'rb'),
content_type='application/octet-stream')
14 changes: 13 additions & 1 deletion django_project/cloud_native_gis/models/layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,18 @@ def absolute_tile_url(self, request):
else:
return None

def absolute_pmtiles_url(self, request):
"""Return absolute pmtiles url."""
if self.tile_url and request:
return (
f'pmtiles://{request.build_absolute_uri("/")[:-1]}'
+ reverse('serve-pmtiles', kwargs={
'layer_uuid': self.unique_id,
})
)
else:
return None

def maputnik_url(self, request):
"""Return absolute url for maputnik."""
from cloud_native_gis.utils.layer import layer_api_url, maputnik_url
Expand Down Expand Up @@ -264,7 +276,7 @@ def generate_pmtiles(self):
'-o',
pmtiles_filepath,
'-l',
'zcta',
'default',
json_filepath],
check=True
)
Expand Down
12 changes: 10 additions & 2 deletions django_project/cloud_native_gis/models/layer_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def import_data(self):
self.update_status(
status=UploadStatus.RUNNING,
note='Save data to database',
progress=50
progress=25
)
metadata = shapefile_to_postgis(
self.filepath(file),
Expand All @@ -143,7 +143,7 @@ def import_data(self):
self.update_status(
status=UploadStatus.RUNNING,
note='Save metadata to database',
progress=70
progress=50
)
self.layer.layerattributes_set.all().delete()
for idx, field in enumerate(
Expand All @@ -160,6 +160,14 @@ def import_data(self):
attribute_order=idx
)

# Generate pmtiles
self.update_status(
status=UploadStatus.RUNNING,
note='Generate pmtiles',
progress=75
)
layer.generate_pmtiles()

layer.is_ready = True
layer.metadata = metadata

Expand Down
9 changes: 8 additions & 1 deletion django_project/cloud_native_gis/serializer/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,16 @@ def get_style(self, obj: Style):
if 'sources' not in style:
style['sources'] = {}
style['sources'][str(layer.unique_id)] = {
"tiles": [layer.absolute_tile_url(request)],
"type": "vector"
}
if layer.pmtile:
style['sources'][str(layer.unique_id)]['url'] = (
layer.absolute_pmtiles_url(request)
)
else:
style['sources'][str(layer.unique_id)]['tiles'] = (
[layer.absolute_tile_url(request)]
)
style = json.dumps(style).replace(
'<uuid>', str(layer.unique_id)
)
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
}

</style>
<script type="module" crossorigin src="{% static "cloud_native_gis/index-DRZ64yjZ.js" %}"></script>
<script type="module" crossorigin src="{% static "cloud_native_gis/index-yKDiDp5s.js" %}"></script>
<link rel="stylesheet" crossorigin href="{% static "cloud_native_gis/index-DW0d2Ij5.css" %}">

<script>
Expand Down
3 changes: 3 additions & 0 deletions django_project/cloud_native_gis/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from cloud_native_gis.api.layer import (
LayerViewSet, LayerStyleViewSet
)
from cloud_native_gis.api.pmtile import serve_pmtiles
from cloud_native_gis.api.vector_tile import (VectorTileLayer)

schema_view = get_schema_view(
Expand Down Expand Up @@ -64,4 +65,6 @@
path('redoc/',
schema_view.with_ui('redoc', cache_timeout=0),
name='schema-redoc-ui'),
path('api/serve-pmtile/<uuid:layer_uuid>/',
serve_pmtiles, name='serve-pmtiles'),
]

0 comments on commit 351618c

Please sign in to comment.