fields.py 4.3 KB

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