widgets.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import logging
  2. from django.conf import settings
  3. from django.contrib.gis import gdal
  4. from django.contrib.gis.geometry import json_regex
  5. from django.contrib.gis.geos import GEOSException, GEOSGeometry
  6. from django.forms.widgets import Widget
  7. from django.utils import translation
  8. logger = logging.getLogger("django.contrib.gis")
  9. class BaseGeometryWidget(Widget):
  10. """
  11. The base class for rich geometry widgets.
  12. Render a map using the WKT of the geometry.
  13. """
  14. geom_type = "GEOMETRY"
  15. map_srid = 4326
  16. display_raw = False
  17. supports_3d = False
  18. template_name = "" # set on subclasses
  19. def __init__(self, attrs=None):
  20. self.attrs = {}
  21. for key in ("geom_type", "map_srid", "display_raw"):
  22. self.attrs[key] = getattr(self, key)
  23. if attrs:
  24. self.attrs.update(attrs)
  25. def serialize(self, value):
  26. return value.wkt if value else ""
  27. def deserialize(self, value):
  28. try:
  29. return GEOSGeometry(value)
  30. except (GEOSException, ValueError, TypeError) as err:
  31. logger.error("Error creating geometry from value '%s' (%s)", value, err)
  32. return None
  33. def get_context(self, name, value, attrs):
  34. context = super().get_context(name, value, attrs)
  35. # If a string reaches here (via a validation error on another
  36. # field) then just reconstruct the Geometry.
  37. if value and isinstance(value, str):
  38. value = self.deserialize(value)
  39. if value:
  40. # Check that srid of value and map match
  41. if value.srid and value.srid != self.map_srid:
  42. try:
  43. ogr = value.ogr
  44. ogr.transform(self.map_srid)
  45. value = ogr
  46. except gdal.GDALException as err:
  47. logger.error(
  48. "Error transforming geometry from srid '%s' to srid '%s' (%s)",
  49. value.srid,
  50. self.map_srid,
  51. err,
  52. )
  53. geom_type = gdal.OGRGeomType(self.attrs["geom_type"]).name
  54. context.update(
  55. self.build_attrs(
  56. self.attrs,
  57. {
  58. "name": name,
  59. "module": "geodjango_%s" % name.replace("-", "_"), # JS-safe
  60. "serialized": self.serialize(value),
  61. "geom_type": "Geometry" if geom_type == "Unknown" else geom_type,
  62. "STATIC_URL": settings.STATIC_URL,
  63. "LANGUAGE_BIDI": translation.get_language_bidi(),
  64. **(attrs or {}),
  65. },
  66. )
  67. )
  68. return context
  69. class OpenLayersWidget(BaseGeometryWidget):
  70. template_name = "gis/openlayers.html"
  71. map_srid = 3857
  72. class Media:
  73. css = {
  74. "all": (
  75. "https://cdn.jsdelivr.net/npm/ol@v7.2.2/ol.css",
  76. "gis/css/ol3.css",
  77. )
  78. }
  79. js = (
  80. "https://cdn.jsdelivr.net/npm/ol@v7.2.2/dist/ol.js",
  81. "gis/js/OLMapWidget.js",
  82. )
  83. def serialize(self, value):
  84. return value.json if value else ""
  85. def deserialize(self, value):
  86. geom = super().deserialize(value)
  87. # GeoJSON assumes WGS84 (4326). Use the map's SRID instead.
  88. if geom and json_regex.match(value) and self.map_srid != 4326:
  89. geom.srid = self.map_srid
  90. return geom
  91. class OSMWidget(OpenLayersWidget):
  92. """
  93. An OpenLayers/OpenStreetMap-based widget.
  94. """
  95. template_name = "gis/openlayers-osm.html"
  96. default_lon = 5
  97. default_lat = 47
  98. default_zoom = 12
  99. def __init__(self, attrs=None):
  100. super().__init__()
  101. for key in ("default_lon", "default_lat", "default_zoom"):
  102. self.attrs[key] = getattr(self, key)
  103. if attrs:
  104. self.attrs.update(attrs)