2
0

widgets.py 4.4 KB

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