Interactive Polygon Field Widget
================================
A widget for GeoDjango ``PolygonField`` built on the `Leaflet `_
open-source map library. It keeps the same UX elements as the Leaflet Point Field widget
(toolbar buttons, footer delete action) and draws polygons using pure Leaflet — no extra
JavaScript dependency.
Preview
^^^^^^^
.. image:: /_static/images/leaflet_polygon_interactive.png
Requirements
^^^^^^^^^^^^
There is no requirement to use `Leaflet `_ open-source interactive map JavaScript library.
Key Features
^^^^^^^^^^^^
**Draw Polygon Action:** Click the "Draw Polygon" button to enter drawing mode, then click the map
to drop vertices. Close the ring by clicking the first vertex or double-clicking the map.
**Editable Vertices:** Once a polygon is created, each vertex is a draggable handle. Dragging a
vertex reshapes the polygon and updates the field value.
**Edit Coordinates:** The "Edit Coordinates" dropdown exposes a textarea showing the polygon as WKT.
You can paste a ``POLYGON((...))`` (WKT) and press "Done" to render it.
**Use My Location Action:** Recenters the map on the user's current location.
**Location Search:** A search bar (powered by `leaflet-geosearch `_) lets users find a place by name/address; selecting a result pans/zooms the map there so the area can be drawn. Configurable via the ``geoSearch`` setting (default provider: OpenStreetMap / Nominatim, no API key required).
Settings
^^^^^^^^
``Default Settings``
.. code-block:: python
MAP_WIDGETS = {
"Leaflet": {
"PolygonField": {
"mapOptions": {"zoom": 12, "scrollWheelZoom": False, "zoomControl": True},
"tileLayer": {
"urlTemplate": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
"options": {"maxZoom": 20},
},
"polygonOptions": {"color": "#3388ff", "weight": 3, "fillOpacity": 0.2},
"polygonFitZoom": 14,
"fitBoundsOnLoad": True,
"showZoomNavigation": True,
"mapCenterLocation": get_default_center_coordinates(),
}
}
}
* **mapOptions**: Leaflet `MapOptions `_ passed to the Leaflet map initialization.
* **tileLayer**: TileLayer source configuration. (Do not override the default settings unless you serve the map tiles from a different source than ``openstreetmap.org``.)
* **polygonOptions**: Leaflet `Path `_ styling for the rendered polygon (``color``, ``weight``, ``fillOpacity``).
* **geoSearch**: Location search bar configuration. ``enabled`` (default ``True``) toggles the search input; ``provider`` is the `leaflet-geosearch `_ provider class name (default ``"OpenStreetMapProvider"``); ``providerOptions`` is passed to the provider constructor (e.g. ``{"params": {"key": "YOUR_API_KEY"}}`` for keyed providers).
* **polygonFitZoom**: Maximum zoom used when fitting the map to the polygon bounds or centering on the user location. Default is 14.
* **fitBoundsOnLoad**: Fit the map to the existing polygon bounds when the widget loads. Default is ``True``.
* **showZoomNavigation**: Enable/Disable zoom in/out UI buttons on the map. Default is ``True``.
* **mapCenterLocation**: Initial map center. Defaults to the project timezone based coordinates.
.. Note::
More details about map widget settings usage can be found in the :ref:`settings guide `.
Usage
^^^^^
In the Django project settings file, the ``MAP_WIDGETS`` dictionary can be defined to customize the default settings.
.. code-block:: python
MAP_WIDGETS = {
"Leaflet": {
"PolygonField": {
"mapOptions": {"scrollWheelZoom": True},
"polygonOptions": {"color": "#e74c3c", "fillOpacity": 0.3},
}
}
}
**Django Admin**
.. code-block:: python
import mapwidgets
class ZoneAdmin(admin.ModelAdmin):
list_display = ("name",)
formfield_overrides = {
models.PolygonField: {"widget": mapwidgets.LeafletPolygonFieldWidget}
}
**Django Forms**
.. code-block:: python
import mapwidgets
class ZoneForm(forms.ModelForm):
class Meta:
model = Zone
fields = ("name", "area")
widgets = {
"area": mapwidgets.LeafletPolygonFieldWidget,
}
Javascript Triggers
^^^^^^^^^^^^^^^^^^^
The widget fires jQuery events on ``document`` when the polygon changes. Each handler receives the
polygon GeoJSON, the widget wrapper selector, and the hidden Django input element.
.. code-block:: javascript
$(document).on("leafletPolygonFieldWidget:polygonCreate", function (e, geojson, mapWrapSelector, djangoInput) { });
$(document).on("leafletPolygonFieldWidget:polygonChange", function (e, geojson, mapWrapSelector, djangoInput) { });
$(document).on("leafletPolygonFieldWidget:polygonDelete", function (e, value, mapWrapSelector, djangoInput) { });