On introduit une API pour avoir un chargement plus fluide de la carte d'édition

Fix #378
This commit is contained in:
Jean-Marie Favreau 2025-05-04 23:19:12 +02:00
parent 0cf9a69525
commit 9a0c7016a5
8 changed files with 107 additions and 35 deletions

View File

@ -0,0 +1 @@
from .place_serializer import PlaceSerializer

View File

@ -0,0 +1,30 @@
from rest_framework import serializers
from ..models import Place
class PlaceSerializer(serializers.ModelSerializer):
lat = serializers.SerializerMethodField()
lng = serializers.SerializerMethodField()
url = serializers.SerializerMethodField()
class Meta:
model = Place
fields = [
"name",
"address",
"postcode",
"city",
"description",
"lat",
"lng",
"url",
]
def get_lat(self, obj):
return obj.location[1] if obj.location else None
def get_lng(self, obj):
return obj.location[0] if obj.location else None
def get_url(self, obj):
return obj.get_absolute_url()

View File

@ -72,6 +72,7 @@ INSTALLED_APPS = [
"django_cleanup.apps.CleanupConfig", "django_cleanup.apps.CleanupConfig",
"django_unused_media", "django_unused_media",
"solo.apps.SoloAppConfig", "solo.apps.SoloAppConfig",
"rest_framework",
] ]
SOLO_CACHE_TIMEOUT = 60 * 15 # 15 minutes SOLO_CACHE_TIMEOUT = 60 * 15 # 15 minutes

View File

