locations.py 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. from __future__ import annotations
  2. from marshmallow import ValidationError, fields, validate
  3. from flexmeasures.data.schemas.utils import FMValidationError, MarshmallowClickMixin
  4. class LatitudeLongitudeValidator(validate.Validator):
  5. """Validator which succeeds if the value passed has at most 7 decimal places."""
  6. def __init__(self, *, error: str | None = None):
  7. self.error = error
  8. def __call__(self, value):
  9. if not round(value, 7) == value:
  10. raise FMValidationError(
  11. "Latitudes and longitudes are limited to 7 decimal places."
  12. )
  13. return value
  14. class LatitudeValidator(validate.Validator):
  15. """Validator which succeeds if the value passed is in the range [-90, 90]."""
  16. def __init__(self, *, error: str | None = None, allow_none: bool = False):
  17. self.error = error
  18. self.allow_none = allow_none
  19. def __call__(self, value):
  20. if self.allow_none and value is None:
  21. return
  22. if value < -90:
  23. raise FMValidationError(
  24. f"Latitude {value} exceeds the minimum latitude of -90 degrees."
  25. )
  26. if value > 90:
  27. raise ValidationError(
  28. f"Latitude {value} exceeds the maximum latitude of 90 degrees."
  29. )
  30. return value
  31. class LongitudeValidator(validate.Validator):
  32. """Validator which succeeds if the value passed is in the range [-180, 180]."""
  33. def __init__(self, *, error: str | None = None, allow_none: bool = False):
  34. self.error = error
  35. self.allow_none = allow_none
  36. def __call__(self, value):
  37. if self.allow_none and value is None:
  38. return
  39. if value < -180:
  40. raise FMValidationError(
  41. f"Longitude {value} exceeds the minimum longitude of -180 degrees."
  42. )
  43. if value > 180:
  44. raise ValidationError(
  45. f"Longitude {value} exceeds the maximum longitude of 180 degrees."
  46. )
  47. return value
  48. class LatitudeField(MarshmallowClickMixin, fields.Float):
  49. """Field that deserializes to a latitude float with max 7 decimal places."""
  50. def __init__(self, *args, **kwargs):
  51. super().__init__(*args, **kwargs)
  52. # Insert validation into self.validators so that multiple errors can be stored.
  53. self.validators.insert(0, LatitudeLongitudeValidator())
  54. self.validators.insert(
  55. 0, LatitudeValidator(allow_none=kwargs.get("allow_none", False))
  56. )
  57. class LongitudeField(MarshmallowClickMixin, fields.Float):
  58. """Field that deserializes to a longitude float with max 7 decimal places."""
  59. def __init__(self, *args, **kwargs):
  60. super().__init__(*args, **kwargs)
  61. # Insert validation into self.validators so that multiple errors can be stored.
  62. self.validators.insert(0, LatitudeLongitudeValidator())
  63. self.validators.insert(
  64. 0, LongitudeValidator(allow_none=kwargs.get("allow_none", False))
  65. )