diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py index e2966f5c6b..0c9bb5da0f 100644 --- a/django/contrib/gis/db/backends/oracle/operations.py +++ b/django/contrib/gis/db/backends/oracle/operations.py @@ -65,6 +65,8 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations): function_names = { 'Area': 'SDO_GEOM.SDO_AREA', 'AsGeoJSON': 'SDO_UTIL.TO_GEOJSON', + 'AsWKB': 'SDO_UTIL.TO_WKBGEOMETRY', + 'AsWKT': 'SDO_UTIL.TO_WKTGEOMETRY', 'BoundingCircle': 'SDO_GEOM.SDO_MBC', 'Centroid': 'SDO_GEOM.SDO_CENTROID', 'Difference': 'SDO_GEOM.SDO_DIFFERENCE', diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 38ba5b9a0c..852a4c8cf5 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -148,6 +148,8 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): @cached_property def function_names(self): function_names = { + 'AsWKB': 'ST_AsBinary', + 'AsWKT': 'ST_AsText', 'BoundingCircle': 'ST_MinimumBoundingCircle', 'NumPoints': 'ST_NPoints', } diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index a1c049b562..3cdc92b845 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -67,6 +67,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): select = 'CAST (AsEWKB(%s) AS BLOB)' function_names = { + 'AsWKB': 'St_AsBinary', 'ForcePolygonCW': 'ST_ForceLHR', 'Length': 'ST_Length', 'LineLocatePoint': 'ST_Line_Locate_Point', diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py index 4cedbb6504..87d9ba41c2 100644 --- a/django/contrib/gis/db/models/functions.py +++ b/django/contrib/gis/db/models/functions.py @@ -5,7 +5,7 @@ from django.contrib.gis.db.models.sql import AreaField, DistanceField from django.contrib.gis.geos import GEOSGeometry from django.core.exceptions import FieldError from django.db.models import ( - BooleanField, FloatField, IntegerField, TextField, Transform, + BinaryField, BooleanField, FloatField, IntegerField, TextField, Transform, ) from django.db.models.expressions import Func, Value from django.db.models.functions import Cast @@ -209,6 +209,16 @@ class AsSVG(GeoFunc): super().__init__(*expressions, **extra) +class AsWKB(GeoFunc): + output_field = BinaryField() + arity = 1 + + +class AsWKT(GeoFunc): + output_field = TextField() + arity = 1 + + class BoundingCircle(OracleToleranceMixin, GeoFunc): def __init__(self, expression, num_seg=48, **extra): super().__init__(expression, num_seg, **extra) diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index 8010f6dcba..e652f7d3f6 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -364,6 +364,8 @@ Function PostGIS Oracle MariaDB MySQL :class:`AsGML` X X X :class:`AsKML` X X :class:`AsSVG` X X +:class:`AsWKB` X X X X X +:class:`AsWKT` X X X X X :class:`Azimuth` X X (LWGEOM) :class:`BoundingCircle` X X :class:`Centroid` X X X X X diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt index a0403faf9b..651b9d3e6e 100644 --- a/docs/ref/contrib/gis/functions.txt +++ b/docs/ref/contrib/gis/functions.txt @@ -27,9 +27,9 @@ Measurement Relationships Operations Edi :class:`Distance` :class:`BoundingCircle` :class:`Intersection` :class:`MakeValid` :class:`AsGML` :class:`MemSize` :class:`GeometryDistance` :class:`Centroid` :class:`SymDifference` :class:`Reverse` :class:`AsKML` :class:`NumGeometries` :class:`Length` :class:`Envelope` :class:`Union` :class:`Scale` :class:`AsSVG` :class:`NumPoints` -:class:`Perimeter` :class:`LineLocatePoint` :class:`SnapToGrid` :class:`GeoHash` -.. :class:`PointOnSurface` :class:`Transform` -.. :class:`Translate` +:class:`Perimeter` :class:`LineLocatePoint` :class:`SnapToGrid` :class:`AsWKB` +.. :class:`PointOnSurface` :class:`Transform` :class:`AsWKT` +.. :class:`Translate` :class:`GeoHash` ========================= ======================== ====================== ======================= ================== ===================== ``Area`` @@ -168,6 +168,48 @@ Keyword Argument Description __ https://www.w3.org/Graphics/SVG/ +``AsWKB`` +========= + +.. class:: AsWKB(expression, **extra) + +.. versionadded:: 3.1 + +*Availability*: MariaDB, `MySQL +`__, +Oracle, `PostGIS `__, SpatiaLite + +Accepts a single geographic field or expression and returns a `Well-known +binary (WKB)`__ representation of the geometry. + +Example:: + + >>> bytes(City.objects.annotate(wkb=AsWKB('point')).get(name='Chelyabinsk').wkb) + b'\x01\x01\x00\x00\x00]3\xf9f\x9b\x91K@\x00X\x1d9\xd2\xb9N@' + +__ https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry#Well-known_binary + +``AsWKT`` +========= + +.. class:: AsWKT(expression, **extra) + +.. versionadded:: 3.1 + +*Availability*: MariaDB, `MySQL +`__, +Oracle, `PostGIS `__, SpatiaLite + +Accepts a single geographic field or expression and returns a `Well-known text +(WKT)`__ representation of the geometry. + +Example:: + + >>> City.objects.annotate(wkt=AsWKT('point')).get(name='Chelyabinsk').wkt + 'POINT (55.137555 61.451728)' + +__ https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry + ``Azimuth`` =========== diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index bff16dc1ab..b7c9806692 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -66,6 +66,9 @@ Minor features * :class:`~django.contrib.gis.db.models.functions.AsGeoJSON` is now supported on Oracle. +* Added the :class:`~django.contrib.gis.db.models.functions.AsWKB` and + :class:`~django.contrib.gis.db.models.functions.AsWKT` functions. + :mod:`django.contrib.messages` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py index a504587ab2..18746f48df 100644 --- a/tests/gis_tests/geoapp/test_functions.py +++ b/tests/gis_tests/geoapp/test_functions.py @@ -147,6 +147,29 @@ class GISFunctionsTests(FuncTestMixin, TestCase): self.assertEqual(svg1, City.objects.annotate(svg=functions.AsSVG('point')).get(name='Pueblo').svg) self.assertEqual(svg2, City.objects.annotate(svg=functions.AsSVG('point', relative=5)).get(name='Pueblo').svg) + @skipUnlessDBFeature('has_AsWKB_function') + def test_aswkb(self): + wkb = City.objects.annotate( + wkb=functions.AsWKB(Point(1, 2, srid=4326)), + ).first().wkb + # WKB is either XDR or NDR encoded. + self.assertIn( + bytes(wkb), + ( + b'\x00\x00\x00\x00\x01?\xf0\x00\x00\x00\x00\x00\x00@\x00\x00' + b'\x00\x00\x00\x00\x00', + b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00' + b'\x00\x00\x00\x00\x00@', + ), + ) + + @skipUnlessDBFeature('has_AsWKT_function') + def test_aswkt(self): + wkt = City.objects.annotate( + wkt=functions.AsWKT(Point(1, 2, srid=4326)), + ).first().wkt + self.assertEqual(wkt, 'POINT (1.0 2.0)' if oracle else 'POINT(1 2)') + @skipUnlessDBFeature("has_Azimuth_function") def test_azimuth(self): # Returns the azimuth in radians.