@ -375,6 +375,10 @@ var SequentialLoader = function() {
_getMap: function(mapOptions) { _getMap: function(mapOptions) {
var map = new L.Map(this.options.id, mapOptions), layer; var map = new L.Map(this.options.id, mapOptions), layer;
if (window.loadTiles !== undefined) {
window.loadTiles(map);
}
if (this.options.provider == 'google') { if (this.options.provider == 'google') {
layer = new L.gridLayer.googleMutant({ layer = new L.gridLayer.googleMutant({
type: this.options.providerOptions.google.mapType.toLowerCase(), type: this.options.providerOptions.google.mapType.toLowerCase(),
@ -397,25 +401,6 @@ var SequentialLoader = function() {
map.addLayer(layer); map.addLayer(layer);
if ((window.other_markers !== null) && (window.other_markers.length > 0)) {
var layerGroup = L.layerGroup().addTo(map);
window.other_markers.forEach(x => layerGroup.addLayer(x));
map.removeLayer(layerGroup);
map.on('zoomend', function () {
var currentZoom = map.getZoom();
if (currentZoom > 12) {
if (!map.hasLayer(layerGroup)) {
map.addLayer(layerGroup);
}
} else {
if (map.hasLayer(layerGroup)) {
map.removeLayer(layerGroup);
}
}
});
}
return map; return map;
}, },
@ -434,7 +419,8 @@ var SequentialLoader = function() {
var self = this, var self = this,
markerOptions = { markerOptions = {
draggable: true, draggable: true,
icon: window.pinIcon icon: window.pinIcon,
zIndexOffset: 1000,
}; };
var marker = L.marker(center, markerOptions).addTo(map); var marker = L.marker(center, markerOptions).addTo(map);

View File

@ -37,18 +37,51 @@
shadowSize: [19, 19] shadowSize: [19, 19]
}); });
window.loadedTiles = new Set();
window.loadTiles = (map) => {
var layerGroup = L.layerGroup().addTo(map);
var dl_places = () => {
const zoom = map.getZoom();
if (zoom < 12) {
if (map.hasLayer(layerGroup))
map.removeLayer(layerGroup);
return;
}
else
if (!map.hasLayer(layerGroup)) {
map.addLayer(layerGroup);
layerGroup.bringToBack();
}
window.other_markers = []; const bounds = map.getBounds();
{% with cache_timeout=user.is_authenticated|yesno:"300,6000" %} const tileSize = 0.1;
{% cache cache_timeout place_lists_js user.is_authenticated %}
const latStart = Math.floor(bounds.getSouth() / tileSize);
const latEnd = Math.ceil(bounds.getNorth() / tileSize);
const lngStart = Math.floor(bounds.getWest() / tileSize);
const lngEnd = Math.ceil(bounds.getEast() / tileSize);
for (let x = lngStart; x <= lngEnd; x++) {
for (let y = latStart; y <= latEnd; y++) {
const tileKey = `${x}_${y}`;
if (window.loadedTiles.has(tileKey)) continue;
fetch(`/api/places/tile/?tile_x=${x}&tile_y=${y}&tile_size=${tileSize}`)
.then((res) => res.json())
.then((data) => {
data.forEach((place) => {
layerGroup.addLayer(L.marker([place.lat, place.lng], {'icon': circleIcon})
.bindPopup(`<a href="${place.url}">${place.name}</a>`));
});
window.loadedTiles.add(tileKey);
});
}
}
};
map.on("moveend", dl_places);
dl_places();
};
{% if place_list %}
{% for place in place_list %}
window.other_markers.push(L.marker([{{ place.location|tocoords }}], {'icon': circleIcon}).bindPopup('<a href="{{ place.get_absolute_url }}">{{ place.name }}</a><br />{% if place.address %}{{ place.address }}, {% endif %}{{ place.city }}'));
{% endfor %}
{% endif %}
{% endcache %}
{% endwith %}
</script> </script>
<link href="{% static 'css/django_better_admin_arrayfield.min.css' %}" <link href="{% static 'css/django_better_admin_arrayfield.min.css' %}"
type="text/css" type="text/css"

View File

@ -116,6 +116,7 @@ from .views import (
UnknownPlaceAddView, UnknownPlaceAddView,
UnknownPlacesListView, UnknownPlacesListView,
fix_unknown_places, fix_unknown_places,
PlaceTileView,
# Search # Search
event_search, event_search,
event_search_full, event_search_full,
@ -550,6 +551,8 @@ urlpatterns = [
load_specialperiods_from_ical, load_specialperiods_from_ical,
name="load_specialperiods_from_ical", name="load_specialperiods_from_ical",
), ),
# API
path("api/places/tile/", PlaceTileView.as_view(), name="place_list"),
] ]
if settings.DEBUG: if settings.DEBUG:

View File

@ -11,11 +11,15 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import ListView, UpdateView, CreateView, DeleteView from django.views.generic import ListView, UpdateView, CreateView, DeleteView
from django.contrib.gis.measure import D from django.contrib.gis.measure import D
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from rest_framework.views import APIView
from rest_framework.response import Response
from django.contrib.gis.geos import Polygon
from .utils import get_event_qs from .utils import get_event_qs
from ..forms import PlaceForm, EventAddPlaceForm from ..forms import PlaceForm, EventAddPlaceForm
from ..models import Place, Event from ..models import Place, Event
from ..utils import PlaceGuesser from ..utils import PlaceGuesser
from ..serializers import PlaceSerializer
class PlaceListView(ListView): class PlaceListView(ListView):
@ -123,11 +127,6 @@ class PlaceCreateView(
success_message = _("The place has been successfully created.") success_message = _("The place has been successfully created.")
form_class = PlaceForm form_class = PlaceForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["place_list"] = Place.objects.all().only("location", "name", "pk")
return context
class PlaceDeleteView(PermissionRequiredMixin, DeleteView): class PlaceDeleteView(PermissionRequiredMixin, DeleteView):
model = Place model = Place
@ -256,3 +255,21 @@ class PlaceFromEventCreateView(PlaceCreateView):
def get_success_url(self): def get_success_url(self):
return self.event.get_absolute_url() return self.event.get_absolute_url()
class PlaceTileView(APIView):
def get(self, request):
tile_x = int(request.query_params.get("tile_x"))
tile_y = int(request.query_params.get("tile_y"))
tile_size = float(request.query_params.get("tile_size", 0.005))
min_lng = tile_x * tile_size
min_lat = tile_y * tile_size
max_lng = min_lng + tile_size
max_lat = min_lat + tile_size
bbox = Polygon.from_bbox((min_lng, min_lat, max_lng, max_lat))
places = Place.objects.filter(location__within=bbox)
serializer = PlaceSerializer(places, many=True)
return Response(serializer.data)

View File

@ -52,3 +52,4 @@ django-unused-media==0.2.2
django-resized==1.0.3 django-resized==1.0.3
django-solo==2.4.0 django-solo==2.4.0
chronostring==0.1.2 chronostring==0.1.2
djangorestframework==3.16.0