feat: Feed map with real data

This commit is contained in:
2025-06-15 17:25:55 +02:00
parent 16998b85d5
commit 0b483ce630
6 changed files with 88 additions and 6 deletions

View File

@@ -0,0 +1,33 @@
from rest_framework.renderers import BaseRenderer
import json
class GeoJSONRenderer(BaseRenderer):
media_type = 'application/json'
format = 'geojson'
charset = 'utf-8'
def render(self, data, accepted_media_type=None, renderer_context=None):
features = []
for item in data:
coords = item["coordinates"]
if coords:
feature = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": coords
},
"properties": {
k: v for k, v in item.items()
},
"id": f"adoptionnotice/{item['id']}"
}
features.append(feature)
geojson = {
"type": "FeatureCollection",
"generator": "notfellchen",
"features": features
}
return json.dumps(geojson)

View File

@@ -1,6 +1,6 @@
from ..models import Animal, RescueOrganization, AdoptionNotice, Species, Image, Location from ..models import Animal, RescueOrganization, AdoptionNotice, Species, Image, Location
from rest_framework import serializers from rest_framework import serializers
import math
class AdoptionNoticeSerializer(serializers.HyperlinkedModelSerializer): class AdoptionNoticeSerializer(serializers.HyperlinkedModelSerializer):
location = serializers.PrimaryKeyRelatedField( location = serializers.PrimaryKeyRelatedField(
@@ -32,6 +32,42 @@ class AdoptionNoticeSerializer(serializers.HyperlinkedModelSerializer):
"group_only", "location", "location_details", "organization", "photos"] "group_only", "location", "location_details", "organization", "photos"]
class AdoptionNoticeGeoJSONSerializer(serializers.ModelSerializer):
species = serializers.SerializerMethodField()
title = serializers.CharField(source='name')
url = serializers.SerializerMethodField()
location_hr = serializers.SerializerMethodField()
coordinates = serializers.SerializerMethodField()
class Meta:
model = AdoptionNotice
fields = ('id', 'species', 'title', 'description', 'url', 'location_hr', 'coordinates')
def get_species(self, obj):
return None
def get_url(self, obj):
return obj.get_absolute_url()
def get_coordinates(self, obj):
"""
Coordinates are randomly moved around real location, roughly in a circle. The object id is used as angle so that
points are always displayed at the same location (as if they were a seed for a random function).
It's not exactly a circle, because the earth is round.
"""
if obj.location:
longitude_addition = math.sin(obj.id)/2000
latitude_addition = math.cos(obj.id)/2000
return [obj.location.longitude+longitude_addition, obj.location.latitude+latitude_addition]
return None
def get_location_hr(self, obj):
if obj.location:
return f"{obj.location.city}"
return None
class AnimalCreateSerializer(serializers.ModelSerializer): class AnimalCreateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Animal model = Animal

View File

@@ -1,11 +1,13 @@
from django.urls import path from django.urls import path
from .views import ( from .views import (
AdoptionNoticeApiView, AdoptionNoticeApiView,
AnimalApiView, RescueOrganizationApiView, AddImageApiView, SpeciesApiView, LocationApiView AnimalApiView, RescueOrganizationApiView, AddImageApiView, SpeciesApiView, LocationApiView,
AdoptionNoticeGeoJSONView
) )
urlpatterns = [ urlpatterns = [
path("adoption_notice", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-list"), path("adoption_notice", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-list"),
path("adoption_notice.geojson", AdoptionNoticeGeoJSONView.as_view(), name="api-adoption-notice-list-geojson"),
path("adoption_notice/<int:id>/", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-detail"), path("adoption_notice/<int:id>/", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-detail"),
path("animals/", AnimalApiView.as_view(), name="api-animal-list"), path("animals/", AnimalApiView.as_view(), name="api-animal-list"),
path("animals/<int:id>/", AnimalApiView.as_view(), name="api-animal-detail"), path("animals/<int:id>/", AnimalApiView.as_view(), name="api-animal-detail"),

View File

@@ -1,13 +1,16 @@
from django.db.models import Q from django.db.models import Q
from rest_framework.generics import ListAPIView
from fellchensammlung.api.serializers import LocationSerializer from fellchensammlung.api.serializers import LocationSerializer, AdoptionNoticeGeoJSONSerializer
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from django.db import transaction from django.db import transaction
from fellchensammlung.models import AdoptionNotice, Animal, Log, TrustLevel, Location from fellchensammlung.models import AdoptionNotice, Animal, Log, TrustLevel, Location, AdoptionNoticeStatus
from fellchensammlung.tasks import post_adoption_notice_save, post_rescue_org_save from fellchensammlung.tasks import post_adoption_notice_save, post_rescue_org_save
from rest_framework import status from rest_framework import status
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from .renderers import GeoJSONRenderer
from .serializers import ( from .serializers import (
AnimalGetSerializer, AnimalGetSerializer,
AnimalCreateSerializer, AnimalCreateSerializer,
@@ -354,3 +357,10 @@ class LocationApiView(APIView):
{"message": "Location created successfully!", "id": location.pk}, {"message": "Location created successfully!", "id": location.pk},
status=status.HTTP_201_CREATED, status=status.HTTP_201_CREATED,
) )
class AdoptionNoticeGeoJSONView(ListAPIView):
queryset = AdoptionNotice.objects.select_related('location').filter(location__isnull=False).filter(
adoptionnoticestatus__major_status=AdoptionNoticeStatus.ACTIVE)
serializer_class = AdoptionNoticeGeoJSONSerializer
renderer_classes = [GeoJSONRenderer]

View File

@@ -1,7 +1,7 @@
{% load custom_tags %} {% load custom_tags %}
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% get_current_language as LANGUAGE_CODE%} {% get_current_language as LANGUAGE_CODE %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE }}"> <html lang="{{ LANGUAGE_CODE }}">
<head> <head>

View File

@@ -63,7 +63,7 @@
// Load adoption notices as geojson // Load adoption notices as geojson
map.addSource('adoption-notices', { map.addSource('adoption-notices', {
type: 'geojson', type: 'geojson',
data: '{% static "fellchensammlung/ans.geojson" %}', data: '{% url "api-adoption-notice-list-geojson" %}',
cluster: true, cluster: true,
clusterMaxZoom: 14, clusterMaxZoom: 14,
clusterRadius: 50 clusterRadius: 50
@@ -121,6 +121,7 @@
} }
}); });
// inspect a cluster on click // inspect a cluster on click
map.on('click', 'clusters', async (e) => { map.on('click', 'clusters', async (e) => {
const features = map.queryRenderedFeatures(e.point, { const features = map.queryRenderedFeatures(e.point, {