I have a GeoDjango app following this tutorial but substituting World Borders polygon data as included in the standard GeoDjango tutorial.

I have created serializers and a django rest api that displays geometry intersecting the current bounding box using leaflet. The problem I've noticed is that when I pan around the map, it renders duplicate polygons on top of each other each time I set a new bounding box extent request. This results in the map slowing down as the number of polygons increases and also results in an opaque symbology.

Initial symbology with one render of polygon After rendering multiple geometry after panning around the map

How do I ensure each polygon only renders once and not each time the map location refreshes?


from django.contrib.gis.db import models

class WorldBorder(models.Model):
    # Regular Django fields corresponding to the attributes in the
    # world borders shapefile.
    name = models.CharField(max_length=50)
    area = models.IntegerField()
    pop2005 = models.IntegerField('Population 2005')
    fips = models.CharField('FIPS Code', max_length=2, null=True)
    iso2 = models.CharField('2 Digit ISO', max_length=2)
    iso3 = models.CharField('3 Digit ISO', max_length=3)
    un = models.IntegerField('United Nations Code')
    region = models.IntegerField('Region Code')
    subregion = models.IntegerField('Sub-Region Code')
    lon = models.FloatField()
    lat = models.FloatField()

    # GeoDjango-specific: a geometry field (MultiPolygonField)
    mpoly = models.MultiPolygonField()

    # Returns the string representation of the model.
    def __str__(self):
        return self.name

    class Meta:
        ordering = ['name']
        verbose_name_plural = "World borders"

World borders viewset:

"""World borders API views."""
from rest_framework import viewsets
from rest_framework_gis import filters

from .models import WorldBorder
from .serializers import WorldBorderSerializer

class WorldBorderViewSet(viewsets.ReadOnlyModelViewSet):
    """World border view set."""

    bbox_filter_field = "mpoly"
    filter_backends = (filters.InBBoxFilter,)
    queryset = WorldBorder.objects.all()
    bbox_filter_include_overlapping = True
    serializer_class = WorldBorderSerializer


from rest_framework_gis import serializers

from .models import WorldBorder

class WorldBorderSerializer(serializers.GeoFeatureModelSerializer):
    """world border GeoJSON serializer."""

    class Meta:
        """World border serializer meta class."""

        fields = ("id", "name")
        geo_field = "mpoly"
        model = WorldBorder

leaflet map js

const copy = "© <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors";
const url = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
const osm = L.tileLayer(url, { attribution: copy });
const map = L.map("map", { layers: [osm] });
  .on("locationfound", (e) => map.setView(e.latlng, 8))
  .on("locationerror", () => map.setView([0, 0], 5));

async function load_world_borders() {
    const world_border_url = `/api/world/?in_bbox=${map.getBounds().toBBoxString()}`
    const response = await fetch(world_border_url)
    const geojson = await response.json()
    return geojson
async function render_world_borders() {
    const world_borders = await load_world_borders();
        .bindPopup((layer) => layer.feature.properties.name)
  map.on("moveend", render_world_borders);

edit - I have adjusted leaflet js as follows:

async function render_world_borders() {
    const world_borders = await load_world_borders();
        .bindPopup((layer) => layer.feature.properties.name)
        .myTag = "myGeoJSON";

var removeMarkers = function() {
    map.eachLayer( function(layer) {

      if ( layer.myTag &&  layer.myTag === "myGeoJSON") {



map.on("moveend", removeMarkers);

map.on("moveend", render_world_borders);

This now removes the layers then re-renders. It seems to work better, but I notice particularly when zoomed out that the rendering of the same feature is still occurring as you can see in the image below.

example of polygon features rendering more than once

  • 1
    This happens because you add new GeoJSON layer with borders on each moveend event. Any special reason for rendering layer on each moveend event?
    – TomazicM
    Jan 18, 2022 at 13:37
  • 1
    I’m voting to close this question because it's a cross-post from stackoverflow.com/questions/70755621/… Jan 18, 2022 at 15:41
  • I've deleted the SO post now
    – ferus89
    Jan 18, 2022 at 17:03
  • @TomazicM thanks, I missed that line out. With your advice I have created a function that clears the layers at moveend before rendering again with render_world_borders which seems to work but there are still cases where it renders multiple polygons
    – ferus89
    Jan 18, 2022 at 17:19

1 Answer 1


Since world borders GeoJSON data is requested by bounding box, you indeed have to clear previous features when loading new ones after map zoom or pan.

The simplest was to do that is to first create world borders layer at global level and then when new data is fetched, first clean it and than add new data.

Code could then look something like this:

const bordersLayer = L.geoJSON(null)
  .bindPopup((layer) => layer.feature.properties.name)

async function render_world_borders() {
  const world_borders = await load_world_borders();
  • Thanks, this solved the problem.
    – ferus89
    Jan 19, 2022 at 11:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.