fields.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. from django import forms
  2. from django.contrib.gis.gdal import GDALException
  3. from django.contrib.gis.geos import GEOSException, GEOSGeometry
  4. from django.core.exceptions import ValidationError
  5. from django.utils.translation import gettext_lazy as _
  6. from .widgets import OpenLayersWidget
  7. class GeometryField(forms.Field):
  8. """
  9. This is the basic form field for a Geometry. Any textual input that is
  10. accepted by GEOSGeometry is accepted by this form. By default,
  11. this includes WKT, HEXEWKB, WKB (in a buffer), and GeoJSON.
  12. """
  13. widget = OpenLayersWidget
  14. geom_type = "GEOMETRY"
  15. default_error_messages = {
  16. "required": _("No geometry value provided."),
  17. "invalid_geom": _("Invalid geometry value."),
  18. "invalid_geom_type": _("Invalid geometry type."),
  19. "transform_error": _(
  20. "An error occurred when transforming the geometry "
  21. "to the SRID of the geometry form field."
  22. ),
  23. }
  24. def __init__(self, *, srid=None, geom_type=None, **kwargs):
  25. self.srid = srid
  26. if geom_type is not None:
  27. self.geom_type = geom_type
  28. super().__init__(**kwargs)
  29. self.widget.attrs["geom_type"] = self.geom_type
  30. def to_python(self, value):
  31. """Transform the value to a Geometry object."""
  32. if value in self.empty_values:
  33. return None
  34. if not isinstance(value, GEOSGeometry):
  35. if hasattr(self.widget, "deserialize"):
  36. try:
  37. value = self.widget.deserialize(value)
  38. except GDALException:
  39. value = None
  40. else:
  41. try:
  42. value = GEOSGeometry(value)
  43. except (GEOSException, ValueError, TypeError):
  44. value = None
  45. if value is None:
  46. raise ValidationError(
  47. self.error_messages["invalid_geom"], code="invalid_geom"
  48. )
  49. # Try to set the srid
  50. if not value.srid:
  51. try:
  52. value.srid = self.widget.map_srid
  53. except AttributeError:
  54. if self.srid:
  55. value.srid = self.srid
  56. return value
  57. def clean(self, value):
  58. """
  59. Validate that the input value can be converted to a Geometry object
  60. and return it. Raise a ValidationError if the value cannot be
  61. instantiated as a Geometry.
  62. """
  63. geom = super().clean(value)
  64. if geom is None:
  65. return geom
  66. # Ensuring that the geometry is of the correct type (indicated
  67. # using the OGC string label).
  68. if (
  69. str(geom.geom_type).upper() != self.geom_type
  70. and self.geom_type != "GEOMETRY"
  71. ):
  72. raise ValidationError(
  73. self.error_messages["invalid_geom_type"], code="invalid_geom_type"
  74. )
  75. # Transforming the geometry if the SRID was set.
  76. if self.srid and self.srid != -1 and self.srid != geom.srid:
  77. try:
  78. geom.transform(self.srid)
  79. except GEOSException:
  80. raise ValidationError(
  81. self.error_messages["transform_error"], code="transform_error"
  82. )
  83. return geom
  84. def has_changed(self, initial, data):
  85. """Compare geographic value of data with its initial value."""
  86. try:
  87. data = self.to_python(data)
  88. initial = self.to_python(initial)
  89. except ValidationError:
  90. return True
  91. # Only do a geographic comparison if both values are available
  92. if initial and data:
  93. data.transform(initial.srid)
  94. # If the initial value was not added by the browser, the geometry
  95. # provided may be slightly different, the first time it is saved.
  96. # The comparison is done with a very low tolerance.
  97. return not initial.equals_exact(data, tolerance=0.000001)
  98. else:
  99. # Check for change of state of existence
  100. return bool(initial) != bool(data)
  101. class GeometryCollectionField(GeometryField):
  102. geom_type = "GEOMETRYCOLLECTION"
  103. class PointField(GeometryField):
  104. geom_type = "POINT"
  105. class MultiPointField(GeometryField):
  106. geom_type = "MULTIPOINT"
  107. class LineStringField(GeometryField):
  108. geom_type = "LINESTRING"
  109. class MultiLineStringField(GeometryField):
  110. geom_type = "MULTILINESTRING"
  111. class PolygonField(GeometryField):
  112. geom_type = "POLYGON"
  113. class MultiPolygonField(GeometryField):
  114. geom_type = "MULTIPOLYGON"