Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates from BIMS #16

Merged
merged 9 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
exclude = */docs/*,*/.tox/*,*/.venv/*,.pycharm_helpers/*,*/migrations/*,docs/*,*/__init__.py,scripts/*,deployment/*,django_project/initialize.py
exclude = */tests/*,*/docs/*,*/.tox/*,*/.venv/*,.pycharm_helpers/*,*/migrations/*,docs/*,*/__init__.py,scripts/*,deployment/*,django_project/initialize.py
max-line-length = 79

# E12x continuation line indentation
Expand Down
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
14 changes: 13 additions & 1 deletion django_project/cloud_native_gis/admin/layer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# coding=utf-8
"""Cloud Native GIS."""

from django.contrib import admin
from django.utils.safestring import mark_safe

Expand Down Expand Up @@ -28,6 +27,17 @@ def start_upload_data(modeladmin, request, queryset):
import_data.delay(layer.pk)


@admin.action(description='Generate pmtiles')
def generate_pmtiles(modeladmin, request, queryset):
"""Generate pmtiles for layer."""
for layer in queryset:
success, message = layer.generate_pmtiles()
modeladmin.message_user(
request,
message,
level='success' if success else 'error')


@admin.register(Layer)
class LayerAdmin(admin.ModelAdmin):
"""Layer admin."""
Expand All @@ -39,6 +49,8 @@ class LayerAdmin(admin.ModelAdmin):
form = LayerForm
inlines = [LayerAttributeInline]
filter_horizontal = ['styles']
actions = [generate_pmtiles]


def get_form(self, request, *args, **kwargs):
"""Return form."""
Expand Down
97 changes: 97 additions & 0 deletions django_project/cloud_native_gis/api/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# coding=utf-8
"""Cloud Native GIS."""

from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from cloud_native_gis.utils.geometry import (
query_features
)
from cloud_native_gis.models.layer import Layer


class ContextAPIView(APIView):
"""
Context API endpoint for collection queries.

Only accessible to authenticated users.
Validates the query, processes data, and returns results.
"""

permission_classes = [IsAuthenticated]

def get(self, request):
"""Handle GET requests."""
try:
key = request.GET.get('key', None)
attributes = request.GET.get('attr', '')
x = request.GET.get('x', None)
y = request.GET.get('y', None)
if None in [key, x, y]:
raise KeyError('Required request argument ('
'registry, key, x, y) missing.')

srid = request.GET.get('srid', 4326)

x_list = x.split(',')
y_list = y.split(',')

if len(x_list) != len(y_list):
raise ValueError(
'The number of x and y coordinates must be the same')

try:
coordinates = [
(float(x), float(y)) for x, y in zip(x_list, y_list)]
except ValueError:
raise ValueError(
'All x and y values must be valid floats.')

try:
tolerance = float(request.GET.get('tolerance', 10.0))
except ValueError:
raise ValueError('Tolerance should be a float')

registry = request.GET.get('registry', '')
if registry.lower() not in [
'collection', 'service', 'group', 'native']:
raise ValueError('Registry should be "collection", '
'"service" or "group".')

outformat = request.GET.get('outformat', 'geojson').lower()
if outformat not in ['geojson', 'json']:
raise ValueError('Output format should be either '
'json or geojson')

data = []

if registry == 'native':
try:
layer = Layer.objects.get(unique_id=key)
if attributes:
attributes = attributes.split(',')
else:
attributes = layer.attribute_names
data = query_features(
layer.query_table_name,
field_names=attributes,
coordinates=coordinates,
tolerance=tolerance,
srid=srid
)
except Layer.DoesNotExist as e:
return Response(str(e), status=status.HTTP_404_NOT_FOUND)

# Todo : for non native layer
# point = parse_coord(x, y, srid)
# data = Worker(
# registry, key, point, tolerance, outformat).retrieve_all()

return Response(data, status=status.HTTP_200_OK)
except KeyError as e:
return Response(str(e), status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return Response(
str(e), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
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')
18 changes: 18 additions & 0 deletions django_project/cloud_native_gis/migrations/0002_layer_pmtile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2024-10-09 14:07

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('cloud_native_gis', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='layer',
name='pmtile',
field=models.FileField(blank=True, help_text='Optional PMTile file associated with the layer.', null=True, upload_to='pmtile_files/'),
),
]
Loading
Loading