diff options
| author | django-bot <ops@djangoproject.com> | 2022-02-03 20:24:19 +0100 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2022-02-07 20:37:05 +0100 |
| commit | 9c19aff7c7561e3a82978a272ecdaad40dda5c00 (patch) | |
| tree | f0506b668a013d0063e5fba3dbf4863b466713ba /django/contrib/gis | |
| parent | f68fa8b45dfac545cfc4111d4e52804c86db68d3 (diff) | |
| download | django-9c19aff7c7561e3a82978a272ecdaad40dda5c00.tar.gz | |
Refs #33476 -- Reformatted code with Black.
Diffstat (limited to 'django/contrib/gis')
112 files changed, 3235 insertions, 2011 deletions
diff --git a/django/contrib/gis/admin/__init__.py b/django/contrib/gis/admin/__init__.py index 4abc4898ae..d5bd54c479 100644 --- a/django/contrib/gis/admin/__init__.py +++ b/django/contrib/gis/admin/__init__.py @@ -1,16 +1,34 @@ from django.contrib.admin import ( - HORIZONTAL, VERTICAL, AdminSite, ModelAdmin, StackedInline, TabularInline, - action, autodiscover, display, register, site, -) -from django.contrib.gis.admin.options import ( - GeoModelAdmin, GISModelAdmin, OSMGeoAdmin, + HORIZONTAL, + VERTICAL, + AdminSite, + ModelAdmin, + StackedInline, + TabularInline, + action, + autodiscover, + display, + register, + site, ) +from django.contrib.gis.admin.options import GeoModelAdmin, GISModelAdmin, OSMGeoAdmin from django.contrib.gis.admin.widgets import OpenLayersWidget __all__ = [ - 'HORIZONTAL', 'VERTICAL', 'AdminSite', 'ModelAdmin', 'StackedInline', - 'TabularInline', 'action', 'autodiscover', 'display', 'register', 'site', - 'GISModelAdmin', 'OpenLayersWidget', + "HORIZONTAL", + "VERTICAL", + "AdminSite", + "ModelAdmin", + "StackedInline", + "TabularInline", + "action", + "autodiscover", + "display", + "register", + "site", + "GISModelAdmin", + "OpenLayersWidget", # RemovedInDjango50Warning. - 'GeoModelAdmin', 'OSMGeoAdmin', + "GeoModelAdmin", + "OSMGeoAdmin", ] diff --git a/django/contrib/gis/admin/options.py b/django/contrib/gis/admin/options.py index 524ba4bdc5..9eb6af6335 100644 --- a/django/contrib/gis/admin/options.py +++ b/django/contrib/gis/admin/options.py @@ -14,11 +14,10 @@ class GeoModelAdminMixin: gis_widget_kwargs = {} def formfield_for_dbfield(self, db_field, request, **kwargs): - if ( - isinstance(db_field, models.GeometryField) and - (db_field.dim < 3 or self.gis_widget.supports_3d) + if isinstance(db_field, models.GeometryField) and ( + db_field.dim < 3 or self.gis_widget.supports_3d ): - kwargs['widget'] = self.gis_widget(**self.gis_widget_kwargs) + kwargs["widget"] = self.gis_widget(**self.gis_widget_kwargs) return db_field.formfield(**kwargs) else: return super().formfield_for_dbfield(db_field, request, **kwargs) @@ -38,6 +37,7 @@ class GeoModelAdmin(ModelAdmin): The administration options class for Geographic models. Map settings may be overloaded from their defaults to create custom maps. """ + # The default map settings that may be overloaded -- still subject # to API changes. default_lon = 0 @@ -60,22 +60,25 @@ class GeoModelAdmin(ModelAdmin): map_width = 600 map_height = 400 map_srid = 4326 - map_template = 'gis/admin/openlayers.html' - openlayers_url = 'https://cdnjs.cloudflare.com/ajax/libs/openlayers/2.13.1/OpenLayers.js' + map_template = "gis/admin/openlayers.html" + openlayers_url = ( + "https://cdnjs.cloudflare.com/ajax/libs/openlayers/2.13.1/OpenLayers.js" + ) point_zoom = num_zoom - 6 - wms_url = 'http://vmap0.tiles.osgeo.org/wms/vmap0' - wms_layer = 'basic' - wms_name = 'OpenLayers WMS' - wms_options = {'format': 'image/jpeg'} + wms_url = "http://vmap0.tiles.osgeo.org/wms/vmap0" + wms_layer = "basic" + wms_name = "OpenLayers WMS" + wms_options = {"format": "image/jpeg"} debug = False widget = OpenLayersWidget def __init__(self, *args, **kwargs): warnings.warn( - 'django.contrib.gis.admin.GeoModelAdmin and OSMGeoAdmin are ' - 'deprecated in favor of django.contrib.admin.ModelAdmin and ' - 'django.contrib.gis.admin.GISModelAdmin.', - RemovedInDjango50Warning, stacklevel=2, + "django.contrib.gis.admin.GeoModelAdmin and OSMGeoAdmin are " + "deprecated in favor of django.contrib.admin.ModelAdmin and " + "django.contrib.gis.admin.GISModelAdmin.", + RemovedInDjango50Warning, + stacklevel=2, ) super().__init__(*args, **kwargs) @@ -92,7 +95,7 @@ class GeoModelAdmin(ModelAdmin): """ if isinstance(db_field, models.GeometryField) and db_field.dim < 3: # Setting the widget with the newly defined widget. - kwargs['widget'] = self.get_map_widget(db_field) + kwargs["widget"] = self.get_map_widget(db_field) return db_field.formfield(**kwargs) else: return super().formfield_for_dbfield(db_field, request, **kwargs) @@ -103,68 +106,75 @@ class GeoModelAdmin(ModelAdmin): in the `widget` attribute) using the settings from the attributes set in this class. """ - is_collection = db_field.geom_type in ('MULTIPOINT', 'MULTILINESTRING', 'MULTIPOLYGON', 'GEOMETRYCOLLECTION') + is_collection = db_field.geom_type in ( + "MULTIPOINT", + "MULTILINESTRING", + "MULTIPOLYGON", + "GEOMETRYCOLLECTION", + ) if is_collection: - if db_field.geom_type == 'GEOMETRYCOLLECTION': - collection_type = 'Any' + if db_field.geom_type == "GEOMETRYCOLLECTION": + collection_type = "Any" else: - collection_type = OGRGeomType(db_field.geom_type.replace('MULTI', '')) + collection_type = OGRGeomType(db_field.geom_type.replace("MULTI", "")) else: - collection_type = 'None' + collection_type = "None" class OLMap(self.widget): template_name = self.map_template geom_type = db_field.geom_type - wms_options = '' + wms_options = "" if self.wms_options: wms_options = ["%s: '%s'" % pair for pair in self.wms_options.items()] - wms_options = ', %s' % ', '.join(wms_options) + wms_options = ", %s" % ", ".join(wms_options) params = { - 'default_lon': self.default_lon, - 'default_lat': self.default_lat, - 'default_zoom': self.default_zoom, - 'display_wkt': self.debug or self.display_wkt, - 'geom_type': OGRGeomType(db_field.geom_type), - 'field_name': db_field.name, - 'is_collection': is_collection, - 'scrollable': self.scrollable, - 'layerswitcher': self.layerswitcher, - 'collection_type': collection_type, - 'is_generic': db_field.geom_type == 'GEOMETRY', - 'is_linestring': db_field.geom_type in ('LINESTRING', 'MULTILINESTRING'), - 'is_polygon': db_field.geom_type in ('POLYGON', 'MULTIPOLYGON'), - 'is_point': db_field.geom_type in ('POINT', 'MULTIPOINT'), - 'num_zoom': self.num_zoom, - 'max_zoom': self.max_zoom, - 'min_zoom': self.min_zoom, - 'units': self.units, # likely should get from object - 'max_resolution': self.max_resolution, - 'max_extent': self.max_extent, - 'modifiable': self.modifiable, - 'mouse_position': self.mouse_position, - 'scale_text': self.scale_text, - 'map_width': self.map_width, - 'map_height': self.map_height, - 'point_zoom': self.point_zoom, - 'srid': self.map_srid, - 'display_srid': self.display_srid, - 'wms_url': self.wms_url, - 'wms_layer': self.wms_layer, - 'wms_name': self.wms_name, - 'wms_options': wms_options, - 'debug': self.debug, + "default_lon": self.default_lon, + "default_lat": self.default_lat, + "default_zoom": self.default_zoom, + "display_wkt": self.debug or self.display_wkt, + "geom_type": OGRGeomType(db_field.geom_type), + "field_name": db_field.name, + "is_collection": is_collection, + "scrollable": self.scrollable, + "layerswitcher": self.layerswitcher, + "collection_type": collection_type, + "is_generic": db_field.geom_type == "GEOMETRY", + "is_linestring": db_field.geom_type + in ("LINESTRING", "MULTILINESTRING"), + "is_polygon": db_field.geom_type in ("POLYGON", "MULTIPOLYGON"), + "is_point": db_field.geom_type in ("POINT", "MULTIPOINT"), + "num_zoom": self.num_zoom, + "max_zoom": self.max_zoom, + "min_zoom": self.min_zoom, + "units": self.units, # likely should get from object + "max_resolution": self.max_resolution, + "max_extent": self.max_extent, + "modifiable": self.modifiable, + "mouse_position": self.mouse_position, + "scale_text": self.scale_text, + "map_width": self.map_width, + "map_height": self.map_height, + "point_zoom": self.point_zoom, + "srid": self.map_srid, + "display_srid": self.display_srid, + "wms_url": self.wms_url, + "wms_layer": self.wms_layer, + "wms_name": self.wms_name, + "wms_options": wms_options, + "debug": self.debug, } + return OLMap # RemovedInDjango50Warning. class OSMGeoAdmin(GeoModelAdmin): - map_template = 'gis/admin/osm.html' + map_template = "gis/admin/osm.html" num_zoom = 20 map_srid = spherical_mercator_srid - max_extent = '-20037508,-20037508,20037508,20037508' - max_resolution = '156543.0339' + max_extent = "-20037508,-20037508,20037508,20037508" + max_resolution = "156543.0339" point_zoom = num_zoom - 6 - units = 'm' + units = "m" diff --git a/django/contrib/gis/admin/widgets.py b/django/contrib/gis/admin/widgets.py index 1ce94ec7c2..420c170608 100644 --- a/django/contrib/gis/admin/widgets.py +++ b/django/contrib/gis/admin/widgets.py @@ -7,26 +7,27 @@ from django.utils import translation # Creating a template context that contains Django settings # values needed by admin map templates. -geo_context = {'LANGUAGE_BIDI': translation.get_language_bidi()} -logger = logging.getLogger('django.contrib.gis') +geo_context = {"LANGUAGE_BIDI": translation.get_language_bidi()} +logger = logging.getLogger("django.contrib.gis") class OpenLayersWidget(Textarea): """ Render an OpenLayers map using the WKT of the geometry. """ + def get_context(self, name, value, attrs): # Update the template parameters with any attributes passed in. if attrs: self.params.update(attrs) - self.params['editable'] = self.params['modifiable'] + self.params["editable"] = self.params["modifiable"] else: - self.params['editable'] = True + self.params["editable"] = True # Defaulting the WKT value to a blank string -- this # will be tested in the JavaScript and the appropriate # interface will be constructed. - self.params['wkt'] = '' + self.params["wkt"] = "" # If a string reaches here (via a validation error on another # field) then just reconstruct the Geometry. @@ -37,26 +38,29 @@ class OpenLayersWidget(Textarea): logger.error("Error creating geometry from value '%s' (%s)", value, err) value = None - if (value and value.geom_type.upper() != self.geom_type and - self.geom_type != 'GEOMETRY'): + if ( + value + and value.geom_type.upper() != self.geom_type + and self.geom_type != "GEOMETRY" + ): value = None # Constructing the dictionary of the map options. - self.params['map_options'] = self.map_options() + self.params["map_options"] = self.map_options() # Constructing the JavaScript module name using the name of # the GeometryField (passed in via the `attrs` keyword). # Use the 'name' attr for the field name (rather than 'field') - self.params['name'] = name + self.params["name"] = name # note: we must switch out dashes for underscores since js # functions are created using the module variable - js_safe_name = self.params['name'].replace('-', '_') - self.params['module'] = 'geodjango_%s' % js_safe_name + js_safe_name = self.params["name"].replace("-", "_") + self.params["module"] = "geodjango_%s" % js_safe_name if value: # Transforming the geometry to the projection used on the # OpenLayers map. - srid = self.params['srid'] + srid = self.params["srid"] if value.srid != srid: try: ogr = value.ogr @@ -65,15 +69,17 @@ class OpenLayersWidget(Textarea): except GDALException as err: logger.error( "Error transforming geometry from srid '%s' to srid '%s' (%s)", - value.srid, srid, err + value.srid, + srid, + err, ) - wkt = '' + wkt = "" else: wkt = value.wkt # Setting the parameter WKT with that of the transformed # geometry. - self.params['wkt'] = wkt + self.params["wkt"] = wkt self.params.update(geo_context) return self.params @@ -82,30 +88,31 @@ class OpenLayersWidget(Textarea): """Build the map options hash for the OpenLayers template.""" # JavaScript construction utilities for the Bounds and Projection. def ol_bounds(extent): - return 'new OpenLayers.Bounds(%s)' % extent + return "new OpenLayers.Bounds(%s)" % extent def ol_projection(srid): return 'new OpenLayers.Projection("EPSG:%s")' % srid # An array of the parameter name, the name of their OpenLayers # counterpart, and the type of variable they are. - map_types = [('srid', 'projection', 'srid'), - ('display_srid', 'displayProjection', 'srid'), - ('units', 'units', str), - ('max_resolution', 'maxResolution', float), - ('max_extent', 'maxExtent', 'bounds'), - ('num_zoom', 'numZoomLevels', int), - ('max_zoom', 'maxZoomLevels', int), - ('min_zoom', 'minZoomLevel', int), - ] + map_types = [ + ("srid", "projection", "srid"), + ("display_srid", "displayProjection", "srid"), + ("units", "units", str), + ("max_resolution", "maxResolution", float), + ("max_extent", "maxExtent", "bounds"), + ("num_zoom", "numZoomLevels", int), + ("max_zoom", "maxZoomLevels", int), + ("min_zoom", "minZoomLevel", int), + ] # Building the map options hash. map_options = {} for param_name, js_name, option_type in map_types: if self.params.get(param_name, False): - if option_type == 'srid': + if option_type == "srid": value = ol_projection(self.params[param_name]) - elif option_type == 'bounds': + elif option_type == "bounds": value = ol_bounds(self.params[param_name]) elif option_type in (float, int): value = self.params[param_name] diff --git a/django/contrib/gis/apps.py b/django/contrib/gis/apps.py index e582e76760..6282501056 100644 --- a/django/contrib/gis/apps.py +++ b/django/contrib/gis/apps.py @@ -4,9 +4,11 @@ from django.utils.translation import gettext_lazy as _ class GISConfig(AppConfig): - default_auto_field = 'django.db.models.AutoField' - name = 'django.contrib.gis' + default_auto_field = "django.db.models.AutoField" + name = "django.contrib.gis" verbose_name = _("GIS") def ready(self): - serializers.BUILTIN_SERIALIZERS.setdefault('geojson', 'django.contrib.gis.serializers.geojson') + serializers.BUILTIN_SERIALIZERS.setdefault( + "geojson", "django.contrib.gis.serializers.geojson" + ) diff --git a/django/contrib/gis/db/backends/base/adapter.py b/django/contrib/gis/db/backends/base/adapter.py index f6f271915d..b472e3aacb 100644 --- a/django/contrib/gis/db/backends/base/adapter.py +++ b/django/contrib/gis/db/backends/base/adapter.py @@ -2,14 +2,16 @@ class WKTAdapter: """ An adaptor for Geometries sent to the MySQL and Oracle database backends. """ + def __init__(self, geom): self.wkt = geom.wkt self.srid = geom.srid def __eq__(self, other): return ( - isinstance(other, WKTAdapter) and - self.wkt == other.wkt and self.srid == other.srid + isinstance(other, WKTAdapter) + and self.wkt == other.wkt + and self.srid == other.srid ) def __hash__(self): diff --git a/django/contrib/gis/db/backends/base/features.py b/django/contrib/gis/db/backends/base/features.py index cf52c4e122..cc4ce1046b 100644 --- a/django/contrib/gis/db/backends/base/features.py +++ b/django/contrib/gis/db/backends/base/features.py @@ -60,15 +60,15 @@ class BaseSpatialFeatures: @property def supports_bbcontains_lookup(self): - return 'bbcontains' in self.connection.ops.gis_operators + return "bbcontains" in self.connection.ops.gis_operators @property def supports_contained_lookup(self): - return 'contained' in self.connection.ops.gis_operators + return "contained" in self.connection.ops.gis_operators @property def supports_crosses_lookup(self): - return 'crosses' in self.connection.ops.gis_operators + return "crosses" in self.connection.ops.gis_operators @property def supports_distances_lookups(self): @@ -76,11 +76,11 @@ class BaseSpatialFeatures: @property def supports_dwithin_lookup(self): - return 'dwithin' in self.connection.ops.gis_operators + return "dwithin" in self.connection.ops.gis_operators @property def supports_relate_lookup(self): - return 'relate' in self.connection.ops.gis_operators + return "relate" in self.connection.ops.gis_operators @property def supports_isvalid_lookup(self): @@ -104,7 +104,7 @@ class BaseSpatialFeatures: return models.Union not in self.connection.ops.disallowed_aggregates def __getattr__(self, name): - m = re.match(r'has_(\w*)_function$', name) + m = re.match(r"has_(\w*)_function$", name) if m: func_name = m[1] return func_name not in self.connection.ops.unsupported_functions diff --git a/django/contrib/gis/db/backends/base/models.py b/django/contrib/gis/db/backends/base/models.py index c25c1ccdee..589c872da6 100644 --- a/django/contrib/gis/db/backends/base/models.py +++ b/django/contrib/gis/db/backends/base/models.py @@ -6,13 +6,14 @@ class SpatialRefSysMixin: The SpatialRefSysMixin is a class used by the database-dependent SpatialRefSys objects to reduce redundant code. """ + @property def srs(self): """ Return a GDAL SpatialReference object. """ # TODO: Is caching really necessary here? Is complexity worth it? - if hasattr(self, '_srs'): + if hasattr(self, "_srs"): # Returning a clone of the cached SpatialReference object. return self._srs.clone() else: @@ -31,7 +32,10 @@ class SpatialRefSysMixin: except Exception as e: msg = e - raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg)) + raise Exception( + "Could not get OSR SpatialReference from WKT: %s\nError:\n%s" + % (self.wkt, msg) + ) @property def ellipsoid(self): @@ -49,12 +53,12 @@ class SpatialRefSysMixin: @property def spheroid(self): "Return the spheroid name for this spatial reference." - return self.srs['spheroid'] + return self.srs["spheroid"] @property def datum(self): "Return the datum for this spatial reference." - return self.srs['datum'] + return self.srs["datum"] @property def projected(self): @@ -117,7 +121,7 @@ class SpatialRefSysMixin: """ srs = gdal.SpatialReference(wkt) sphere_params = srs.ellipsoid - sphere_name = srs['spheroid'] + sphere_name = srs["spheroid"] if not string: return sphere_name, sphere_params diff --git a/django/contrib/gis/db/backends/base/operations.py b/django/contrib/gis/db/backends/base/operations.py index 84b1785a1c..5e56b82a78 100644 --- a/django/contrib/gis/db/backends/base/operations.py +++ b/django/contrib/gis/db/backends/base/operations.py @@ -1,8 +1,7 @@ from django.contrib.gis.db.models import GeometryField from django.contrib.gis.db.models.functions import Distance -from django.contrib.gis.measure import ( - Area as AreaMeasure, Distance as DistanceMeasure, -) +from django.contrib.gis.measure import Area as AreaMeasure +from django.contrib.gis.measure import Distance as DistanceMeasure from django.db import NotSupportedError from django.utils.functional import cached_property @@ -18,7 +17,7 @@ class BaseSpatialOperations: spatial_version = None # How the geometry column should be selected. - select = '%s' + select = "%s" @cached_property def select_extent(self): @@ -27,7 +26,7 @@ class BaseSpatialOperations: # Aggregates disallowed_aggregates = () - geom_func_prefix = '' + geom_func_prefix = "" # Mapping between Django function names and backend names, when names do not # match; used in spatial_function_name(). @@ -35,12 +34,36 @@ class BaseSpatialOperations: # Set of known unsupported functions of the backend unsupported_functions = { - 'Area', 'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG', 'Azimuth', - 'BoundingCircle', 'Centroid', 'Difference', 'Distance', 'Envelope', - 'GeoHash', 'GeometryDistance', 'Intersection', 'IsValid', 'Length', - 'LineLocatePoint', 'MakeValid', 'MemSize', 'NumGeometries', - 'NumPoints', 'Perimeter', 'PointOnSurface', 'Reverse', 'Scale', - 'SnapToGrid', 'SymDifference', 'Transform', 'Translate', 'Union', + "Area", + "AsGeoJSON", + "AsGML", + "AsKML", + "AsSVG", + "Azimuth", + "BoundingCircle", + "Centroid", + "Difference", + "Distance", + "Envelope", + "GeoHash", + "GeometryDistance", + "Intersection", + "IsValid", + "Length", + "LineLocatePoint", + "MakeValid", + "MemSize", + "NumGeometries", + "NumPoints", + "Perimeter", + "PointOnSurface", + "Reverse", + "Scale", + "SnapToGrid", + "SymDifference", + "Transform", + "Translate", + "Union", } # Constructors @@ -49,10 +72,14 @@ class BaseSpatialOperations: # Default conversion functions for aggregates; will be overridden if implemented # for the spatial backend. def convert_extent(self, box, srid): - raise NotImplementedError('Aggregate extent not implemented for this spatial backend.') + raise NotImplementedError( + "Aggregate extent not implemented for this spatial backend." + ) def convert_extent3d(self, box, srid): - raise NotImplementedError('Aggregate 3D extent not implemented for this spatial backend.') + raise NotImplementedError( + "Aggregate 3D extent not implemented for this spatial backend." + ) # For quoting column values, rather than columns. def geo_quote_name(self, name): @@ -64,14 +91,18 @@ class BaseSpatialOperations: Return the database column type for the geometry field on the spatial backend. """ - raise NotImplementedError('subclasses of BaseSpatialOperations must provide a geo_db_type() method') + raise NotImplementedError( + "subclasses of BaseSpatialOperations must provide a geo_db_type() method" + ) def get_distance(self, f, value, lookup_type): """ Return the distance parameters for the given geometry field, lookup value, and lookup type. """ - raise NotImplementedError('Distance operations not available on this spatial backend.') + raise NotImplementedError( + "Distance operations not available on this spatial backend." + ) def get_geom_placeholder(self, f, value, compiler): """ @@ -80,48 +111,60 @@ class BaseSpatialOperations: stored procedure call to the transformation function of the spatial backend. """ + def transform_value(value, field): return value is not None and value.srid != field.srid - if hasattr(value, 'as_sql'): + if hasattr(value, "as_sql"): return ( - '%s(%%s, %s)' % (self.spatial_function_name('Transform'), f.srid) + "%s(%%s, %s)" % (self.spatial_function_name("Transform"), f.srid) if transform_value(value.output_field, f) - else '%s' + else "%s" ) if transform_value(value, f): # Add Transform() to the SQL placeholder. - return '%s(%s(%%s,%s), %s)' % ( - self.spatial_function_name('Transform'), - self.from_text, value.srid, f.srid, + return "%s(%s(%%s,%s), %s)" % ( + self.spatial_function_name("Transform"), + self.from_text, + value.srid, + f.srid, ) elif self.connection.features.has_spatialrefsys_table: - return '%s(%%s,%s)' % (self.from_text, f.srid) + return "%s(%%s,%s)" % (self.from_text, f.srid) else: # For backwards compatibility on MySQL (#27464). - return '%s(%%s)' % self.from_text + return "%s(%%s)" % self.from_text def check_expression_support(self, expression): if isinstance(expression, self.disallowed_aggregates): raise NotSupportedError( - "%s spatial aggregation is not supported by this database backend." % expression.name + "%s spatial aggregation is not supported by this database backend." + % expression.name ) super().check_expression_support(expression) def spatial_aggregate_name(self, agg_name): - raise NotImplementedError('Aggregate support not implemented for this spatial backend.') + raise NotImplementedError( + "Aggregate support not implemented for this spatial backend." + ) def spatial_function_name(self, func_name): if func_name in self.unsupported_functions: - raise NotSupportedError("This backend doesn't support the %s function." % func_name) + raise NotSupportedError( + "This backend doesn't support the %s function." % func_name + ) return self.function_names.get(func_name, self.geom_func_prefix + func_name) # Routines for getting the OGC-compliant models. def geometry_columns(self): - raise NotImplementedError('Subclasses of BaseSpatialOperations must provide a geometry_columns() method.') + raise NotImplementedError( + "Subclasses of BaseSpatialOperations must provide a geometry_columns() method." + ) def spatial_ref_sys(self): - raise NotImplementedError('subclasses of BaseSpatialOperations must a provide spatial_ref_sys() method') + raise NotImplementedError( + "subclasses of BaseSpatialOperations must a provide spatial_ref_sys() method" + ) distance_expr_for_lookup = staticmethod(Distance) @@ -133,15 +176,17 @@ class BaseSpatialOperations: def get_geometry_converter(self, expression): raise NotImplementedError( - 'Subclasses of BaseSpatialOperations must provide a ' - 'get_geometry_converter() method.' + "Subclasses of BaseSpatialOperations must provide a " + "get_geometry_converter() method." ) def get_area_att_for_field(self, field): if field.geodetic(self.connection): if self.connection.features.supports_area_geodetic: - return 'sq_m' - raise NotImplementedError('Area on geodetic coordinate systems not supported.') + return "sq_m" + raise NotImplementedError( + "Area on geodetic coordinate systems not supported." + ) else: units_name = field.units_name(self.connection) if units_name: @@ -151,7 +196,7 @@ class BaseSpatialOperations: dist_att = None if field.geodetic(self.connection): if self.connection.features.supports_distance_geodetic: - dist_att = 'm' + dist_att = "m" else: units = field.units_name(self.connection) if units: diff --git a/django/contrib/gis/db/backends/mysql/base.py b/django/contrib/gis/db/backends/mysql/base.py index fccea5919d..4abc052e6e 100644 --- a/django/contrib/gis/db/backends/mysql/base.py +++ b/django/contrib/gis/db/backends/mysql/base.py @@ -1,6 +1,4 @@ -from django.db.backends.mysql.base import ( - DatabaseWrapper as MySQLDatabaseWrapper, -) +from django.db.backends.mysql.base import DatabaseWrapper as MySQLDatabaseWrapper from .features import DatabaseFeatures from .introspection import MySQLIntrospection diff --git a/django/contrib/gis/db/backends/mysql/features.py b/django/contrib/gis/db/backends/mysql/features.py index 613ccce9c5..8999a38bf9 100644 --- a/django/contrib/gis/db/backends/mysql/features.py +++ b/django/contrib/gis/db/backends/mysql/features.py @@ -1,7 +1,5 @@ from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures -from django.db.backends.mysql.features import ( - DatabaseFeatures as MySQLDatabaseFeatures, -) +from django.db.backends.mysql.features import DatabaseFeatures as MySQLDatabaseFeatures from django.utils.functional import cached_property @@ -14,13 +12,13 @@ class DatabaseFeatures(BaseSpatialFeatures, MySQLDatabaseFeatures): supports_transform = False supports_null_geometries = False supports_num_points_poly = False - unsupported_geojson_options = {'crs'} + unsupported_geojson_options = {"crs"} @cached_property def empty_intersection_returns_none(self): return ( - not self.connection.mysql_is_mariadb and - self.connection.mysql_version < (5, 7, 5) + not self.connection.mysql_is_mariadb + and self.connection.mysql_version < (5, 7, 5) ) @cached_property @@ -31,13 +29,16 @@ class DatabaseFeatures(BaseSpatialFeatures, MySQLDatabaseFeatures): @cached_property def django_test_skips(self): skips = super().django_test_skips - if ( - not self.connection.mysql_is_mariadb and - self.connection.mysql_version < (8, 0, 0) + if not self.connection.mysql_is_mariadb and self.connection.mysql_version < ( + 8, + 0, + 0, ): - skips.update({ - 'MySQL < 8 gives different results.': { - 'gis_tests.geoapp.tests.GeoLookupTest.test_disjoint_lookup', - }, - }) + skips.update( + { + "MySQL < 8 gives different results.": { + "gis_tests.geoapp.tests.GeoLookupTest.test_disjoint_lookup", + }, + } + ) return skips diff --git a/django/contrib/gis/db/backends/mysql/introspection.py b/django/contrib/gis/db/backends/mysql/introspection.py index ad78595e24..7497ad520c 100644 --- a/django/contrib/gis/db/backends/mysql/introspection.py +++ b/django/contrib/gis/db/backends/mysql/introspection.py @@ -8,14 +8,13 @@ class MySQLIntrospection(DatabaseIntrospection): # Updating the data_types_reverse dictionary with the appropriate # type for Geometry fields. data_types_reverse = DatabaseIntrospection.data_types_reverse.copy() - data_types_reverse[FIELD_TYPE.GEOMETRY] = 'GeometryField' + data_types_reverse[FIELD_TYPE.GEOMETRY] = "GeometryField" def get_geometry_type(self, table_name, description): with self.connection.cursor() as cursor: # In order to get the specific geometry type of the field, # we introspect on the table definition using `DESCRIBE`. - cursor.execute('DESCRIBE %s' % - self.connection.ops.quote_name(table_name)) + cursor.execute("DESCRIBE %s" % self.connection.ops.quote_name(table_name)) # Increment over description info until we get to the geometry # column. for column, typ, null, key, default, extra in cursor.fetchall(): @@ -31,8 +30,8 @@ class MySQLIntrospection(DatabaseIntrospection): def supports_spatial_index(self, cursor, table_name): # Supported with MyISAM/Aria, or InnoDB on MySQL 5.7.5+/MariaDB. storage_engine = self.get_storage_engine(cursor, table_name) - if storage_engine == 'InnoDB': + if storage_engine == "InnoDB": if self.connection.mysql_is_mariadb: return True return self.connection.mysql_version >= (5, 7, 5) - return storage_engine in ('MyISAM', 'Aria') + return storage_engine in ("MyISAM", "Aria") diff --git a/django/contrib/gis/db/backends/mysql/operations.py b/django/contrib/gis/db/backends/mysql/operations.py index 87ab371aa3..4ef014b36b 100644 --- a/django/contrib/gis/db/backends/mysql/operations.py +++ b/django/contrib/gis/db/backends/mysql/operations.py @@ -1,8 +1,6 @@ from django.contrib.gis.db import models from django.contrib.gis.db.backends.base.adapter import WKTAdapter -from django.contrib.gis.db.backends.base.operations import ( - BaseSpatialOperations, -) +from django.contrib.gis.db.backends.base.operations import BaseSpatialOperations from django.contrib.gis.db.backends.utils import SpatialOperator from django.contrib.gis.geos.geometry import GEOSGeometryBase from django.contrib.gis.geos.prototypes.io import wkb_r @@ -12,8 +10,8 @@ from django.utils.functional import cached_property class MySQLOperations(BaseSpatialOperations, DatabaseOperations): - name = 'mysql' - geom_func_prefix = 'ST_' + name = "mysql" + geom_func_prefix = "ST_" Adapter = WKTAdapter @@ -27,51 +25,69 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations): @cached_property def select(self): - return self.geom_func_prefix + 'AsBinary(%s)' + return self.geom_func_prefix + "AsBinary(%s)" @cached_property def from_text(self): - return self.geom_func_prefix + 'GeomFromText' + return self.geom_func_prefix + "GeomFromText" @cached_property def gis_operators(self): operators = { - 'bbcontains': SpatialOperator(func='MBRContains'), # For consistency w/PostGIS API - 'bboverlaps': SpatialOperator(func='MBROverlaps'), # ... - 'contained': SpatialOperator(func='MBRWithin'), # ... - 'contains': SpatialOperator(func='ST_Contains'), - 'crosses': SpatialOperator(func='ST_Crosses'), - 'disjoint': SpatialOperator(func='ST_Disjoint'), - 'equals': SpatialOperator(func='ST_Equals'), - 'exact': SpatialOperator(func='ST_Equals'), - 'intersects': SpatialOperator(func='ST_Intersects'), - 'overlaps': SpatialOperator(func='ST_Overlaps'), - 'same_as': SpatialOperator(func='ST_Equals'), - 'touches': SpatialOperator(func='ST_Touches'), - 'within': SpatialOperator(func='ST_Within'), + "bbcontains": SpatialOperator( + func="MBRContains" + ), # For consistency w/PostGIS API + "bboverlaps": SpatialOperator(func="MBROverlaps"), # ... + "contained": SpatialOperator(func="MBRWithin"), # ... + "contains": SpatialOperator(func="ST_Contains"), + "crosses": SpatialOperator(func="ST_Crosses"), + "disjoint": SpatialOperator(func="ST_Disjoint"), + "equals": SpatialOperator(func="ST_Equals"), + "exact": SpatialOperator(func="ST_Equals"), + "intersects": SpatialOperator(func="ST_Intersects"), + "overlaps": SpatialOperator(func="ST_Overlaps"), + "same_as": SpatialOperator(func="ST_Equals"), + "touches": SpatialOperator(func="ST_Touches"), + "within": SpatialOperator(func="ST_Within"), } if self.connection.mysql_is_mariadb: - operators['relate'] = SpatialOperator(func='ST_Relate') + operators["relate"] = SpatialOperator(func="ST_Relate") return operators disallowed_aggregates = ( - models.Collect, models.Extent, models.Extent3D, models.MakeLine, + models.Collect, + models.Extent, + models.Extent3D, + models.MakeLine, models.Union, ) @cached_property def unsupported_functions(self): unsupported = { - 'AsGML', 'AsKML', 'AsSVG', 'Azimuth', 'BoundingCircle', - 'ForcePolygonCW', 'GeometryDistance', 'LineLocatePoint', - 'MakeValid', 'MemSize', 'Perimeter', 'PointOnSurface', 'Reverse', - 'Scale', 'SnapToGrid', 'Transform', 'Translate', + "AsGML", + "AsKML", + "AsSVG", + "Azimuth", + "BoundingCircle", + "ForcePolygonCW", + "GeometryDistance", + "LineLocatePoint", + "MakeValid", + "MemSize", + "Perimeter", + "PointOnSurface", + "Reverse", + "Scale", + "SnapToGrid", + "Transform", + "Translate", } if self.connection.mysql_is_mariadb: - unsupported.remove('PointOnSurface') - unsupported.update({'GeoHash', 'IsValid'}) + unsupported.remove("PointOnSurface") + unsupported.update({"GeoHash", "IsValid"}) elif self.connection.mysql_version < (5, 7, 5): - unsupported.update({'AsGeoJSON', 'GeoHash', 'IsValid'}) + unsupported.update({"AsGeoJSON", "GeoHash", "IsValid"}) return unsupported def geo_db_type(self, f): @@ -82,10 +98,12 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations): if isinstance(value, Distance): if f.geodetic(self.connection): raise ValueError( - 'Only numeric values of degree units are allowed on ' - 'geodetic distance queries.' + "Only numeric values of degree units are allowed on " + "geodetic distance queries." ) - dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection))) + dist_param = getattr( + value, Distance.unit_attname(f.units_name(self.connection)) + ) else: dist_param = value return [dist_param] @@ -103,4 +121,5 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations): if srid: geom.srid = srid return geom + return converter diff --git a/django/contrib/gis/db/backends/mysql/schema.py b/django/contrib/gis/db/backends/mysql/schema.py index da033ce5d6..b173df0198 100644 --- a/django/contrib/gis/db/backends/mysql/schema.py +++ b/django/contrib/gis/db/backends/mysql/schema.py @@ -4,12 +4,12 @@ from django.contrib.gis.db.models import GeometryField from django.db import OperationalError from django.db.backends.mysql.schema import DatabaseSchemaEditor -logger = logging.getLogger('django.contrib.gis') +logger = logging.getLogger("django.contrib.gis") class MySQLGISSchemaEditor(DatabaseSchemaEditor): - sql_add_spatial_index = 'CREATE SPATIAL INDEX %(index)s ON %(table)s(%(column)s)' - sql_drop_spatial_index = 'DROP INDEX %(index)s ON %(table)s' + sql_add_spatial_index = "CREATE SPATIAL INDEX %(index)s ON %(table)s(%(column)s)" + sql_drop_spatial_index = "DROP INDEX %(index)s ON %(table)s" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -18,7 +18,10 @@ class MySQLGISSchemaEditor(DatabaseSchemaEditor): def skip_default(self, field): # Geometry fields are stored as BLOB/TEXT, for which MySQL < 8.0.13 # doesn't support defaults. - if isinstance(field, GeometryField) and not self._supports_limited_data_type_defaults: + if ( + isinstance(field, GeometryField) + and not self._supports_limited_data_type_defaults + ): return True return super().skip_default(field) @@ -34,10 +37,11 @@ class MySQLGISSchemaEditor(DatabaseSchemaEditor): qn = self.connection.ops.quote_name db_table = model._meta.db_table self.geometry_sql.append( - self.sql_add_spatial_index % { - 'index': qn(self._create_spatial_index_name(model, field)), - 'table': qn(db_table), - 'column': qn(field.column), + self.sql_add_spatial_index + % { + "index": qn(self._create_spatial_index_name(model, field)), + "table": qn(db_table), + "column": qn(field.column), } ) return column_sql @@ -54,21 +58,22 @@ class MySQLGISSchemaEditor(DatabaseSchemaEditor): if isinstance(field, GeometryField) and field.spatial_index: qn = self.connection.ops.quote_name sql = self.sql_drop_spatial_index % { - 'index': qn(self._create_spatial_index_name(model, field)), - 'table': qn(model._meta.db_table), + "index": qn(self._create_spatial_index_name(model, field)), + "table": qn(model._meta.db_table), } try: self.execute(sql) except OperationalError: logger.error( "Couldn't remove spatial index: %s (may be expected " - "if your storage engine doesn't support them).", sql + "if your storage engine doesn't support them).", + sql, ) super().remove_field(model, field) def _create_spatial_index_name(self, model, field): - return '%s_%s_id' % (model._meta.db_table, field.column) + return "%s_%s_id" % (model._meta.db_table, field.column) def create_spatial_indexes(self): for sql in self.geometry_sql: @@ -77,6 +82,7 @@ class MySQLGISSchemaEditor(DatabaseSchemaEditor): except OperationalError: logger.error( "Cannot create SPATIAL INDEX %s. Only MyISAM and (as of " - "MySQL 5.7.5) InnoDB support them.", sql + "MySQL 5.7.5) InnoDB support them.", + sql, ) self.geometry_sql = [] diff --git a/django/contrib/gis/db/backends/oracle/adapter.py b/django/contrib/gis/db/backends/oracle/adapter.py index 3f96178091..7556b121b8 100644 --- a/django/contrib/gis/db/backends/oracle/adapter.py +++ b/django/contrib/gis/db/backends/oracle/adapter.py @@ -19,7 +19,9 @@ class OracleSpatialAdapter(WKTAdapter): if self._polygon_must_be_fixed(geom): geom = self._fix_polygon(geom) elif isinstance(geom, GeometryCollection): - if any(isinstance(g, Polygon) and self._polygon_must_be_fixed(g) for g in geom): + if any( + isinstance(g, Polygon) and self._polygon_must_be_fixed(g) for g in geom + ): geom = self._fix_geometry_collection(geom) self.wkt = geom.wkt @@ -27,12 +29,9 @@ class OracleSpatialAdapter(WKTAdapter): @staticmethod def _polygon_must_be_fixed(poly): - return ( - not poly.empty and - ( - not poly.exterior_ring.is_counterclockwise or - any(x.is_counterclockwise for x in poly) - ) + return not poly.empty and ( + not poly.exterior_ring.is_counterclockwise + or any(x.is_counterclockwise for x in poly) ) @classmethod diff --git a/django/contrib/gis/db/backends/oracle/base.py b/django/contrib/gis/db/backends/oracle/base.py index 0093ef83bb..d43516a72f 100644 --- a/django/contrib/gis/db/backends/oracle/base.py +++ b/django/contrib/gis/db/backends/oracle/base.py @@ -1,6 +1,4 @@ -from django.db.backends.oracle.base import ( - DatabaseWrapper as OracleDatabaseWrapper, -) +from django.db.backends.oracle.base import DatabaseWrapper as OracleDatabaseWrapper from .features import DatabaseFeatures from .introspection import OracleIntrospection diff --git a/django/contrib/gis/db/backends/oracle/features.py b/django/contrib/gis/db/backends/oracle/features.py index 2ba951de42..ee4b296c07 100644 --- a/django/contrib/gis/db/backends/oracle/features.py +++ b/django/contrib/gis/db/backends/oracle/features.py @@ -12,14 +12,16 @@ class DatabaseFeatures(BaseSpatialFeatures, OracleDatabaseFeatures): supports_perimeter_geodetic = True supports_dwithin_distance_expr = False supports_tolerance_parameter = True - unsupported_geojson_options = {'bbox', 'crs', 'precision'} + unsupported_geojson_options = {"bbox", "crs", "precision"} @cached_property def django_test_skips(self): skips = super().django_test_skips - skips.update({ - "Oracle doesn't support spatial operators in constraints.": { - 'gis_tests.gis_migrations.test_operations.OperationTests.test_add_check_constraint', - }, - }) + skips.update( + { + "Oracle doesn't support spatial operators in constraints.": { + "gis_tests.gis_migrations.test_operations.OperationTests.test_add_check_constraint", + }, + } + ) return skips diff --git a/django/contrib/gis/db/backends/oracle/introspection.py b/django/contrib/gis/db/backends/oracle/introspection.py index 493fe74b46..096fee54e1 100644 --- a/django/contrib/gis/db/backends/oracle/introspection.py +++ b/django/contrib/gis/db/backends/oracle/introspection.py @@ -12,7 +12,7 @@ class OracleIntrospection(DatabaseIntrospection): def data_types_reverse(self): return { **super().data_types_reverse, - cx_Oracle.OBJECT: 'GeometryField', + cx_Oracle.OBJECT: "GeometryField", } def get_geometry_type(self, table_name, description): @@ -22,26 +22,26 @@ class OracleIntrospection(DatabaseIntrospection): cursor.execute( 'SELECT "DIMINFO", "SRID" FROM "USER_SDO_GEOM_METADATA" ' 'WHERE "TABLE_NAME"=%s AND "COLUMN_NAME"=%s', - (table_name.upper(), description.name.upper()) + (table_name.upper(), description.name.upper()), ) row = cursor.fetchone() except Exception as exc: raise Exception( - 'Could not find entry in USER_SDO_GEOM_METADATA ' + "Could not find entry in USER_SDO_GEOM_METADATA " 'corresponding to "%s"."%s"' % (table_name, description.name) ) from exc # TODO: Research way to find a more specific geometry field type for # the column's contents. - field_type = 'GeometryField' + field_type = "GeometryField" # Getting the field parameters. field_params = {} dim, srid = row if srid != 4326: - field_params['srid'] = srid + field_params["srid"] = srid # Size of object array (SDO_DIM_ARRAY) is number of dimensions. dim = dim.size() if dim != 2: - field_params['dim'] = dim + field_params["dim"] = dim return field_type, field_params diff --git a/django/contrib/gis/db/backends/oracle/models.py b/django/contrib/gis/db/backends/oracle/models.py index 6876eecefd..f06f73148e 100644 --- a/django/contrib/gis/db/backends/oracle/models.py +++ b/django/contrib/gis/db/backends/oracle/models.py @@ -19,12 +19,12 @@ class OracleGeometryColumns(models.Model): # TODO: Add support for `diminfo` column (type MDSYS.SDO_DIM_ARRAY). class Meta: - app_label = 'gis' - db_table = 'USER_SDO_GEOM_METADATA' + app_label = "gis" + db_table = "USER_SDO_GEOM_METADATA" managed = False def __str__(self): - return '%s - %s (SRID: %s)' % (self.table_name, self.column_name, self.srid) + return "%s - %s (SRID: %s)" % (self.table_name, self.column_name, self.srid) @classmethod def table_name_col(cls): @@ -32,7 +32,7 @@ class OracleGeometryColumns(models.Model): Return the name of the metadata column used to store the feature table name. """ - return 'table_name' + return "table_name" @classmethod def geom_col_name(cls): @@ -40,7 +40,7 @@ class OracleGeometryColumns(models.Model): Return the name of the metadata column used to store the feature geometry column. """ - return 'column_name' + return "column_name" class OracleSpatialRefSys(models.Model, SpatialRefSysMixin): @@ -55,8 +55,8 @@ class OracleSpatialRefSys(models.Model, SpatialRefSysMixin): cs_bounds = models.PolygonField(null=True) class Meta: - app_label = 'gis' - db_table = 'CS_SRS' + app_label = "gis" + db_table = "CS_SRS" managed = False @property diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py index 9b363aaf4e..45010ef517 100644 --- a/django/contrib/gis/db/backends/oracle/operations.py +++ b/django/contrib/gis/db/backends/oracle/operations.py @@ -10,9 +10,7 @@ import re from django.contrib.gis.db import models -from django.contrib.gis.db.backends.base.operations import ( - BaseSpatialOperations, -) +from django.contrib.gis.db.backends.base.operations import BaseSpatialOperations from django.contrib.gis.db.backends.oracle.adapter import OracleSpatialAdapter from django.contrib.gis.db.backends.utils import SpatialOperator from django.contrib.gis.geos.geometry import GEOSGeometry, GEOSGeometryBase @@ -20,7 +18,7 @@ from django.contrib.gis.geos.prototypes.io import wkb_r from django.contrib.gis.measure import Distance from django.db.backends.oracle.operations import DatabaseOperations -DEFAULT_TOLERANCE = '0.05' +DEFAULT_TOLERANCE = "0.05" class SDOOperator(SpatialOperator): @@ -32,57 +30,60 @@ class SDODWithin(SpatialOperator): class SDODisjoint(SpatialOperator): - sql_template = "SDO_GEOM.RELATE(%%(lhs)s, 'DISJOINT', %%(rhs)s, %s) = 'DISJOINT'" % DEFAULT_TOLERANCE + sql_template = ( + "SDO_GEOM.RELATE(%%(lhs)s, 'DISJOINT', %%(rhs)s, %s) = 'DISJOINT'" + % DEFAULT_TOLERANCE + ) class SDORelate(SpatialOperator): sql_template = "SDO_RELATE(%(lhs)s, %(rhs)s, 'mask=%(mask)s') = 'TRUE'" def check_relate_argument(self, arg): - masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON' - mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I) + masks = "TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON" + mask_regex = re.compile(r"^(%s)(\+(%s))*$" % (masks, masks), re.I) if not isinstance(arg, str) or not mask_regex.match(arg): raise ValueError('Invalid SDO_RELATE mask: "%s"' % arg) def as_sql(self, connection, lookup, template_params, sql_params): - template_params['mask'] = sql_params[-1] + template_params["mask"] = sql_params[-1] return super().as_sql(connection, lookup, template_params, sql_params[:-1]) class OracleOperations(BaseSpatialOperations, DatabaseOperations): - name = 'oracle' + name = "oracle" oracle = True disallowed_aggregates = (models.Collect, models.Extent3D, models.MakeLine) Adapter = OracleSpatialAdapter - extent = 'SDO_AGGR_MBR' - unionagg = 'SDO_AGGR_UNION' + extent = "SDO_AGGR_MBR" + unionagg = "SDO_AGGR_UNION" - from_text = 'SDO_GEOMETRY' + from_text = "SDO_GEOMETRY" 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', - 'Distance': 'SDO_GEOM.SDO_DISTANCE', - 'Envelope': 'SDO_GEOM_MBR', - 'Intersection': 'SDO_GEOM.SDO_INTERSECTION', - 'IsValid': 'SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT', - 'Length': 'SDO_GEOM.SDO_LENGTH', - 'NumGeometries': 'SDO_UTIL.GETNUMELEM', - 'NumPoints': 'SDO_UTIL.GETNUMVERTICES', - 'Perimeter': 'SDO_GEOM.SDO_LENGTH', - 'PointOnSurface': 'SDO_GEOM.SDO_POINTONSURFACE', - 'Reverse': 'SDO_UTIL.REVERSE_LINESTRING', - 'SymDifference': 'SDO_GEOM.SDO_XOR', - 'Transform': 'SDO_CS.TRANSFORM', - 'Union': 'SDO_GEOM.SDO_UNION', + "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", + "Distance": "SDO_GEOM.SDO_DISTANCE", + "Envelope": "SDO_GEOM_MBR", + "Intersection": "SDO_GEOM.SDO_INTERSECTION", + "IsValid": "SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT", + "Length": "SDO_GEOM.SDO_LENGTH", + "NumGeometries": "SDO_UTIL.GETNUMELEM", + "NumPoints": "SDO_UTIL.GETNUMVERTICES", + "Perimeter": "SDO_GEOM.SDO_LENGTH", + "PointOnSurface": "SDO_GEOM.SDO_POINTONSURFACE", + "Reverse": "SDO_UTIL.REVERSE_LINESTRING", + "SymDifference": "SDO_GEOM.SDO_XOR", + "Transform": "SDO_CS.TRANSFORM", + "Union": "SDO_GEOM.SDO_UNION", } # We want to get SDO Geometries as WKT because it is much easier to @@ -90,28 +91,39 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations): # However, this adversely affects performance (i.e., Java is called # to convert to WKT on every query). If someone wishes to write a # SDO_GEOMETRY(...) parser in Python, let me know =) - select = 'SDO_UTIL.TO_WKBGEOMETRY(%s)' + select = "SDO_UTIL.TO_WKBGEOMETRY(%s)" gis_operators = { - 'contains': SDOOperator(func='SDO_CONTAINS'), - 'coveredby': SDOOperator(func='SDO_COVEREDBY'), - 'covers': SDOOperator(func='SDO_COVERS'), - 'disjoint': SDODisjoint(), - 'intersects': SDOOperator(func='SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()? - 'equals': SDOOperator(func='SDO_EQUAL'), - 'exact': SDOOperator(func='SDO_EQUAL'), - 'overlaps': SDOOperator(func='SDO_OVERLAPS'), - 'same_as': SDOOperator(func='SDO_EQUAL'), - 'relate': SDORelate(), # Oracle uses a different syntax, e.g., 'mask=inside+touch' - 'touches': SDOOperator(func='SDO_TOUCH'), - 'within': SDOOperator(func='SDO_INSIDE'), - 'dwithin': SDODWithin(), + "contains": SDOOperator(func="SDO_CONTAINS"), + "coveredby": SDOOperator(func="SDO_COVEREDBY"), + "covers": SDOOperator(func="SDO_COVERS"), + "disjoint": SDODisjoint(), + "intersects": SDOOperator( + func="SDO_OVERLAPBDYINTERSECT" + ), # TODO: Is this really the same as ST_Intersects()? + "equals": SDOOperator(func="SDO_EQUAL"), + "exact": SDOOperator(func="SDO_EQUAL"), + "overlaps": SDOOperator(func="SDO_OVERLAPS"), + "same_as": SDOOperator(func="SDO_EQUAL"), + "relate": SDORelate(), # Oracle uses a different syntax, e.g., 'mask=inside+touch' + "touches": SDOOperator(func="SDO_TOUCH"), + "within": SDOOperator(func="SDO_INSIDE"), + "dwithin": SDODWithin(), } unsupported_functions = { - 'AsKML', 'AsSVG', 'Azimuth', 'ForcePolygonCW', 'GeoHash', - 'GeometryDistance', 'LineLocatePoint', 'MakeValid', 'MemSize', - 'Scale', 'SnapToGrid', 'Translate', + "AsKML", + "AsSVG", + "Azimuth", + "ForcePolygonCW", + "GeoHash", + "GeometryDistance", + "LineLocatePoint", + "MakeValid", + "MemSize", + "Scale", + "SnapToGrid", + "Translate", } def geo_quote_name(self, name): @@ -124,15 +136,17 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations): # table. ext_geom = GEOSGeometry(memoryview(clob.read())) gtype = str(ext_geom.geom_type) - if gtype == 'Polygon': + if gtype == "Polygon": # Construct the 4-tuple from the coordinates in the polygon. shell = ext_geom.shell ll, ur = shell[0][:2], shell[2][:2] - elif gtype == 'Point': + elif gtype == "Point": ll = ext_geom.coords[:2] ur = ll else: - raise Exception('Unexpected geometry type returned for extent: %s' % gtype) + raise Exception( + "Unexpected geometry type returned for extent: %s" % gtype + ) xmin, ymin = ll xmax, ymax = ur return (xmin, ymin, xmax, ymax) @@ -145,7 +159,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations): backends, no stored procedure is necessary and it's the same for all geometry types. """ - return 'MDSYS.SDO_GEOMETRY' + return "MDSYS.SDO_GEOMETRY" def get_distance(self, f, value, lookup_type): """ @@ -161,47 +175,47 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations): if f.geodetic(self.connection): dist_param = value.m else: - dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection))) + dist_param = getattr( + value, Distance.unit_attname(f.units_name(self.connection)) + ) else: dist_param = value # dwithin lookups on Oracle require a special string parameter # that starts with "distance=". - if lookup_type == 'dwithin': - dist_param = 'distance=%s' % dist_param + if lookup_type == "dwithin": + dist_param = "distance=%s" % dist_param return [dist_param] def get_geom_placeholder(self, f, value, compiler): if value is None: - return 'NULL' + return "NULL" return super().get_geom_placeholder(f, value, compiler) def spatial_aggregate_name(self, agg_name): """ Return the spatial aggregate SQL name. """ - agg_name = 'unionagg' if agg_name.lower() == 'union' else agg_name.lower() + agg_name = "unionagg" if agg_name.lower() == "union" else agg_name.lower() return getattr(self, agg_name) # Routines for getting the OGC-compliant models. def geometry_columns(self): - from django.contrib.gis.db.backends.oracle.models import ( - OracleGeometryColumns, - ) + from django.contrib.gis.db.backends.oracle.models import OracleGeometryColumns + return OracleGeometryColumns def spatial_ref_sys(self): - from django.contrib.gis.db.backends.oracle.models import ( - OracleSpatialRefSys, - ) + from django.contrib.gis.db.backends.oracle.models import OracleSpatialRefSys + return OracleSpatialRefSys def modify_insert_params(self, placeholder, params): """Drop out insert parameters for NULL placeholder. Needed for Oracle Spatial backend due to #10888. """ - if placeholder == 'NULL': + if placeholder == "NULL": return [] return super().modify_insert_params(placeholder, params) @@ -218,7 +232,8 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations): if srid: geom.srid = srid return geom + return converter def get_area_att_for_field(self, field): - return 'sq_m' + return "sq_m" diff --git a/django/contrib/gis/db/backends/oracle/schema.py b/django/contrib/gis/db/backends/oracle/schema.py index b281abc81b..edc692ffc7 100644 --- a/django/contrib/gis/db/backends/oracle/schema.py +++ b/django/contrib/gis/db/backends/oracle/schema.py @@ -4,7 +4,7 @@ from django.db.backends.utils import strip_quotes, truncate_name class OracleGISSchemaEditor(DatabaseSchemaEditor): - sql_add_geometry_metadata = (""" + sql_add_geometry_metadata = """ INSERT INTO USER_SDO_GEOM_METADATA ("TABLE_NAME", "COLUMN_NAME", "DIMINFO", "SRID") VALUES ( @@ -15,13 +15,15 @@ class OracleGISSchemaEditor(DatabaseSchemaEditor): MDSYS.SDO_DIM_ELEMENT('LAT', %(dim1)s, %(dim3)s, %(tolerance)s) ), %(srid)s - )""") - sql_add_spatial_index = 'CREATE INDEX %(index)s ON %(table)s(%(column)s) INDEXTYPE IS MDSYS.SPATIAL_INDEX' - sql_drop_spatial_index = 'DROP INDEX %(index)s' - sql_clear_geometry_table_metadata = 'DELETE FROM USER_SDO_GEOM_METADATA WHERE TABLE_NAME = %(table)s' + )""" + sql_add_spatial_index = "CREATE INDEX %(index)s ON %(table)s(%(column)s) INDEXTYPE IS MDSYS.SPATIAL_INDEX" + sql_drop_spatial_index = "DROP INDEX %(index)s" + sql_clear_geometry_table_metadata = ( + "DELETE FROM USER_SDO_GEOM_METADATA WHERE TABLE_NAME = %(table)s" + ) sql_clear_geometry_field_metadata = ( - 'DELETE FROM USER_SDO_GEOM_METADATA WHERE TABLE_NAME = %(table)s ' - 'AND COLUMN_NAME = %(column)s' + "DELETE FROM USER_SDO_GEOM_METADATA WHERE TABLE_NAME = %(table)s " + "AND COLUMN_NAME = %(column)s" ) def __init__(self, *args, **kwargs): @@ -41,23 +43,27 @@ class OracleGISSchemaEditor(DatabaseSchemaEditor): if isinstance(field, GeometryField): db_table = model._meta.db_table self.geometry_sql.append( - self.sql_add_geometry_metadata % { - 'table': self.geo_quote_name(db_table), - 'column': self.geo_quote_name(field.column), - 'dim0': field._extent[0], - 'dim1': field._extent[1], - 'dim2': field._extent[2], - 'dim3': field._extent[3], - 'tolerance': field._tolerance, - 'srid': field.srid, + self.sql_add_geometry_metadata + % { + "table": self.geo_quote_name(db_table), + "column": self.geo_quote_name(field.column), + "dim0": field._extent[0], + "dim1": field._extent[1], + "dim2": field._extent[2], + "dim3": field._extent[3], + "tolerance": field._tolerance, + "srid": field.srid, } ) if field.spatial_index: self.geometry_sql.append( - self.sql_add_spatial_index % { - 'index': self.quote_name(self._create_spatial_index_name(model, field)), - 'table': self.quote_name(db_table), - 'column': self.quote_name(field.column), + self.sql_add_spatial_index + % { + "index": self.quote_name( + self._create_spatial_index_name(model, field) + ), + "table": self.quote_name(db_table), + "column": self.quote_name(field.column), } ) return column_sql @@ -68,9 +74,12 @@ class OracleGISSchemaEditor(DatabaseSchemaEditor): def delete_model(self, model): super().delete_model(model) - self.execute(self.sql_clear_geometry_table_metadata % { - 'table': self.geo_quote_name(model._meta.db_table), - }) + self.execute( + self.sql_clear_geometry_table_metadata + % { + "table": self.geo_quote_name(model._meta.db_table), + } + ) def add_field(self, model, field): super().add_field(model, field) @@ -78,14 +87,22 @@ class OracleGISSchemaEditor(DatabaseSchemaEditor): def remove_field(self, model, field): if isinstance(field, GeometryField): - self.execute(self.sql_clear_geometry_field_metadata % { - 'table': self.geo_quote_name(model._meta.db_table), - 'column': self.geo_quote_name(field.column), - }) + self.execute( + self.sql_clear_geometry_field_metadata + % { + "table": self.geo_quote_name(model._meta.db_table), + "column": self.geo_quote_name(field.column), + } + ) if field.spatial_index: - self.execute(self.sql_drop_spatial_index % { - 'index': self.quote_name(self._create_spatial_index_name(model, field)), - }) + self.execute( + self.sql_drop_spatial_index + % { + "index": self.quote_name( + self._create_spatial_index_name(model, field) + ), + } + ) super().remove_field(model, field) def run_geometry_sql(self): @@ -96,4 +113,6 @@ class OracleGISSchemaEditor(DatabaseSchemaEditor): def _create_spatial_index_name(self, model, field): # Oracle doesn't allow object names > 30 characters. Use this scheme # instead of self._create_index_name() for backwards compatibility. - return truncate_name('%s_%s_id' % (strip_quotes(model._meta.db_table), field.column), 30) + return truncate_name( + "%s_%s_id" % (strip_quotes(model._meta.db_table), field.column), 30 + ) diff --git a/django/contrib/gis/db/backends/postgis/adapter.py b/django/contrib/gis/db/backends/postgis/adapter.py index d6cb6ca47d..0e02427811 100644 --- a/django/contrib/gis/db/backends/postgis/adapter.py +++ b/django/contrib/gis/db/backends/postgis/adapter.py @@ -31,7 +31,9 @@ class PostGISAdapter: if proto == ISQLQuote: return self else: - raise Exception('Error implementing psycopg2 protocol. Is psycopg2 installed?') + raise Exception( + "Error implementing psycopg2 protocol. Is psycopg2 installed?" + ) def __eq__(self, other): return isinstance(other, PostGISAdapter) and self.ewkb == other.ewkb @@ -60,9 +62,9 @@ class PostGISAdapter: """ if self.is_geometry: # Psycopg will figure out whether to use E'\\000' or '\000'. - return b'%s(%s)' % ( - b'ST_GeogFromWKB' if self.geography else b'ST_GeomFromEWKB', - self._adapter.getquoted() + return b"%s(%s)" % ( + b"ST_GeogFromWKB" if self.geography else b"ST_GeomFromEWKB", + self._adapter.getquoted(), ) else: # For rasters, add explicit type cast to WKB string. diff --git a/django/contrib/gis/db/backends/postgis/base.py b/django/contrib/gis/db/backends/postgis/base.py index 5b93d7cf1e..87a30004a1 100644 --- a/django/contrib/gis/db/backends/postgis/base.py +++ b/django/contrib/gis/db/backends/postgis/base.py @@ -14,7 +14,7 @@ class DatabaseWrapper(Psycopg2DatabaseWrapper): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - if kwargs.get('alias', '') != NO_DB_ALIAS: + if kwargs.get("alias", "") != NO_DB_ALIAS: self.features = DatabaseFeatures(self) self.ops = PostGISOperations(self) self.introspection = PostGISIntrospection(self) diff --git a/django/contrib/gis/db/backends/postgis/const.py b/django/contrib/gis/db/backends/postgis/const.py index 193aa3fc36..d2ad51799f 100644 --- a/django/contrib/gis/db/backends/postgis/const.py +++ b/django/contrib/gis/db/backends/postgis/const.py @@ -15,13 +15,23 @@ POSTGIS_TO_GDAL = [1, 1, 1, 3, 1, 3, 2, 5, 4, None, 6, 7, None, None] # # Scale, origin, and skew have x and y values. PostGIS currently uses # a fixed endianness (1) and there is only one version (0). -POSTGIS_HEADER_STRUCTURE = 'B H H d d d d d d i H H' +POSTGIS_HEADER_STRUCTURE = "B H H d d d d d d i H H" # Lookup values to convert GDAL pixel types to struct characters. This is # used to pack and unpack the pixel values of PostGIS raster bands. GDAL_TO_STRUCT = [ - None, 'B', 'H', 'h', 'L', 'l', 'f', 'd', - None, None, None, None, + None, + "B", + "H", + "h", + "L", + "l", + "f", + "d", + None, + None, + None, + None, ] # Size of the packed value in bytes for different numerical types. @@ -29,17 +39,17 @@ GDAL_TO_STRUCT = [ # when decomposing them into GDALRasters. # See https://docs.python.org/library/struct.html#format-characters STRUCT_SIZE = { - 'b': 1, # Signed char - 'B': 1, # Unsigned char - '?': 1, # _Bool - 'h': 2, # Short - 'H': 2, # Unsigned short - 'i': 4, # Integer - 'I': 4, # Unsigned Integer - 'l': 4, # Long - 'L': 4, # Unsigned Long - 'f': 4, # Float - 'd': 8, # Double + "b": 1, # Signed char + "B": 1, # Unsigned char + "?": 1, # _Bool + "h": 2, # Short + "H": 2, # Unsigned short + "i": 4, # Integer + "I": 4, # Unsigned Integer + "l": 4, # Long + "L": 4, # Unsigned Long + "f": 4, # Float + "d": 8, # Double } # Pixel type specifies type of pixel values in a band. Storage flag specifies diff --git a/django/contrib/gis/db/backends/postgis/introspection.py b/django/contrib/gis/db/backends/postgis/introspection.py index 0647012b04..b12b7b912f 100644 --- a/django/contrib/gis/db/backends/postgis/introspection.py +++ b/django/contrib/gis/db/backends/postgis/introspection.py @@ -6,11 +6,11 @@ class PostGISIntrospection(DatabaseIntrospection): postgis_oid_lookup = {} # Populated when introspection is performed. ignored_tables = DatabaseIntrospection.ignored_tables + [ - 'geography_columns', - 'geometry_columns', - 'raster_columns', - 'spatial_ref_sys', - 'raster_overviews', + "geography_columns", + "geometry_columns", + "raster_columns", + "spatial_ref_sys", + "raster_overviews", ] def get_field_type(self, data_type, description): @@ -21,9 +21,13 @@ class PostGISIntrospection(DatabaseIntrospection): # requests upon connection initialization, the `data_types_reverse` # dictionary isn't updated until introspection is performed here. with self.connection.cursor() as cursor: - cursor.execute("SELECT oid, typname FROM pg_type WHERE typname IN ('geometry', 'geography')") + cursor.execute( + "SELECT oid, typname FROM pg_type WHERE typname IN ('geometry', 'geography')" + ) self.postgis_oid_lookup = dict(cursor.fetchall()) - self.data_types_reverse.update((oid, 'GeometryField') for oid in self.postgis_oid_lookup) + self.data_types_reverse.update( + (oid, "GeometryField") for oid in self.postgis_oid_lookup + ) return super().get_field_type(data_type, description) def get_geometry_type(self, table_name, description): @@ -34,27 +38,32 @@ class PostGISIntrospection(DatabaseIntrospection): metadata tables to determine the geometry type. """ with self.connection.cursor() as cursor: - cursor.execute(""" + cursor.execute( + """ SELECT t.coord_dimension, t.srid, t.type FROM ( SELECT * FROM geometry_columns UNION ALL SELECT * FROM geography_columns ) AS t WHERE t.f_table_name = %s AND t.f_geometry_column = %s - """, (table_name, description.name)) + """, + (table_name, description.name), + ) row = cursor.fetchone() if not row: - raise Exception('Could not find a geometry or geography column for "%s"."%s"' % - (table_name, description.name)) + raise Exception( + 'Could not find a geometry or geography column for "%s"."%s"' + % (table_name, description.name) + ) dim, srid, field_type = row # OGRGeomType does not require GDAL and makes it easy to convert # from OGC geom type name to Django field. field_type = OGRGeomType(field_type).django # Getting any GeometryField keyword arguments that are not the default. field_params = {} - if self.postgis_oid_lookup.get(description.type_code) == 'geography': - field_params['geography'] = True + if self.postgis_oid_lookup.get(description.type_code) == "geography": + field_params["geography"] = True if srid != 4326: - field_params['srid'] = srid + field_params["srid"] = srid if dim != 2: - field_params['dim'] = dim + field_params["dim"] = dim return field_type, field_params diff --git a/django/contrib/gis/db/backends/postgis/models.py b/django/contrib/gis/db/backends/postgis/models.py index 0cd4d5505c..b7b568274a 100644 --- a/django/contrib/gis/db/backends/postgis/models.py +++ b/django/contrib/gis/db/backends/postgis/models.py @@ -10,6 +10,7 @@ class PostGISGeometryColumns(models.Model): The 'geometry_columns' view from PostGIS. See the PostGIS documentation at Ch. 4.3.2. """ + f_table_catalog = models.CharField(max_length=256) f_table_schema = models.CharField(max_length=256) f_table_name = models.CharField(max_length=256) @@ -19,12 +20,12 @@ class PostGISGeometryColumns(models.Model): type = models.CharField(max_length=30) class Meta: - app_label = 'gis' - db_table = 'geometry_columns' + app_label = "gis" + db_table = "geometry_columns" managed = False def __str__(self): - return '%s.%s - %dD %s field (SRID: %d)' % ( + return "%s.%s - %dD %s field (SRID: %d)" % ( self.f_table_name, self.f_geometry_column, self.coord_dimension, @@ -38,7 +39,7 @@ class PostGISGeometryColumns(models.Model): Return the name of the metadata column used to store the feature table name. """ - return 'f_table_name' + return "f_table_name" @classmethod def geom_col_name(cls): @@ -46,7 +47,7 @@ class PostGISGeometryColumns(models.Model): Return the name of the metadata column used to store the feature geometry column. """ - return 'f_geometry_column' + return "f_geometry_column" class PostGISSpatialRefSys(models.Model, SpatialRefSysMixin): @@ -54,6 +55,7 @@ class PostGISSpatialRefSys(models.Model, SpatialRefSysMixin): The 'spatial_ref_sys' table from PostGIS. See the PostGIS documentation at Ch. 4.2.1. """ + srid = models.IntegerField(primary_key=True) auth_name = models.CharField(max_length=256) auth_srid = models.IntegerField() @@ -61,8 +63,8 @@ class PostGISSpatialRefSys(models.Model, SpatialRefSysMixin): proj4text = models.CharField(max_length=2048) class Meta: - app_label = 'gis' - db_table = 'spatial_ref_sys' + app_label = "gis" + db_table = "spatial_ref_sys" managed = False @property diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 84110fce0f..b11b2efcf3 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -1,9 +1,7 @@ import re from django.conf import settings -from django.contrib.gis.db.backends.base.operations import ( - BaseSpatialOperations, -) +from django.contrib.gis.db.backends.base.operations import BaseSpatialOperations from django.contrib.gis.db.backends.utils import SpatialOperator from django.contrib.gis.db.models import GeometryField, RasterField from django.contrib.gis.gdal import GDALRaster @@ -22,7 +20,7 @@ from .models import PostGISGeometryColumns, PostGISSpatialRefSys from .pgraster import from_pgraster # Identifier to mark raster lookups as bilateral. -BILATERAL = 'bilateral' +BILATERAL = "bilateral" class PostGISOperator(SpatialOperator): @@ -39,56 +37,70 @@ class PostGISOperator(SpatialOperator): def as_sql(self, connection, lookup, template_params, *args): if lookup.lhs.output_field.geography and not self.geography: - raise ValueError('PostGIS geography does not support the "%s" ' - 'function/operator.' % (self.func or self.op,)) + raise ValueError( + 'PostGIS geography does not support the "%s" ' + "function/operator." % (self.func or self.op,) + ) template_params = self.check_raster(lookup, template_params) return super().as_sql(connection, lookup, template_params, *args) def check_raster(self, lookup, template_params): - spheroid = lookup.rhs_params and lookup.rhs_params[-1] == 'spheroid' + spheroid = lookup.rhs_params and lookup.rhs_params[-1] == "spheroid" # Check which input is a raster. - lhs_is_raster = lookup.lhs.field.geom_type == 'RASTER' + lhs_is_raster = lookup.lhs.field.geom_type == "RASTER" rhs_is_raster = isinstance(lookup.rhs, GDALRaster) # Look for band indices and inject them if provided. if lookup.band_lhs is not None and lhs_is_raster: if not self.func: - raise ValueError('Band indices are not allowed for this operator, it works on bbox only.') - template_params['lhs'] = '%s, %s' % (template_params['lhs'], lookup.band_lhs) + raise ValueError( + "Band indices are not allowed for this operator, it works on bbox only." + ) + template_params["lhs"] = "%s, %s" % ( + template_params["lhs"], + lookup.band_lhs, + ) if lookup.band_rhs is not None and rhs_is_raster: if not self.func: - raise ValueError('Band indices are not allowed for this operator, it works on bbox only.') - template_params['rhs'] = '%s, %s' % (template_params['rhs'], lookup.band_rhs) + raise ValueError( + "Band indices are not allowed for this operator, it works on bbox only." + ) + template_params["rhs"] = "%s, %s" % ( + template_params["rhs"], + lookup.band_rhs, + ) # Convert rasters to polygons if necessary. if not self.raster or spheroid: # Operators without raster support. if lhs_is_raster: - template_params['lhs'] = 'ST_Polygon(%s)' % template_params['lhs'] + template_params["lhs"] = "ST_Polygon(%s)" % template_params["lhs"] if rhs_is_raster: - template_params['rhs'] = 'ST_Polygon(%s)' % template_params['rhs'] + template_params["rhs"] = "ST_Polygon(%s)" % template_params["rhs"] elif self.raster == BILATERAL: # Operators with raster support but don't support mixed (rast-geom) # lookups. if lhs_is_raster and not rhs_is_raster: - template_params['lhs'] = 'ST_Polygon(%s)' % template_params['lhs'] + template_params["lhs"] = "ST_Polygon(%s)" % template_params["lhs"] elif rhs_is_raster and not lhs_is_raster: - template_params['rhs'] = 'ST_Polygon(%s)' % template_params['rhs'] + template_params["rhs"] = "ST_Polygon(%s)" % template_params["rhs"] return template_params class ST_Polygon(Func): - function = 'ST_Polygon' + function = "ST_Polygon" def __init__(self, expr): super().__init__(expr) expr = self.source_expressions[0] if isinstance(expr, Value) and not expr._output_field_or_none: - self.source_expressions[0] = Value(expr.value, output_field=RasterField(srid=expr.value.srid)) + self.source_expressions[0] = Value( + expr.value, output_field=RasterField(srid=expr.value.srid) + ) @cached_property def output_field(self): @@ -96,64 +108,70 @@ class ST_Polygon(Func): class PostGISOperations(BaseSpatialOperations, DatabaseOperations): - name = 'postgis' + name = "postgis" postgis = True - geom_func_prefix = 'ST_' + geom_func_prefix = "ST_" Adapter = PostGISAdapter - collect = geom_func_prefix + 'Collect' - extent = geom_func_prefix + 'Extent' - extent3d = geom_func_prefix + '3DExtent' - length3d = geom_func_prefix + '3DLength' - makeline = geom_func_prefix + 'MakeLine' - perimeter3d = geom_func_prefix + '3DPerimeter' - unionagg = geom_func_prefix + 'Union' + collect = geom_func_prefix + "Collect" + extent = geom_func_prefix + "Extent" + extent3d = geom_func_prefix + "3DExtent" + length3d = geom_func_prefix + "3DLength" + makeline = geom_func_prefix + "MakeLine" + perimeter3d = geom_func_prefix + "3DPerimeter" + unionagg = geom_func_prefix + "Union" gis_operators = { - 'bbcontains': PostGISOperator(op='~', raster=True), - 'bboverlaps': PostGISOperator(op='&&', geography=True, raster=True), - 'contained': PostGISOperator(op='@', raster=True), - 'overlaps_left': PostGISOperator(op='&<', raster=BILATERAL), - 'overlaps_right': PostGISOperator(op='&>', raster=BILATERAL), - 'overlaps_below': PostGISOperator(op='&<|'), - 'overlaps_above': PostGISOperator(op='|&>'), - 'left': PostGISOperator(op='<<'), - 'right': PostGISOperator(op='>>'), - 'strictly_below': PostGISOperator(op='<<|'), - 'strictly_above': PostGISOperator(op='|>>'), - 'same_as': PostGISOperator(op='~=', raster=BILATERAL), - 'exact': PostGISOperator(op='~=', raster=BILATERAL), # alias of same_as - 'contains': PostGISOperator(func='ST_Contains', raster=BILATERAL), - 'contains_properly': PostGISOperator(func='ST_ContainsProperly', raster=BILATERAL), - 'coveredby': PostGISOperator(func='ST_CoveredBy', geography=True, raster=BILATERAL), - 'covers': PostGISOperator(func='ST_Covers', geography=True, raster=BILATERAL), - 'crosses': PostGISOperator(func='ST_Crosses'), - 'disjoint': PostGISOperator(func='ST_Disjoint', raster=BILATERAL), - 'equals': PostGISOperator(func='ST_Equals'), - 'intersects': PostGISOperator(func='ST_Intersects', geography=True, raster=BILATERAL), - 'overlaps': PostGISOperator(func='ST_Overlaps', raster=BILATERAL), - 'relate': PostGISOperator(func='ST_Relate'), - 'touches': PostGISOperator(func='ST_Touches', raster=BILATERAL), - 'within': PostGISOperator(func='ST_Within', raster=BILATERAL), - 'dwithin': PostGISOperator(func='ST_DWithin', geography=True, raster=BILATERAL), + "bbcontains": PostGISOperator(op="~", raster=True), + "bboverlaps": PostGISOperator(op="&&", geography=True, raster=True), + "contained": PostGISOperator(op="@", raster=True), + "overlaps_left": PostGISOperator(op="&<", raster=BILATERAL), + "overlaps_right": PostGISOperator(op="&>", raster=BILATERAL), + "overlaps_below": PostGISOperator(op="&<|"), + "overlaps_above": PostGISOperator(op="|&>"), + "left": PostGISOperator(op="<<"), + "right": PostGISOperator(op=">>"), + "strictly_below": PostGISOperator(op="<<|"), + "strictly_above": PostGISOperator(op="|>>"), + "same_as": PostGISOperator(op="~=", raster=BILATERAL), + "exact": PostGISOperator(op="~=", raster=BILATERAL), # alias of same_as + "contains": PostGISOperator(func="ST_Contains", raster=BILATERAL), + "contains_properly": PostGISOperator( + func="ST_ContainsProperly", raster=BILATERAL + ), + "coveredby": PostGISOperator( + func="ST_CoveredBy", geography=True, raster=BILATERAL + ), + "covers": PostGISOperator(func="ST_Covers", geography=True, raster=BILATERAL), + "crosses": PostGISOperator(func="ST_Crosses"), + "disjoint": PostGISOperator(func="ST_Disjoint", raster=BILATERAL), + "equals": PostGISOperator(func="ST_Equals"), + "intersects": PostGISOperator( + func="ST_Intersects", geography=True, raster=BILATERAL + ), + "overlaps": PostGISOperator(func="ST_Overlaps", raster=BILATERAL), + "relate": PostGISOperator(func="ST_Relate"), + "touches": PostGISOperator(func="ST_Touches", raster=BILATERAL), + "within": PostGISOperator(func="ST_Within", raster=BILATERAL), + "dwithin": PostGISOperator(func="ST_DWithin", geography=True, raster=BILATERAL), } unsupported_functions = set() - select = '%s::bytea' + select = "%s::bytea" select_extent = None @cached_property def function_names(self): function_names = { - 'AsWKB': 'ST_AsBinary', - 'AsWKT': 'ST_AsText', - 'BoundingCircle': 'ST_MinimumBoundingCircle', - 'NumPoints': 'ST_NPoints', + "AsWKB": "ST_AsBinary", + "AsWKT": "ST_AsText", + "BoundingCircle": "ST_MinimumBoundingCircle", + "NumPoints": "ST_NPoints", } if self.spatial_version < (2, 4, 0): - function_names['ForcePolygonCW'] = 'ST_ForceRHR' + function_names["ForcePolygonCW"] = "ST_ForceRHR" return function_names @cached_property @@ -165,13 +183,13 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): # can be mitigated by setting `POSTGIS_VERSION` with a 3-tuple # comprising user-supplied values for the major, minor, and # subminor revision of PostGIS. - if hasattr(settings, 'POSTGIS_VERSION'): + if hasattr(settings, "POSTGIS_VERSION"): version = settings.POSTGIS_VERSION else: # Run a basic query to check the status of the connection so we're # sure we only raise the error below if the problem comes from # PostGIS and not from PostgreSQL itself (see #24862). - self._get_postgis_func('version') + self._get_postgis_func("version") try: vtup = self.postgis_version_tuple() @@ -179,9 +197,9 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): raise ImproperlyConfigured( 'Cannot determine PostGIS version for database "%s" ' 'using command "SELECT postgis_lib_version()". ' - 'GeoDjango requires at least PostGIS version 2.4. ' - 'Was the database created from a spatial database ' - 'template?' % self.connection.settings_dict['NAME'] + "GeoDjango requires at least PostGIS version 2.4. " + "Was the database created from a spatial database " + "template?" % self.connection.settings_dict["NAME"] ) version = vtup[1:] return version @@ -194,7 +212,7 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): """ if box is None: return None - ll, ur = box[4:-1].split(',') + ll, ur = box[4:-1].split(",") xmin, ymin = map(float, ll.split()) xmax, ymax = map(float, ur.split()) return (xmin, ymin, xmax, ymax) @@ -207,7 +225,7 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): """ if box3d is None: return None - ll, ur = box3d[6:-1].split(',') + ll, ur = box3d[6:-1].split(",") xmin, ymin, zmin = map(float, ll.split()) xmax, ymax, zmax = map(float, ur.split()) return (xmin, ymin, zmin, xmax, ymax, zmax) @@ -216,22 +234,24 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): """ Return the database field type for the given spatial field. """ - if f.geom_type == 'RASTER': - return 'raster' + if f.geom_type == "RASTER": + return "raster" # Type-based geometries. # TODO: Support 'M' extension. if f.dim == 3: - geom_type = f.geom_type + 'Z' + geom_type = f.geom_type + "Z" else: geom_type = f.geom_type if f.geography: if f.srid != 4326: - raise NotSupportedError('PostGIS only supports geography columns with an SRID of 4326.') + raise NotSupportedError( + "PostGIS only supports geography columns with an SRID of 4326." + ) - return 'geography(%s,%d)' % (geom_type, f.srid) + return "geography(%s,%d)" % (geom_type, f.srid) else: - return 'geometry(%s,%d)' % (geom_type, f.srid) + return "geometry(%s,%d)" % (geom_type, f.srid) def get_distance(self, f, dist_val, lookup_type): """ @@ -254,12 +274,16 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): if geography: dist_param = value.m elif geodetic: - if lookup_type == 'dwithin': - raise ValueError('Only numeric values of degree units are ' - 'allowed on geographic DWithin queries.') + if lookup_type == "dwithin": + raise ValueError( + "Only numeric values of degree units are " + "allowed on geographic DWithin queries." + ) dist_param = value.m else: - dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection))) + dist_param = getattr( + value, Distance.unit_attname(f.units_name(self.connection)) + ) else: # Assuming the distance is in the units of the field. dist_param = value @@ -272,12 +296,12 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): not in the SRID of the field. Specifically, this routine will substitute in the ST_Transform() function call. """ - transform_func = self.spatial_function_name('Transform') - if hasattr(value, 'as_sql'): + transform_func = self.spatial_function_name("Transform") + if hasattr(value, "as_sql"): if value.field.srid == f.srid: - placeholder = '%s' + placeholder = "%s" else: - placeholder = '%s(%%s, %s)' % (transform_func, f.srid) + placeholder = "%s(%%s, %s)" % (transform_func, f.srid) return placeholder # Get the srid for this object @@ -289,9 +313,9 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): # Adding Transform() to the SQL placeholder if the value srid # is not equal to the field srid. if value_srid is None or value_srid == f.srid: - placeholder = '%s' + placeholder = "%s" else: - placeholder = '%s(%%s, %s)' % (transform_func, f.srid) + placeholder = "%s(%%s, %s)" % (transform_func, f.srid) return placeholder @@ -301,28 +325,28 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): """ # Close out the connection. See #9437. with self.connection.temporary_connection() as cursor: - cursor.execute('SELECT %s()' % func) + cursor.execute("SELECT %s()" % func) return cursor.fetchone()[0] def postgis_geos_version(self): "Return the version of the GEOS library used with PostGIS." - return self._get_postgis_func('postgis_geos_version') + return self._get_postgis_func("postgis_geos_version") def postgis_lib_version(self): "Return the version number of the PostGIS library used with PostgreSQL." - return self._get_postgis_func('postgis_lib_version') + return self._get_postgis_func("postgis_lib_version") def postgis_proj_version(self): """Return the version of the PROJ library used with PostGIS.""" - return self._get_postgis_func('postgis_proj_version') + return self._get_postgis_func("postgis_proj_version") def postgis_version(self): "Return PostGIS version number and compile-time options." - return self._get_postgis_func('postgis_version') + return self._get_postgis_func("postgis_version") def postgis_full_version(self): "Return PostGIS version number and compile-time options." - return self._get_postgis_func('postgis_full_version') + return self._get_postgis_func("postgis_full_version") def postgis_version_tuple(self): """ @@ -337,16 +361,16 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): Return the version of PROJ used by PostGIS as a tuple of the major, minor, and subminor release numbers. """ - proj_regex = re.compile(r'(\d+)\.(\d+)\.(\d+)') + proj_regex = re.compile(r"(\d+)\.(\d+)\.(\d+)") proj_ver_str = self.postgis_proj_version() m = proj_regex.search(proj_ver_str) if m: return tuple(map(int, m.groups())) else: - raise Exception('Could not determine PROJ version from PostGIS.') + raise Exception("Could not determine PROJ version from PostGIS.") def spatial_aggregate_name(self, agg_name): - if agg_name == 'Extent3D': + if agg_name == "Extent3D": return self.extent3d else: return self.geom_func_prefix + agg_name @@ -366,15 +390,15 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): return super().distance_expr_for_lookup( self._normalize_distance_lookup_arg(lhs), self._normalize_distance_lookup_arg(rhs), - **kwargs + **kwargs, ) @staticmethod def _normalize_distance_lookup_arg(arg): is_raster = ( - arg.field.geom_type == 'RASTER' - if hasattr(arg, 'field') else - isinstance(arg, GDALRaster) + arg.field.geom_type == "RASTER" + if hasattr(arg, "field") + else isinstance(arg, GDALRaster) ) return ST_Polygon(arg) if is_raster else arg @@ -384,7 +408,8 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): def converter(value, expression, connection): return None if value is None else GEOSGeometryBase(read(value), geom_class) + return converter def get_area_att_for_field(self, field): - return 'sq_m' + return "sq_m" diff --git a/django/contrib/gis/db/backends/postgis/pgraster.py b/django/contrib/gis/db/backends/postgis/pgraster.py index bfdeb6faf3..22794342ca 100644 --- a/django/contrib/gis/db/backends/postgis/pgraster.py +++ b/django/contrib/gis/db/backends/postgis/pgraster.py @@ -3,8 +3,13 @@ import struct from django.core.exceptions import ValidationError from .const import ( - BANDTYPE_FLAG_HASNODATA, BANDTYPE_PIXTYPE_MASK, GDAL_TO_POSTGIS, - GDAL_TO_STRUCT, POSTGIS_HEADER_STRUCTURE, POSTGIS_TO_GDAL, STRUCT_SIZE, + BANDTYPE_FLAG_HASNODATA, + BANDTYPE_PIXTYPE_MASK, + GDAL_TO_POSTGIS, + GDAL_TO_STRUCT, + POSTGIS_HEADER_STRUCTURE, + POSTGIS_TO_GDAL, + STRUCT_SIZE, ) @@ -12,14 +17,14 @@ def pack(structure, data): """ Pack data into hex string with little endian format. """ - return struct.pack('<' + structure, *data) + return struct.pack("<" + structure, *data) def unpack(structure, data): """ Unpack little endian hexlified binary string into a list. """ - return struct.unpack('<' + structure, bytes.fromhex(data)) + return struct.unpack("<" + structure, bytes.fromhex(data)) def chunk(data, index): @@ -46,7 +51,7 @@ def from_pgraster(data): while data: # Get pixel type for this band pixeltype_with_flags, data = chunk(data, 2) - pixeltype_with_flags = unpack('B', pixeltype_with_flags)[0] + pixeltype_with_flags = unpack("B", pixeltype_with_flags)[0] pixeltype = pixeltype_with_flags & BANDTYPE_PIXTYPE_MASK # Convert datatype from PostGIS to GDAL & get pack type and size @@ -62,11 +67,11 @@ def from_pgraster(data): # Chunk and unpack band data (pack size times nr of pixels) band, data = chunk(data, pack_size * header[10] * header[11]) - band_result = {'data': bytes.fromhex(band)} + band_result = {"data": bytes.fromhex(band)} # Set the nodata value if the nodata flag is set. if pixeltype_with_flags & BANDTYPE_FLAG_HASNODATA: - band_result['nodata_value'] = nodata + band_result["nodata_value"] = nodata # Append band data to band list bands.append(band_result) @@ -81,13 +86,14 @@ def from_pgraster(data): raise ValidationError("Band pixeltypes are not all equal.") return { - 'srid': int(header[9]), - 'width': header[10], 'height': header[11], - 'datatype': pixeltypes[0], - 'origin': (header[5], header[6]), - 'scale': (header[3], header[4]), - 'skew': (header[7], header[8]), - 'bands': bands, + "srid": int(header[9]), + "width": header[10], + "height": header[11], + "datatype": pixeltypes[0], + "origin": (header[5], header[6]), + "scale": (header[3], header[4]), + "skew": (header[7], header[8]), + "bands": bands, } @@ -99,9 +105,18 @@ def to_pgraster(rast): # the endianness and the PostGIS Raster Version, both are fixed by # PostGIS at the moment. rasterheader = ( - 1, 0, len(rast.bands), rast.scale.x, rast.scale.y, - rast.origin.x, rast.origin.y, rast.skew.x, rast.skew.y, - rast.srs.srid, rast.width, rast.height, + 1, + 0, + len(rast.bands), + rast.scale.x, + rast.scale.y, + rast.origin.x, + rast.origin.y, + rast.skew.x, + rast.skew.y, + rast.srs.srid, + rast.width, + rast.height, ) # Pack raster header. @@ -119,7 +134,7 @@ def to_pgraster(rast): # For example, if the byte value is 71, then the datatype is # 71 & ~BANDTYPE_FLAG_HASNODATA = 7 (32BSI) # and the nodata value is True. - structure = 'B' + GDAL_TO_STRUCT[band.datatype()] + structure = "B" + GDAL_TO_STRUCT[band.datatype()] # Get band pixel type in PostGIS notation pixeltype = GDAL_TO_POSTGIS[band.datatype()] diff --git a/django/contrib/gis/db/backends/postgis/schema.py b/django/contrib/gis/db/backends/postgis/schema.py index c574bed84f..77a9096ef4 100644 --- a/django/contrib/gis/db/backends/postgis/schema.py +++ b/django/contrib/gis/db/backends/postgis/schema.py @@ -3,29 +3,33 @@ from django.db.models.expressions import Col, Func class PostGISSchemaEditor(DatabaseSchemaEditor): - geom_index_type = 'GIST' - geom_index_ops_nd = 'GIST_GEOMETRY_OPS_ND' - rast_index_template = 'ST_ConvexHull(%(expressions)s)' + geom_index_type = "GIST" + geom_index_ops_nd = "GIST_GEOMETRY_OPS_ND" + rast_index_template = "ST_ConvexHull(%(expressions)s)" - sql_alter_column_to_3d = "ALTER COLUMN %(column)s TYPE %(type)s USING ST_Force3D(%(column)s)::%(type)s" - sql_alter_column_to_2d = "ALTER COLUMN %(column)s TYPE %(type)s USING ST_Force2D(%(column)s)::%(type)s" + sql_alter_column_to_3d = ( + "ALTER COLUMN %(column)s TYPE %(type)s USING ST_Force3D(%(column)s)::%(type)s" + ) + sql_alter_column_to_2d = ( + "ALTER COLUMN %(column)s TYPE %(type)s USING ST_Force2D(%(column)s)::%(type)s" + ) def geo_quote_name(self, name): return self.connection.ops.geo_quote_name(name) def _field_should_be_indexed(self, model, field): - if getattr(field, 'spatial_index', False): + if getattr(field, "spatial_index", False): return True return super()._field_should_be_indexed(model, field) def _create_index_sql(self, model, *, fields=None, **kwargs): - if fields is None or len(fields) != 1 or not hasattr(fields[0], 'geodetic'): + if fields is None or len(fields) != 1 or not hasattr(fields[0], "geodetic"): return super()._create_index_sql(model, fields=fields, **kwargs) field = fields[0] expressions = None opclasses = None - if field.geom_type == 'RASTER': + if field.geom_type == "RASTER": # For raster fields, wrap index creation SQL statement with ST_ConvexHull. # Indexes on raster columns are based on the convex hull of the raster. expressions = Func(Col(None, field), template=self.rast_index_template) @@ -33,15 +37,15 @@ class PostGISSchemaEditor(DatabaseSchemaEditor): elif field.dim > 2 and not field.geography: # Use "nd" ops which are fast on multidimensional cases opclasses = [self.geom_index_ops_nd] - name = kwargs.get('name') + name = kwargs.get("name") if not name: - name = self._create_index_name(model._meta.db_table, [field.column], '_id') + name = self._create_index_name(model._meta.db_table, [field.column], "_id") return super()._create_index_sql( model, fields=fields, name=name, - using=' USING %s' % self.geom_index_type, + using=" USING %s" % self.geom_index_type, opclasses=opclasses, expressions=expressions, ) @@ -50,7 +54,7 @@ class PostGISSchemaEditor(DatabaseSchemaEditor): """ Special case when dimension changed. """ - if not hasattr(old_field, 'dim') or not hasattr(new_field, 'dim'): + if not hasattr(old_field, "dim") or not hasattr(new_field, "dim"): return super()._alter_column_type_sql(table, old_field, new_field, new_type) if old_field.dim == 2 and new_field.dim == 3: @@ -61,7 +65,8 @@ class PostGISSchemaEditor(DatabaseSchemaEditor): sql_alter = self.sql_alter_column_type return ( ( - sql_alter % { + sql_alter + % { "column": self.quote_name(new_field.column), "type": new_type, }, diff --git a/django/contrib/gis/db/backends/spatialite/adapter.py b/django/contrib/gis/db/backends/spatialite/adapter.py index 2a36159b06..91d14dc3c7 100644 --- a/django/contrib/gis/db/backends/spatialite/adapter.py +++ b/django/contrib/gis/db/backends/spatialite/adapter.py @@ -4,6 +4,7 @@ from django.db.backends.sqlite3.base import Database class SpatiaLiteAdapter(WKTAdapter): "SQLite adapter for geometry objects." + def __conform__(self, protocol): if protocol is Database.PrepareProtocol: return str(self) diff --git a/django/contrib/gis/db/backends/spatialite/base.py b/django/contrib/gis/db/backends/spatialite/base.py index fef7de62af..3359a7a971 100644 --- a/django/contrib/gis/db/backends/spatialite/base.py +++ b/django/contrib/gis/db/backends/spatialite/base.py @@ -2,9 +2,7 @@ from ctypes.util import find_library from django.conf import settings from django.core.exceptions import ImproperlyConfigured -from django.db.backends.sqlite3.base import ( - DatabaseWrapper as SQLiteDatabaseWrapper, -) +from django.db.backends.sqlite3.base import DatabaseWrapper as SQLiteDatabaseWrapper from .client import SpatiaLiteClient from .features import DatabaseFeatures @@ -27,12 +25,16 @@ class DatabaseWrapper(SQLiteDatabaseWrapper): # (`libspatialite`). If it's not in the system library path (e.g., it # cannot be found by `ctypes.util.find_library`), then it may be set # manually in the settings via the `SPATIALITE_LIBRARY_PATH` setting. - self.lib_spatialite_paths = [name for name in [ - getattr(settings, 'SPATIALITE_LIBRARY_PATH', None), - 'mod_spatialite.so', - 'mod_spatialite', - find_library('spatialite'), - ] if name is not None] + self.lib_spatialite_paths = [ + name + for name in [ + getattr(settings, "SPATIALITE_LIBRARY_PATH", None), + "mod_spatialite.so", + "mod_spatialite", + find_library("spatialite"), + ] + if name is not None + ] super().__init__(*args, **kwargs) def get_new_connection(self, conn_params): @@ -42,26 +44,26 @@ class DatabaseWrapper(SQLiteDatabaseWrapper): conn.enable_load_extension(True) except AttributeError: raise ImproperlyConfigured( - 'SpatiaLite requires SQLite to be configured to allow ' - 'extension loading.' + "SpatiaLite requires SQLite to be configured to allow " + "extension loading." ) # Load the SpatiaLite library extension on the connection. for path in self.lib_spatialite_paths: try: conn.load_extension(path) except Exception: - if getattr(settings, 'SPATIALITE_LIBRARY_PATH', None): + if getattr(settings, "SPATIALITE_LIBRARY_PATH", None): raise ImproperlyConfigured( - 'Unable to load the SpatiaLite library extension ' - 'as specified in your SPATIALITE_LIBRARY_PATH setting.' + "Unable to load the SpatiaLite library extension " + "as specified in your SPATIALITE_LIBRARY_PATH setting." ) continue else: break else: raise ImproperlyConfigured( - 'Unable to load the SpatiaLite library extension. ' - 'Library names tried: %s' % ', '.join(self.lib_spatialite_paths) + "Unable to load the SpatiaLite library extension. " + "Library names tried: %s" % ", ".join(self.lib_spatialite_paths) ) return conn @@ -72,6 +74,6 @@ class DatabaseWrapper(SQLiteDatabaseWrapper): cursor.execute("PRAGMA table_info(geometry_columns);") if cursor.fetchall() == []: if self.ops.spatial_version < (5,): - cursor.execute('SELECT InitSpatialMetaData(1)') + cursor.execute("SELECT InitSpatialMetaData(1)") else: - cursor.execute('SELECT InitSpatialMetaDataFull(1)') + cursor.execute("SELECT InitSpatialMetaDataFull(1)") diff --git a/django/contrib/gis/db/backends/spatialite/client.py b/django/contrib/gis/db/backends/spatialite/client.py index c9dfd1a527..527fe153bb 100644 --- a/django/contrib/gis/db/backends/spatialite/client.py +++ b/django/contrib/gis/db/backends/spatialite/client.py @@ -2,4 +2,4 @@ from django.db.backends.sqlite3.client import DatabaseClient class SpatiaLiteClient(DatabaseClient): - executable_name = 'spatialite' + executable_name = "spatialite" diff --git a/django/contrib/gis/db/backends/spatialite/features.py b/django/contrib/gis/db/backends/spatialite/features.py index b6c1746c07..9504bb0949 100644 --- a/django/contrib/gis/db/backends/spatialite/features.py +++ b/django/contrib/gis/db/backends/spatialite/features.py @@ -16,9 +16,11 @@ class DatabaseFeatures(BaseSpatialFeatures, SQLiteDatabaseFeatures): @cached_property def django_test_skips(self): skips = super().django_test_skips - skips.update({ - "SpatiaLite doesn't support distance lookups with Distance objects.": { - 'gis_tests.geogapp.tests.GeographyTest.test02_distance_lookup', - }, - }) + skips.update( + { + "SpatiaLite doesn't support distance lookups with Distance objects.": { + "gis_tests.geogapp.tests.GeographyTest.test02_distance_lookup", + }, + } + ) return skips diff --git a/django/contrib/gis/db/backends/spatialite/introspection.py b/django/contrib/gis/db/backends/spatialite/introspection.py index 0feeb24397..8d0003fd53 100644 --- a/django/contrib/gis/db/backends/spatialite/introspection.py +++ b/django/contrib/gis/db/backends/spatialite/introspection.py @@ -1,6 +1,7 @@ from django.contrib.gis.gdal import OGRGeomType from django.db.backends.sqlite3.introspection import ( - DatabaseIntrospection, FlexibleFieldLookupDict, + DatabaseIntrospection, + FlexibleFieldLookupDict, ) @@ -9,15 +10,16 @@ class GeoFlexibleFieldLookupDict(FlexibleFieldLookupDict): Subclass that includes updates the `base_data_types_reverse` dict for geometry field types. """ + base_data_types_reverse = { **FlexibleFieldLookupDict.base_data_types_reverse, - 'point': 'GeometryField', - 'linestring': 'GeometryField', - 'polygon': 'GeometryField', - 'multipoint': 'GeometryField', - 'multilinestring': 'GeometryField', - 'multipolygon': 'GeometryField', - 'geometrycollection': 'GeometryField', + "point": "GeometryField", + "linestring": "GeometryField", + "polygon": "GeometryField", + "multipoint": "GeometryField", + "multilinestring": "GeometryField", + "multipolygon": "GeometryField", + "geometrycollection": "GeometryField", } @@ -27,14 +29,18 @@ class SpatiaLiteIntrospection(DatabaseIntrospection): def get_geometry_type(self, table_name, description): with self.connection.cursor() as cursor: # Querying the `geometry_columns` table to get additional metadata. - cursor.execute('SELECT coord_dimension, srid, geometry_type ' - 'FROM geometry_columns ' - 'WHERE f_table_name=%s AND f_geometry_column=%s', - (table_name, description.name)) + cursor.execute( + "SELECT coord_dimension, srid, geometry_type " + "FROM geometry_columns " + "WHERE f_table_name=%s AND f_geometry_column=%s", + (table_name, description.name), + ) row = cursor.fetchone() if not row: - raise Exception('Could not find a geometry column for "%s"."%s"' % - (table_name, description.name)) + raise Exception( + 'Could not find a geometry column for "%s"."%s"' + % (table_name, description.name) + ) # OGRGeomType does not require GDAL and makes it easy to convert # from OGC geom type name to Django field. @@ -51,18 +57,21 @@ class SpatiaLiteIntrospection(DatabaseIntrospection): srid = row[1] field_params = {} if srid != 4326: - field_params['srid'] = srid - if (isinstance(dim, str) and 'Z' in dim) or dim == 3: - field_params['dim'] = 3 + field_params["srid"] = srid + if (isinstance(dim, str) and "Z" in dim) or dim == 3: + field_params["dim"] = 3 return field_type, field_params def get_constraints(self, cursor, table_name): constraints = super().get_constraints(cursor, table_name) - cursor.execute('SELECT f_geometry_column ' - 'FROM geometry_columns ' - 'WHERE f_table_name=%s AND spatial_index_enabled=1', (table_name,)) + cursor.execute( + "SELECT f_geometry_column " + "FROM geometry_columns " + "WHERE f_table_name=%s AND spatial_index_enabled=1", + (table_name,), + ) for row in cursor.fetchall(): - constraints['%s__spatial__index' % row[0]] = { + constraints["%s__spatial__index" % row[0]] = { "columns": [row[0]], "primary_key": False, "unique": False, diff --git a/django/contrib/gis/db/backends/spatialite/models.py b/django/contrib/gis/db/backends/spatialite/models.py index 577c7236e3..7cc98ae126 100644 --- a/django/contrib/gis/db/backends/spatialite/models.py +++ b/django/contrib/gis/db/backends/spatialite/models.py @@ -9,20 +9,21 @@ class SpatialiteGeometryColumns(models.Model): """ The 'geometry_columns' table from SpatiaLite. """ + f_table_name = models.CharField(max_length=256) f_geometry_column = models.CharField(max_length=256) coord_dimension = models.IntegerField() srid = models.IntegerField(primary_key=True) spatial_index_enabled = models.IntegerField() - type = models.IntegerField(db_column='geometry_type') + type = models.IntegerField(db_column="geometry_type") class Meta: - app_label = 'gis' - db_table = 'geometry_columns' + app_label = "gis" + db_table = "geometry_columns" managed = False def __str__(self): - return '%s.%s - %dD %s field (SRID: %d)' % ( + return "%s.%s - %dD %s field (SRID: %d)" % ( self.f_table_name, self.f_geometry_column, self.coord_dimension, @@ -36,7 +37,7 @@ class SpatialiteGeometryColumns(models.Model): Return the name of the metadata column used to store the feature table name. """ - return 'f_table_name' + return "f_table_name" @classmethod def geom_col_name(cls): @@ -44,13 +45,14 @@ class SpatialiteGeometryColumns(models.Model): Return the name of the metadata column used to store the feature geometry column. """ - return 'f_geometry_column' + return "f_geometry_column" class SpatialiteSpatialRefSys(models.Model, SpatialRefSysMixin): """ The 'spatial_ref_sys' table from SpatiaLite. """ + srid = models.IntegerField(primary_key=True) auth_name = models.CharField(max_length=256) auth_srid = models.IntegerField() @@ -59,8 +61,8 @@ class SpatialiteSpatialRefSys(models.Model, SpatialRefSysMixin): srtext = models.CharField(max_length=2048) class Meta: - app_label = 'gis' - db_table = 'spatial_ref_sys' + app_label = "gis" + db_table = "spatial_ref_sys" managed = False @property diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 8dcd62de9b..8003fcb6c6 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -3,9 +3,7 @@ SQL functions reference lists: https://www.gaia-gis.it/gaia-sins/spatialite-sql-4.3.0.html """ from django.contrib.gis.db import models -from django.contrib.gis.db.backends.base.operations import ( - BaseSpatialOperations, -) +from django.contrib.gis.db.backends.base.operations import BaseSpatialOperations from django.contrib.gis.db.backends.spatialite.adapter import SpatiaLiteAdapter from django.contrib.gis.db.backends.utils import SpatialOperator from django.contrib.gis.geos.geometry import GEOSGeometry, GEOSGeometryBase @@ -20,69 +18,69 @@ from django.utils.version import get_version_tuple class SpatialiteNullCheckOperator(SpatialOperator): def as_sql(self, connection, lookup, template_params, sql_params): sql, params = super().as_sql(connection, lookup, template_params, sql_params) - return '%s > 0' % sql, params + return "%s > 0" % sql, params class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): - name = 'spatialite' + name = "spatialite" spatialite = True Adapter = SpatiaLiteAdapter - collect = 'Collect' - extent = 'Extent' - makeline = 'MakeLine' - unionagg = 'GUnion' + collect = "Collect" + extent = "Extent" + makeline = "MakeLine" + unionagg = "GUnion" - from_text = 'GeomFromText' + from_text = "GeomFromText" gis_operators = { # Binary predicates - 'equals': SpatialiteNullCheckOperator(func='Equals'), - 'disjoint': SpatialiteNullCheckOperator(func='Disjoint'), - 'touches': SpatialiteNullCheckOperator(func='Touches'), - 'crosses': SpatialiteNullCheckOperator(func='Crosses'), - 'within': SpatialiteNullCheckOperator(func='Within'), - 'overlaps': SpatialiteNullCheckOperator(func='Overlaps'), - 'contains': SpatialiteNullCheckOperator(func='Contains'), - 'intersects': SpatialiteNullCheckOperator(func='Intersects'), - 'relate': SpatialiteNullCheckOperator(func='Relate'), - 'coveredby': SpatialiteNullCheckOperator(func='CoveredBy'), - 'covers': SpatialiteNullCheckOperator(func='Covers'), + "equals": SpatialiteNullCheckOperator(func="Equals"), + "disjoint": SpatialiteNullCheckOperator(func="Disjoint"), + "touches": SpatialiteNullCheckOperator(func="Touches"), + "crosses": SpatialiteNullCheckOperator(func="Crosses"), + "within": SpatialiteNullCheckOperator(func="Within"), + "overlaps": SpatialiteNullCheckOperator(func="Overlaps"), + "contains": SpatialiteNullCheckOperator(func="Contains"), + "intersects": SpatialiteNullCheckOperator(func="Intersects"), + "relate": SpatialiteNullCheckOperator(func="Relate"), + "coveredby": SpatialiteNullCheckOperator(func="CoveredBy"), + "covers": SpatialiteNullCheckOperator(func="Covers"), # Returns true if B's bounding box completely contains A's bounding box. - 'contained': SpatialOperator(func='MbrWithin'), + "contained": SpatialOperator(func="MbrWithin"), # Returns true if A's bounding box completely contains B's bounding box. - 'bbcontains': SpatialOperator(func='MbrContains'), + "bbcontains": SpatialOperator(func="MbrContains"), # Returns true if A's bounding box overlaps B's bounding box. - 'bboverlaps': SpatialOperator(func='MbrOverlaps'), + "bboverlaps": SpatialOperator(func="MbrOverlaps"), # These are implemented here as synonyms for Equals - 'same_as': SpatialiteNullCheckOperator(func='Equals'), - 'exact': SpatialiteNullCheckOperator(func='Equals'), + "same_as": SpatialiteNullCheckOperator(func="Equals"), + "exact": SpatialiteNullCheckOperator(func="Equals"), # Distance predicates - 'dwithin': SpatialOperator(func='PtDistWithin'), + "dwithin": SpatialOperator(func="PtDistWithin"), } disallowed_aggregates = (models.Extent3D,) - select = 'CAST (AsEWKB(%s) AS BLOB)' + select = "CAST (AsEWKB(%s) AS BLOB)" function_names = { - 'AsWKB': 'St_AsBinary', - 'ForcePolygonCW': 'ST_ForceLHR', - 'Length': 'ST_Length', - 'LineLocatePoint': 'ST_Line_Locate_Point', - 'NumPoints': 'ST_NPoints', - 'Reverse': 'ST_Reverse', - 'Scale': 'ScaleCoords', - 'Translate': 'ST_Translate', - 'Union': 'ST_Union', + "AsWKB": "St_AsBinary", + "ForcePolygonCW": "ST_ForceLHR", + "Length": "ST_Length", + "LineLocatePoint": "ST_Line_Locate_Point", + "NumPoints": "ST_NPoints", + "Reverse": "ST_Reverse", + "Scale": "ScaleCoords", + "Translate": "ST_Translate", + "Union": "ST_Union", } @cached_property def unsupported_functions(self): - unsupported = {'BoundingCircle', 'GeometryDistance', 'MemSize'} + unsupported = {"BoundingCircle", "GeometryDistance", "MemSize"} if not self.geom_lib_version(): - unsupported |= {'Azimuth', 'GeoHash', 'MakeValid'} + unsupported |= {"Azimuth", "GeoHash", "MakeValid"} return unsupported @cached_property @@ -93,12 +91,11 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): except Exception as exc: raise ImproperlyConfigured( 'Cannot determine the SpatiaLite version for the "%s" database. ' - 'Was the SpatiaLite initialization SQL loaded on this database?' % ( - self.connection.settings_dict['NAME'], - ) + "Was the SpatiaLite initialization SQL loaded on this database?" + % (self.connection.settings_dict["NAME"],) ) from exc if version < (4, 3, 0): - raise ImproperlyConfigured('GeoDjango supports SpatiaLite 4.3.0 and above.') + raise ImproperlyConfigured("GeoDjango supports SpatiaLite 4.3.0 and above.") return version def convert_extent(self, box): @@ -129,14 +126,16 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): value = value[0] if isinstance(value, Distance): if f.geodetic(self.connection): - if lookup_type == 'dwithin': + if lookup_type == "dwithin": raise ValueError( - 'Only numeric values of degree units are allowed on ' - 'geographic DWithin queries.' + "Only numeric values of degree units are allowed on " + "geographic DWithin queries." ) dist_param = value.m else: - dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection))) + dist_param = getattr( + value, Distance.unit_attname(f.units_name(self.connection)) + ) else: dist_param = value return [dist_param] @@ -149,7 +148,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): """ cursor = self.connection._cursor() try: - cursor.execute('SELECT %s' % func) + cursor.execute("SELECT %s" % func) row = cursor.fetchone() finally: cursor.close() @@ -157,19 +156,19 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): def geos_version(self): "Return the version of GEOS used by SpatiaLite as a string." - return self._get_spatialite_func('geos_version()') + return self._get_spatialite_func("geos_version()") def proj_version(self): """Return the version of the PROJ library used by SpatiaLite.""" - return self._get_spatialite_func('proj4_version()') + return self._get_spatialite_func("proj4_version()") def lwgeom_version(self): """Return the version of LWGEOM library used by SpatiaLite.""" - return self._get_spatialite_func('lwgeom_version()') + return self._get_spatialite_func("lwgeom_version()") def rttopo_version(self): """Return the version of RTTOPO library used by SpatiaLite.""" - return self._get_spatialite_func('rttopo_version()') + return self._get_spatialite_func("rttopo_version()") def geom_lib_version(self): """ @@ -183,7 +182,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): def spatialite_version(self): "Return the SpatiaLite library version as a string." - return self._get_spatialite_func('spatialite_version()') + return self._get_spatialite_func("spatialite_version()") def spatialite_version_tuple(self): """ @@ -198,7 +197,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): Return the spatial aggregate SQL template and function for the given Aggregate instance. """ - agg_name = 'unionagg' if agg_name.lower() == 'union' else agg_name.lower() + agg_name = "unionagg" if agg_name.lower() == "union" else agg_name.lower() return getattr(self, agg_name) # Routines for getting the OGC-compliant models. @@ -206,12 +205,14 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): from django.contrib.gis.db.backends.spatialite.models import ( SpatialiteGeometryColumns, ) + return SpatialiteGeometryColumns def spatial_ref_sys(self): from django.contrib.gis.db.backends.spatialite.models import ( SpatialiteSpatialRefSys, ) + return SpatialiteSpatialRefSys def get_geometry_converter(self, expression): @@ -220,4 +221,5 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): def converter(value, expression, connection): return None if value is None else GEOSGeometryBase(read(value), geom_class) + return converter diff --git a/django/contrib/gis/db/backends/spatialite/schema.py b/django/contrib/gis/db/backends/spatialite/schema.py index 066ce6d732..d37632edf8 100644 --- a/django/contrib/gis/db/backends/spatialite/schema.py +++ b/django/contrib/gis/db/backends/spatialite/schema.py @@ -14,7 +14,9 @@ class SpatialiteSchemaEditor(DatabaseSchemaEditor): "%(geom_type)s, %(dim)s)" ) sql_remove_geometry_metadata = "SELECT DiscardGeometryColumn(%(table)s, %(column)s)" - sql_discard_geometry_columns = "DELETE FROM %(geom_table)s WHERE f_table_name = %(table)s" + sql_discard_geometry_columns = ( + "DELETE FROM %(geom_table)s WHERE f_table_name = %(table)s" + ) sql_update_geometry_columns = ( "UPDATE %(geom_table)s SET f_table_name = %(new_table)s " "WHERE f_table_name = %(old_table)s" @@ -36,12 +38,14 @@ class SpatialiteSchemaEditor(DatabaseSchemaEditor): def column_sql(self, model, field, include_default=False): from django.contrib.gis.db.models import GeometryField + if not isinstance(field, GeometryField): return super().column_sql(model, field, include_default) # Geometry columns are created by the `AddGeometryColumn` function self.geometry_sql.append( - self.sql_add_geometry_column % { + self.sql_add_geometry_column + % { "table": self.geo_quote_name(model._meta.db_table), "column": self.geo_quote_name(field.column), "srid": field.srid, @@ -53,7 +57,8 @@ class SpatialiteSchemaEditor(DatabaseSchemaEditor): if field.spatial_index: self.geometry_sql.append( - self.sql_add_spatial_index % { + self.sql_add_spatial_index + % { "table": self.quote_name(model._meta.db_table), "column": self.quote_name(field.column), } @@ -62,13 +67,15 @@ class SpatialiteSchemaEditor(DatabaseSchemaEditor): def remove_geometry_metadata(self, model, field): self.execute( - self.sql_remove_geometry_metadata % { + self.sql_remove_geometry_metadata + % { "table": self.quote_name(model._meta.db_table), "column": self.quote_name(field.column), } ) self.execute( - self.sql_drop_spatial_index % { + self.sql_drop_spatial_index + % { "table": model._meta.db_table, "column": field.column, } @@ -92,7 +99,8 @@ class SpatialiteSchemaEditor(DatabaseSchemaEditor): for geom_table in self.geometry_tables: try: self.execute( - self.sql_discard_geometry_columns % { + self.sql_discard_geometry_columns + % { "geom_table": geom_table, "table": self.quote_name(model._meta.db_table), } @@ -103,6 +111,7 @@ class SpatialiteSchemaEditor(DatabaseSchemaEditor): def add_field(self, model, field): from django.contrib.gis.db.models import GeometryField + if isinstance(field, GeometryField): # Populate self.geometry_sql self.column_sql(model, field) @@ -125,14 +134,17 @@ class SpatialiteSchemaEditor(DatabaseSchemaEditor): else: super().remove_field(model, field) - def alter_db_table(self, model, old_db_table, new_db_table, disable_constraints=True): + def alter_db_table( + self, model, old_db_table, new_db_table, disable_constraints=True + ): from django.contrib.gis.db.models import GeometryField # Remove geometry-ness from temp table for field in model._meta.local_fields: if isinstance(field, GeometryField): self.execute( - self.sql_remove_geometry_metadata % { + self.sql_remove_geometry_metadata + % { "table": self.quote_name(old_db_table), "column": self.quote_name(field.column), } @@ -143,7 +155,8 @@ class SpatialiteSchemaEditor(DatabaseSchemaEditor): for geom_table in self.geometry_tables: try: self.execute( - self.sql_update_geometry_columns % { + self.sql_update_geometry_columns + % { "geom_table": geom_table, "old_table": self.quote_name(old_db_table), "new_table": self.quote_name(new_db_table), @@ -154,15 +167,25 @@ class SpatialiteSchemaEditor(DatabaseSchemaEditor): # Re-add geometry-ness and rename spatial index tables for field in model._meta.local_fields: if isinstance(field, GeometryField): - self.execute(self.sql_recover_geometry_metadata % { - "table": self.geo_quote_name(new_db_table), - "column": self.geo_quote_name(field.column), - "srid": field.srid, - "geom_type": self.geo_quote_name(field.geom_type), - "dim": field.dim, - }) - if getattr(field, 'spatial_index', False): - self.execute(self.sql_rename_table % { - "old_table": self.quote_name("idx_%s_%s" % (old_db_table, field.column)), - "new_table": self.quote_name("idx_%s_%s" % (new_db_table, field.column)), - }) + self.execute( + self.sql_recover_geometry_metadata + % { + "table": self.geo_quote_name(new_db_table), + "column": self.geo_quote_name(field.column), + "srid": field.srid, + "geom_type": self.geo_quote_name(field.geom_type), + "dim": field.dim, + } + ) + if getattr(field, "spatial_index", False): + self.execute( + self.sql_rename_table + % { + "old_table": self.quote_name( + "idx_%s_%s" % (old_db_table, field.column) + ), + "new_table": self.quote_name( + "idx_%s_%s" % (new_db_table, field.column) + ), + } + ) diff --git a/django/contrib/gis/db/backends/utils.py b/django/contrib/gis/db/backends/utils.py index d479009d4e..ffb7420019 100644 --- a/django/contrib/gis/db/backends/utils.py +++ b/django/contrib/gis/db/backends/utils.py @@ -8,6 +8,7 @@ class SpatialOperator: """ Class encapsulating the behavior specific to a GIS operation (used by lookups). """ + sql_template = None def __init__(self, op=None, func=None): @@ -17,11 +18,11 @@ class SpatialOperator: @property def default_template(self): if self.func: - return '%(func)s(%(lhs)s, %(rhs)s)' + return "%(func)s(%(lhs)s, %(rhs)s)" else: - return '%(lhs)s %(op)s %(rhs)s' + return "%(lhs)s %(op)s %(rhs)s" def as_sql(self, connection, lookup, template_params, sql_params): sql_template = self.sql_template or lookup.sql_template or self.default_template - template_params.update({'op': self.op, 'func': self.func}) + template_params.update({"op": self.op, "func": self.func}) return sql_template % template_params, sql_params diff --git a/django/contrib/gis/db/models/__init__.py b/django/contrib/gis/db/models/__init__.py index 2e472557d8..0d5d45d272 100644 --- a/django/contrib/gis/db/models/__init__.py +++ b/django/contrib/gis/db/models/__init__.py @@ -5,14 +5,26 @@ import django.contrib.gis.db.models.lookups # NOQA from django.contrib.gis.db.models.aggregates import * # NOQA from django.contrib.gis.db.models.aggregates import __all__ as aggregates_all from django.contrib.gis.db.models.fields import ( - GeometryCollectionField, GeometryField, LineStringField, - MultiLineStringField, MultiPointField, MultiPolygonField, PointField, - PolygonField, RasterField, + GeometryCollectionField, + GeometryField, + LineStringField, + MultiLineStringField, + MultiPointField, + MultiPolygonField, + PointField, + PolygonField, + RasterField, ) __all__ = models_all + aggregates_all __all__ += [ - 'GeometryCollectionField', 'GeometryField', 'LineStringField', - 'MultiLineStringField', 'MultiPointField', 'MultiPolygonField', 'PointField', - 'PolygonField', 'RasterField', + "GeometryCollectionField", + "GeometryField", + "LineStringField", + "MultiLineStringField", + "MultiPointField", + "MultiPolygonField", + "PointField", + "PolygonField", + "RasterField", ] diff --git a/django/contrib/gis/db/models/aggregates.py b/django/contrib/gis/db/models/aggregates.py index 3aa46a52c0..c19cbd06c3 100644 --- a/django/contrib/gis/db/models/aggregates.py +++ b/django/contrib/gis/db/models/aggregates.py @@ -1,10 +1,13 @@ from django.contrib.gis.db.models.fields import ( - ExtentField, GeometryCollectionField, GeometryField, LineStringField, + ExtentField, + GeometryCollectionField, + GeometryField, + LineStringField, ) from django.db.models import Aggregate, Value from django.utils.functional import cached_property -__all__ = ['Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union'] +__all__ = ["Collect", "Extent", "Extent3D", "MakeLine", "Union"] class GeoAggregate(Aggregate): @@ -23,37 +26,45 @@ class GeoAggregate(Aggregate): compiler, connection, function=function or connection.ops.spatial_aggregate_name(self.name), - **extra_context + **extra_context, ) def as_oracle(self, compiler, connection, **extra_context): if not self.is_extent: - tolerance = self.extra.get('tolerance') or getattr(self, 'tolerance', 0.05) + tolerance = self.extra.get("tolerance") or getattr(self, "tolerance", 0.05) clone = self.copy() - clone.set_source_expressions([ - *self.get_source_expressions(), - Value(tolerance), - ]) - template = '%(function)s(SDOAGGRTYPE(%(expressions)s))' - return clone.as_sql(compiler, connection, template=template, **extra_context) + clone.set_source_expressions( + [ + *self.get_source_expressions(), + Value(tolerance), + ] + ) + template = "%(function)s(SDOAGGRTYPE(%(expressions)s))" + return clone.as_sql( + compiler, connection, template=template, **extra_context + ) return self.as_sql(compiler, connection, **extra_context) - def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False): + def resolve_expression( + self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False + ): c = super().resolve_expression(query, allow_joins, reuse, summarize, for_save) for expr in c.get_source_expressions(): - if not hasattr(expr.field, 'geom_type'): - raise ValueError('Geospatial aggregates only allowed on geometry fields.') + if not hasattr(expr.field, "geom_type"): + raise ValueError( + "Geospatial aggregates only allowed on geometry fields." + ) return c class Collect(GeoAggregate): - name = 'Collect' + name = "Collect" output_field_class = GeometryCollectionField class Extent(GeoAggregate): - name = 'Extent' - is_extent = '2D' + name = "Extent" + is_extent = "2D" def __init__(self, expression, **extra): super().__init__(expression, output_field=ExtentField(), **extra) @@ -63,8 +74,8 @@ class Extent(GeoAggregate): class Extent3D(GeoAggregate): - name = 'Extent3D' - is_extent = '3D' + name = "Extent3D" + is_extent = "3D" def __init__(self, expression, **extra): super().__init__(expression, output_field=ExtentField(), **extra) @@ -74,10 +85,10 @@ class Extent3D(GeoAggregate): class MakeLine(GeoAggregate): - name = 'MakeLine' + name = "MakeLine" output_field_class = LineStringField class Union(GeoAggregate): - name = 'Union' + name = "Union" output_field_class = GeometryField diff --git a/django/contrib/gis/db/models/fields.py b/django/contrib/gis/db/models/fields.py index 16b3bb5d08..889c1cfe84 100644 --- a/django/contrib/gis/db/models/fields.py +++ b/django/contrib/gis/db/models/fields.py @@ -4,8 +4,15 @@ from django.contrib.gis import forms, gdal from django.contrib.gis.db.models.proxy import SpatialProxy from django.contrib.gis.gdal.error import GDALException from django.contrib.gis.geos import ( - GeometryCollection, GEOSException, GEOSGeometry, LineString, - MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, + GeometryCollection, + GEOSException, + GEOSGeometry, + LineString, + MultiLineString, + MultiPoint, + MultiPolygon, + Point, + Polygon, ) from django.core.exceptions import ImproperlyConfigured from django.db.models import Field @@ -17,7 +24,9 @@ from django.utils.translation import gettext_lazy as _ _srid_cache = defaultdict(dict) -SRIDCacheEntry = namedtuple('SRIDCacheEntry', ['units', 'units_name', 'spheroid', 'geodetic']) +SRIDCacheEntry = namedtuple( + "SRIDCacheEntry", ["units", "units_name", "spheroid", "geodetic"] +) def get_srid_info(srid, connection): @@ -27,6 +36,7 @@ def get_srid_info(srid, connection): table for the given database connection. These results are cached. """ from django.contrib.gis.gdal import SpatialReference + global _srid_cache try: @@ -36,9 +46,14 @@ def get_srid_info(srid, connection): SpatialRefSys = None alias, get_srs = ( - (connection.alias, lambda srid: SpatialRefSys.objects.using(connection.alias).get(srid=srid).srs) - if SpatialRefSys else - (None, SpatialReference) + ( + connection.alias, + lambda srid: SpatialRefSys.objects.using(connection.alias) + .get(srid=srid) + .srs, + ) + if SpatialRefSys + else (None, SpatialReference) ) if srid not in _srid_cache[alias]: srs = get_srs(srid) @@ -46,7 +61,8 @@ def get_srid_info(srid, connection): _srid_cache[alias][srid] = SRIDCacheEntry( units=units, units_name=units_name, - spheroid='SPHEROID["%s",%s,%s]' % (srs['spheroid'], srs.semi_major, srs.inverse_flattening), + spheroid='SPHEROID["%s",%s,%s]' + % (srs["spheroid"], srs.semi_major, srs.inverse_flattening), geodetic=srs.geographic, ) @@ -61,6 +77,7 @@ class BaseSpatialField(Field): properties that are common to all GIS fields such as the characteristics of the spatial reference system of the field. """ + description = _("The base GIS field.") empty_strings_allowed = False @@ -88,7 +105,7 @@ class BaseSpatialField(Field): # Setting the verbose_name keyword argument with the positional # first parameter, so this works like normal fields. - kwargs['verbose_name'] = verbose_name + kwargs["verbose_name"] = verbose_name super().__init__(**kwargs) @@ -96,9 +113,9 @@ class BaseSpatialField(Field): name, path, args, kwargs = super().deconstruct() # Always include SRID for less fragility; include spatial index if it's # not the default value. - kwargs['srid'] = self.srid + kwargs["srid"] = self.srid if self.spatial_index is not True: - kwargs['spatial_index'] = self.spatial_index + kwargs["spatial_index"] = self.spatial_index return name, path, args, kwargs def db_type(self, connection): @@ -146,10 +163,10 @@ class BaseSpatialField(Field): return connection.ops.Adapter( super().get_db_prep_value(value, connection, *args, **kwargs), **( - {'geography': True} + {"geography": True} if self.geography and connection.features.supports_geography else {} - ) + ), ) def get_raster_prep_value(self, value, is_candidate): @@ -167,7 +184,9 @@ class BaseSpatialField(Field): try: return gdal.GDALRaster(value) except GDALException: - raise ValueError("Couldn't create spatial object from lookup value '%s'." % value) + raise ValueError( + "Couldn't create spatial object from lookup value '%s'." % value + ) def get_prep_value(self, value): obj = super().get_prep_value(value) @@ -179,7 +198,9 @@ class BaseSpatialField(Field): pass else: # Check if input is a candidate for conversion to raster or geometry. - is_candidate = isinstance(obj, (bytes, str)) or hasattr(obj, '__geo_interface__') + is_candidate = isinstance(obj, (bytes, str)) or hasattr( + obj, "__geo_interface__" + ) # Try to convert the input to raster. raster = self.get_raster_prep_value(obj, is_candidate) @@ -189,9 +210,14 @@ class BaseSpatialField(Field): try: obj = GEOSGeometry(obj) except (GEOSException, GDALException): - raise ValueError("Couldn't create spatial object from lookup value '%s'." % obj) + raise ValueError( + "Couldn't create spatial object from lookup value '%s'." % obj + ) else: - raise ValueError('Cannot use object with type %s for a spatial lookup parameter.' % type(obj).__name__) + raise ValueError( + "Cannot use object with type %s for a spatial lookup parameter." + % type(obj).__name__ + ) # Assigning the SRID value. obj.srid = self.get_srid(obj) @@ -202,14 +228,25 @@ class GeometryField(BaseSpatialField): """ The base Geometry field -- maps to the OpenGIS Specification Geometry type. """ - description = _('The base Geometry field — maps to the OpenGIS Specification Geometry type.') + + description = _( + "The base Geometry field — maps to the OpenGIS Specification Geometry type." + ) form_class = forms.GeometryField # The OpenGIS Geometry name. - geom_type = 'GEOMETRY' + geom_type = "GEOMETRY" geom_class = None - def __init__(self, verbose_name=None, dim=2, geography=False, *, extent=(-180.0, -90.0, 180.0, 90.0), - tolerance=0.05, **kwargs): + def __init__( + self, + verbose_name=None, + dim=2, + geography=False, + *, + extent=(-180.0, -90.0, 180.0, 90.0), + tolerance=0.05, + **kwargs, + ): """ The initialization function for geometry fields. In addition to the parameters from BaseSpatialField, it takes the following as keyword @@ -244,30 +281,36 @@ class GeometryField(BaseSpatialField): name, path, args, kwargs = super().deconstruct() # Include kwargs if they're not the default values. if self.dim != 2: - kwargs['dim'] = self.dim + kwargs["dim"] = self.dim if self.geography is not False: - kwargs['geography'] = self.geography + kwargs["geography"] = self.geography if self._extent != (-180.0, -90.0, 180.0, 90.0): - kwargs['extent'] = self._extent + kwargs["extent"] = self._extent if self._tolerance != 0.05: - kwargs['tolerance'] = self._tolerance + kwargs["tolerance"] = self._tolerance return name, path, args, kwargs def contribute_to_class(self, cls, name, **kwargs): super().contribute_to_class(cls, name, **kwargs) # Setup for lazy-instantiated Geometry object. - setattr(cls, self.attname, SpatialProxy(self.geom_class or GEOSGeometry, self, load_func=GEOSGeometry)) + setattr( + cls, + self.attname, + SpatialProxy(self.geom_class or GEOSGeometry, self, load_func=GEOSGeometry), + ) def formfield(self, **kwargs): defaults = { - 'form_class': self.form_class, - 'geom_type': self.geom_type, - 'srid': self.srid, + "form_class": self.form_class, + "geom_type": self.geom_type, + "srid": self.srid, **kwargs, } - if self.dim > 2 and not getattr(defaults['form_class'].widget, 'supports_3d', False): - defaults.setdefault('widget', forms.Textarea) + if self.dim > 2 and not getattr( + defaults["form_class"].widget, "supports_3d", False + ): + defaults.setdefault("widget", forms.Textarea) return super().formfield(**defaults) def select_format(self, compiler, sql, params): @@ -283,49 +326,49 @@ class GeometryField(BaseSpatialField): # The OpenGIS Geometry Type Fields class PointField(GeometryField): - geom_type = 'POINT' + geom_type = "POINT" geom_class = Point form_class = forms.PointField description = _("Point") class LineStringField(GeometryField): - geom_type = 'LINESTRING' + geom_type = "LINESTRING" geom_class = LineString form_class = forms.LineStringField description = _("Line string") class PolygonField(GeometryField): - geom_type = 'POLYGON' + geom_type = "POLYGON" geom_class = Polygon form_class = forms.PolygonField description = _("Polygon") class MultiPointField(GeometryField): - geom_type = 'MULTIPOINT' + geom_type = "MULTIPOINT" geom_class = MultiPoint form_class = forms.MultiPointField description = _("Multi-point") class MultiLineStringField(GeometryField): - geom_type = 'MULTILINESTRING' + geom_type = "MULTILINESTRING" geom_class = MultiLineString form_class = forms.MultiLineStringField description = _("Multi-line string") class MultiPolygonField(GeometryField): - geom_type = 'MULTIPOLYGON' + geom_type = "MULTIPOLYGON" geom_class = MultiPolygon form_class = forms.MultiPolygonField description = _("Multi polygon") class GeometryCollectionField(GeometryField): - geom_type = 'GEOMETRYCOLLECTION' + geom_type = "GEOMETRYCOLLECTION" geom_class = GeometryCollection form_class = forms.GeometryCollectionField description = _("Geometry collection") @@ -350,13 +393,18 @@ class RasterField(BaseSpatialField): """ description = _("Raster Field") - geom_type = 'RASTER' + geom_type = "RASTER" geography = False def _check_connection(self, connection): # Make sure raster fields are used only on backends with raster support. - if not connection.features.gis_enabled or not connection.features.supports_raster: - raise ImproperlyConfigured('Raster fields require backends with raster support.') + if ( + not connection.features.gis_enabled + or not connection.features.supports_raster + ): + raise ImproperlyConfigured( + "Raster fields require backends with raster support." + ) def db_type(self, connection): self._check_connection(connection) @@ -375,12 +423,13 @@ class RasterField(BaseSpatialField): def get_transform(self, name): from django.contrib.gis.db.models.lookups import RasterBandTransform + try: band_index = int(name) return type( - 'SpecificRasterBandTransform', + "SpecificRasterBandTransform", (RasterBandTransform,), - {'band_index': band_index} + {"band_index": band_index}, ) except ValueError: pass diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py index 1f2d372ebb..c7b071c7c9 100644 --- a/django/contrib/gis/db/models/functions.py +++ b/django/contrib/gis/db/models/functions.py @@ -6,8 +6,14 @@ from django.contrib.gis.geos import GEOSGeometry from django.core.exceptions import FieldError from django.db import NotSupportedError from django.db.models import ( - BinaryField, BooleanField, FloatField, Func, IntegerField, TextField, - Transform, Value, + BinaryField, + BooleanField, + FloatField, + Func, + IntegerField, + TextField, + Transform, + Value, ) from django.db.models.functions import Cast from django.utils.functional import cached_property @@ -32,12 +38,21 @@ class GeoFuncMixin: except FieldError: output_field = None geom = expr.value - if not isinstance(geom, GEOSGeometry) or output_field and not isinstance(output_field, GeometryField): - raise TypeError("%s function requires a geometric argument in position %d." % (self.name, pos + 1)) + if ( + not isinstance(geom, GEOSGeometry) + or output_field + and not isinstance(output_field, GeometryField) + ): + raise TypeError( + "%s function requires a geometric argument in position %d." + % (self.name, pos + 1) + ) if not geom.srid and not output_field: raise ValueError("SRID is required for all geometries.") if not output_field: - self.source_expressions[pos] = Value(geom, output_field=GeometryField(srid=geom.srid)) + self.source_expressions[pos] = Value( + geom, output_field=GeometryField(srid=geom.srid) + ) @property def name(self): @@ -61,8 +76,11 @@ class GeoFuncMixin: field = source_fields[pos] if not isinstance(field, GeometryField): raise TypeError( - "%s function requires a GeometryField in position %s, got %s." % ( - self.name, pos + 1, type(field).__name__, + "%s function requires a GeometryField in position %s, got %s." + % ( + self.name, + pos + 1, + type(field).__name__, ) ) @@ -72,15 +90,17 @@ class GeoFuncMixin: expr_srid = expr.output_field.srid if expr_srid != base_srid: # Automatic SRID conversion so objects are comparable. - res.source_expressions[pos] = Transform(expr, base_srid).resolve_expression(*args, **kwargs) + res.source_expressions[pos] = Transform( + expr, base_srid + ).resolve_expression(*args, **kwargs) return res - def _handle_param(self, value, param_name='', check_types=None): - if not hasattr(value, 'resolve_expression'): + def _handle_param(self, value, param_name="", check_types=None): + if not hasattr(value, "resolve_expression"): if check_types and not isinstance(value, check_types): raise TypeError( - "The %s parameter has the wrong type: should be %s." % ( - param_name, check_types) + "The %s parameter has the wrong type: should be %s." + % (param_name, check_types) ) return value @@ -100,13 +120,17 @@ class SQLiteDecimalToFloatMixin: By default, Decimal values are converted to str by the SQLite backend, which is not acceptable by the GIS functions expecting numeric values. """ + def as_sqlite(self, compiler, connection, **extra_context): copy = self.copy() - copy.set_source_expressions([ - Value(float(expr.value)) if hasattr(expr, 'value') and isinstance(expr.value, Decimal) - else expr - for expr in copy.get_source_expressions() - ]) + copy.set_source_expressions( + [ + Value(float(expr.value)) + if hasattr(expr, "value") and isinstance(expr.value, Decimal) + else expr + for expr in copy.get_source_expressions() + ] + ) return copy.as_sql(compiler, connection, **extra_context) @@ -114,11 +138,13 @@ class OracleToleranceMixin: tolerance = 0.05 def as_oracle(self, compiler, connection, **extra_context): - tolerance = Value(self._handle_param( - self.extra.get('tolerance', self.tolerance), - 'tolerance', - NUMERIC_TYPES, - )) + tolerance = Value( + self._handle_param( + self.extra.get("tolerance", self.tolerance), + "tolerance", + NUMERIC_TYPES, + ) + ) clone = self.copy() clone.set_source_expressions([*self.get_source_expressions(), tolerance]) return clone.as_sql(compiler, connection, **extra_context) @@ -132,14 +158,18 @@ class Area(OracleToleranceMixin, GeoFunc): return AreaField(self.geo_field) def as_sql(self, compiler, connection, **extra_context): - if not connection.features.supports_area_geodetic and self.geo_field.geodetic(connection): - raise NotSupportedError('Area on geodetic coordinate systems not supported.') + if not connection.features.supports_area_geodetic and self.geo_field.geodetic( + connection + ): + raise NotSupportedError( + "Area on geodetic coordinate systems not supported." + ) return super().as_sql(compiler, connection, **extra_context) def as_sqlite(self, compiler, connection, **extra_context): if self.geo_field.geodetic(connection): - extra_context['template'] = '%(function)s(%(expressions)s, %(spheroid)d)' - extra_context['spheroid'] = True + extra_context["template"] = "%(function)s(%(expressions)s, %(spheroid)d)" + extra_context["spheroid"] = True return self.as_sql(compiler, connection, **extra_context) @@ -155,7 +185,7 @@ class AsGeoJSON(GeoFunc): def __init__(self, expression, bbox=False, crs=False, precision=8, **extra): expressions = [expression] if precision is not None: - expressions.append(self._handle_param(precision, 'precision', int)) + expressions.append(self._handle_param(precision, "precision", int)) options = 0 if crs and bbox: options = 3 @@ -181,7 +211,7 @@ class AsGML(GeoFunc): def __init__(self, expression, version=2, precision=8, **extra): expressions = [version, expression] if precision is not None: - expressions.append(self._handle_param(precision, 'precision', int)) + expressions.append(self._handle_param(precision, "precision", int)) super().__init__(*expressions, **extra) def as_oracle(self, compiler, connection, **extra_context): @@ -189,7 +219,11 @@ class AsGML(GeoFunc): version = source_expressions[0] clone = self.copy() clone.set_source_expressions([source_expressions[1]]) - extra_context['function'] = 'SDO_UTIL.TO_GML311GEOMETRY' if version.value == 3 else 'SDO_UTIL.TO_GMLGEOMETRY' + extra_context["function"] = ( + "SDO_UTIL.TO_GML311GEOMETRY" + if version.value == 3 + else "SDO_UTIL.TO_GMLGEOMETRY" + ) return super(AsGML, clone).as_sql(compiler, connection, **extra_context) @@ -199,7 +233,7 @@ class AsKML(GeoFunc): def __init__(self, expression, precision=8, **extra): expressions = [expression] if precision is not None: - expressions.append(self._handle_param(precision, 'precision', int)) + expressions.append(self._handle_param(precision, "precision", int)) super().__init__(*expressions, **extra) @@ -207,11 +241,13 @@ class AsSVG(GeoFunc): output_field = TextField() def __init__(self, expression, relative=False, precision=8, **extra): - relative = relative if hasattr(relative, 'resolve_expression') else int(relative) + relative = ( + relative if hasattr(relative, "resolve_expression") else int(relative) + ) expressions = [ expression, relative, - self._handle_param(precision, 'precision', int), + self._handle_param(precision, "precision", int), ] super().__init__(*expressions, **extra) @@ -233,7 +269,9 @@ class BoundingCircle(OracleToleranceMixin, GeomOutputGeoFunc): def as_oracle(self, compiler, connection, **extra_context): clone = self.copy() clone.set_source_expressions([self.get_source_expressions()[0]]) - return super(BoundingCircle, clone).as_oracle(compiler, connection, **extra_context) + return super(BoundingCircle, clone).as_oracle( + compiler, connection, **extra_context + ) class Centroid(OracleToleranceMixin, GeomOutputGeoFunc): @@ -261,7 +299,7 @@ class Distance(DistanceResultMixin, OracleToleranceMixin, GeoFunc): def __init__(self, expr1, expr2, spheroid=None, **extra): expressions = [expr1, expr2] if spheroid is not None: - self.spheroid = self._handle_param(spheroid, 'spheroid', bool) + self.spheroid = self._handle_param(spheroid, "spheroid", bool) super().__init__(*expressions, **extra) def as_postgresql(self, compiler, connection, **extra_context): @@ -282,18 +320,24 @@ class Distance(DistanceResultMixin, OracleToleranceMixin, GeoFunc): # Geometry fields with geodetic (lon/lat) coordinates need special distance functions if self.spheroid: # DistanceSpheroid is more accurate and resource intensive than DistanceSphere - function = connection.ops.spatial_function_name('DistanceSpheroid') + function = connection.ops.spatial_function_name("DistanceSpheroid") # Replace boolean param by the real spheroid of the base field - clone.source_expressions.append(Value(self.geo_field.spheroid(connection))) + clone.source_expressions.append( + Value(self.geo_field.spheroid(connection)) + ) else: - function = connection.ops.spatial_function_name('DistanceSphere') - return super(Distance, clone).as_sql(compiler, connection, function=function, **extra_context) + function = connection.ops.spatial_function_name("DistanceSphere") + return super(Distance, clone).as_sql( + compiler, connection, function=function, **extra_context + ) def as_sqlite(self, compiler, connection, **extra_context): if self.geo_field.geodetic(connection): # SpatiaLite returns NULL instead of zero on geodetic coordinates - extra_context['template'] = 'COALESCE(%(function)s(%(expressions)s, %(spheroid)s), 0)' - extra_context['spheroid'] = int(bool(self.spheroid)) + extra_context[ + "template" + ] = "COALESCE(%(function)s(%(expressions)s, %(spheroid)s), 0)" + extra_context["spheroid"] = int(bool(self.spheroid)) return super().as_sql(compiler, connection, **extra_context) @@ -311,7 +355,7 @@ class GeoHash(GeoFunc): def __init__(self, expression, precision=None, **extra): expressions = [expression] if precision is not None: - expressions.append(self._handle_param(precision, 'precision', int)) + expressions.append(self._handle_param(precision, "precision", int)) super().__init__(*expressions, **extra) def as_mysql(self, compiler, connection, **extra_context): @@ -325,8 +369,8 @@ class GeoHash(GeoFunc): class GeometryDistance(GeoFunc): output_field = FloatField() arity = 2 - function = '' - arg_joiner = ' <-> ' + function = "" + arg_joiner = " <-> " geom_param_pos = (0, 1) @@ -337,7 +381,7 @@ class Intersection(OracleToleranceMixin, GeomOutputGeoFunc): @BaseSpatialField.register_lookup class IsValid(OracleToleranceMixin, GeoFuncMixin, Transform): - lookup_name = 'isvalid' + lookup_name = "isvalid" output_field = BooleanField() def as_oracle(self, compiler, connection, **extra_context): @@ -351,8 +395,13 @@ class Length(DistanceResultMixin, OracleToleranceMixin, GeoFunc): super().__init__(expr1, **extra) def as_sql(self, compiler, connection, **extra_context): - if self.geo_field.geodetic(connection) and not connection.features.supports_length_geodetic: - raise NotSupportedError("This backend doesn't support Length on geodetic fields") + if ( + self.geo_field.geodetic(connection) + and not connection.features.supports_length_geodetic + ): + raise NotSupportedError( + "This backend doesn't support Length on geodetic fields" + ) return super().as_sql(compiler, connection, **extra_context) def as_postgresql(self, compiler, connection, **extra_context): @@ -362,18 +411,20 @@ class Length(DistanceResultMixin, OracleToleranceMixin, GeoFunc): clone.source_expressions.append(Value(self.spheroid)) elif self.geo_field.geodetic(connection): # Geometry fields with geodetic (lon/lat) coordinates need length_spheroid - function = connection.ops.spatial_function_name('LengthSpheroid') + function = connection.ops.spatial_function_name("LengthSpheroid") clone.source_expressions.append(Value(self.geo_field.spheroid(connection))) else: dim = min(f.dim for f in self.get_source_fields() if f) if dim > 2: function = connection.ops.length3d - return super(Length, clone).as_sql(compiler, connection, function=function, **extra_context) + return super(Length, clone).as_sql( + compiler, connection, function=function, **extra_context + ) def as_sqlite(self, compiler, connection, **extra_context): function = None if self.geo_field.geodetic(connection): - function = 'GeodesicLength' if self.spheroid else 'GreatCircleLength' + function = "GeodesicLength" if self.spheroid else "GreatCircleLength" return super().as_sql(compiler, connection, function=function, **extra_context) @@ -408,7 +459,9 @@ class Perimeter(DistanceResultMixin, OracleToleranceMixin, GeoFunc): def as_postgresql(self, compiler, connection, **extra_context): function = None if self.geo_field.geodetic(connection) and not self.source_is_geography(): - raise NotSupportedError("ST_Perimeter cannot use a non-projected non-geography field.") + raise NotSupportedError( + "ST_Perimeter cannot use a non-projected non-geography field." + ) dim = min(f.dim for f in self.get_source_fields()) if dim > 2: function = connection.ops.perimeter3d @@ -432,11 +485,11 @@ class Scale(SQLiteDecimalToFloatMixin, GeomOutputGeoFunc): def __init__(self, expression, x, y, z=0.0, **extra): expressions = [ expression, - self._handle_param(x, 'x', NUMERIC_TYPES), - self._handle_param(y, 'y', NUMERIC_TYPES), + self._handle_param(x, "x", NUMERIC_TYPES), + self._handle_param(y, "y", NUMERIC_TYPES), ] if z != 0.0: - expressions.append(self._handle_param(z, 'z', NUMERIC_TYPES)) + expressions.append(self._handle_param(z, "z", NUMERIC_TYPES)) super().__init__(*expressions, **extra) @@ -446,16 +499,16 @@ class SnapToGrid(SQLiteDecimalToFloatMixin, GeomOutputGeoFunc): expressions = [expression] if nargs in (1, 2): expressions.extend( - [self._handle_param(arg, '', NUMERIC_TYPES) for arg in args] + [self._handle_param(arg, "", NUMERIC_TYPES) for arg in args] ) elif nargs == 4: # Reverse origin and size param ordering expressions += [ - *(self._handle_param(arg, '', NUMERIC_TYPES) for arg in args[2:]), - *(self._handle_param(arg, '', NUMERIC_TYPES) for arg in args[0:2]), + *(self._handle_param(arg, "", NUMERIC_TYPES) for arg in args[2:]), + *(self._handle_param(arg, "", NUMERIC_TYPES) for arg in args[0:2]), ] else: - raise ValueError('Must provide 1, 2, or 4 arguments to `SnapToGrid`.') + raise ValueError("Must provide 1, 2, or 4 arguments to `SnapToGrid`.") super().__init__(*expressions, **extra) @@ -468,10 +521,10 @@ class Transform(GeomOutputGeoFunc): def __init__(self, expression, srid, **extra): expressions = [ expression, - self._handle_param(srid, 'srid', int), + self._handle_param(srid, "srid", int), ] - if 'output_field' not in extra: - extra['output_field'] = GeometryField(srid=srid) + if "output_field" not in extra: + extra["output_field"] = GeometryField(srid=srid) super().__init__(*expressions, **extra) diff --git a/django/contrib/gis/db/models/lookups.py b/django/contrib/gis/db/models/lookups.py index c1d021b28e..9c06922508 100644 --- a/django/contrib/gis/db/models/lookups.py +++ b/django/contrib/gis/db/models/lookups.py @@ -27,10 +27,10 @@ class GISLookup(Lookup): def process_rhs_params(self): if self.rhs_params: # Check if a band index was passed in the query argument. - if len(self.rhs_params) == (2 if self.lookup_name == 'relate' else 1): + if len(self.rhs_params) == (2 if self.lookup_name == "relate" else 1): self.process_band_indices() elif len(self.rhs_params) > 1: - raise ValueError('Tuple too long for lookup %s.' % self.lookup_name) + raise ValueError("Tuple too long for lookup %s." % self.lookup_name) elif isinstance(self.lhs, RasterBandTransform): self.process_band_indices(only_lhs=True) @@ -55,7 +55,7 @@ class GISLookup(Lookup): def get_db_prep_lookup(self, value, connection): # get_db_prep_lookup is called by process_rhs from super class - return ('%s', [connection.ops.Adapter(value)]) + return ("%s", [connection.ops.Adapter(value)]) def process_rhs(self, compiler, connection): if isinstance(self.rhs, Query): @@ -64,7 +64,9 @@ class GISLookup(Lookup): if isinstance(self.rhs, Expression): self.rhs = self.rhs.resolve_expression(compiler.query) rhs, rhs_params = super().process_rhs(compiler, connection) - placeholder = connection.ops.get_geom_placeholder(self.lhs.output_field, self.rhs, compiler) + placeholder = connection.ops.get_geom_placeholder( + self.lhs.output_field, self.rhs, compiler + ) return placeholder % rhs, rhs_params def get_rhs_op(self, connection, rhs): @@ -78,7 +80,12 @@ class GISLookup(Lookup): rhs_sql, rhs_params = self.process_rhs(compiler, connection) sql_params = (*lhs_params, *rhs_params) - template_params = {'lhs': lhs_sql, 'rhs': rhs_sql, 'value': '%s', **self.template_params} + template_params = { + "lhs": lhs_sql, + "rhs": rhs_sql, + "value": "%s", + **self.template_params, + } rhs_op = self.get_rhs_op(connection, rhs_sql) return rhs_op.as_sql(connection, self, template_params, sql_params) @@ -87,13 +94,15 @@ class GISLookup(Lookup): # Geometry operators # ------------------ + @BaseSpatialField.register_lookup class OverlapsLeftLookup(GISLookup): """ The overlaps_left operator returns true if A's bounding box overlaps or is to the left of B's bounding box. """ - lookup_name = 'overlaps_left' + + lookup_name = "overlaps_left" @BaseSpatialField.register_lookup @@ -102,7 +111,8 @@ class OverlapsRightLookup(GISLookup): The 'overlaps_right' operator returns true if A's bounding box overlaps or is to the right of B's bounding box. """ - lookup_name = 'overlaps_right' + + lookup_name = "overlaps_right" @BaseSpatialField.register_lookup @@ -111,7 +121,8 @@ class OverlapsBelowLookup(GISLookup): The 'overlaps_below' operator returns true if A's bounding box overlaps or is below B's bounding box. """ - lookup_name = 'overlaps_below' + + lookup_name = "overlaps_below" @BaseSpatialField.register_lookup @@ -120,7 +131,8 @@ class OverlapsAboveLookup(GISLookup): The 'overlaps_above' operator returns true if A's bounding box overlaps or is above B's bounding box. """ - lookup_name = 'overlaps_above' + + lookup_name = "overlaps_above" @BaseSpatialField.register_lookup @@ -129,7 +141,8 @@ class LeftLookup(GISLookup): The 'left' operator returns true if A's bounding box is strictly to the left of B's bounding box. """ - lookup_name = 'left' + + lookup_name = "left" @BaseSpatialField.register_lookup @@ -138,7 +151,8 @@ class RightLookup(GISLookup): The 'right' operator returns true if A's bounding box is strictly to the right of B's bounding box. """ - lookup_name = 'right' + + lookup_name = "right" @BaseSpatialField.register_lookup @@ -147,7 +161,8 @@ class StrictlyBelowLookup(GISLookup): The 'strictly_below' operator returns true if A's bounding box is strictly below B's bounding box. """ - lookup_name = 'strictly_below' + + lookup_name = "strictly_below" @BaseSpatialField.register_lookup @@ -156,7 +171,8 @@ class StrictlyAboveLookup(GISLookup): The 'strictly_above' operator returns true if A's bounding box is strictly above B's bounding box. """ - lookup_name = 'strictly_above' + + lookup_name = "strictly_above" @BaseSpatialField.register_lookup @@ -166,10 +182,11 @@ class SameAsLookup(GISLookup): equality of two features. So if A and B are the same feature, vertex-by-vertex, the operator returns true. """ - lookup_name = 'same_as' + lookup_name = "same_as" -BaseSpatialField.register_lookup(SameAsLookup, 'exact') + +BaseSpatialField.register_lookup(SameAsLookup, "exact") @BaseSpatialField.register_lookup @@ -178,7 +195,8 @@ class BBContainsLookup(GISLookup): The 'bbcontains' operator returns true if A's bounding box completely contains by B's bounding box. """ - lookup_name = 'bbcontains' + + lookup_name = "bbcontains" @BaseSpatialField.register_lookup @@ -186,7 +204,8 @@ class BBOverlapsLookup(GISLookup): """ The 'bboverlaps' operator returns true if A's bounding box overlaps B's bounding box. """ - lookup_name = 'bboverlaps' + + lookup_name = "bboverlaps" @BaseSpatialField.register_lookup @@ -195,69 +214,71 @@ class ContainedLookup(GISLookup): The 'contained' operator returns true if A's bounding box is completely contained by B's bounding box. """ - lookup_name = 'contained' + + lookup_name = "contained" # ------------------ # Geometry functions # ------------------ + @BaseSpatialField.register_lookup class ContainsLookup(GISLookup): - lookup_name = 'contains' + lookup_name = "contains" @BaseSpatialField.register_lookup class ContainsProperlyLookup(GISLookup): - lookup_name = 'contains_properly' + lookup_name = "contains_properly" @BaseSpatialField.register_lookup class CoveredByLookup(GISLookup): - lookup_name = 'coveredby' + lookup_name = "coveredby" @BaseSpatialField.register_lookup class CoversLookup(GISLookup): - lookup_name = 'covers' + lookup_name = "covers" @BaseSpatialField.register_lookup class CrossesLookup(GISLookup): - lookup_name = 'crosses' + lookup_name = "crosses" @BaseSpatialField.register_lookup class DisjointLookup(GISLookup): - lookup_name = 'disjoint' + lookup_name = "disjoint" @BaseSpatialField.register_lookup class EqualsLookup(GISLookup): - lookup_name = 'equals' + lookup_name = "equals" @BaseSpatialField.register_lookup class IntersectsLookup(GISLookup): - lookup_name = 'intersects' + lookup_name = "intersects" @BaseSpatialField.register_lookup class OverlapsLookup(GISLookup): - lookup_name = 'overlaps' + lookup_name = "overlaps" @BaseSpatialField.register_lookup class RelateLookup(GISLookup): - lookup_name = 'relate' - sql_template = '%(func)s(%(lhs)s, %(rhs)s, %%s)' - pattern_regex = _lazy_re_compile(r'^[012TF\*]{9}$') + lookup_name = "relate" + sql_template = "%(func)s(%(lhs)s, %(rhs)s, %%s)" + pattern_regex = _lazy_re_compile(r"^[012TF\*]{9}$") def process_rhs(self, compiler, connection): # Check the pattern argument pattern = self.rhs_params[0] backend_op = connection.ops.gis_operators[self.lookup_name] - if hasattr(backend_op, 'check_relate_argument'): + if hasattr(backend_op, "check_relate_argument"): backend_op.check_relate_argument(pattern) elif not isinstance(pattern, str) or not self.pattern_regex.match(pattern): raise ValueError('Invalid intersection matrix pattern "%s".' % pattern) @@ -267,93 +288,106 @@ class RelateLookup(GISLookup): @BaseSpatialField.register_lookup class TouchesLookup(GISLookup): - lookup_name = 'touches' + lookup_name = "touches" @BaseSpatialField.register_lookup class WithinLookup(GISLookup): - lookup_name = 'within' + lookup_name = "within" class DistanceLookupBase(GISLookup): distance = True - sql_template = '%(func)s(%(lhs)s, %(rhs)s) %(op)s %(value)s' + sql_template = "%(func)s(%(lhs)s, %(rhs)s) %(op)s %(value)s" def process_rhs_params(self): if not 1 <= len(self.rhs_params) <= 3: - raise ValueError("2, 3, or 4-element tuple required for '%s' lookup." % self.lookup_name) - elif len(self.rhs_params) == 3 and self.rhs_params[2] != 'spheroid': - raise ValueError("For 4-element tuples the last argument must be the 'spheroid' directive.") + raise ValueError( + "2, 3, or 4-element tuple required for '%s' lookup." % self.lookup_name + ) + elif len(self.rhs_params) == 3 and self.rhs_params[2] != "spheroid": + raise ValueError( + "For 4-element tuples the last argument must be the 'spheroid' directive." + ) # Check if the second parameter is a band index. - if len(self.rhs_params) > 1 and self.rhs_params[1] != 'spheroid': + if len(self.rhs_params) > 1 and self.rhs_params[1] != "spheroid": self.process_band_indices() def process_distance(self, compiler, connection): dist_param = self.rhs_params[0] return ( compiler.compile(dist_param.resolve_expression(compiler.query)) - if hasattr(dist_param, 'resolve_expression') else - ('%s', connection.ops.get_distance(self.lhs.output_field, self.rhs_params, self.lookup_name)) + if hasattr(dist_param, "resolve_expression") + else ( + "%s", + connection.ops.get_distance( + self.lhs.output_field, self.rhs_params, self.lookup_name + ), + ) ) @BaseSpatialField.register_lookup class DWithinLookup(DistanceLookupBase): - lookup_name = 'dwithin' - sql_template = '%(func)s(%(lhs)s, %(rhs)s, %(value)s)' + lookup_name = "dwithin" + sql_template = "%(func)s(%(lhs)s, %(rhs)s, %(value)s)" def process_distance(self, compiler, connection): dist_param = self.rhs_params[0] if ( - not connection.features.supports_dwithin_distance_expr and - hasattr(dist_param, 'resolve_expression') and - not isinstance(dist_param, Distance) + not connection.features.supports_dwithin_distance_expr + and hasattr(dist_param, "resolve_expression") + and not isinstance(dist_param, Distance) ): raise NotSupportedError( - 'This backend does not support expressions for specifying ' - 'distance in the dwithin lookup.' + "This backend does not support expressions for specifying " + "distance in the dwithin lookup." ) return super().process_distance(compiler, connection) def process_rhs(self, compiler, connection): dist_sql, dist_params = self.process_distance(compiler, connection) - self.template_params['value'] = dist_sql + self.template_params["value"] = dist_sql rhs_sql, params = super().process_rhs(compiler, connection) return rhs_sql, params + dist_params class DistanceLookupFromFunction(DistanceLookupBase): def as_sql(self, compiler, connection): - spheroid = (len(self.rhs_params) == 2 and self.rhs_params[-1] == 'spheroid') or None - distance_expr = connection.ops.distance_expr_for_lookup(self.lhs, self.rhs, spheroid=spheroid) + spheroid = ( + len(self.rhs_params) == 2 and self.rhs_params[-1] == "spheroid" + ) or None + distance_expr = connection.ops.distance_expr_for_lookup( + self.lhs, self.rhs, spheroid=spheroid + ) sql, params = compiler.compile(distance_expr.resolve_expression(compiler.query)) dist_sql, dist_params = self.process_distance(compiler, connection) return ( - '%(func)s %(op)s %(dist)s' % {'func': sql, 'op': self.op, 'dist': dist_sql}, + "%(func)s %(op)s %(dist)s" % {"func": sql, "op": self.op, "dist": dist_sql}, params + dist_params, ) @BaseSpatialField.register_lookup class DistanceGTLookup(DistanceLookupFromFunction): - lookup_name = 'distance_gt' - op = '>' + lookup_name = "distance_gt" + op = ">" @BaseSpatialField.register_lookup class DistanceGTELookup(DistanceLookupFromFunction): - lookup_name = 'distance_gte' - op = '>=' + lookup_name = "distance_gte" + op = ">=" @BaseSpatialField.register_lookup class DistanceLTLookup(DistanceLookupFromFunction): - lookup_name = 'distance_lt' - op = '<' + lookup_name = "distance_lt" + op = "<" @BaseSpatialField.register_lookup class DistanceLTELookup(DistanceLookupFromFunction): - lookup_name = 'distance_lte' - op = '<=' + lookup_name = "distance_lte" + op = "<=" diff --git a/django/contrib/gis/db/models/proxy.py b/django/contrib/gis/db/models/proxy.py index 07b26e9910..4db365dc16 100644 --- a/django/contrib/gis/db/models/proxy.py +++ b/django/contrib/gis/db/models/proxy.py @@ -37,7 +37,7 @@ class SpatialProxy(DeferredAttribute): if isinstance(geo_value, self._klass): geo_obj = geo_value - elif (geo_value is None) or (geo_value == ''): + elif (geo_value is None) or (geo_value == ""): geo_obj = None else: # Otherwise, a geometry or raster object is built using the field's @@ -57,7 +57,9 @@ class SpatialProxy(DeferredAttribute): # The geographic type of the field. gtype = self.field.geom_type - if gtype == 'RASTER' and (value is None or isinstance(value, (str, dict, self._klass))): + if gtype == "RASTER" and ( + value is None or isinstance(value, (str, dict, self._klass)) + ): # For raster fields, ensure input is None or a string, dict, or # raster instance. pass @@ -71,8 +73,10 @@ class SpatialProxy(DeferredAttribute): # Set geometries with None, WKT, HEX, or WKB pass else: - raise TypeError('Cannot set %s SpatialProxy (%s) with value of type: %s' % ( - instance.__class__.__name__, gtype, type(value))) + raise TypeError( + "Cannot set %s SpatialProxy (%s) with value of type: %s" + % (instance.__class__.__name__, gtype, type(value)) + ) # Setting the objects dictionary with the value, and returning. instance.__dict__[self.field.attname] = value diff --git a/django/contrib/gis/db/models/sql/__init__.py b/django/contrib/gis/db/models/sql/__init__.py index 850c644d5d..1376e8d7d1 100644 --- a/django/contrib/gis/db/models/sql/__init__.py +++ b/django/contrib/gis/db/models/sql/__init__.py @@ -1,7 +1,6 @@ -from django.contrib.gis.db.models.sql.conversion import ( - AreaField, DistanceField, -) +from django.contrib.gis.db.models.sql.conversion import AreaField, DistanceField __all__ = [ - 'AreaField', 'DistanceField', + "AreaField", + "DistanceField", ] diff --git a/django/contrib/gis/db/models/sql/conversion.py b/django/contrib/gis/db/models/sql/conversion.py index 99ab51e239..be712319fb 100644 --- a/django/contrib/gis/db/models/sql/conversion.py +++ b/django/contrib/gis/db/models/sql/conversion.py @@ -10,13 +10,14 @@ from django.db import models class AreaField(models.FloatField): "Wrapper for Area values." + def __init__(self, geo_field): super().__init__() self.geo_field = geo_field def get_prep_value(self, value): if not isinstance(value, Area): - raise ValueError('AreaField only accepts Area measurement objects.') + raise ValueError("AreaField only accepts Area measurement objects.") return value def get_db_prep_value(self, value, connection, prepared=False): @@ -37,11 +38,12 @@ class AreaField(models.FloatField): return Area(**{area_att: value}) if area_att else value def get_internal_type(self): - return 'AreaField' + return "AreaField" class DistanceField(models.FloatField): "Wrapper for Distance values." + def __init__(self, geo_field): super().__init__() self.geo_field = geo_field @@ -56,7 +58,9 @@ class DistanceField(models.FloatField): return value distance_att = connection.ops.get_distance_att_for_field(self.geo_field) if not distance_att: - raise ValueError('Distance measure is supplied, but units are unknown for result.') + raise ValueError( + "Distance measure is supplied, but units are unknown for result." + ) return getattr(value, distance_att) def from_db_value(self, value, expression, connection): @@ -66,4 +70,4 @@ class DistanceField(models.FloatField): return Distance(**{distance_att: value}) if distance_att else value def get_internal_type(self): - return 'DistanceField' + return "DistanceField" diff --git a/django/contrib/gis/feeds.py b/django/contrib/gis/feeds.py index cfc078b781..ebd4511889 100644 --- a/django/contrib/gis/feeds.py +++ b/django/contrib/gis/feeds.py @@ -14,7 +14,7 @@ class GeoFeedMixin: a single white space. Given a tuple of coordinates, return a string GeoRSS representation. """ - return ' '.join('%f %f' % (coord[1], coord[0]) for coord in coords) + return " ".join("%f %f" % (coord[1], coord[0]) for coord in coords) def add_georss_point(self, handler, coords, w3c_geo=False): """ @@ -24,15 +24,15 @@ class GeoFeedMixin: """ if w3c_geo: lon, lat = coords[:2] - handler.addQuickElement('geo:lat', '%f' % lat) - handler.addQuickElement('geo:lon', '%f' % lon) + handler.addQuickElement("geo:lat", "%f" % lat) + handler.addQuickElement("geo:lon", "%f" % lon) else: - handler.addQuickElement('georss:point', self.georss_coords((coords,))) + handler.addQuickElement("georss:point", self.georss_coords((coords,))) def add_georss_element(self, handler, item, w3c_geo=False): """Add a GeoRSS XML element using the given item and handler.""" # Getting the Geometry object. - geom = item.get('geometry') + geom = item.get("geometry") if geom is not None: if isinstance(geom, (list, tuple)): # Special case if a tuple/list was passed in. The tuple may be @@ -43,7 +43,7 @@ class GeoFeedMixin: if len(geom) == 2: box_coords = geom else: - raise ValueError('Only should be two sets of coordinates.') + raise ValueError("Only should be two sets of coordinates.") else: if len(geom) == 2: # Point: (X, Y) @@ -52,36 +52,46 @@ class GeoFeedMixin: # Box: (X0, Y0, X1, Y1) box_coords = (geom[:2], geom[2:]) else: - raise ValueError('Only should be 2 or 4 numeric elements.') + raise ValueError("Only should be 2 or 4 numeric elements.") # If a GeoRSS box was given via tuple. if box_coords is not None: if w3c_geo: - raise ValueError('Cannot use simple GeoRSS box in W3C Geo feeds.') - handler.addQuickElement('georss:box', self.georss_coords(box_coords)) + raise ValueError( + "Cannot use simple GeoRSS box in W3C Geo feeds." + ) + handler.addQuickElement( + "georss:box", self.georss_coords(box_coords) + ) else: # Getting the lowercase geometry type. gtype = str(geom.geom_type).lower() - if gtype == 'point': + if gtype == "point": self.add_georss_point(handler, geom.coords, w3c_geo=w3c_geo) else: if w3c_geo: - raise ValueError('W3C Geo only supports Point geometries.') + raise ValueError("W3C Geo only supports Point geometries.") # For formatting consistent w/the GeoRSS simple standard: # http://georss.org/1.0#simple - if gtype in ('linestring', 'linearring'): - handler.addQuickElement('georss:line', self.georss_coords(geom.coords)) - elif gtype in ('polygon',): + if gtype in ("linestring", "linearring"): + handler.addQuickElement( + "georss:line", self.georss_coords(geom.coords) + ) + elif gtype in ("polygon",): # Only support the exterior ring. - handler.addQuickElement('georss:polygon', self.georss_coords(geom[0].coords)) + handler.addQuickElement( + "georss:polygon", self.georss_coords(geom[0].coords) + ) else: - raise ValueError('Geometry type "%s" not supported.' % geom.geom_type) + raise ValueError( + 'Geometry type "%s" not supported.' % geom.geom_type + ) # ### SyndicationFeed subclasses ### class GeoRSSFeed(Rss201rev2Feed, GeoFeedMixin): def rss_attributes(self): attrs = super().rss_attributes() - attrs['xmlns:georss'] = 'http://www.georss.org/georss' + attrs["xmlns:georss"] = "http://www.georss.org/georss" return attrs def add_item_elements(self, handler, item): @@ -96,7 +106,7 @@ class GeoRSSFeed(Rss201rev2Feed, GeoFeedMixin): class GeoAtom1Feed(Atom1Feed, GeoFeedMixin): def root_attributes(self): attrs = super().root_attributes() - attrs['xmlns:georss'] = 'http://www.georss.org/georss' + attrs["xmlns:georss"] = "http://www.georss.org/georss" return attrs def add_item_elements(self, handler, item): @@ -111,7 +121,7 @@ class GeoAtom1Feed(Atom1Feed, GeoFeedMixin): class W3CGeoFeed(Rss201rev2Feed, GeoFeedMixin): def rss_attributes(self): attrs = super().rss_attributes() - attrs['xmlns:geo'] = 'http://www.w3.org/2003/01/geo/wgs84_pos#' + attrs["xmlns:geo"] = "http://www.w3.org/2003/01/geo/wgs84_pos#" return attrs def add_item_elements(self, handler, item): @@ -131,10 +141,11 @@ class Feed(BaseFeed): methods on their own subclasses so that geo-referenced information may placed in the feed. """ + feed_type = GeoRSSFeed def feed_extra_kwargs(self, obj): - return {'geometry': self._get_dynamic_attr('geometry', obj)} + return {"geometry": self._get_dynamic_attr("geometry", obj)} def item_extra_kwargs(self, item): - return {'geometry': self._get_dynamic_attr('item_geometry', item)} + return {"geometry": self._get_dynamic_attr("item_geometry", item)} diff --git a/django/contrib/gis/forms/__init__.py b/django/contrib/gis/forms/__init__.py index 237cbace8d..c07720b2d0 100644 --- a/django/contrib/gis/forms/__init__.py +++ b/django/contrib/gis/forms/__init__.py @@ -1,8 +1,13 @@ from django.forms import * # NOQA from .fields import ( # NOQA - GeometryCollectionField, GeometryField, LineStringField, - MultiLineStringField, MultiPointField, MultiPolygonField, PointField, + GeometryCollectionField, + GeometryField, + LineStringField, + MultiLineStringField, + MultiPointField, + MultiPolygonField, + PointField, PolygonField, ) from .widgets import BaseGeometryWidget, OpenLayersWidget, OSMWidget # NOQA diff --git a/django/contrib/gis/forms/fields.py b/django/contrib/gis/forms/fields.py index 301d770836..1fd31530c1 100644 --- a/django/contrib/gis/forms/fields.py +++ b/django/contrib/gis/forms/fields.py @@ -13,15 +13,18 @@ class GeometryField(forms.Field): accepted by GEOSGeometry is accepted by this form. By default, this includes WKT, HEXEWKB, WKB (in a buffer), and GeoJSON. """ + widget = OpenLayersWidget - geom_type = 'GEOMETRY' + geom_type = "GEOMETRY" default_error_messages = { - 'required': _('No geometry value provided.'), - 'invalid_geom': _('Invalid geometry value.'), - 'invalid_geom_type': _('Invalid geometry type.'), - 'transform_error': _('An error occurred when transforming the geometry ' - 'to the SRID of the geometry form field.'), + "required": _("No geometry value provided."), + "invalid_geom": _("Invalid geometry value."), + "invalid_geom_type": _("Invalid geometry type."), + "transform_error": _( + "An error occurred when transforming the geometry " + "to the SRID of the geometry form field." + ), } def __init__(self, *, srid=None, geom_type=None, **kwargs): @@ -29,7 +32,7 @@ class GeometryField(forms.Field): if geom_type is not None: self.geom_type = geom_type super().__init__(**kwargs) - self.widget.attrs['geom_type'] = self.geom_type + self.widget.attrs["geom_type"] = self.geom_type def to_python(self, value): """Transform the value to a Geometry object.""" @@ -37,7 +40,7 @@ class GeometryField(forms.Field): return None if not isinstance(value, GEOSGeometry): - if hasattr(self.widget, 'deserialize'): + if hasattr(self.widget, "deserialize"): try: value = self.widget.deserialize(value) except GDALException: @@ -48,7 +51,9 @@ class GeometryField(forms.Field): except (GEOSException, ValueError, TypeError): value = None if value is None: - raise ValidationError(self.error_messages['invalid_geom'], code='invalid_geom') + raise ValidationError( + self.error_messages["invalid_geom"], code="invalid_geom" + ) # Try to set the srid if not value.srid: @@ -71,8 +76,13 @@ class GeometryField(forms.Field): # Ensuring that the geometry is of the correct type (indicated # using the OGC string label). - if str(geom.geom_type).upper() != self.geom_type and self.geom_type != 'GEOMETRY': - raise ValidationError(self.error_messages['invalid_geom_type'], code='invalid_geom_type') + if ( + str(geom.geom_type).upper() != self.geom_type + and self.geom_type != "GEOMETRY" + ): + raise ValidationError( + self.error_messages["invalid_geom_type"], code="invalid_geom_type" + ) # Transforming the geometry if the SRID was set. if self.srid and self.srid != -1 and self.srid != geom.srid: @@ -80,12 +90,13 @@ class GeometryField(forms.Field): geom.transform(self.srid) except GEOSException: raise ValidationError( - self.error_messages['transform_error'], code='transform_error') + self.error_messages["transform_error"], code="transform_error" + ) return geom def has_changed(self, initial, data): - """ Compare geographic value of data with its initial value. """ + """Compare geographic value of data with its initial value.""" try: data = self.to_python(data) @@ -106,28 +117,28 @@ class GeometryField(forms.Field): class GeometryCollectionField(GeometryField): - geom_type = 'GEOMETRYCOLLECTION' + geom_type = "GEOMETRYCOLLECTION" class PointField(GeometryField): - geom_type = 'POINT' + geom_type = "POINT" class MultiPointField(GeometryField): - geom_type = 'MULTIPOINT' + geom_type = "MULTIPOINT" class LineStringField(GeometryField): - geom_type = 'LINESTRING' + geom_type = "LINESTRING" class MultiLineStringField(GeometryField): - geom_type = 'MULTILINESTRING' + geom_type = "MULTILINESTRING" class PolygonField(GeometryField): - geom_type = 'POLYGON' + geom_type = "POLYGON" class MultiPolygonField(GeometryField): - geom_type = 'MULTIPOLYGON' + geom_type = "MULTIPOLYGON" diff --git a/django/contrib/gis/forms/widgets.py b/django/contrib/gis/forms/widgets.py index e38f173a98..0f53ee2e96 100644 --- a/django/contrib/gis/forms/widgets.py +++ b/django/contrib/gis/forms/widgets.py @@ -7,7 +7,7 @@ from django.contrib.gis.geos import GEOSException, GEOSGeometry from django.forms.widgets import Widget from django.utils import translation -logger = logging.getLogger('django.contrib.gis') +logger = logging.getLogger("django.contrib.gis") class BaseGeometryWidget(Widget): @@ -15,24 +15,25 @@ class BaseGeometryWidget(Widget): The base class for rich geometry widgets. Render a map using the WKT of the geometry. """ - geom_type = 'GEOMETRY' + + geom_type = "GEOMETRY" map_srid = 4326 map_width = 600 map_height = 400 display_raw = False supports_3d = False - template_name = '' # set on subclasses + template_name = "" # set on subclasses def __init__(self, attrs=None): self.attrs = {} - for key in ('geom_type', 'map_srid', 'map_width', 'map_height', 'display_raw'): + for key in ("geom_type", "map_srid", "map_width", "map_height", "display_raw"): self.attrs[key] = getattr(self, key) if attrs: self.attrs.update(attrs) def serialize(self, value): - return value.wkt if value else '' + return value.wkt if value else "" def deserialize(self, value): try: @@ -58,40 +59,47 @@ class BaseGeometryWidget(Widget): except gdal.GDALException as err: logger.error( "Error transforming geometry from srid '%s' to srid '%s' (%s)", - value.srid, self.map_srid, err + value.srid, + self.map_srid, + err, ) - geom_type = gdal.OGRGeomType(self.attrs['geom_type']).name - context.update(self.build_attrs(self.attrs, { - 'name': name, - 'module': 'geodjango_%s' % name.replace('-', '_'), # JS-safe - 'serialized': self.serialize(value), - 'geom_type': 'Geometry' if geom_type == 'Unknown' else geom_type, - 'STATIC_URL': settings.STATIC_URL, - 'LANGUAGE_BIDI': translation.get_language_bidi(), - **(attrs or {}), - })) + geom_type = gdal.OGRGeomType(self.attrs["geom_type"]).name + context.update( + self.build_attrs( + self.attrs, + { + "name": name, + "module": "geodjango_%s" % name.replace("-", "_"), # JS-safe + "serialized": self.serialize(value), + "geom_type": "Geometry" if geom_type == "Unknown" else geom_type, + "STATIC_URL": settings.STATIC_URL, + "LANGUAGE_BIDI": translation.get_language_bidi(), + **(attrs or {}), + }, + ) + ) return context class OpenLayersWidget(BaseGeometryWidget): - template_name = 'gis/openlayers.html' + template_name = "gis/openlayers.html" map_srid = 3857 class Media: css = { - 'all': ( - 'https://cdnjs.cloudflare.com/ajax/libs/ol3/4.6.5/ol.css', - 'gis/css/ol3.css', + "all": ( + "https://cdnjs.cloudflare.com/ajax/libs/ol3/4.6.5/ol.css", + "gis/css/ol3.css", ) } js = ( - 'https://cdnjs.cloudflare.com/ajax/libs/ol3/4.6.5/ol.js', - 'gis/js/OLMapWidget.js', + "https://cdnjs.cloudflare.com/ajax/libs/ol3/4.6.5/ol.js", + "gis/js/OLMapWidget.js", ) def serialize(self, value): - return value.json if value else '' + return value.json if value else "" def deserialize(self, value): geom = super().deserialize(value) @@ -105,14 +113,15 @@ class OSMWidget(OpenLayersWidget): """ An OpenLayers/OpenStreetMap-based widget. """ - template_name = 'gis/openlayers-osm.html' + + template_name = "gis/openlayers-osm.html" default_lon = 5 default_lat = 47 default_zoom = 12 def __init__(self, attrs=None): super().__init__() - for key in ('default_lon', 'default_lat', 'default_zoom'): + for key in ("default_lon", "default_lat", "default_zoom"): self.attrs[key] = getattr(self, key) if attrs: self.attrs.update(attrs) diff --git a/django/contrib/gis/gdal/__init__.py b/django/contrib/gis/gdal/__init__.py index 1d8c6884bd..9ed6e31156 100644 --- a/django/contrib/gis/gdal/__init__.py +++ b/django/contrib/gis/gdal/__init__.py @@ -28,22 +28,31 @@ from django.contrib.gis.gdal.datasource import DataSource from django.contrib.gis.gdal.driver import Driver from django.contrib.gis.gdal.envelope import Envelope -from django.contrib.gis.gdal.error import ( - GDALException, SRSException, check_err, -) +from django.contrib.gis.gdal.error import GDALException, SRSException, check_err from django.contrib.gis.gdal.geometries import OGRGeometry from django.contrib.gis.gdal.geomtype import OGRGeomType from django.contrib.gis.gdal.libgdal import ( - GDAL_VERSION, gdal_full_version, gdal_version, + GDAL_VERSION, + gdal_full_version, + gdal_version, ) from django.contrib.gis.gdal.raster.source import GDALRaster -from django.contrib.gis.gdal.srs import ( - AxisOrder, CoordTransform, SpatialReference, -) +from django.contrib.gis.gdal.srs import AxisOrder, CoordTransform, SpatialReference __all__ = ( - 'AxisOrder', 'Driver', 'DataSource', 'CoordTransform', 'Envelope', - 'GDALException', 'GDALRaster', 'GDAL_VERSION', 'OGRGeometry', - 'OGRGeomType', 'SpatialReference', 'SRSException', 'check_err', - 'gdal_version', 'gdal_full_version', + "AxisOrder", + "Driver", + "DataSource", + "CoordTransform", + "Envelope", + "GDALException", + "GDALRaster", + "GDAL_VERSION", + "OGRGeometry", + "OGRGeomType", + "SpatialReference", + "SRSException", + "check_err", + "gdal_version", + "gdal_full_version", ) diff --git a/django/contrib/gis/gdal/datasource.py b/django/contrib/gis/gdal/datasource.py index f2091c864a..dfd043ab0c 100644 --- a/django/contrib/gis/gdal/datasource.py +++ b/django/contrib/gis/gdal/datasource.py @@ -52,7 +52,7 @@ class DataSource(GDALBase): "Wraps an OGR Data Source object." destructor = capi.destroy_ds - def __init__(self, ds_input, ds_driver=False, write=False, encoding='utf-8'): + def __init__(self, ds_input, ds_driver=False, write=False, encoding="utf-8"): # The write flag. if write: self._write = 1 @@ -73,10 +73,12 @@ class DataSource(GDALBase): # Making the error message more clear rather than something # like "Invalid pointer returned from OGROpen". raise GDALException('Could not open the datasource at "%s"' % ds_input) - elif isinstance(ds_input, self.ptr_type) and isinstance(ds_driver, Driver.ptr_type): + elif isinstance(ds_input, self.ptr_type) and isinstance( + ds_driver, Driver.ptr_type + ): ds = ds_input else: - raise GDALException('Invalid data source input type: %s' % type(ds_input)) + raise GDALException("Invalid data source input type: %s" % type(ds_input)) if ds: self.ptr = ds @@ -91,14 +93,17 @@ class DataSource(GDALBase): try: layer = capi.get_layer_by_name(self.ptr, force_bytes(index)) except GDALException: - raise IndexError('Invalid OGR layer name given: %s.' % index) + raise IndexError("Invalid OGR layer name given: %s." % index) elif isinstance(index, int): if 0 <= index < self.layer_count: layer = capi.get_layer(self._ptr, index) else: - raise IndexError('Index out of range when accessing layers in a datasource: %s.' % index) + raise IndexError( + "Index out of range when accessing layers in a datasource: %s." + % index + ) else: - raise TypeError('Invalid index type: %s' % type(index)) + raise TypeError("Invalid index type: %s" % type(index)) return Layer(layer, self) def __len__(self): @@ -107,7 +112,7 @@ class DataSource(GDALBase): def __str__(self): "Return OGR GetName and Driver for the Data Source." - return '%s (%s)' % (self.name, self.driver) + return "%s (%s)" % (self.name, self.driver) @property def layer_count(self): diff --git a/django/contrib/gis/gdal/driver.py b/django/contrib/gis/gdal/driver.py index f90b886251..0ce7a2cdc8 100644 --- a/django/contrib/gis/gdal/driver.py +++ b/django/contrib/gis/gdal/driver.py @@ -2,7 +2,8 @@ from ctypes import c_void_p from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.error import GDALException -from django.contrib.gis.gdal.prototypes import ds as vcapi, raster as rcapi +from django.contrib.gis.gdal.prototypes import ds as vcapi +from django.contrib.gis.gdal.prototypes import raster as rcapi from django.utils.encoding import force_bytes, force_str @@ -20,16 +21,16 @@ class Driver(GDALBase): # https://gdal.org/drivers/raster/ _alias = { # vector - 'esri': 'ESRI Shapefile', - 'shp': 'ESRI Shapefile', - 'shape': 'ESRI Shapefile', - 'tiger': 'TIGER', - 'tiger/line': 'TIGER', + "esri": "ESRI Shapefile", + "shp": "ESRI Shapefile", + "shape": "ESRI Shapefile", + "tiger": "TIGER", + "tiger/line": "TIGER", # raster - 'tiff': 'GTiff', - 'tif': 'GTiff', - 'jpeg': 'JPEG', - 'jpg': 'JPEG', + "tiff": "GTiff", + "tif": "GTiff", + "jpeg": "JPEG", + "jpg": "JPEG", } def __init__(self, dr_input): @@ -61,11 +62,15 @@ class Driver(GDALBase): elif isinstance(dr_input, c_void_p): driver = dr_input else: - raise GDALException('Unrecognized input type for GDAL/OGR Driver: %s' % type(dr_input)) + raise GDALException( + "Unrecognized input type for GDAL/OGR Driver: %s" % type(dr_input) + ) # Making sure we get a valid pointer to the OGR Driver if not driver: - raise GDALException('Could not initialize GDAL/OGR Driver on input: %s' % dr_input) + raise GDALException( + "Could not initialize GDAL/OGR Driver on input: %s" % dr_input + ) self.ptr = driver def __str__(self): diff --git a/django/contrib/gis/gdal/envelope.py b/django/contrib/gis/gdal/envelope.py index 98c333f483..4c2c1e4a1a 100644 --- a/django/contrib/gis/gdal/envelope.py +++ b/django/contrib/gis/gdal/envelope.py @@ -20,11 +20,12 @@ from django.contrib.gis.gdal.error import GDALException # https://gdal.org/doxygen/ogr__core_8h_source.html class OGREnvelope(Structure): "Represent the OGREnvelope C Structure." - _fields_ = [("MinX", c_double), - ("MaxX", c_double), - ("MinY", c_double), - ("MaxY", c_double), - ] + _fields_ = [ + ("MinX", c_double), + ("MaxX", c_double), + ("MinY", c_double), + ("MaxY", c_double), + ] class Envelope: @@ -47,23 +48,25 @@ class Envelope: elif isinstance(args[0], (tuple, list)): # A tuple was passed in. if len(args[0]) != 4: - raise GDALException('Incorrect number of tuple elements (%d).' % len(args[0])) + raise GDALException( + "Incorrect number of tuple elements (%d)." % len(args[0]) + ) else: self._from_sequence(args[0]) else: - raise TypeError('Incorrect type of argument: %s' % type(args[0])) + raise TypeError("Incorrect type of argument: %s" % type(args[0])) elif len(args) == 4: # Individual parameters passed in. # Thanks to ww for the help self._from_sequence([float(a) for a in args]) else: - raise GDALException('Incorrect number (%d) of arguments.' % len(args)) + raise GDALException("Incorrect number (%d) of arguments." % len(args)) # Checking the x,y coordinates if self.min_x > self.max_x: - raise GDALException('Envelope minimum X > maximum X.') + raise GDALException("Envelope minimum X > maximum X.") if self.min_y > self.max_y: - raise GDALException('Envelope minimum Y > maximum Y.') + raise GDALException("Envelope minimum Y > maximum Y.") def __eq__(self, other): """ @@ -71,13 +74,21 @@ class Envelope: other Envelopes and 4-tuples. """ if isinstance(other, Envelope): - return (self.min_x == other.min_x) and (self.min_y == other.min_y) and \ - (self.max_x == other.max_x) and (self.max_y == other.max_y) + return ( + (self.min_x == other.min_x) + and (self.min_y == other.min_y) + and (self.max_x == other.max_x) + and (self.max_y == other.max_y) + ) elif isinstance(other, tuple) and len(other) == 4: - return (self.min_x == other[0]) and (self.min_y == other[1]) and \ - (self.max_x == other[2]) and (self.max_y == other[3]) + return ( + (self.min_x == other[0]) + and (self.min_y == other[1]) + and (self.max_x == other[2]) + and (self.max_y == other[3]) + ) else: - raise GDALException('Equivalence testing only works with other Envelopes.') + raise GDALException("Equivalence testing only works with other Envelopes.") def __str__(self): "Return a string representation of the tuple." @@ -104,12 +115,16 @@ class Envelope: if len(args) == 1: if isinstance(args[0], Envelope): return self.expand_to_include(args[0].tuple) - elif hasattr(args[0], 'x') and hasattr(args[0], 'y'): - return self.expand_to_include(args[0].x, args[0].y, args[0].x, args[0].y) + elif hasattr(args[0], "x") and hasattr(args[0], "y"): + return self.expand_to_include( + args[0].x, args[0].y, args[0].x, args[0].y + ) elif isinstance(args[0], (tuple, list)): # A tuple was passed in. if len(args[0]) == 2: - return self.expand_to_include((args[0][0], args[0][1], args[0][0], args[0][1])) + return self.expand_to_include( + (args[0][0], args[0][1], args[0][0], args[0][1]) + ) elif len(args[0]) == 4: (minx, miny, maxx, maxy) = args[0] if minx < self._envelope.MinX: @@ -121,9 +136,11 @@ class Envelope: if maxy > self._envelope.MaxY: self._envelope.MaxY = maxy else: - raise GDALException('Incorrect number of tuple elements (%d).' % len(args[0])) + raise GDALException( + "Incorrect number of tuple elements (%d)." % len(args[0]) + ) else: - raise TypeError('Incorrect type of argument: %s' % type(args[0])) + raise TypeError("Incorrect type of argument: %s" % type(args[0])) elif len(args) == 2: # An x and an y parameter were passed in return self.expand_to_include((args[0], args[1], args[0], args[1])) @@ -131,7 +148,7 @@ class Envelope: # Individual parameters passed in. return self.expand_to_include(args) else: - raise GDALException('Incorrect number (%d) of arguments.' % len(args[0])) + raise GDALException("Incorrect number (%d) of arguments." % len(args[0])) @property def min_x(self): @@ -172,7 +189,15 @@ class Envelope: def wkt(self): "Return WKT representing a Polygon for this envelope." # TODO: Fix significant figures. - return 'POLYGON((%s %s,%s %s,%s %s,%s %s,%s %s))' % \ - (self.min_x, self.min_y, self.min_x, self.max_y, - self.max_x, self.max_y, self.max_x, self.min_y, - self.min_x, self.min_y) + return "POLYGON((%s %s,%s %s,%s %s,%s %s,%s %s))" % ( + self.min_x, + self.min_y, + self.min_x, + self.max_y, + self.max_x, + self.max_y, + self.max_x, + self.min_y, + self.min_x, + self.min_y, + ) diff --git a/django/contrib/gis/gdal/error.py b/django/contrib/gis/gdal/error.py index b5646dd751..df19c2e4a7 100644 --- a/django/contrib/gis/gdal/error.py +++ b/django/contrib/gis/gdal/error.py @@ -18,29 +18,29 @@ class SRSException(Exception): # OGR Error Codes OGRERR_DICT = { - 1: (GDALException, 'Not enough data.'), - 2: (GDALException, 'Not enough memory.'), - 3: (GDALException, 'Unsupported geometry type.'), - 4: (GDALException, 'Unsupported operation.'), - 5: (GDALException, 'Corrupt data.'), - 6: (GDALException, 'OGR failure.'), - 7: (SRSException, 'Unsupported SRS.'), - 8: (GDALException, 'Invalid handle.'), + 1: (GDALException, "Not enough data."), + 2: (GDALException, "Not enough memory."), + 3: (GDALException, "Unsupported geometry type."), + 4: (GDALException, "Unsupported operation."), + 5: (GDALException, "Corrupt data."), + 6: (GDALException, "OGR failure."), + 7: (SRSException, "Unsupported SRS."), + 8: (GDALException, "Invalid handle."), } # CPL Error Codes # https://gdal.org/api/cpl.html#cpl-error-h CPLERR_DICT = { - 1: (GDALException, 'AppDefined'), - 2: (GDALException, 'OutOfMemory'), - 3: (GDALException, 'FileIO'), - 4: (GDALException, 'OpenFailed'), - 5: (GDALException, 'IllegalArg'), - 6: (GDALException, 'NotSupported'), - 7: (GDALException, 'AssertionFailed'), - 8: (GDALException, 'NoWriteAccess'), - 9: (GDALException, 'UserInterrupt'), - 10: (GDALException, 'ObjectNull'), + 1: (GDALException, "AppDefined"), + 2: (GDALException, "OutOfMemory"), + 3: (GDALException, "FileIO"), + 4: (GDALException, "OpenFailed"), + 5: (GDALException, "IllegalArg"), + 6: (GDALException, "NotSupported"), + 7: (GDALException, "AssertionFailed"), + 8: (GDALException, "NoWriteAccess"), + 9: (GDALException, "UserInterrupt"), + 10: (GDALException, "ObjectNull"), } ERR_NONE = 0 diff --git a/django/contrib/gis/gdal/feature.py b/django/contrib/gis/gdal/feature.py index 82a9089c16..6f08969984 100644 --- a/django/contrib/gis/gdal/feature.py +++ b/django/contrib/gis/gdal/feature.py @@ -2,7 +2,8 @@ from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.error import GDALException from django.contrib.gis.gdal.field import Field from django.contrib.gis.gdal.geometries import OGRGeometry, OGRGeomType -from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api +from django.contrib.gis.gdal.prototypes import ds as capi +from django.contrib.gis.gdal.prototypes import geom as geom_api from django.utils.encoding import force_bytes, force_str @@ -15,6 +16,7 @@ class Feature(GDALBase): This class that wraps an OGR Feature, needs to be instantiated from a Layer object. """ + destructor = capi.destroy_feature def __init__(self, feat, layer): @@ -22,7 +24,7 @@ class Feature(GDALBase): Initialize Feature from a pointer and its Layer object. """ if not feat: - raise GDALException('Cannot create OGR Feature, invalid pointer given.') + raise GDALException("Cannot create OGR Feature, invalid pointer given.") self.ptr = feat self._layer = layer @@ -38,7 +40,9 @@ class Feature(GDALBase): elif 0 <= index < self.num_fields: i = index else: - raise IndexError('Index out of range when accessing field in a feature: %s.' % index) + raise IndexError( + "Index out of range when accessing field in a feature: %s." % index + ) return Field(self, i) def __len__(self): @@ -47,7 +51,7 @@ class Feature(GDALBase): def __str__(self): "The string name of the feature." - return 'Feature FID %d in Layer<%s>' % (self.fid, self.layer_name) + return "Feature FID %d in Layer<%s>" % (self.fid, self.layer_name) def __eq__(self, other): "Do equivalence testing on the features." @@ -81,8 +85,9 @@ class Feature(GDALBase): force_str( capi.get_field_name(capi.get_field_defn(self._layer._ldefn, i)), self.encoding, - strings_only=True - ) for i in range(self.num_fields) + strings_only=True, + ) + for i in range(self.num_fields) ] @property @@ -104,12 +109,12 @@ class Feature(GDALBase): object. May take a string of the field name or a Field object as parameters. """ - field_name = getattr(field, 'name', field) + field_name = getattr(field, "name", field) return self[field_name].value def index(self, field_name): "Return the index of the given field name." i = capi.get_field_index(self.ptr, force_bytes(field_name)) if i < 0: - raise IndexError('Invalid OFT field name given: %s.' % field_name) + raise IndexError("Invalid OFT field name given: %s." % field_name) return i diff --git a/django/contrib/gis/gdal/field.py b/django/contrib/gis/gdal/field.py index e803886182..5374f6c3cb 100644 --- a/django/contrib/gis/gdal/field.py +++ b/django/contrib/gis/gdal/field.py @@ -28,7 +28,7 @@ class Field(GDALBase): # Getting the pointer for this field. fld_ptr = capi.get_feat_field_defn(feat.ptr, index) if not fld_ptr: - raise GDALException('Cannot create OGR Field, invalid pointer given.') + raise GDALException("Cannot create OGR Field, invalid pointer given.") self.ptr = fld_ptr # Setting the class depending upon the OGR Field Type (OFT) @@ -41,14 +41,26 @@ class Field(GDALBase): # #### Field Methods #### def as_double(self): "Retrieve the Field's value as a double (float)." - return capi.get_field_as_double(self._feat.ptr, self._index) if self.is_set else None + return ( + capi.get_field_as_double(self._feat.ptr, self._index) + if self.is_set + else None + ) def as_int(self, is_64=False): "Retrieve the Field's value as an integer." if is_64: - return capi.get_field_as_integer64(self._feat.ptr, self._index) if self.is_set else None + return ( + capi.get_field_as_integer64(self._feat.ptr, self._index) + if self.is_set + else None + ) else: - return capi.get_field_as_integer(self._feat.ptr, self._index) if self.is_set else None + return ( + capi.get_field_as_integer(self._feat.ptr, self._index) + if self.is_set + else None + ) def as_string(self): "Retrieve the Field's value as a string." @@ -63,12 +75,22 @@ class Field(GDALBase): return None yy, mm, dd, hh, mn, ss, tz = [c_int() for i in range(7)] status = capi.get_field_as_datetime( - self._feat.ptr, self._index, byref(yy), byref(mm), byref(dd), - byref(hh), byref(mn), byref(ss), byref(tz)) + self._feat.ptr, + self._index, + byref(yy), + byref(mm), + byref(dd), + byref(hh), + byref(mn), + byref(ss), + byref(tz), + ) if status: return (yy, mm, dd, hh, mn, ss, tz) else: - raise GDALException('Unable to retrieve date & time information from the field.') + raise GDALException( + "Unable to retrieve date & time information from the field." + ) # #### Field Properties #### @property diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index d807b3e8b9..dbb391f75f 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -46,7 +46,8 @@ from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope from django.contrib.gis.gdal.error import GDALException, SRSException from django.contrib.gis.gdal.geomtype import OGRGeomType -from django.contrib.gis.gdal.prototypes import geom as capi, srs as srs_api +from django.contrib.gis.gdal.prototypes import geom as capi +from django.contrib.gis.gdal.prototypes import srs as srs_api from django.contrib.gis.gdal.srs import CoordTransform, SpatialReference from django.contrib.gis.geometry import hex_regex, json_regex, wkt_regex from django.utils.encoding import force_bytes @@ -58,6 +59,7 @@ from django.utils.encoding import force_bytes # The OGR_G_* routines are relevant here. class OGRGeometry(GDALBase): """Encapsulate an OGR geometry.""" + destructor = capi.destroy_geom def __init__(self, geom_input, srs=None): @@ -74,16 +76,18 @@ class OGRGeometry(GDALBase): wkt_m = wkt_regex.match(geom_input) json_m = json_regex.match(geom_input) if wkt_m: - if wkt_m['srid']: + if wkt_m["srid"]: # If there's EWKT, set the SRS w/value of the SRID. - srs = int(wkt_m['srid']) - if wkt_m['type'].upper() == 'LINEARRING': + srs = int(wkt_m["srid"]) + if wkt_m["type"].upper() == "LINEARRING": # OGR_G_CreateFromWkt doesn't work with LINEARRING WKT. # See https://trac.osgeo.org/gdal/ticket/1992. - g = capi.create_geom(OGRGeomType(wkt_m['type']).num) - capi.import_wkt(g, byref(c_char_p(wkt_m['wkt'].encode()))) + g = capi.create_geom(OGRGeomType(wkt_m["type"]).num) + capi.import_wkt(g, byref(c_char_p(wkt_m["wkt"].encode()))) else: - g = capi.from_wkt(byref(c_char_p(wkt_m['wkt'].encode())), None, byref(c_void_p())) + g = capi.from_wkt( + byref(c_char_p(wkt_m["wkt"].encode())), None, byref(c_void_p()) + ) elif json_m: g = self._from_json(geom_input.encode()) else: @@ -101,12 +105,17 @@ class OGRGeometry(GDALBase): # OGR pointer (c_void_p) was the input. g = geom_input else: - raise GDALException('Invalid input type for OGR Geometry construction: %s' % type(geom_input)) + raise GDALException( + "Invalid input type for OGR Geometry construction: %s" + % type(geom_input) + ) # Now checking the Geometry pointer before finishing initialization # by setting the pointer for the object. if not g: - raise GDALException('Cannot create OGR Geometry from input: %s' % geom_input) + raise GDALException( + "Cannot create OGR Geometry from input: %s" % geom_input + ) self.ptr = g # Assigning the SpatialReference object to the geometry, if valid. @@ -129,13 +138,15 @@ class OGRGeometry(GDALBase): wkb, srs = state ptr = capi.from_wkb(wkb, None, byref(c_void_p()), len(wkb)) if not ptr: - raise GDALException('Invalid OGRGeometry loaded from pickled state.') + raise GDALException("Invalid OGRGeometry loaded from pickled state.") self.ptr = ptr self.srs = srs @classmethod def _from_wkb(cls, geom_input): - return capi.from_wkb(bytes(geom_input), None, byref(c_void_p()), len(geom_input)) + return capi.from_wkb( + bytes(geom_input), None, byref(c_void_p()), len(geom_input) + ) @staticmethod def _from_json(geom_input): @@ -145,8 +156,10 @@ class OGRGeometry(GDALBase): def from_bbox(cls, bbox): "Construct a Polygon from a bounding box (4-tuple)." x0, y0, x1, y1 = bbox - return OGRGeometry('POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % ( - x0, y0, x0, y1, x1, y1, x1, y0, x0, y0)) + return OGRGeometry( + "POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))" + % (x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) + ) @staticmethod def from_json(geom_input): @@ -198,7 +211,7 @@ class OGRGeometry(GDALBase): def _set_coord_dim(self, dim): "Set the coordinate dimension of this Geometry." if dim not in (2, 3): - raise ValueError('Geometry dimension must be either 2 or 3') + raise ValueError("Geometry dimension must be either 2 or 3") capi.set_coord_dim(self.ptr, dim) coord_dim = property(_get_coord_dim, _set_coord_dim) @@ -278,7 +291,9 @@ class OGRGeometry(GDALBase): elif srs is None: srs_ptr = None else: - raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs)) + raise TypeError( + "Cannot assign spatial reference with object of type: %s" % type(srs) + ) capi.assign_srs(self.ptr, srs_ptr) srs = property(_get_srs, _set_srs) @@ -294,19 +309,21 @@ class OGRGeometry(GDALBase): if isinstance(srid, int) or srid is None: self.srs = srid else: - raise TypeError('SRID must be set with an integer.') + raise TypeError("SRID must be set with an integer.") srid = property(_get_srid, _set_srid) # #### Output Methods #### def _geos_ptr(self): from django.contrib.gis.geos import GEOSGeometry + return GEOSGeometry._from_wkb(self.wkb) @property def geos(self): "Return a GEOSGeometry object from this OGRGeometry." from django.contrib.gis.geos import GEOSGeometry + return GEOSGeometry(self._geos_ptr(), self.srid) @property @@ -325,6 +342,7 @@ class OGRGeometry(GDALBase): Return the GeoJSON representation of this Geometry. """ return capi.to_json(self.ptr) + geojson = json @property @@ -340,7 +358,7 @@ class OGRGeometry(GDALBase): @property def wkb(self): "Return the WKB representation of the Geometry." - if sys.byteorder == 'little': + if sys.byteorder == "little": byteorder = 1 # wkbNDR (from ogr_core.h) else: byteorder = 0 # wkbXDR @@ -361,7 +379,7 @@ class OGRGeometry(GDALBase): "Return the EWKT representation of the Geometry." srs = self.srs if srs and srs.srid: - return 'SRID=%s;%s' % (srs.srid, self.wkt) + return "SRID=%s;%s" % (srs.srid, self.wkt) else: return self.wkt @@ -402,15 +420,19 @@ class OGRGeometry(GDALBase): sr = SpatialReference(coord_trans) capi.geom_transform_to(self.ptr, sr.ptr) else: - raise TypeError('Transform only accepts CoordTransform, ' - 'SpatialReference, string, and integer objects.') + raise TypeError( + "Transform only accepts CoordTransform, " + "SpatialReference, string, and integer objects." + ) # #### Topology Methods #### def _topology(self, func, other): """A generalized function for topology operations, takes a GDAL function and the other geometry to perform the operation on.""" if not isinstance(other, OGRGeometry): - raise TypeError('Must use another OGRGeometry object for topology operations!') + raise TypeError( + "Must use another OGRGeometry object for topology operations!" + ) # Returning the output of the given function with the other geometry's # pointer. @@ -500,14 +522,14 @@ class OGRGeometry(GDALBase): # The subclasses for OGR Geometry. class Point(OGRGeometry): - def _geos_ptr(self): from django.contrib.gis import geos + return geos.Point._create_empty() if self.empty else super()._geos_ptr() @classmethod def _create_empty(cls): - return capi.create_geom(OGRGeomType('point').num) + return capi.create_geom(OGRGeomType("point").num) @property def x(self): @@ -532,11 +554,11 @@ class Point(OGRGeometry): return (self.x, self.y) elif self.coord_dim == 3: return (self.x, self.y, self.z) + coords = tuple class LineString(OGRGeometry): - def __getitem__(self, index): "Return the Point at the given index." if 0 <= index < self.point_count: @@ -550,7 +572,9 @@ class LineString(OGRGeometry): elif dim == 3: return (x.value, y.value, z.value) else: - raise IndexError('Index out of range when accessing points of a line string: %s.' % index) + raise IndexError( + "Index out of range when accessing points of a line string: %s." % index + ) def __len__(self): "Return the number of points in the LineString." @@ -560,6 +584,7 @@ class LineString(OGRGeometry): def tuple(self): "Return the tuple representation of this LineString." return tuple(self[i] for i in range(len(self))) + coords = tuple def _listarr(self, func): @@ -592,7 +617,6 @@ class LinearRing(LineString): class Polygon(OGRGeometry): - def __len__(self): "Return the number of interior rings in this Polygon." return self.geom_count @@ -600,21 +624,27 @@ class Polygon(OGRGeometry): def __getitem__(self, index): "Get the ring at the specified index." if 0 <= index < self.geom_count: - return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs) + return OGRGeometry( + capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs + ) else: - raise IndexError('Index out of range when accessing rings of a polygon: %s.' % index) + raise IndexError( + "Index out of range when accessing rings of a polygon: %s." % index + ) # Polygon Properties @property def shell(self): "Return the shell of this Polygon." return self[0] # First ring is the shell + exterior_ring = shell @property def tuple(self): "Return a tuple of LinearRing coordinate tuples." return tuple(self[i].tuple for i in range(self.geom_count)) + coords = tuple @property @@ -627,7 +657,7 @@ class Polygon(OGRGeometry): def centroid(self): "Return the centroid (a Point) of this Polygon." # The centroid is a Point, create a geometry for this. - p = OGRGeometry(OGRGeomType('Point')) + p = OGRGeometry(OGRGeomType("Point")) capi.get_centroid(self.ptr, p.ptr) return p @@ -639,9 +669,14 @@ class GeometryCollection(OGRGeometry): def __getitem__(self, index): "Get the Geometry at the specified index." if 0 <= index < self.geom_count: - return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs) + return OGRGeometry( + capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs + ) else: - raise IndexError('Index out of range when accessing geometry in a collection: %s.' % index) + raise IndexError( + "Index out of range when accessing geometry in a collection: %s." + % index + ) def __len__(self): "Return the number of geometries in this Geometry Collection." @@ -659,7 +694,7 @@ class GeometryCollection(OGRGeometry): tmp = OGRGeometry(geom) capi.add_geom(self.ptr, tmp.ptr) else: - raise GDALException('Must add an OGRGeometry.') + raise GDALException("Must add an OGRGeometry.") @property def point_count(self): @@ -671,6 +706,7 @@ class GeometryCollection(OGRGeometry): def tuple(self): "Return a tuple representation of this Geometry Collection." return tuple(self[i].tuple for i in range(self.geom_count)) + coords = tuple diff --git a/django/contrib/gis/gdal/geomtype.py b/django/contrib/gis/gdal/geomtype.py index 591c680c59..8b77cafa34 100644 --- a/django/contrib/gis/gdal/geomtype.py +++ b/django/contrib/gis/gdal/geomtype.py @@ -8,24 +8,24 @@ class OGRGeomType: # Dictionary of acceptable OGRwkbGeometryType s and their string names. _types = { - 0: 'Unknown', - 1: 'Point', - 2: 'LineString', - 3: 'Polygon', - 4: 'MultiPoint', - 5: 'MultiLineString', - 6: 'MultiPolygon', - 7: 'GeometryCollection', - 100: 'None', - 101: 'LinearRing', - 102: 'PointZ', - 1 + wkb25bit: 'Point25D', - 2 + wkb25bit: 'LineString25D', - 3 + wkb25bit: 'Polygon25D', - 4 + wkb25bit: 'MultiPoint25D', - 5 + wkb25bit: 'MultiLineString25D', - 6 + wkb25bit: 'MultiPolygon25D', - 7 + wkb25bit: 'GeometryCollection25D', + 0: "Unknown", + 1: "Point", + 2: "LineString", + 3: "Polygon", + 4: "MultiPoint", + 5: "MultiLineString", + 6: "MultiPolygon", + 7: "GeometryCollection", + 100: "None", + 101: "LinearRing", + 102: "PointZ", + 1 + wkb25bit: "Point25D", + 2 + wkb25bit: "LineString25D", + 3 + wkb25bit: "Polygon25D", + 4 + wkb25bit: "MultiPoint25D", + 5 + wkb25bit: "MultiLineString25D", + 6 + wkb25bit: "MultiPolygon25D", + 7 + wkb25bit: "GeometryCollection25D", } # Reverse type dictionary, keyed by lowercase of the name. _str_types = {v.lower(): k for k, v in _types.items()} @@ -36,17 +36,17 @@ class OGRGeomType: num = type_input.num elif isinstance(type_input, str): type_input = type_input.lower() - if type_input == 'geometry': - type_input = 'unknown' + if type_input == "geometry": + type_input = "unknown" num = self._str_types.get(type_input) if num is None: raise GDALException('Invalid OGR String Type "%s"' % type_input) elif isinstance(type_input, int): if type_input not in self._types: - raise GDALException('Invalid OGR Integer Type: %d' % type_input) + raise GDALException("Invalid OGR Integer Type: %d" % type_input) num = type_input else: - raise TypeError('Invalid OGR input type given.') + raise TypeError("Invalid OGR input type given.") # Setting the OGR geometry type number. self.num = num @@ -77,19 +77,19 @@ class OGRGeomType: @property def django(self): "Return the Django GeometryField for this OGR Type." - s = self.name.replace('25D', '') - if s in ('LinearRing', 'None'): + s = self.name.replace("25D", "") + if s in ("LinearRing", "None"): return None - elif s == 'Unknown': - s = 'Geometry' - elif s == 'PointZ': - s = 'Point' - return s + 'Field' + elif s == "Unknown": + s = "Geometry" + elif s == "PointZ": + s = "Point" + return s + "Field" def to_multi(self): """ Transform Point, LineString, Polygon, and their 25D equivalents to their Multi... counterpart. """ - if self.name.startswith(('Point', 'LineString', 'Polygon')): + if self.name.startswith(("Point", "LineString", "Polygon")): self.num += 3 diff --git a/django/contrib/gis/gdal/layer.py b/django/contrib/gis/gdal/layer.py index 012f70bdb0..e8f97b7552 100644 --- a/django/contrib/gis/gdal/layer.py +++ b/django/contrib/gis/gdal/layer.py @@ -7,9 +7,9 @@ from django.contrib.gis.gdal.feature import Feature from django.contrib.gis.gdal.field import OGRFieldTypes from django.contrib.gis.gdal.geometries import OGRGeometry from django.contrib.gis.gdal.geomtype import OGRGeomType -from django.contrib.gis.gdal.prototypes import ( - ds as capi, geom as geom_api, srs as srs_api, -) +from django.contrib.gis.gdal.prototypes import ds as capi +from django.contrib.gis.gdal.prototypes import geom as geom_api +from django.contrib.gis.gdal.prototypes import srs as srs_api from django.contrib.gis.gdal.srs import SpatialReference from django.utils.encoding import force_bytes, force_str @@ -29,12 +29,12 @@ class Layer(GDALBase): collection of the `DataSource` while this Layer is still active. """ if not layer_ptr: - raise GDALException('Cannot create Layer, invalid pointer given') + raise GDALException("Cannot create Layer, invalid pointer given") self.ptr = layer_ptr self._ds = ds self._ldefn = capi.get_layer_defn(self._ptr) # Does the Layer support random reading? - self._random_read = self.test_capability(b'RandomRead') + self._random_read = self.test_capability(b"RandomRead") def __getitem__(self, index): "Get the Feature at the specified index." @@ -43,14 +43,16 @@ class Layer(GDALBase): # number of features because the beginning and ending feature IDs # are not guaranteed to be 0 and len(layer)-1, respectively. if index < 0: - raise IndexError('Negative indices are not allowed on OGR Layers.') + raise IndexError("Negative indices are not allowed on OGR Layers.") return self._make_feature(index) elif isinstance(index, slice): # A slice was given start, stop, stride = index.indices(self.num_feat) return [self._make_feature(fid) for fid in range(start, stop, stride)] else: - raise TypeError('Integers and slices may only be used when indexing OGR Layers.') + raise TypeError( + "Integers and slices may only be used when indexing OGR Layers." + ) def __iter__(self): "Iterate over each Feature in the Layer." @@ -87,7 +89,7 @@ class Layer(GDALBase): if feat.fid == feat_id: return feat # Should have returned a Feature, raise an IndexError. - raise IndexError('Invalid feature id: %s.' % feat_id) + raise IndexError("Invalid feature id: %s." % feat_id) # #### Layer properties #### @property @@ -133,10 +135,14 @@ class Layer(GDALBase): Return a list of string names corresponding to each of the Fields available in this Layer. """ - return [force_str( - capi.get_field_name(capi.get_field_defn(self._ldefn, i)), - self._ds.encoding, strings_only=True, - ) for i in range(self.num_fields)] + return [ + force_str( + capi.get_field_name(capi.get_field_defn(self._ldefn, i)), + self._ds.encoding, + strings_only=True, + ) + for i in range(self.num_fields) + ] @property def field_types(self): @@ -145,20 +151,26 @@ class Layer(GDALBase): return the list [OFTInteger, OFTReal, OFTString] for an OGR layer that has an integer, a floating-point, and string fields. """ - return [OGRFieldTypes[capi.get_field_type(capi.get_field_defn(self._ldefn, i))] - for i in range(self.num_fields)] + return [ + OGRFieldTypes[capi.get_field_type(capi.get_field_defn(self._ldefn, i))] + for i in range(self.num_fields) + ] @property def field_widths(self): "Return a list of the maximum field widths for the features." - return [capi.get_field_width(capi.get_field_defn(self._ldefn, i)) - for i in range(self.num_fields)] + return [ + capi.get_field_width(capi.get_field_defn(self._ldefn, i)) + for i in range(self.num_fields) + ] @property def field_precisions(self): "Return the field precisions for the features." - return [capi.get_field_precision(capi.get_field_defn(self._ldefn, i)) - for i in range(self.num_fields)] + return [ + capi.get_field_precision(capi.get_field_defn(self._ldefn, i)) + for i in range(self.num_fields) + ] def _get_spatial_filter(self): try: @@ -171,7 +183,7 @@ class Layer(GDALBase): capi.set_spatial_filter(self.ptr, filter.ptr) elif isinstance(filter, (tuple, list)): if not len(filter) == 4: - raise ValueError('Spatial filter list/tuple must have 4 elements.') + raise ValueError("Spatial filter list/tuple must have 4 elements.") # Map c_double onto params -- if a bad type is passed in it # will be caught here. xmin, ymin, xmax, ymax = map(c_double, filter) @@ -179,7 +191,9 @@ class Layer(GDALBase): elif filter is None: capi.set_spatial_filter(self.ptr, None) else: - raise TypeError('Spatial filter must be either an OGRGeometry instance, a 4-tuple, or None.') + raise TypeError( + "Spatial filter must be either an OGRGeometry instance, a 4-tuple, or None." + ) spatial_filter = property(_get_spatial_filter, _set_spatial_filter) @@ -190,7 +204,7 @@ class Layer(GDALBase): in the Layer. """ if field_name not in self.fields: - raise GDALException('invalid field name: %s' % field_name) + raise GDALException("invalid field name: %s" % field_name) return [feat.get(field_name) for feat in self] def get_geoms(self, geos=False): @@ -200,6 +214,7 @@ class Layer(GDALBase): """ if geos: from django.contrib.gis.geos import GEOSGeometry + return [GEOSGeometry(feat.geom.wkb) for feat in self] else: return [feat.geom for feat in self] diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py index 3ff82ef086..2cb8329451 100644 --- a/django/contrib/gis/gdal/libgdal.py +++ b/django/contrib/gis/gdal/libgdal.py @@ -7,29 +7,41 @@ from ctypes.util import find_library from django.contrib.gis.gdal.error import GDALException from django.core.exceptions import ImproperlyConfigured -logger = logging.getLogger('django.contrib.gis') +logger = logging.getLogger("django.contrib.gis") # Custom library path set? try: from django.conf import settings + lib_path = settings.GDAL_LIBRARY_PATH except (AttributeError, ImportError, ImproperlyConfigured, OSError): lib_path = None if lib_path: lib_names = None -elif os.name == 'nt': +elif os.name == "nt": # Windows NT shared libraries lib_names = [ - 'gdal303', 'gdal302', 'gdal301', 'gdal300', - 'gdal204', 'gdal203', 'gdal202', + "gdal303", + "gdal302", + "gdal301", + "gdal300", + "gdal204", + "gdal203", + "gdal202", ] -elif os.name == 'posix': +elif os.name == "posix": # *NIX library names. lib_names = [ - 'gdal', 'GDAL', - 'gdal3.3.0', 'gdal3.2.0', 'gdal3.1.0', 'gdal3.0.0', - 'gdal2.4.0', 'gdal2.3.0', 'gdal2.2.0', + "gdal", + "GDAL", + "gdal3.3.0", + "gdal3.2.0", + "gdal3.1.0", + "gdal3.0.0", + "gdal2.4.0", + "gdal2.3.0", + "gdal2.2.0", ] else: raise ImproperlyConfigured('GDAL is unsupported on OS "%s".' % os.name) @@ -45,7 +57,7 @@ if lib_names: if lib_path is None: raise ImproperlyConfigured( 'Could not find the GDAL library (tried "%s"). Is GDAL installed? ' - 'If it is, try setting GDAL_LIBRARY_PATH in your settings.' + "If it is, try setting GDAL_LIBRARY_PATH in your settings." % '", "'.join(lib_names) ) @@ -56,8 +68,9 @@ lgdal = CDLL(lib_path) # STDCALL, while others are not. Thus, the library will also need to # be loaded up as WinDLL for said OSR functions that require the # different calling convention. -if os.name == 'nt': +if os.name == "nt": from ctypes import WinDLL + lwingdal = WinDLL(lib_path) @@ -66,7 +79,7 @@ def std_call(func): Return the correct STDCALL function for certain OSR routines on Win32 platforms. """ - if os.name == 'nt': + if os.name == "nt": return lwingdal[func] else: return lgdal[func] @@ -75,24 +88,24 @@ def std_call(func): # #### Version-information functions. #### # Return GDAL library version information with the given key. -_version_info = std_call('GDALVersionInfo') +_version_info = std_call("GDALVersionInfo") _version_info.argtypes = [c_char_p] _version_info.restype = c_char_p def gdal_version(): "Return only the GDAL version number information." - return _version_info(b'RELEASE_NAME') + return _version_info(b"RELEASE_NAME") def gdal_full_version(): "Return the full GDAL version information." - return _version_info(b'') + return _version_info(b"") def gdal_version_info(): ver = gdal_version() - m = re.match(br'^(?P<major>\d+)\.(?P<minor>\d+)(?:\.(?P<subminor>\d+))?', ver) + m = re.match(rb"^(?P<major>\d+)\.(?P<minor>\d+)(?:\.(?P<subminor>\d+))?", ver) if not m: raise GDALException('Could not parse GDAL version string "%s"' % ver) major, minor, subminor = m.groups() @@ -106,7 +119,7 @@ CPLErrorHandler = CFUNCTYPE(None, c_int, c_int, c_char_p) def err_handler(error_class, error_number, message): - logger.error('GDAL_ERROR %d: %s', error_number, message) + logger.error("GDAL_ERROR %d: %s", error_number, message) err_handler = CPLErrorHandler(err_handler) @@ -119,5 +132,5 @@ def function(name, args, restype): return func -set_error_handler = function('CPLSetErrorHandler', [CPLErrorHandler], CPLErrorHandler) +set_error_handler = function("CPLSetErrorHandler", [CPLErrorHandler], CPLErrorHandler) set_error_handler(err_handler) diff --git a/django/contrib/gis/gdal/prototypes/ds.py b/django/contrib/gis/gdal/prototypes/ds.py index 0729ab3386..bc5250e2db 100644 --- a/django/contrib/gis/gdal/prototypes/ds.py +++ b/django/contrib/gis/gdal/prototypes/ds.py @@ -8,8 +8,15 @@ from ctypes import POINTER, c_char_p, c_double, c_int, c_long, c_void_p from django.contrib.gis.gdal.envelope import OGREnvelope from django.contrib.gis.gdal.libgdal import lgdal from django.contrib.gis.gdal.prototypes.generation import ( - bool_output, const_string_output, double_output, geom_output, int64_output, - int_output, srs_output, void_output, voidptr_output, + bool_output, + const_string_output, + double_output, + geom_output, + int64_output, + int_output, + srs_output, + void_output, + voidptr_output, ) c_int_p = POINTER(c_int) # shortcut type @@ -18,9 +25,13 @@ c_int_p = POINTER(c_int) # shortcut type register_all = void_output(lgdal.OGRRegisterAll, [], errcheck=False) cleanup_all = void_output(lgdal.OGRCleanupAll, [], errcheck=False) get_driver = voidptr_output(lgdal.OGRGetDriver, [c_int]) -get_driver_by_name = voidptr_output(lgdal.OGRGetDriverByName, [c_char_p], errcheck=False) +get_driver_by_name = voidptr_output( + lgdal.OGRGetDriverByName, [c_char_p], errcheck=False +) get_driver_count = int_output(lgdal.OGRGetDriverCount, []) -get_driver_name = const_string_output(lgdal.OGR_Dr_GetName, [c_void_p], decoding='ascii') +get_driver_name = const_string_output( + lgdal.OGR_Dr_GetName, [c_void_p], decoding="ascii" +) # DataSource open_ds = voidptr_output(lgdal.OGROpen, [c_char_p, c_int, POINTER(c_void_p)]) @@ -41,10 +52,13 @@ get_next_feature = voidptr_output(lgdal.OGR_L_GetNextFeature, [c_void_p]) reset_reading = void_output(lgdal.OGR_L_ResetReading, [c_void_p], errcheck=False) test_capability = int_output(lgdal.OGR_L_TestCapability, [c_void_p, c_char_p]) get_spatial_filter = geom_output(lgdal.OGR_L_GetSpatialFilter, [c_void_p]) -set_spatial_filter = void_output(lgdal.OGR_L_SetSpatialFilter, [c_void_p, c_void_p], errcheck=False) +set_spatial_filter = void_output( + lgdal.OGR_L_SetSpatialFilter, [c_void_p, c_void_p], errcheck=False +) set_spatial_filter_rect = void_output( lgdal.OGR_L_SetSpatialFilterRect, - [c_void_p, c_double, c_double, c_double, c_double], errcheck=False + [c_void_p, c_double, c_double, c_double, c_double], + errcheck=False, ) # Feature Definition Routines @@ -64,13 +78,17 @@ get_feat_field_defn = voidptr_output(lgdal.OGR_F_GetFieldDefnRef, [c_void_p, c_i get_fid = int_output(lgdal.OGR_F_GetFID, [c_void_p]) get_field_as_datetime = int_output( lgdal.OGR_F_GetFieldAsDateTime, - [c_void_p, c_int, c_int_p, c_int_p, c_int_p, c_int_p, c_int_p, c_int_p] + [c_void_p, c_int, c_int_p, c_int_p, c_int_p, c_int_p, c_int_p, c_int_p], ) get_field_as_double = double_output(lgdal.OGR_F_GetFieldAsDouble, [c_void_p, c_int]) get_field_as_integer = int_output(lgdal.OGR_F_GetFieldAsInteger, [c_void_p, c_int]) -get_field_as_integer64 = int64_output(lgdal.OGR_F_GetFieldAsInteger64, [c_void_p, c_int]) +get_field_as_integer64 = int64_output( + lgdal.OGR_F_GetFieldAsInteger64, [c_void_p, c_int] +) is_field_set = bool_output(lgdal.OGR_F_IsFieldSetAndNotNull, [c_void_p, c_int]) -get_field_as_string = const_string_output(lgdal.OGR_F_GetFieldAsString, [c_void_p, c_int]) +get_field_as_string = const_string_output( + lgdal.OGR_F_GetFieldAsString, [c_void_p, c_int] +) get_field_index = int_output(lgdal.OGR_F_GetFieldIndex, [c_void_p, c_char_p]) # Field Routines diff --git a/django/contrib/gis/gdal/prototypes/errcheck.py b/django/contrib/gis/gdal/prototypes/errcheck.py index 405f2e7070..52bb7cb083 100644 --- a/django/contrib/gis/gdal/prototypes/errcheck.py +++ b/django/contrib/gis/gdal/prototypes/errcheck.py @@ -4,9 +4,7 @@ """ from ctypes import c_void_p, string_at -from django.contrib.gis.gdal.error import ( - GDALException, SRSException, check_err, -) +from django.contrib.gis.gdal.error import GDALException, SRSException, check_err from django.contrib.gis.gdal.libgdal import lgdal @@ -63,6 +61,7 @@ def check_string(result, func, cargs, offset=-1, str_result=False): lgdal.VSIFree(ptr) return s + # ### DataSource, Layer error-checking ### @@ -80,7 +79,9 @@ def check_geom(result, func, cargs): if isinstance(result, int): result = c_void_p(result) if not result: - raise GDALException('Invalid geometry pointer returned from "%s".' % func.__name__) + raise GDALException( + 'Invalid geometry pointer returned from "%s".' % func.__name__ + ) return result @@ -96,7 +97,9 @@ def check_srs(result, func, cargs): if isinstance(result, int): result = c_void_p(result) if not result: - raise SRSException('Invalid spatial reference pointer returned from "%s".' % func.__name__) + raise SRSException( + 'Invalid spatial reference pointer returned from "%s".' % func.__name__ + ) return result diff --git a/django/contrib/gis/gdal/prototypes/generation.py b/django/contrib/gis/gdal/prototypes/generation.py index 0b26585161..230e56f665 100644 --- a/django/contrib/gis/gdal/prototypes/generation.py +++ b/django/contrib/gis/gdal/prototypes/generation.py @@ -2,14 +2,19 @@ This module contains functions that generate ctypes prototypes for the GDAL routines. """ -from ctypes import ( - POINTER, c_bool, c_char_p, c_double, c_int, c_int64, c_void_p, -) +from ctypes import POINTER, c_bool, c_char_p, c_double, c_int, c_int64, c_void_p from functools import partial from django.contrib.gis.gdal.prototypes.errcheck import ( - check_arg_errcode, check_const_string, check_errcode, check_geom, - check_geom_offset, check_pointer, check_srs, check_str_arg, check_string, + check_arg_errcode, + check_const_string, + check_errcode, + check_geom, + check_geom_offset, + check_pointer, + check_srs, + check_str_arg, + check_string, ) @@ -55,6 +60,7 @@ def geom_output(func, argtypes, offset=None): def geomerrcheck(result, func, cargs): return check_geom_offset(result, func, cargs, offset) + func.errcheck = geomerrcheck return func @@ -100,6 +106,7 @@ def const_string_output(func, argtypes, offset=None, decoding=None, cpl=False): if res and decoding: res = res.decode(decoding) return res + func.errcheck = _check_const return func @@ -129,6 +136,7 @@ def string_output(func, argtypes, offset=-1, str_result=False, decoding=None): if res and decoding: res = res.decode(decoding) return res + func.errcheck = _check_str return func diff --git a/django/contrib/gis/gdal/prototypes/geom.py b/django/contrib/gis/gdal/prototypes/geom.py index f7745f552f..06c2a75f3a 100644 --- a/django/contrib/gis/gdal/prototypes/geom.py +++ b/django/contrib/gis/gdal/prototypes/geom.py @@ -4,8 +4,13 @@ from django.contrib.gis.gdal.envelope import OGREnvelope from django.contrib.gis.gdal.libgdal import lgdal from django.contrib.gis.gdal.prototypes.errcheck import check_envelope from django.contrib.gis.gdal.prototypes.generation import ( - const_string_output, double_output, geom_output, int_output, srs_output, - string_output, void_output, + const_string_output, + double_output, + geom_output, + int_output, + srs_output, + string_output, + void_output, ) @@ -34,8 +39,12 @@ def topology_func(f): # GeoJSON routines. from_json = geom_output(lgdal.OGR_G_CreateGeometryFromJson, [c_char_p]) -to_json = string_output(lgdal.OGR_G_ExportToJson, [c_void_p], str_result=True, decoding='ascii') -to_kml = string_output(lgdal.OGR_G_ExportToKML, [c_void_p, c_char_p], str_result=True, decoding='ascii') +to_json = string_output( + lgdal.OGR_G_ExportToJson, [c_void_p], str_result=True, decoding="ascii" +) +to_kml = string_output( + lgdal.OGR_G_ExportToKML, [c_void_p, c_char_p], str_result=True, decoding="ascii" +) # GetX, GetY, GetZ all return doubles. getx = pnt_func(lgdal.OGR_G_GetX) @@ -43,8 +52,14 @@ gety = pnt_func(lgdal.OGR_G_GetY) getz = pnt_func(lgdal.OGR_G_GetZ) # Geometry creation routines. -from_wkb = geom_output(lgdal.OGR_G_CreateFromWkb, [c_char_p, c_void_p, POINTER(c_void_p), c_int], offset=-2) -from_wkt = geom_output(lgdal.OGR_G_CreateFromWkt, [POINTER(c_char_p), c_void_p, POINTER(c_void_p)], offset=-1) +from_wkb = geom_output( + lgdal.OGR_G_CreateFromWkb, [c_char_p, c_void_p, POINTER(c_void_p), c_int], offset=-2 +) +from_wkt = geom_output( + lgdal.OGR_G_CreateFromWkt, + [POINTER(c_char_p), c_void_p, POINTER(c_void_p)], + offset=-1, +) from_gml = geom_output(lgdal.OGR_G_CreateFromGML, [c_char_p]) create_geom = geom_output(lgdal.OGR_G_CreateGeometry, [c_int]) clone_geom = geom_output(lgdal.OGR_G_Clone, [c_void_p]) @@ -64,13 +79,21 @@ import_wkt = void_output(lgdal.OGR_G_ImportFromWkt, [c_void_p, POINTER(c_char_p) destroy_geom = void_output(lgdal.OGR_G_DestroyGeometry, [c_void_p], errcheck=False) # Geometry export routines. -to_wkb = void_output(lgdal.OGR_G_ExportToWkb, None, errcheck=True) # special handling for WKB. -to_wkt = string_output(lgdal.OGR_G_ExportToWkt, [c_void_p, POINTER(c_char_p)], decoding='ascii') -to_gml = string_output(lgdal.OGR_G_ExportToGML, [c_void_p], str_result=True, decoding='ascii') +to_wkb = void_output( + lgdal.OGR_G_ExportToWkb, None, errcheck=True +) # special handling for WKB. +to_wkt = string_output( + lgdal.OGR_G_ExportToWkt, [c_void_p, POINTER(c_char_p)], decoding="ascii" +) +to_gml = string_output( + lgdal.OGR_G_ExportToGML, [c_void_p], str_result=True, decoding="ascii" +) get_wkbsize = int_output(lgdal.OGR_G_WkbSize, [c_void_p]) # Geometry spatial-reference related routines. -assign_srs = void_output(lgdal.OGR_G_AssignSpatialReference, [c_void_p, c_void_p], errcheck=False) +assign_srs = void_output( + lgdal.OGR_G_AssignSpatialReference, [c_void_p, c_void_p], errcheck=False +) get_geom_srs = srs_output(lgdal.OGR_G_GetSpatialReference, [c_void_p]) # Geometry properties @@ -78,16 +101,23 @@ get_area = double_output(lgdal.OGR_G_GetArea, [c_void_p]) get_centroid = void_output(lgdal.OGR_G_Centroid, [c_void_p, c_void_p]) get_dims = int_output(lgdal.OGR_G_GetDimension, [c_void_p]) get_coord_dim = int_output(lgdal.OGR_G_GetCoordinateDimension, [c_void_p]) -set_coord_dim = void_output(lgdal.OGR_G_SetCoordinateDimension, [c_void_p, c_int], errcheck=False) -is_empty = int_output(lgdal.OGR_G_IsEmpty, [c_void_p], errcheck=lambda result, func, cargs: bool(result)) +set_coord_dim = void_output( + lgdal.OGR_G_SetCoordinateDimension, [c_void_p, c_int], errcheck=False +) +is_empty = int_output( + lgdal.OGR_G_IsEmpty, [c_void_p], errcheck=lambda result, func, cargs: bool(result) +) get_geom_count = int_output(lgdal.OGR_G_GetGeometryCount, [c_void_p]) -get_geom_name = const_string_output(lgdal.OGR_G_GetGeometryName, [c_void_p], decoding='ascii') +get_geom_name = const_string_output( + lgdal.OGR_G_GetGeometryName, [c_void_p], decoding="ascii" +) get_geom_type = int_output(lgdal.OGR_G_GetGeometryType, [c_void_p]) get_point_count = int_output(lgdal.OGR_G_GetPointCount, [c_void_p]) get_point = void_output( lgdal.OGR_G_GetPoint, - [c_void_p, c_int, POINTER(c_double), POINTER(c_double), POINTER(c_double)], errcheck=False + [c_void_p, c_int, POINTER(c_double), POINTER(c_double), POINTER(c_double)], + errcheck=False, ) geom_close_rings = void_output(lgdal.OGR_G_CloseRings, [c_void_p], errcheck=False) diff --git a/django/contrib/gis/gdal/prototypes/raster.py b/django/contrib/gis/gdal/prototypes/raster.py index 3fc9446ce8..59b930cb02 100644 --- a/django/contrib/gis/gdal/prototypes/raster.py +++ b/django/contrib/gis/gdal/prototypes/raster.py @@ -7,8 +7,12 @@ from functools import partial from django.contrib.gis.gdal.libgdal import std_call from django.contrib.gis.gdal.prototypes.generation import ( - chararray_output, const_string_output, double_output, int_output, - void_output, voidptr_output, + chararray_output, + const_string_output, + double_output, + int_output, + void_output, + voidptr_output, ) # For more detail about c function names and definitions see @@ -22,80 +26,152 @@ const_string_output = partial(const_string_output, cpl=True) double_output = partial(double_output, cpl=True) # Raster Driver Routines -register_all = void_output(std_call('GDALAllRegister'), [], errcheck=False) -get_driver = voidptr_output(std_call('GDALGetDriver'), [c_int]) -get_driver_by_name = voidptr_output(std_call('GDALGetDriverByName'), [c_char_p], errcheck=False) -get_driver_count = int_output(std_call('GDALGetDriverCount'), []) -get_driver_description = const_string_output(std_call('GDALGetDescription'), [c_void_p]) +register_all = void_output(std_call("GDALAllRegister"), [], errcheck=False) +get_driver = voidptr_output(std_call("GDALGetDriver"), [c_int]) +get_driver_by_name = voidptr_output( + std_call("GDALGetDriverByName"), [c_char_p], errcheck=False +) +get_driver_count = int_output(std_call("GDALGetDriverCount"), []) +get_driver_description = const_string_output(std_call("GDALGetDescription"), [c_void_p]) # Raster Data Source Routines -create_ds = voidptr_output(std_call('GDALCreate'), [c_void_p, c_char_p, c_int, c_int, c_int, c_int, c_void_p]) -open_ds = voidptr_output(std_call('GDALOpen'), [c_char_p, c_int]) -close_ds = void_output(std_call('GDALClose'), [c_void_p], errcheck=False) -flush_ds = int_output(std_call('GDALFlushCache'), [c_void_p]) +create_ds = voidptr_output( + std_call("GDALCreate"), [c_void_p, c_char_p, c_int, c_int, c_int, c_int, c_void_p] +) +open_ds = voidptr_output(std_call("GDALOpen"), [c_char_p, c_int]) +close_ds = void_output(std_call("GDALClose"), [c_void_p], errcheck=False) +flush_ds = int_output(std_call("GDALFlushCache"), [c_void_p]) copy_ds = voidptr_output( - std_call('GDALCreateCopy'), - [c_void_p, c_char_p, c_void_p, c_int, POINTER(c_char_p), c_void_p, c_void_p] -) -add_band_ds = void_output(std_call('GDALAddBand'), [c_void_p, c_int]) -get_ds_description = const_string_output(std_call('GDALGetDescription'), [c_void_p]) -get_ds_driver = voidptr_output(std_call('GDALGetDatasetDriver'), [c_void_p]) -get_ds_info = const_string_output(std_call('GDALInfo'), [c_void_p, c_void_p]) -get_ds_xsize = int_output(std_call('GDALGetRasterXSize'), [c_void_p]) -get_ds_ysize = int_output(std_call('GDALGetRasterYSize'), [c_void_p]) -get_ds_raster_count = int_output(std_call('GDALGetRasterCount'), [c_void_p]) -get_ds_raster_band = voidptr_output(std_call('GDALGetRasterBand'), [c_void_p, c_int]) -get_ds_projection_ref = const_string_output(std_call('GDALGetProjectionRef'), [c_void_p]) -set_ds_projection_ref = void_output(std_call('GDALSetProjection'), [c_void_p, c_char_p]) -get_ds_geotransform = void_output(std_call('GDALGetGeoTransform'), [c_void_p, POINTER(c_double * 6)], errcheck=False) -set_ds_geotransform = void_output(std_call('GDALSetGeoTransform'), [c_void_p, POINTER(c_double * 6)]) + std_call("GDALCreateCopy"), + [c_void_p, c_char_p, c_void_p, c_int, POINTER(c_char_p), c_void_p, c_void_p], +) +add_band_ds = void_output(std_call("GDALAddBand"), [c_void_p, c_int]) +get_ds_description = const_string_output(std_call("GDALGetDescription"), [c_void_p]) +get_ds_driver = voidptr_output(std_call("GDALGetDatasetDriver"), [c_void_p]) +get_ds_info = const_string_output(std_call("GDALInfo"), [c_void_p, c_void_p]) +get_ds_xsize = int_output(std_call("GDALGetRasterXSize"), [c_void_p]) +get_ds_ysize = int_output(std_call("GDALGetRasterYSize"), [c_void_p]) +get_ds_raster_count = int_output(std_call("GDALGetRasterCount"), [c_void_p]) +get_ds_raster_band = voidptr_output(std_call("GDALGetRasterBand"), [c_void_p, c_int]) +get_ds_projection_ref = const_string_output( + std_call("GDALGetProjectionRef"), [c_void_p] +) +set_ds_projection_ref = void_output(std_call("GDALSetProjection"), [c_void_p, c_char_p]) +get_ds_geotransform = void_output( + std_call("GDALGetGeoTransform"), [c_void_p, POINTER(c_double * 6)], errcheck=False +) +set_ds_geotransform = void_output( + std_call("GDALSetGeoTransform"), [c_void_p, POINTER(c_double * 6)] +) -get_ds_metadata = chararray_output(std_call('GDALGetMetadata'), [c_void_p, c_char_p], errcheck=False) -set_ds_metadata = void_output(std_call('GDALSetMetadata'), [c_void_p, POINTER(c_char_p), c_char_p]) -get_ds_metadata_domain_list = chararray_output(std_call('GDALGetMetadataDomainList'), [c_void_p], errcheck=False) -get_ds_metadata_item = const_string_output(std_call('GDALGetMetadataItem'), [c_void_p, c_char_p, c_char_p]) -set_ds_metadata_item = const_string_output(std_call('GDALSetMetadataItem'), [c_void_p, c_char_p, c_char_p, c_char_p]) -free_dsl = void_output(std_call('CSLDestroy'), [POINTER(c_char_p)], errcheck=False) +get_ds_metadata = chararray_output( + std_call("GDALGetMetadata"), [c_void_p, c_char_p], errcheck=False +) +set_ds_metadata = void_output( + std_call("GDALSetMetadata"), [c_void_p, POINTER(c_char_p), c_char_p] +) +get_ds_metadata_domain_list = chararray_output( + std_call("GDALGetMetadataDomainList"), [c_void_p], errcheck=False +) +get_ds_metadata_item = const_string_output( + std_call("GDALGetMetadataItem"), [c_void_p, c_char_p, c_char_p] +) +set_ds_metadata_item = const_string_output( + std_call("GDALSetMetadataItem"), [c_void_p, c_char_p, c_char_p, c_char_p] +) +free_dsl = void_output(std_call("CSLDestroy"), [POINTER(c_char_p)], errcheck=False) # Raster Band Routines band_io = void_output( - std_call('GDALRasterIO'), - [c_void_p, c_int, c_int, c_int, c_int, c_int, c_void_p, c_int, c_int, c_int, c_int, c_int] -) -get_band_xsize = int_output(std_call('GDALGetRasterBandXSize'), [c_void_p]) -get_band_ysize = int_output(std_call('GDALGetRasterBandYSize'), [c_void_p]) -get_band_index = int_output(std_call('GDALGetBandNumber'), [c_void_p]) -get_band_description = const_string_output(std_call('GDALGetDescription'), [c_void_p]) -get_band_ds = voidptr_output(std_call('GDALGetBandDataset'), [c_void_p]) -get_band_datatype = int_output(std_call('GDALGetRasterDataType'), [c_void_p]) -get_band_color_interp = int_output(std_call('GDALGetRasterColorInterpretation'), [c_void_p]) -get_band_nodata_value = double_output(std_call('GDALGetRasterNoDataValue'), [c_void_p, POINTER(c_int)]) -set_band_nodata_value = void_output(std_call('GDALSetRasterNoDataValue'), [c_void_p, c_double]) -delete_band_nodata_value = void_output(std_call('GDALDeleteRasterNoDataValue'), [c_void_p]) + std_call("GDALRasterIO"), + [ + c_void_p, + c_int, + c_int, + c_int, + c_int, + c_int, + c_void_p, + c_int, + c_int, + c_int, + c_int, + c_int, + ], +) +get_band_xsize = int_output(std_call("GDALGetRasterBandXSize"), [c_void_p]) +get_band_ysize = int_output(std_call("GDALGetRasterBandYSize"), [c_void_p]) +get_band_index = int_output(std_call("GDALGetBandNumber"), [c_void_p]) +get_band_description = const_string_output(std_call("GDALGetDescription"), [c_void_p]) +get_band_ds = voidptr_output(std_call("GDALGetBandDataset"), [c_void_p]) +get_band_datatype = int_output(std_call("GDALGetRasterDataType"), [c_void_p]) +get_band_color_interp = int_output( + std_call("GDALGetRasterColorInterpretation"), [c_void_p] +) +get_band_nodata_value = double_output( + std_call("GDALGetRasterNoDataValue"), [c_void_p, POINTER(c_int)] +) +set_band_nodata_value = void_output( + std_call("GDALSetRasterNoDataValue"), [c_void_p, c_double] +) +delete_band_nodata_value = void_output( + std_call("GDALDeleteRasterNoDataValue"), [c_void_p] +) get_band_statistics = void_output( - std_call('GDALGetRasterStatistics'), + std_call("GDALGetRasterStatistics"), [ - c_void_p, c_int, c_int, POINTER(c_double), POINTER(c_double), - POINTER(c_double), POINTER(c_double), c_void_p, c_void_p, + c_void_p, + c_int, + c_int, + POINTER(c_double), + POINTER(c_double), + POINTER(c_double), + POINTER(c_double), + c_void_p, + c_void_p, ], ) compute_band_statistics = void_output( - std_call('GDALComputeRasterStatistics'), - [c_void_p, c_int, POINTER(c_double), POINTER(c_double), POINTER(c_double), POINTER(c_double), c_void_p, c_void_p], + std_call("GDALComputeRasterStatistics"), + [ + c_void_p, + c_int, + POINTER(c_double), + POINTER(c_double), + POINTER(c_double), + POINTER(c_double), + c_void_p, + c_void_p, + ], ) # Reprojection routine reproject_image = void_output( - std_call('GDALReprojectImage'), - [c_void_p, c_char_p, c_void_p, c_char_p, c_int, c_double, c_double, c_void_p, c_void_p, c_void_p] + std_call("GDALReprojectImage"), + [ + c_void_p, + c_char_p, + c_void_p, + c_char_p, + c_int, + c_double, + c_double, + c_void_p, + c_void_p, + c_void_p, + ], ) auto_create_warped_vrt = voidptr_output( - std_call('GDALAutoCreateWarpedVRT'), - [c_void_p, c_char_p, c_char_p, c_int, c_double, c_void_p] + std_call("GDALAutoCreateWarpedVRT"), + [c_void_p, c_char_p, c_char_p, c_int, c_double, c_void_p], ) # Create VSI gdal raster files from in-memory buffers. # https://gdal.org/api/cpl.html#cpl-vsi-h -create_vsi_file_from_mem_buffer = voidptr_output(std_call('VSIFileFromMemBuffer'), [c_char_p, c_void_p, c_int, c_int]) -get_mem_buffer_from_vsi_file = voidptr_output(std_call('VSIGetMemFileBuffer'), [c_char_p, POINTER(c_int), c_bool]) -unlink_vsi_file = int_output(std_call('VSIUnlink'), [c_char_p]) +create_vsi_file_from_mem_buffer = voidptr_output( + std_call("VSIFileFromMemBuffer"), [c_char_p, c_void_p, c_int, c_int] +) +get_mem_buffer_from_vsi_file = voidptr_output( + std_call("VSIGetMemFileBuffer"), [c_char_p, POINTER(c_int), c_bool] +) +unlink_vsi_file = int_output(std_call("VSIUnlink"), [c_char_p]) diff --git a/django/contrib/gis/gdal/prototypes/srs.py b/django/contrib/gis/gdal/prototypes/srs.py index c392b7bf2a..721eba8622 100644 --- a/django/contrib/gis/gdal/prototypes/srs.py +++ b/django/contrib/gis/gdal/prototypes/srs.py @@ -2,7 +2,11 @@ from ctypes import POINTER, c_char_p, c_int, c_void_p from django.contrib.gis.gdal.libgdal import GDAL_VERSION, lgdal, std_call from django.contrib.gis.gdal.prototypes.generation import ( - const_string_output, double_output, int_output, srs_output, string_output, + const_string_output, + double_output, + int_output, + srs_output, + string_output, void_output, ) @@ -25,14 +29,18 @@ def units_func(f): # Creation & destruction. -clone_srs = srs_output(std_call('OSRClone'), [c_void_p]) -new_srs = srs_output(std_call('OSRNewSpatialReference'), [c_char_p]) +clone_srs = srs_output(std_call("OSRClone"), [c_void_p]) +new_srs = srs_output(std_call("OSRNewSpatialReference"), [c_char_p]) release_srs = void_output(lgdal.OSRRelease, [c_void_p], errcheck=False) -destroy_srs = void_output(std_call('OSRDestroySpatialReference'), [c_void_p], errcheck=False) +destroy_srs = void_output( + std_call("OSRDestroySpatialReference"), [c_void_p], errcheck=False +) srs_validate = void_output(lgdal.OSRValidate, [c_void_p]) if GDAL_VERSION >= (3, 0): - set_axis_strategy = void_output(lgdal.OSRSetAxisMappingStrategy, [c_void_p, c_int], errcheck=False) + set_axis_strategy = void_output( + lgdal.OSRSetAxisMappingStrategy, [c_void_p, c_int], errcheck=False + ) # Getting the semi_major, semi_minor, and flattening functions. semi_major = srs_double(lgdal.OSRGetSemiMajor) @@ -42,9 +50,9 @@ invflattening = srs_double(lgdal.OSRGetInvFlattening) # WKT, PROJ, EPSG, XML importation routines. from_wkt = void_output(lgdal.OSRImportFromWkt, [c_void_p, POINTER(c_char_p)]) from_proj = void_output(lgdal.OSRImportFromProj4, [c_void_p, c_char_p]) -from_epsg = void_output(std_call('OSRImportFromEPSG'), [c_void_p, c_int]) +from_epsg = void_output(std_call("OSRImportFromEPSG"), [c_void_p, c_int]) from_xml = void_output(lgdal.OSRImportFromXML, [c_void_p, c_char_p]) -from_user_input = void_output(std_call('OSRSetFromUserInput'), [c_void_p, c_char_p]) +from_user_input = void_output(std_call("OSRSetFromUserInput"), [c_void_p, c_char_p]) # Morphing to/from ESRI WKT. morph_to_esri = void_output(lgdal.OSRMorphToESRI, [c_void_p]) @@ -58,19 +66,36 @@ linear_units = units_func(lgdal.OSRGetLinearUnits) angular_units = units_func(lgdal.OSRGetAngularUnits) # For exporting to WKT, PROJ, "Pretty" WKT, and XML. -to_wkt = string_output(std_call('OSRExportToWkt'), [c_void_p, POINTER(c_char_p)], decoding='utf-8') -to_proj = string_output(std_call('OSRExportToProj4'), [c_void_p, POINTER(c_char_p)], decoding='ascii') +to_wkt = string_output( + std_call("OSRExportToWkt"), [c_void_p, POINTER(c_char_p)], decoding="utf-8" +) +to_proj = string_output( + std_call("OSRExportToProj4"), [c_void_p, POINTER(c_char_p)], decoding="ascii" +) to_pretty_wkt = string_output( - std_call('OSRExportToPrettyWkt'), - [c_void_p, POINTER(c_char_p), c_int], offset=-2, decoding='utf-8' + std_call("OSRExportToPrettyWkt"), + [c_void_p, POINTER(c_char_p), c_int], + offset=-2, + decoding="utf-8", ) -to_xml = string_output(lgdal.OSRExportToXML, [c_void_p, POINTER(c_char_p), c_char_p], offset=-2, decoding='utf-8') +to_xml = string_output( + lgdal.OSRExportToXML, + [c_void_p, POINTER(c_char_p), c_char_p], + offset=-2, + decoding="utf-8", +) # String attribute retrieval routines. -get_attr_value = const_string_output(std_call('OSRGetAttrValue'), [c_void_p, c_char_p, c_int], decoding='utf-8') -get_auth_name = const_string_output(lgdal.OSRGetAuthorityName, [c_void_p, c_char_p], decoding='ascii') -get_auth_code = const_string_output(lgdal.OSRGetAuthorityCode, [c_void_p, c_char_p], decoding='ascii') +get_attr_value = const_string_output( + std_call("OSRGetAttrValue"), [c_void_p, c_char_p, c_int], decoding="utf-8" +) +get_auth_name = const_string_output( + lgdal.OSRGetAuthorityName, [c_void_p, c_char_p], decoding="ascii" +) +get_auth_code = const_string_output( + lgdal.OSRGetAuthorityCode, [c_void_p, c_char_p], decoding="ascii" +) # SRS Properties isgeographic = int_output(lgdal.OSRIsGeographic, [c_void_p]) @@ -78,5 +103,7 @@ islocal = int_output(lgdal.OSRIsLocal, [c_void_p]) isprojected = int_output(lgdal.OSRIsProjected, [c_void_p]) # Coordinate transformation -new_ct = srs_output(std_call('OCTNewCoordinateTransformation'), [c_void_p, c_void_p]) -destroy_ct = void_output(std_call('OCTDestroyCoordinateTransformation'), [c_void_p], errcheck=False) +new_ct = srs_output(std_call("OCTNewCoordinateTransformation"), [c_void_p, c_void_p]) +destroy_ct = void_output( + std_call("OCTDestroyCoordinateTransformation"), [c_void_p], errcheck=False +) diff --git a/django/contrib/gis/gdal/raster/band.py b/django/contrib/gis/gdal/raster/band.py index 6739a52628..c3ec960643 100644 --- a/django/contrib/gis/gdal/raster/band.py +++ b/django/contrib/gis/gdal/raster/band.py @@ -7,7 +7,10 @@ from django.contrib.gis.shortcuts import numpy from django.utils.encoding import force_str from .const import ( - GDAL_COLOR_TYPES, GDAL_INTEGER_TYPES, GDAL_PIXEL_TYPES, GDAL_TO_CTYPES, + GDAL_COLOR_TYPES, + GDAL_INTEGER_TYPES, + GDAL_PIXEL_TYPES, + GDAL_TO_CTYPES, ) @@ -15,6 +18,7 @@ class GDALBand(GDALRasterBase): """ Wrap a GDAL raster band, needs to be obtained from a GDALRaster object. """ + def __init__(self, source, index): self.source = source self._ptr = capi.get_ds_raster_band(source._ptr, index) @@ -79,8 +83,14 @@ class GDALBand(GDALRasterBase): # Prepare array with arguments for capi function smin, smax, smean, sstd = c_double(), c_double(), c_double(), c_double() stats_args = [ - self._ptr, c_int(approximate), byref(smin), byref(smax), - byref(smean), byref(sstd), c_void_p(), c_void_p(), + self._ptr, + c_int(approximate), + byref(smin), + byref(smax), + byref(smean), + byref(sstd), + c_void_p(), + c_void_p(), ] if refresh or self._stats_refresh: @@ -154,7 +164,7 @@ class GDALBand(GDALRasterBase): if value is None: capi.delete_band_nodata_value(self._ptr) elif not isinstance(value, (int, float)): - raise ValueError('Nodata value must be numeric or None.') + raise ValueError("Nodata value must be numeric or None.") else: capi.set_band_nodata_value(self._ptr, value) self._flush() @@ -188,10 +198,10 @@ class GDALBand(GDALRasterBase): size = size or (self.width - offset[0], self.height - offset[1]) shape = shape or size if any(x <= 0 for x in size): - raise ValueError('Offset too big for this raster.') + raise ValueError("Offset too big for this raster.") if size[0] > self.width or size[1] > self.height: - raise ValueError('Size is larger than raster.') + raise ValueError("Size is larger than raster.") # Create ctypes type array generator ctypes_array = GDAL_TO_CTYPES[self.datatype()] * (shape[0] * shape[1]) @@ -206,15 +216,28 @@ class GDALBand(GDALRasterBase): access_flag = 1 # Instantiate ctypes array holding the input data - if isinstance(data, (bytes, memoryview)) or (numpy and isinstance(data, numpy.ndarray)): + if isinstance(data, (bytes, memoryview)) or ( + numpy and isinstance(data, numpy.ndarray) + ): data_array = ctypes_array.from_buffer_copy(data) else: data_array = ctypes_array(*data) # Access band - capi.band_io(self._ptr, access_flag, offset[0], offset[1], - size[0], size[1], byref(data_array), shape[0], - shape[1], self.datatype(), 0, 0) + capi.band_io( + self._ptr, + access_flag, + offset[0], + offset[1], + size[0], + size[1], + byref(data_array), + shape[0], + shape[1], + self.datatype(), + 0, + 0, + ) # Return data as numpy array if possible, otherwise as list if data is None: @@ -247,4 +270,4 @@ class BandList(list): try: return GDALBand(self.source, index + 1) except GDALException: - raise GDALException('Unable to get band index %d' % index) + raise GDALException("Unable to get band index %d" % index) diff --git a/django/contrib/gis/gdal/raster/base.py b/django/contrib/gis/gdal/raster/base.py index c5b438ee64..3d95c90dc7 100644 --- a/django/contrib/gis/gdal/raster/base.py +++ b/django/contrib/gis/gdal/raster/base.py @@ -6,6 +6,7 @@ class GDALRasterBase(GDALBase): """ Attributes that exist on both GDALRaster and GDALBand. """ + @property def metadata(self): """ @@ -15,7 +16,7 @@ class GDALRasterBase(GDALBase): """ # The initial metadata domain list contains the default domain. # The default is returned if domain name is None. - domain_list = ['DEFAULT'] + domain_list = ["DEFAULT"] # Get additional metadata domains from the raster. meta_list = capi.get_ds_metadata_domain_list(self._ptr) @@ -38,7 +39,7 @@ class GDALRasterBase(GDALBase): # Get metadata for this domain. data = capi.get_ds_metadata( self._ptr, - (None if domain == 'DEFAULT' else domain.encode()), + (None if domain == "DEFAULT" else domain.encode()), ) if not data: continue @@ -48,12 +49,12 @@ class GDALRasterBase(GDALBase): counter = 0 item = data[counter] while item: - key, val = item.decode().split('=') + key, val = item.decode().split("=") domain_meta[key] = val counter += 1 item = data[counter] # The default domain values are returned if domain is None. - result[domain or 'DEFAULT'] = domain_meta + result[domain or "DEFAULT"] = domain_meta return result @metadata.setter @@ -65,11 +66,12 @@ class GDALRasterBase(GDALBase): # Loop through domains. for domain, metadata in value.items(): # Set the domain to None for the default, otherwise encode. - domain = None if domain == 'DEFAULT' else domain.encode() + domain = None if domain == "DEFAULT" else domain.encode() # Set each metadata entry separately. for meta_name, meta_value in metadata.items(): capi.set_ds_metadata_item( - self._ptr, meta_name.encode(), + self._ptr, + meta_name.encode(), meta_value.encode() if meta_value else None, domain, ) diff --git a/django/contrib/gis/gdal/raster/const.py b/django/contrib/gis/gdal/raster/const.py index c246a6a190..6d3761d9fb 100644 --- a/django/contrib/gis/gdal/raster/const.py +++ b/django/contrib/gis/gdal/raster/const.py @@ -1,24 +1,22 @@ """ GDAL - Constant definitions """ -from ctypes import ( - c_double, c_float, c_int16, c_int32, c_ubyte, c_uint16, c_uint32, -) +from ctypes import c_double, c_float, c_int16, c_int32, c_ubyte, c_uint16, c_uint32 # See https://gdal.org/api/raster_c_api.html#_CPPv412GDALDataType GDAL_PIXEL_TYPES = { - 0: 'GDT_Unknown', # Unknown or unspecified type - 1: 'GDT_Byte', # Eight bit unsigned integer - 2: 'GDT_UInt16', # Sixteen bit unsigned integer - 3: 'GDT_Int16', # Sixteen bit signed integer - 4: 'GDT_UInt32', # Thirty-two bit unsigned integer - 5: 'GDT_Int32', # Thirty-two bit signed integer - 6: 'GDT_Float32', # Thirty-two bit floating point - 7: 'GDT_Float64', # Sixty-four bit floating point - 8: 'GDT_CInt16', # Complex Int16 - 9: 'GDT_CInt32', # Complex Int32 - 10: 'GDT_CFloat32', # Complex Float32 - 11: 'GDT_CFloat64', # Complex Float64 + 0: "GDT_Unknown", # Unknown or unspecified type + 1: "GDT_Byte", # Eight bit unsigned integer + 2: "GDT_UInt16", # Sixteen bit unsigned integer + 3: "GDT_Int16", # Sixteen bit signed integer + 4: "GDT_UInt32", # Thirty-two bit unsigned integer + 5: "GDT_Int32", # Thirty-two bit signed integer + 6: "GDT_Float32", # Thirty-two bit floating point + 7: "GDT_Float64", # Sixty-four bit floating point + 8: "GDT_CInt16", # Complex Int16 + 9: "GDT_CInt32", # Complex Int32 + 10: "GDT_CFloat32", # Complex Float32 + 11: "GDT_CFloat64", # Complex Float64 } # A list of gdal datatypes that are integers. @@ -29,47 +27,57 @@ GDAL_INTEGER_TYPES = [1, 2, 3, 4, 5] # or to hold the space for data to be read into. The lookup below helps # selecting the right ctypes object for a given gdal pixel type. GDAL_TO_CTYPES = [ - None, c_ubyte, c_uint16, c_int16, c_uint32, c_int32, - c_float, c_double, None, None, None, None + None, + c_ubyte, + c_uint16, + c_int16, + c_uint32, + c_int32, + c_float, + c_double, + None, + None, + None, + None, ] # List of resampling algorithms that can be used to warp a GDALRaster. GDAL_RESAMPLE_ALGORITHMS = { - 'NearestNeighbour': 0, - 'Bilinear': 1, - 'Cubic': 2, - 'CubicSpline': 3, - 'Lanczos': 4, - 'Average': 5, - 'Mode': 6, + "NearestNeighbour": 0, + "Bilinear": 1, + "Cubic": 2, + "CubicSpline": 3, + "Lanczos": 4, + "Average": 5, + "Mode": 6, } # See https://gdal.org/api/raster_c_api.html#_CPPv415GDALColorInterp GDAL_COLOR_TYPES = { - 0: 'GCI_Undefined', # Undefined, default value, i.e. not known - 1: 'GCI_GrayIndex', # Grayscale - 2: 'GCI_PaletteIndex', # Paletted - 3: 'GCI_RedBand', # Red band of RGBA image - 4: 'GCI_GreenBand', # Green band of RGBA image - 5: 'GCI_BlueBand', # Blue band of RGBA image - 6: 'GCI_AlphaBand', # Alpha (0=transparent, 255=opaque) - 7: 'GCI_HueBand', # Hue band of HLS image - 8: 'GCI_SaturationBand', # Saturation band of HLS image - 9: 'GCI_LightnessBand', # Lightness band of HLS image - 10: 'GCI_CyanBand', # Cyan band of CMYK image - 11: 'GCI_MagentaBand', # Magenta band of CMYK image - 12: 'GCI_YellowBand', # Yellow band of CMYK image - 13: 'GCI_BlackBand', # Black band of CMLY image - 14: 'GCI_YCbCr_YBand', # Y Luminance - 15: 'GCI_YCbCr_CbBand', # Cb Chroma - 16: 'GCI_YCbCr_CrBand', # Cr Chroma, also GCI_Max + 0: "GCI_Undefined", # Undefined, default value, i.e. not known + 1: "GCI_GrayIndex", # Grayscale + 2: "GCI_PaletteIndex", # Paletted + 3: "GCI_RedBand", # Red band of RGBA image + 4: "GCI_GreenBand", # Green band of RGBA image + 5: "GCI_BlueBand", # Blue band of RGBA image + 6: "GCI_AlphaBand", # Alpha (0=transparent, 255=opaque) + 7: "GCI_HueBand", # Hue band of HLS image + 8: "GCI_SaturationBand", # Saturation band of HLS image + 9: "GCI_LightnessBand", # Lightness band of HLS image + 10: "GCI_CyanBand", # Cyan band of CMYK image + 11: "GCI_MagentaBand", # Magenta band of CMYK image + 12: "GCI_YellowBand", # Yellow band of CMYK image + 13: "GCI_BlackBand", # Black band of CMLY image + 14: "GCI_YCbCr_YBand", # Y Luminance + 15: "GCI_YCbCr_CbBand", # Cb Chroma + 16: "GCI_YCbCr_CrBand", # Cr Chroma, also GCI_Max } # GDAL virtual filesystems prefix. -VSI_FILESYSTEM_PREFIX = '/vsi' +VSI_FILESYSTEM_PREFIX = "/vsi" # Fixed base path for buffer-based GDAL in-memory files. -VSI_MEM_FILESYSTEM_BASE_PATH = '/vsimem/' +VSI_MEM_FILESYSTEM_BASE_PATH = "/vsimem/" # Should the memory file system take ownership of the buffer, freeing it when # the file is deleted? (No, GDALRaster.__del__() will delete the buffer.) diff --git a/django/contrib/gis/gdal/raster/source.py b/django/contrib/gis/gdal/raster/source.py index 7c17b85925..ca7875752b 100644 --- a/django/contrib/gis/gdal/raster/source.py +++ b/django/contrib/gis/gdal/raster/source.py @@ -3,7 +3,14 @@ import os import sys import uuid from ctypes import ( - addressof, byref, c_buffer, c_char_p, c_double, c_int, c_void_p, string_at, + addressof, + byref, + c_buffer, + c_char_p, + c_double, + c_int, + c_void_p, + string_at, ) from django.contrib.gis.gdal.driver import Driver @@ -12,8 +19,11 @@ from django.contrib.gis.gdal.prototypes import raster as capi from django.contrib.gis.gdal.raster.band import BandList from django.contrib.gis.gdal.raster.base import GDALRasterBase from django.contrib.gis.gdal.raster.const import ( - GDAL_RESAMPLE_ALGORITHMS, VSI_DELETE_BUFFER_ON_READ, VSI_FILESYSTEM_PREFIX, - VSI_MEM_FILESYSTEM_BASE_PATH, VSI_TAKE_BUFFER_OWNERSHIP, + GDAL_RESAMPLE_ALGORITHMS, + VSI_DELETE_BUFFER_ON_READ, + VSI_FILESYSTEM_PREFIX, + VSI_MEM_FILESYSTEM_BASE_PATH, + VSI_TAKE_BUFFER_OWNERSHIP, ) from django.contrib.gis.gdal.srs import SpatialReference, SRSException from django.contrib.gis.geometry import json_regex @@ -23,9 +33,9 @@ from django.utils.functional import cached_property class TransformPoint(list): indices = { - 'origin': (0, 3), - 'scale': (1, 5), - 'skew': (2, 4), + "origin": (0, 3), + "scale": (1, 5), + "skew": (2, 4), } def __init__(self, raster, prop): @@ -60,6 +70,7 @@ class GDALRaster(GDALRasterBase): """ Wrap a raster GDAL Data Source object. """ + destructor = capi.close_ds def __init__(self, ds_input, write=False): @@ -73,9 +84,8 @@ class GDALRaster(GDALRasterBase): # If input is a valid file path, try setting file as source. if isinstance(ds_input, str): - if ( - not ds_input.startswith(VSI_FILESYSTEM_PREFIX) and - not os.path.exists(ds_input) + if not ds_input.startswith(VSI_FILESYSTEM_PREFIX) and not os.path.exists( + ds_input ): raise GDALException( 'Unable to read raster source input "%s".' % ds_input @@ -84,7 +94,9 @@ class GDALRaster(GDALRasterBase): # GDALOpen will auto-detect the data source type. self._ptr = capi.open_ds(force_bytes(ds_input), self._write) except GDALException as err: - raise GDALException('Could not open the datasource at "{}" ({}).'.format(ds_input, err)) + raise GDALException( + 'Could not open the datasource at "{}" ({}).'.format(ds_input, err) + ) elif isinstance(ds_input, bytes): # Create a new raster in write mode. self._write = 1 @@ -109,30 +121,36 @@ class GDALRaster(GDALRasterBase): except GDALException: # Remove the broken file from the VSI filesystem. capi.unlink_vsi_file(force_bytes(vsi_path)) - raise GDALException('Failed creating VSI raster from the input buffer.') + raise GDALException("Failed creating VSI raster from the input buffer.") elif isinstance(ds_input, dict): # A new raster needs to be created in write mode self._write = 1 # Create driver (in memory by default) - driver = Driver(ds_input.get('driver', 'MEM')) + driver = Driver(ds_input.get("driver", "MEM")) # For out of memory drivers, check filename argument - if driver.name != 'MEM' and 'name' not in ds_input: - raise GDALException('Specify name for creation of raster with driver "{}".'.format(driver.name)) + if driver.name != "MEM" and "name" not in ds_input: + raise GDALException( + 'Specify name for creation of raster with driver "{}".'.format( + driver.name + ) + ) # Check if width and height where specified - if 'width' not in ds_input or 'height' not in ds_input: - raise GDALException('Specify width and height attributes for JSON or dict input.') + if "width" not in ds_input or "height" not in ds_input: + raise GDALException( + "Specify width and height attributes for JSON or dict input." + ) # Check if srid was specified - if 'srid' not in ds_input: - raise GDALException('Specify srid for JSON or dict input.') + if "srid" not in ds_input: + raise GDALException("Specify srid for JSON or dict input.") # Create null terminated gdal options array. papsz_options = [] - for key, val in ds_input.get('papsz_options', {}).items(): - option = '{}={}'.format(key, val) + for key, val in ds_input.get("papsz_options", {}).items(): + option = "{}={}".format(key, val) papsz_options.append(option.upper().encode()) papsz_options.append(None) @@ -142,51 +160,54 @@ class GDALRaster(GDALRasterBase): # Create GDAL Raster self._ptr = capi.create_ds( driver._ptr, - force_bytes(ds_input.get('name', '')), - ds_input['width'], - ds_input['height'], - ds_input.get('nr_of_bands', len(ds_input.get('bands', []))), - ds_input.get('datatype', 6), + force_bytes(ds_input.get("name", "")), + ds_input["width"], + ds_input["height"], + ds_input.get("nr_of_bands", len(ds_input.get("bands", []))), + ds_input.get("datatype", 6), byref(papsz_options), ) # Set band data if provided - for i, band_input in enumerate(ds_input.get('bands', [])): + for i, band_input in enumerate(ds_input.get("bands", [])): band = self.bands[i] - if 'nodata_value' in band_input: - band.nodata_value = band_input['nodata_value'] + if "nodata_value" in band_input: + band.nodata_value = band_input["nodata_value"] # Instantiate band filled with nodata values if only # partial input data has been provided. if band.nodata_value is not None and ( - 'data' not in band_input or - 'size' in band_input or - 'shape' in band_input): + "data" not in band_input + or "size" in band_input + or "shape" in band_input + ): band.data(data=(band.nodata_value,), shape=(1, 1)) # Set band data values from input. band.data( - data=band_input.get('data'), - size=band_input.get('size'), - shape=band_input.get('shape'), - offset=band_input.get('offset'), + data=band_input.get("data"), + size=band_input.get("size"), + shape=band_input.get("shape"), + offset=band_input.get("offset"), ) # Set SRID - self.srs = ds_input.get('srid') + self.srs = ds_input.get("srid") # Set additional properties if provided - if 'origin' in ds_input: - self.origin.x, self.origin.y = ds_input['origin'] + if "origin" in ds_input: + self.origin.x, self.origin.y = ds_input["origin"] - if 'scale' in ds_input: - self.scale.x, self.scale.y = ds_input['scale'] + if "scale" in ds_input: + self.scale.x, self.scale.y = ds_input["scale"] - if 'skew' in ds_input: - self.skew.x, self.skew.y = ds_input['skew'] + if "skew" in ds_input: + self.skew.x, self.skew.y = ds_input["skew"] elif isinstance(ds_input, c_void_p): # Instantiate the object using an existing pointer to a gdal raster. self._ptr = ds_input else: - raise GDALException('Invalid data source input type: "{}".'.format(type(ds_input))) + raise GDALException( + 'Invalid data source input type: "{}".'.format(type(ds_input)) + ) def __del__(self): if self.is_vsi_based: @@ -201,7 +222,7 @@ class GDALRaster(GDALRasterBase): """ Short-hand representation because WKB may be very large. """ - return '<Raster object at %s>' % hex(addressof(self._ptr)) + return "<Raster object at %s>" % hex(addressof(self._ptr)) def _flush(self): """ @@ -212,14 +233,15 @@ class GDALRaster(GDALRasterBase): """ # Raise an Exception if the value is being changed in read mode. if not self._write: - raise GDALException('Raster needs to be opened in write mode to change values.') + raise GDALException( + "Raster needs to be opened in write mode to change values." + ) capi.flush_ds(self._ptr) @property def vsi_buffer(self): if not ( - self.is_vsi_based and - self.name.startswith(VSI_MEM_FILESYSTEM_BASE_PATH) + self.is_vsi_based and self.name.startswith(VSI_MEM_FILESYSTEM_BASE_PATH) ): return None # Prepare an integer that will contain the buffer length. @@ -276,7 +298,7 @@ class GDALRaster(GDALRasterBase): wkt = capi.get_ds_projection_ref(self._ptr) if not wkt: return None - return SpatialReference(wkt, srs_type='wkt') + return SpatialReference(wkt, srs_type="wkt") except SRSException: return None @@ -292,7 +314,7 @@ class GDALRaster(GDALRasterBase): elif isinstance(value, (int, str)): srs = SpatialReference(value) else: - raise ValueError('Could not create a SpatialReference from input.') + raise ValueError("Could not create a SpatialReference from input.") capi.set_ds_projection_ref(self._ptr, srs.wkt.encode()) self._flush() @@ -326,7 +348,7 @@ class GDALRaster(GDALRasterBase): def geotransform(self, values): "Set the geotransform for the data source." if len(values) != 6 or not all(isinstance(x, (int, float)) for x in values): - raise ValueError('Geotransform must consist of 6 numeric values.') + raise ValueError("Geotransform must consist of 6 numeric values.") # Create ctypes double array with input and write data values = (c_double * 6)(*values) capi.set_ds_geotransform(self._ptr, byref(values)) @@ -337,21 +359,21 @@ class GDALRaster(GDALRasterBase): """ Coordinates of the raster origin. """ - return TransformPoint(self, 'origin') + return TransformPoint(self, "origin") @property def scale(self): """ Pixel scale in units of the raster projection. """ - return TransformPoint(self, 'scale') + return TransformPoint(self, "scale") @property def skew(self): """ Skew of pixels (rotation parameters). """ - return TransformPoint(self, 'skew') + return TransformPoint(self, "skew") @property def extent(self): @@ -373,7 +395,7 @@ class GDALRaster(GDALRasterBase): def bands(self): return BandList(self) - def warp(self, ds_input, resampling='NearestNeighbour', max_error=0.0): + def warp(self, ds_input, resampling="NearestNeighbour", max_error=0.0): """ Return a warped GDALRaster with the given input characteristics. @@ -391,23 +413,23 @@ class GDALRaster(GDALRasterBase): consult the GDAL_RESAMPLE_ALGORITHMS constant. """ # Get the parameters defining the geotransform, srid, and size of the raster - ds_input.setdefault('width', self.width) - ds_input.setdefault('height', self.height) - ds_input.setdefault('srid', self.srs.srid) - ds_input.setdefault('origin', self.origin) - ds_input.setdefault('scale', self.scale) - ds_input.setdefault('skew', self.skew) + ds_input.setdefault("width", self.width) + ds_input.setdefault("height", self.height) + ds_input.setdefault("srid", self.srs.srid) + ds_input.setdefault("origin", self.origin) + ds_input.setdefault("scale", self.scale) + ds_input.setdefault("skew", self.skew) # Get the driver, name, and datatype of the target raster - ds_input.setdefault('driver', self.driver.name) + ds_input.setdefault("driver", self.driver.name) - if 'name' not in ds_input: - ds_input['name'] = self.name + '_copy.' + self.driver.name + if "name" not in ds_input: + ds_input["name"] = self.name + "_copy." + self.driver.name - if 'datatype' not in ds_input: - ds_input['datatype'] = self.bands[0].datatype() + if "datatype" not in ds_input: + ds_input["datatype"] = self.bands[0].datatype() # Instantiate raster bands filled with nodata values. - ds_input['bands'] = [{'nodata_value': bnd.nodata_value} for bnd in self.bands] + ds_input["bands"] = [{"nodata_value": bnd.nodata_value} for bnd in self.bands] # Create target raster target = GDALRaster(ds_input, write=True) @@ -417,10 +439,16 @@ class GDALRaster(GDALRasterBase): # Reproject image capi.reproject_image( - self._ptr, self.srs.wkt.encode(), - target._ptr, target.srs.wkt.encode(), - algorithm, 0.0, max_error, - c_void_p(), c_void_p(), c_void_p() + self._ptr, + self.srs.wkt.encode(), + target._ptr, + target.srs.wkt.encode(), + algorithm, + 0.0, + max_error, + c_void_p(), + c_void_p(), + c_void_p(), ) # Make sure all data is written to file @@ -432,8 +460,8 @@ class GDALRaster(GDALRasterBase): """Return a clone of this GDALRaster.""" if name: clone_name = name - elif self.driver.name != 'MEM': - clone_name = self.name + '_copy.' + self.driver.name + elif self.driver.name != "MEM": + clone_name = self.name + "_copy." + self.driver.name else: clone_name = os.path.join(VSI_MEM_FILESYSTEM_BASE_PATH, str(uuid.uuid4())) return GDALRaster( @@ -449,8 +477,9 @@ class GDALRaster(GDALRasterBase): write=self._write, ) - def transform(self, srs, driver=None, name=None, resampling='NearestNeighbour', - max_error=0.0): + def transform( + self, srs, driver=None, name=None, resampling="NearestNeighbour", max_error=0.0 + ): """ Return a copy of this raster reprojected into the given spatial reference system. @@ -464,35 +493,39 @@ class GDALRaster(GDALRasterBase): target_srs = SpatialReference(srs) else: raise TypeError( - 'Transform only accepts SpatialReference, string, and integer ' - 'objects.' + "Transform only accepts SpatialReference, string, and integer " + "objects." ) if target_srs.srid == self.srid and (not driver or driver == self.driver.name): return self.clone(name) # Create warped virtual dataset in the target reference system target = capi.auto_create_warped_vrt( - self._ptr, self.srs.wkt.encode(), target_srs.wkt.encode(), - algorithm, max_error, c_void_p() + self._ptr, + self.srs.wkt.encode(), + target_srs.wkt.encode(), + algorithm, + max_error, + c_void_p(), ) target = GDALRaster(target) # Construct the target warp dictionary from the virtual raster data = { - 'srid': target_srs.srid, - 'width': target.width, - 'height': target.height, - 'origin': [target.origin.x, target.origin.y], - 'scale': [target.scale.x, target.scale.y], - 'skew': [target.skew.x, target.skew.y], + "srid": target_srs.srid, + "width": target.width, + "height": target.height, + "origin": [target.origin.x, target.origin.y], + "scale": [target.scale.x, target.scale.y], + "skew": [target.skew.x, target.skew.y], } # Set the driver and filepath if provided if driver: - data['driver'] = driver + data["driver"] = driver if name: - data['name'] = name + data["name"] = name # Warp the raster into new srid return self.warp(data, resampling=resampling, max_error=max_error) diff --git a/django/contrib/gis/gdal/srs.py b/django/contrib/gis/gdal/srs.py index 5df8b54ab2..41477d55db 100644 --- a/django/contrib/gis/gdal/srs.py +++ b/django/contrib/gis/gdal/srs.py @@ -47,9 +47,10 @@ class SpatialReference(GDALBase): the SpatialReference object "provide[s] services to represent coordinate systems (projections and datums) and to transform between them." """ + destructor = capi.release_srs - def __init__(self, srs_input='', srs_type='user', axis_order=None): + def __init__(self, srs_input="", srs_type="user", axis_order=None): """ Create a GDAL OSR Spatial Reference object from the given input. The input may be string of OGC Well Known Text (WKT), an integer @@ -58,56 +59,58 @@ class SpatialReference(GDALBase): """ if not isinstance(axis_order, (type(None), AxisOrder)): raise ValueError( - 'SpatialReference.axis_order must be an AxisOrder instance.' + "SpatialReference.axis_order must be an AxisOrder instance." ) self.axis_order = axis_order or AxisOrder.TRADITIONAL - if srs_type == 'wkt': - self.ptr = capi.new_srs(c_char_p(b'')) + if srs_type == "wkt": + self.ptr = capi.new_srs(c_char_p(b"")) self.import_wkt(srs_input) if self.axis_order == AxisOrder.TRADITIONAL and GDAL_VERSION >= (3, 0): capi.set_axis_strategy(self.ptr, self.axis_order) elif self.axis_order != AxisOrder.TRADITIONAL and GDAL_VERSION < (3, 0): - raise ValueError('%s is not supported in GDAL < 3.0.' % self.axis_order) + raise ValueError("%s is not supported in GDAL < 3.0." % self.axis_order) return elif isinstance(srs_input, str): try: # If SRID is a string, e.g., '4326', then make acceptable # as user input. srid = int(srs_input) - srs_input = 'EPSG:%d' % srid + srs_input = "EPSG:%d" % srid except ValueError: pass elif isinstance(srs_input, int): # EPSG integer code was input. - srs_type = 'epsg' + srs_type = "epsg" elif isinstance(srs_input, self.ptr_type): srs = srs_input - srs_type = 'ogr' + srs_type = "ogr" else: raise TypeError('Invalid SRS type "%s"' % srs_type) - if srs_type == 'ogr': + if srs_type == "ogr": # Input is already an SRS pointer. srs = srs_input else: # Creating a new SRS pointer, using the string buffer. - buf = c_char_p(b'') + buf = c_char_p(b"") srs = capi.new_srs(buf) # If the pointer is NULL, throw an exception. if not srs: - raise SRSException('Could not create spatial reference from: %s' % srs_input) + raise SRSException( + "Could not create spatial reference from: %s" % srs_input + ) else: self.ptr = srs if self.axis_order == AxisOrder.TRADITIONAL and GDAL_VERSION >= (3, 0): capi.set_axis_strategy(self.ptr, self.axis_order) elif self.axis_order != AxisOrder.TRADITIONAL and GDAL_VERSION < (3, 0): - raise ValueError('%s is not supported in GDAL < 3.0.' % self.axis_order) + raise ValueError("%s is not supported in GDAL < 3.0." % self.axis_order) # Importing from either the user input string or an integer SRID. - if srs_type == 'user': + if srs_type == "user": self.import_user_input(srs_input) - elif srs_type == 'epsg': + elif srs_type == "epsg": self.import_epsg(srs_input) def __getitem__(self, target): @@ -188,11 +191,11 @@ class SpatialReference(GDALBase): def name(self): "Return the name of this Spatial Reference." if self.projected: - return self.attr_value('PROJCS') + return self.attr_value("PROJCS") elif self.geographic: - return self.attr_value('GEOGCS') + return self.attr_value("GEOGCS") elif self.local: - return self.attr_value('LOCAL_CS') + return self.attr_value("LOCAL_CS") else: return None @@ -200,7 +203,7 @@ class SpatialReference(GDALBase): def srid(self): "Return the SRID of top-level authority, or None if undefined." try: - return int(self.attr_value('AUTHORITY', 1)) + return int(self.attr_value("AUTHORITY", 1)) except (TypeError, ValueError): return None @@ -333,7 +336,7 @@ class SpatialReference(GDALBase): return self.proj @property - def xml(self, dialect=''): + def xml(self, dialect=""): "Return the XML representation of this Spatial Reference." return capi.to_xml(self.ptr, byref(c_char_p()), force_bytes(dialect)) @@ -344,8 +347,10 @@ class CoordTransform(GDALBase): def __init__(self, source, target): "Initialize on a source and target SpatialReference objects." - if not isinstance(source, SpatialReference) or not isinstance(target, SpatialReference): - raise TypeError('source and target must be of type SpatialReference') + if not isinstance(source, SpatialReference) or not isinstance( + target, SpatialReference + ): + raise TypeError("source and target must be of type SpatialReference") self.ptr = capi.new_ct(source._ptr, target._ptr) self._srs1_name = source.name self._srs2_name = target.name diff --git a/django/contrib/gis/geoip2/__init__.py b/django/contrib/gis/geoip2/__init__.py index 191706b01e..71b71f68db 100644 --- a/django/contrib/gis/geoip2/__init__.py +++ b/django/contrib/gis/geoip2/__init__.py @@ -11,7 +11,7 @@ downloaded from MaxMind at https://dev.maxmind.com/geoip/geoip2/geolite2/. Grab GeoLite2-Country.mmdb.gz and GeoLite2-City.mmdb.gz, and unzip them in the directory corresponding to settings.GEOIP_PATH. """ -__all__ = ['HAS_GEOIP2'] +__all__ = ["HAS_GEOIP2"] try: import geoip2 # NOQA @@ -19,5 +19,6 @@ except ImportError: HAS_GEOIP2 = False else: from .base import GeoIP2, GeoIP2Exception + HAS_GEOIP2 = True - __all__ += ['GeoIP2', 'GeoIP2Exception'] + __all__ += ["GeoIP2", "GeoIP2Exception"] diff --git a/django/contrib/gis/geoip2/base.py b/django/contrib/gis/geoip2/base.py index bcd8f1e2d8..e74984fe45 100644 --- a/django/contrib/gis/geoip2/base.py +++ b/django/contrib/gis/geoip2/base.py @@ -12,9 +12,9 @@ from .resources import City, Country # Creating the settings dictionary with any settings, if needed. GEOIP_SETTINGS = { - 'GEOIP_PATH': getattr(settings, 'GEOIP_PATH', None), - 'GEOIP_CITY': getattr(settings, 'GEOIP_CITY', 'GeoLite2-City.mmdb'), - 'GEOIP_COUNTRY': getattr(settings, 'GEOIP_COUNTRY', 'GeoLite2-Country.mmdb'), + "GEOIP_PATH": getattr(settings, "GEOIP_PATH", None), + "GEOIP_CITY": getattr(settings, "GEOIP_CITY", "GeoLite2-City.mmdb"), + "GEOIP_COUNTRY": getattr(settings, "GEOIP_COUNTRY", "GeoLite2-Country.mmdb"), } @@ -34,11 +34,13 @@ class GeoIP2: MODE_FILE = 4 # Load database into memory. Pure Python. MODE_MEMORY = 8 - cache_options = frozenset((MODE_AUTO, MODE_MMAP_EXT, MODE_MMAP, MODE_FILE, MODE_MEMORY)) + cache_options = frozenset( + (MODE_AUTO, MODE_MMAP_EXT, MODE_MMAP, MODE_FILE, MODE_MEMORY) + ) # Paths to the city & country binary databases. - _city_file = '' - _country_file = '' + _city_file = "" + _country_file = "" # Initially, pointers to GeoIP file references are NULL. _city = None @@ -69,29 +71,31 @@ class GeoIP2: """ # Checking the given cache option. if cache not in self.cache_options: - raise GeoIP2Exception('Invalid GeoIP caching option: %s' % cache) + raise GeoIP2Exception("Invalid GeoIP caching option: %s" % cache) # Getting the GeoIP data path. - path = path or GEOIP_SETTINGS['GEOIP_PATH'] + path = path or GEOIP_SETTINGS["GEOIP_PATH"] if not path: - raise GeoIP2Exception('GeoIP path must be provided via parameter or the GEOIP_PATH setting.') + raise GeoIP2Exception( + "GeoIP path must be provided via parameter or the GEOIP_PATH setting." + ) path = to_path(path) if path.is_dir(): # Constructing the GeoIP database filenames using the settings # dictionary. If the database files for the GeoLite country # and/or city datasets exist, then try to open them. - country_db = path / (country or GEOIP_SETTINGS['GEOIP_COUNTRY']) + country_db = path / (country or GEOIP_SETTINGS["GEOIP_COUNTRY"]) if country_db.is_file(): self._country = geoip2.database.Reader(str(country_db), mode=cache) self._country_file = country_db - city_db = path / (city or GEOIP_SETTINGS['GEOIP_CITY']) + city_db = path / (city or GEOIP_SETTINGS["GEOIP_CITY"]) if city_db.is_file(): self._city = geoip2.database.Reader(str(city_db), mode=cache) self._city_file = city_db if not self._reader: - raise GeoIP2Exception('Could not load a database from %s.' % path) + raise GeoIP2Exception("Could not load a database from %s." % path) elif path.is_file(): # Otherwise, some detective work will be needed to figure out # whether the given database path is for the GeoIP country or city @@ -99,18 +103,20 @@ class GeoIP2: reader = geoip2.database.Reader(str(path), mode=cache) db_type = reader.metadata().database_type - if db_type.endswith('City'): + if db_type.endswith("City"): # GeoLite City database detected. self._city = reader self._city_file = path - elif db_type.endswith('Country'): + elif db_type.endswith("Country"): # GeoIP Country database detected. self._country = reader self._country_file = path else: - raise GeoIP2Exception('Unable to recognize database edition: %s' % db_type) + raise GeoIP2Exception( + "Unable to recognize database edition: %s" % db_type + ) else: - raise GeoIP2Exception('GeoIP path must be a valid file or directory.') + raise GeoIP2Exception("GeoIP path must be a valid file or directory.") @property def _reader(self): @@ -130,25 +136,33 @@ class GeoIP2: def __repr__(self): meta = self._reader.metadata() - version = '[v%s.%s]' % (meta.binary_format_major_version, meta.binary_format_minor_version) - return '<%(cls)s %(version)s _country_file="%(country)s", _city_file="%(city)s">' % { - 'cls': self.__class__.__name__, - 'version': version, - 'country': self._country_file, - 'city': self._city_file, - } + version = "[v%s.%s]" % ( + meta.binary_format_major_version, + meta.binary_format_minor_version, + ) + return ( + '<%(cls)s %(version)s _country_file="%(country)s", _city_file="%(city)s">' + % { + "cls": self.__class__.__name__, + "version": version, + "country": self._country_file, + "city": self._city_file, + } + ) def _check_query(self, query, city=False, city_or_country=False): "Check the query and database availability." # Making sure a string was passed in for the query. if not isinstance(query, str): - raise TypeError('GeoIP query must be a string, not type %s' % type(query).__name__) + raise TypeError( + "GeoIP query must be a string, not type %s" % type(query).__name__ + ) # Extra checks for the existence of country and city databases. if city_or_country and not (self._country or self._city): - raise GeoIP2Exception('Invalid GeoIP country and city data files.') + raise GeoIP2Exception("Invalid GeoIP country and city data files.") elif city and not self._city: - raise GeoIP2Exception('Invalid GeoIP city data file: %s' % self._city_file) + raise GeoIP2Exception("Invalid GeoIP city data file: %s" % self._city_file) # Return the query string back to the caller. GeoIP2 only takes IP addresses. try: @@ -169,11 +183,11 @@ class GeoIP2: def country_code(self, query): "Return the country code for the given IP Address or FQDN." - return self.country(query)['country_code'] + return self.country(query)["country_code"] def country_name(self, query): "Return the country name for the given IP Address or FQDN." - return self.country(query)['country_name'] + return self.country(query)["country_name"] def country(self, query): """ @@ -186,7 +200,7 @@ class GeoIP2: return Country(self._country_or_city(enc_query)) # #### Coordinate retrieval routines #### - def coords(self, query, ordering=('longitude', 'latitude')): + def coords(self, query, ordering=("longitude", "latitude")): cdict = self.city(query) if cdict is None: return None @@ -199,7 +213,7 @@ class GeoIP2: def lat_lon(self, query): "Return a tuple of the (latitude, longitude) for the given query." - return self.coords(query, ('latitude', 'longitude')) + return self.coords(query, ("latitude", "longitude")) def geos(self, query): "Return a GEOS Point object for the given query." @@ -214,7 +228,10 @@ class GeoIP2: def info(self): "Return information about the GeoIP library and databases in use." meta = self._reader.metadata() - return 'GeoIP Library:\n\t%s.%s\n' % (meta.binary_format_major_version, meta.binary_format_minor_version) + return "GeoIP Library:\n\t%s.%s\n" % ( + meta.binary_format_major_version, + meta.binary_format_minor_version, + ) @classmethod def open(cls, full_path, cache): diff --git a/django/contrib/gis/geoip2/resources.py b/django/contrib/gis/geoip2/resources.py index 08923b0998..74f4228697 100644 --- a/django/contrib/gis/geoip2/resources.py +++ b/django/contrib/gis/geoip2/resources.py @@ -1,22 +1,22 @@ def City(response): return { - 'city': response.city.name, - 'continent_code': response.continent.code, - 'continent_name': response.continent.name, - 'country_code': response.country.iso_code, - 'country_name': response.country.name, - 'dma_code': response.location.metro_code, - 'is_in_european_union': response.country.is_in_european_union, - 'latitude': response.location.latitude, - 'longitude': response.location.longitude, - 'postal_code': response.postal.code, - 'region': response.subdivisions[0].iso_code if response.subdivisions else None, - 'time_zone': response.location.time_zone, + "city": response.city.name, + "continent_code": response.continent.code, + "continent_name": response.continent.name, + "country_code": response.country.iso_code, + "country_name": response.country.name, + "dma_code": response.location.metro_code, + "is_in_european_union": response.country.is_in_european_union, + "latitude": response.location.latitude, + "longitude": response.location.longitude, + "postal_code": response.postal.code, + "region": response.subdivisions[0].iso_code if response.subdivisions else None, + "time_zone": response.location.time_zone, } def Country(response): return { - 'country_code': response.country.iso_code, - 'country_name': response.country.name, + "country_code": response.country.iso_code, + "country_name": response.country.name, } diff --git a/django/contrib/gis/geometry.py b/django/contrib/gis/geometry.py index f7a70618fa..0b289cf355 100644 --- a/django/contrib/gis/geometry.py +++ b/django/contrib/gis/geometry.py @@ -5,13 +5,13 @@ from django.utils.regex_helper import _lazy_re_compile # Regular expression for recognizing HEXEWKB and WKT. A prophylactic measure # to prevent potentially malicious input from reaching the underlying C # library. Not a substitute for good web security programming practices. -hex_regex = _lazy_re_compile(r'^[0-9A-F]+$', re.I) +hex_regex = _lazy_re_compile(r"^[0-9A-F]+$", re.I) wkt_regex = _lazy_re_compile( - r'^(SRID=(?P<srid>\-?[0-9]+);)?' - r'(?P<wkt>' - r'(?P<type>POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|' - r'MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)' - r'[ACEGIMLONPSRUTYZ0-9,\.\-\+\(\) ]+)$', - re.I + r"^(SRID=(?P<srid>\-?[0-9]+);)?" + r"(?P<wkt>" + r"(?P<type>POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|" + r"MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)" + r"[ACEGIMLONPSRUTYZ0-9,\.\-\+\(\) ]+)$", + re.I, ) -json_regex = _lazy_re_compile(r'^(\s+)?\{.*}(\s+)?$', re.DOTALL) +json_regex = _lazy_re_compile(r"^(\s+)?\{.*}(\s+)?$", re.DOTALL) diff --git a/django/contrib/gis/geos/__init__.py b/django/contrib/gis/geos/__init__.py index 65e7e54817..27de1ca8e6 100644 --- a/django/contrib/gis/geos/__init__.py +++ b/django/contrib/gis/geos/__init__.py @@ -3,7 +3,10 @@ The GeoDjango GEOS module. Please consult the GeoDjango documentation for more details: https://docs.djangoproject.com/en/dev/ref/contrib/gis/geos/ """ from .collections import ( # NOQA - GeometryCollection, MultiLineString, MultiPoint, MultiPolygon, + GeometryCollection, + MultiLineString, + MultiPoint, + MultiPolygon, ) from .error import GEOSException # NOQA from .factory import fromfile, fromstr # NOQA diff --git a/django/contrib/gis/geos/collections.py b/django/contrib/gis/geos/collections.py index 3c1f0c858e..abfec8af3d 100644 --- a/django/contrib/gis/geos/collections.py +++ b/django/contrib/gis/geos/collections.py @@ -46,11 +46,14 @@ class GeometryCollection(GEOSGeometry): # ### Methods for compatibility with ListMixin ### def _create_collection(self, length, items): # Creating the geometry pointer array. - geoms = (GEOM_PTR * length)(*[ - # this is a little sloppy, but makes life easier - # allow GEOSGeometry types (python wrappers) or pointer types - capi.geom_clone(getattr(g, 'ptr', g)) for g in items - ]) + geoms = (GEOM_PTR * length)( + *[ + # this is a little sloppy, but makes life easier + # allow GEOSGeometry types (python wrappers) or pointer types + capi.geom_clone(getattr(g, "ptr", g)) + for g in items + ] + ) return capi.create_collection(self._typeid, geoms, length) def _get_single_internal(self, index): @@ -59,7 +62,9 @@ class GeometryCollection(GEOSGeometry): def _get_single_external(self, index): "Return the Geometry from this Collection at the given index (0-based)." # Checking the index and returning the corresponding GEOS geometry. - return GEOSGeometry(capi.geom_clone(self._get_single_internal(index)), srid=self.srid) + return GEOSGeometry( + capi.geom_clone(self._get_single_internal(index)), srid=self.srid + ) def _set_list(self, length, items): "Create a new collection, and destroy the contents of the previous pointer." @@ -76,12 +81,13 @@ class GeometryCollection(GEOSGeometry): @property def kml(self): "Return the KML for this Geometry Collection." - return '<MultiGeometry>%s</MultiGeometry>' % ''.join(g.kml for g in self) + return "<MultiGeometry>%s</MultiGeometry>" % "".join(g.kml for g in self) @property def tuple(self): "Return a tuple of all the coordinates in this Geometry Collection" return tuple(g.tuple for g in self) + coords = tuple @@ -103,4 +109,12 @@ class MultiPolygon(GeometryCollection): # Setting the allowed types here since GeometryCollection is defined before # its subclasses. -GeometryCollection._allowed = (Point, LineString, LinearRing, Polygon, MultiPoint, MultiLineString, MultiPolygon) +GeometryCollection._allowed = ( + Point, + LineString, + LinearRing, + Polygon, + MultiPoint, + MultiLineString, + MultiPolygon, +) diff --git a/django/contrib/gis/geos/coordseq.py b/django/contrib/gis/geos/coordseq.py index d6a7c6cdc7..07a3b7d213 100644 --- a/django/contrib/gis/geos/coordseq.py +++ b/django/contrib/gis/geos/coordseq.py @@ -20,7 +20,7 @@ class GEOSCoordSeq(GEOSBase): def __init__(self, ptr, z=False): "Initialize from a GEOS pointer." if not isinstance(ptr, CS_PTR): - raise TypeError('Coordinate sequence should initialize with a CS_PTR.') + raise TypeError("Coordinate sequence should initialize with a CS_PTR.") self._ptr = ptr self._z = z @@ -50,7 +50,9 @@ class GEOSCoordSeq(GEOSBase): elif numpy and isinstance(value, numpy.ndarray): pass else: - raise TypeError('Must set coordinate with a sequence (list, tuple, or numpy array).') + raise TypeError( + "Must set coordinate with a sequence (list, tuple, or numpy array)." + ) # Checking the dims of the input if self.dims == 3 and self._z: n_args = 3 @@ -59,7 +61,7 @@ class GEOSCoordSeq(GEOSBase): n_args = 2 point_setter = self._set_point_2d if len(value) != n_args: - raise TypeError('Dimension of value does not match.') + raise TypeError("Dimension of value does not match.") self._checkindex(index) point_setter(index, value) @@ -67,7 +69,7 @@ class GEOSCoordSeq(GEOSBase): def _checkindex(self, index): "Check the given index." if not (0 <= index < self.size): - raise IndexError('invalid GEOS Geometry index: %s' % index) + raise IndexError("invalid GEOS Geometry index: %s" % index) def _checkdim(self, dim): "Check the given dimension." @@ -180,11 +182,13 @@ class GEOSCoordSeq(GEOSBase): # Getting the substitution string depending on whether the coordinates have # a Z dimension. if self.hasz: - substr = '%s,%s,%s ' + substr = "%s,%s,%s " else: - substr = '%s,%s,0 ' - return '<coordinates>%s</coordinates>' % \ - ''.join(substr % self[i] for i in range(len(self))).strip() + substr = "%s,%s,0 " + return ( + "<coordinates>%s</coordinates>" + % "".join(substr % self[i] for i in range(len(self))).strip() + ) @property def tuple(self): diff --git a/django/contrib/gis/geos/factory.py b/django/contrib/gis/geos/factory.py index 46ed1f0c51..2c1c24741b 100644 --- a/django/contrib/gis/geos/factory.py +++ b/django/contrib/gis/geos/factory.py @@ -8,7 +8,7 @@ def fromfile(file_h): """ # If given a file name, get a real handle. if isinstance(file_h, str): - with open(file_h, 'rb') as file_h: + with open(file_h, "rb") as file_h: buf = file_h.read() else: buf = file_h.read() diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index 36f6895224..9d22425fab 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -14,9 +14,7 @@ from django.contrib.gis.geos.error import GEOSException from django.contrib.gis.geos.libgeos import GEOM_PTR, geos_version_tuple from django.contrib.gis.geos.mutable_list import ListMixin from django.contrib.gis.geos.prepared import PreparedGeometry -from django.contrib.gis.geos.prototypes.io import ( - ewkb_w, wkb_r, wkb_w, wkt_r, wkt_w, -) +from django.contrib.gis.geos.prototypes.io import ewkb_w, wkb_r, wkb_w, wkt_r, wkt_w from django.utils.deconstruct import deconstructible from django.utils.encoding import force_bytes, force_str @@ -38,12 +36,15 @@ class GEOSGeometryBase(GEOSBase): if GEOSGeometryBase._GEOS_CLASSES is None: # Inner imports avoid import conflicts with GEOSGeometry. from .collections import ( - GeometryCollection, MultiLineString, MultiPoint, + GeometryCollection, + MultiLineString, + MultiPoint, MultiPolygon, ) from .linestring import LinearRing, LineString from .point import Point from .polygon import Polygon + GEOSGeometryBase._GEOS_CLASSES = { 0: Point, 1: LineString, @@ -62,7 +63,9 @@ class GEOSGeometryBase(GEOSBase): "Perform post-initialization setup." # Setting the coordinate sequence for the geometry (will be None on # geometries that do not have coordinate sequences) - self._cs = GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz) if self.has_cs else None + self._cs = ( + GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz) if self.has_cs else None + ) def __copy__(self): """ @@ -85,7 +88,7 @@ class GEOSGeometryBase(GEOSBase): def __repr__(self): "Short-hand representation because WKT may be very large." - return '<%s object at %s>' % (self.geom_type, hex(addressof(self.ptr))) + return "<%s object at %s>" % (self.geom_type, hex(addressof(self.ptr))) # Pickling support def _to_pickle_wkb(self): @@ -104,7 +107,7 @@ class GEOSGeometryBase(GEOSBase): wkb, srid = state ptr = self._from_pickle_wkb(wkb) if not ptr: - raise GEOSException('Invalid Geometry loaded from pickled state.') + raise GEOSException("Invalid Geometry loaded from pickled state.") self.ptr = ptr self._post_init() self.srid = srid @@ -117,17 +120,17 @@ class GEOSGeometryBase(GEOSBase): def from_ewkt(ewkt): ewkt = force_bytes(ewkt) srid = None - parts = ewkt.split(b';', 1) + parts = ewkt.split(b";", 1) if len(parts) == 2: srid_part, wkt = parts - match = re.match(br'SRID=(?P<srid>\-?\d+)', srid_part) + match = re.match(rb"SRID=(?P<srid>\-?\d+)", srid_part) if not match: - raise ValueError('EWKT has invalid SRID part.') - srid = int(match['srid']) + raise ValueError("EWKT has invalid SRID part.") + srid = int(match["srid"]) else: wkt = ewkt if not wkt: - raise ValueError('Expected WKT but got an empty string.') + raise ValueError("Expected WKT but got an empty string.") return GEOSGeometry(GEOSGeometry._from_wkt(wkt), srid=srid) @staticmethod @@ -149,7 +152,11 @@ class GEOSGeometryBase(GEOSBase): other = GEOSGeometry.from_ewkt(other) except (ValueError, GEOSException): return False - return isinstance(other, GEOSGeometry) and self.srid == other.srid and self.equals_exact(other) + return ( + isinstance(other, GEOSGeometry) + and self.srid == other.srid + and self.equals_exact(other) + ) def __hash__(self): return hash((self.srid, self.wkt)) @@ -225,7 +232,7 @@ class GEOSGeometryBase(GEOSBase): without losing any of the input vertices. """ if geos_version_tuple() < (3, 8): - raise GEOSException('GEOSGeometry.make_valid() requires GEOS >= 3.8.0.') + raise GEOSException("GEOSGeometry.make_valid() requires GEOS >= 3.8.0.") return GEOSGeometry(capi.geos_makevalid(self.ptr), srid=self.srid) # #### Unary predicates #### @@ -323,7 +330,7 @@ class GEOSGeometryBase(GEOSBase): two Geometries match the elements in pattern. """ if not isinstance(pattern, str) or len(pattern) > 9: - raise GEOSException('invalid intersection matrix pattern') + raise GEOSException("invalid intersection matrix pattern") return capi.geos_relatepattern(self.ptr, other.ptr, force_bytes(pattern)) def touches(self, other): @@ -362,7 +369,7 @@ class GEOSGeometryBase(GEOSBase): Return the EWKT (SRID + WKT) of the Geometry. """ srid = self.srid - return 'SRID=%s;%s' % (srid, self.wkt) if srid else self.wkt + return "SRID=%s;%s" % (srid, self.wkt) if srid else self.wkt @property def wkt(self): @@ -395,6 +402,7 @@ class GEOSGeometryBase(GEOSBase): Return GeoJSON representation of this Geometry. """ return self.ogr.json + geojson = json @property @@ -419,7 +427,7 @@ class GEOSGeometryBase(GEOSBase): def kml(self): "Return the KML representation of this Geometry." gtype = self.geom_type - return '<%s>%s</%s>' % (gtype, self.coord_seq.kml, gtype) + return "<%s>%s</%s>" % (gtype, self.coord_seq.kml, gtype) @property def prepared(self): @@ -493,7 +501,7 @@ class GEOSGeometryBase(GEOSBase): self._post_init() self.srid = g.srid else: - raise GEOSException('Transformed WKB was invalid.') + raise GEOSException("Transformed WKB was invalid.") # #### Topology Routines #### def _topology(self, gptr): @@ -515,7 +523,9 @@ class GEOSGeometryBase(GEOSBase): """ return self._topology(capi.geos_buffer(self.ptr, width, quadsegs)) - def buffer_with_style(self, width, quadsegs=8, end_cap_style=1, join_style=1, mitre_limit=5.0): + def buffer_with_style( + self, width, quadsegs=8, end_cap_style=1, join_style=1, mitre_limit=5.0 + ): """ Same as buffer() but allows customizing the style of the buffer. @@ -524,7 +534,9 @@ class GEOSGeometryBase(GEOSBase): Mitre ratio limit only affects mitered join style. """ return self._topology( - capi.geos_bufferwithstyle(self.ptr, width, quadsegs, end_cap_style, join_style, mitre_limit), + capi.geos_bufferwithstyle( + self.ptr, width, quadsegs, end_cap_style, join_style, mitre_limit + ), ) @property @@ -615,7 +627,7 @@ class GEOSGeometryBase(GEOSBase): the Geometry. """ if not isinstance(other, GEOSGeometry): - raise TypeError('distance() works only on other GEOS Geometries.') + raise TypeError("distance() works only on other GEOS Geometries.") return capi.geos_distance(self.ptr, other.ptr, byref(c_double())) @property @@ -625,6 +637,7 @@ class GEOSGeometryBase(GEOSBase): (xmin, ymin, xmax, ymax). """ from .point import Point + env = self.envelope if isinstance(env, Point): xmin, ymin = env.tuple @@ -651,6 +664,7 @@ class LinearGeometryMixin: """ Used for LineString and MultiLineString. """ + def interpolate(self, distance): return self._topology(capi.geos_interpolate(self.ptr, distance)) @@ -659,14 +673,16 @@ class LinearGeometryMixin: def project(self, point): from .point import Point + if not isinstance(point, Point): - raise TypeError('locate_point argument must be a Point') + raise TypeError("locate_point argument must be a Point") return capi.geos_project(self.ptr, point.ptr) def project_normalized(self, point): from .point import Point + if not isinstance(point, Point): - raise TypeError('locate_point argument must be a Point') + raise TypeError("locate_point argument must be a Point") return capi.geos_project_normalized(self.ptr, point.ptr) @property @@ -710,9 +726,9 @@ class GEOSGeometry(GEOSGeometryBase, ListMixin): wkt_m = wkt_regex.match(geo_input) if wkt_m: # Handle WKT input. - if wkt_m['srid']: - input_srid = int(wkt_m['srid']) - g = self._from_wkt(force_bytes(wkt_m['wkt'])) + if wkt_m["srid"]: + input_srid = int(wkt_m["srid"]) + g = self._from_wkt(force_bytes(wkt_m["wkt"])) elif hex_regex.match(geo_input): # Handle HEXEWKB input. g = wkb_r().read(force_bytes(geo_input)) @@ -722,7 +738,7 @@ class GEOSGeometry(GEOSGeometryBase, ListMixin): g = ogr._geos_ptr() input_srid = ogr.srid else: - raise ValueError('String input unrecognized as WKT EWKT, and HEXEWKB.') + raise ValueError("String input unrecognized as WKT EWKT, and HEXEWKB.") elif isinstance(geo_input, GEOM_PTR): # When the input is a pointer to a geometry (GEOM_PTR). g = geo_input @@ -732,14 +748,14 @@ class GEOSGeometry(GEOSGeometryBase, ListMixin): elif isinstance(geo_input, GEOSGeometry): g = capi.geom_clone(geo_input.ptr) else: - raise TypeError('Improper geometry input type: %s' % type(geo_input)) + raise TypeError("Improper geometry input type: %s" % type(geo_input)) if not g: - raise GEOSException('Could not initialize GEOS Geometry with given input.') + raise GEOSException("Could not initialize GEOS Geometry with given input.") input_srid = input_srid or capi.geos_get_srid(g) or None if input_srid and srid and input_srid != srid: - raise ValueError('Input geometry already has SRID: %d.' % input_srid) + raise ValueError("Input geometry already has SRID: %d." % input_srid) super().__init__(g, None) # Set the SRID, if given. diff --git a/django/contrib/gis/geos/io.py b/django/contrib/gis/geos/io.py index 15027ff68f..d7898065f0 100644 --- a/django/contrib/gis/geos/io.py +++ b/django/contrib/gis/geos/io.py @@ -5,10 +5,13 @@ reader and writer classes. """ from django.contrib.gis.geos.geometry import GEOSGeometry from django.contrib.gis.geos.prototypes.io import ( - WKBWriter, WKTWriter, _WKBReader, _WKTReader, + WKBWriter, + WKTWriter, + _WKBReader, + _WKTReader, ) -__all__ = ['WKBWriter', 'WKTWriter', 'WKBReader', 'WKTReader'] +__all__ = ["WKBWriter", "WKTWriter", "WKBReader", "WKTReader"] # Public classes for (WKB|WKT)Reader, which return GEOSGeometry diff --git a/django/contrib/gis/geos/libgeos.py b/django/contrib/gis/geos/libgeos.py index aa19b3a706..1121b4f715 100644 --- a/django/contrib/gis/geos/libgeos.py +++ b/django/contrib/gis/geos/libgeos.py @@ -15,13 +15,14 @@ from django.core.exceptions import ImproperlyConfigured from django.utils.functional import SimpleLazyObject, cached_property from django.utils.version import get_version_tuple -logger = logging.getLogger('django.contrib.gis') +logger = logging.getLogger("django.contrib.gis") def load_geos(): # Custom library path set? try: from django.conf import settings + lib_path = settings.GEOS_LIBRARY_PATH except (AttributeError, ImportError, ImproperlyConfigured, OSError): lib_path = None @@ -29,12 +30,12 @@ def load_geos(): # Setting the appropriate names for the GEOS-C library. if lib_path: lib_names = None - elif os.name == 'nt': + elif os.name == "nt": # Windows NT libraries - lib_names = ['geos_c', 'libgeos_c-1'] - elif os.name == 'posix': + lib_names = ["geos_c", "libgeos_c-1"] + elif os.name == "posix": # *NIX libraries - lib_names = ['geos_c', 'GEOS'] + lib_names = ["geos_c", "GEOS"] else: raise ImportError('Unsupported OS "%s"' % os.name) @@ -51,8 +52,7 @@ def load_geos(): if lib_path is None: raise ImportError( 'Could not find the GEOS library (tried "%s"). ' - 'Try setting GEOS_LIBRARY_PATH in your settings.' % - '", "'.join(lib_names) + "Try setting GEOS_LIBRARY_PATH in your settings." % '", "'.join(lib_names) ) # Getting the GEOS C library. The C interface (CDLL) is used for # both *NIX and Windows. @@ -82,7 +82,7 @@ def notice_h(fmt, lst): warn_msg = fmt % lst except TypeError: warn_msg = fmt - logger.warning('GEOS_NOTICE: %s\n', warn_msg) + logger.warning("GEOS_NOTICE: %s\n", warn_msg) notice_h = NOTICEFUNC(notice_h) @@ -96,7 +96,7 @@ def error_h(fmt, lst): err_msg = fmt % lst except TypeError: err_msg = fmt - logger.error('GEOS_ERROR: %s\n', err_msg) + logger.error("GEOS_ERROR: %s\n", err_msg) error_h = ERRORFUNC(error_h) @@ -135,6 +135,7 @@ class GEOSFuncFactory: """ Lazy loading of GEOS functions. """ + argtypes = None restype = None errcheck = None @@ -154,6 +155,7 @@ class GEOSFuncFactory: @cached_property def func(self): from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc + func = GEOSFunc(self.func_name) func.argtypes = self.argtypes or [] func.restype = self.restype diff --git a/django/contrib/gis/geos/linestring.py b/django/contrib/gis/geos/linestring.py index a85ecfff8c..78a265cebf 100644 --- a/django/contrib/gis/geos/linestring.py +++ b/django/contrib/gis/geos/linestring.py @@ -29,11 +29,15 @@ class LineString(LinearGeometryMixin, GEOSGeometry): else: coords = args - if not (isinstance(coords, (tuple, list)) or numpy and isinstance(coords, numpy.ndarray)): - raise TypeError('Invalid initialization input for LineStrings.') + if not ( + isinstance(coords, (tuple, list)) + or numpy + and isinstance(coords, numpy.ndarray) + ): + raise TypeError("Invalid initialization input for LineStrings.") # If SRID was passed in with the keyword arguments - srid = kwargs.get('srid') + srid = kwargs.get("srid") ncoords = len(coords) if not ncoords: @@ -42,7 +46,8 @@ class LineString(LinearGeometryMixin, GEOSGeometry): if ncoords < self._minlength: raise ValueError( - '%s requires at least %d points, got %s.' % ( + "%s requires at least %d points, got %s." + % ( self.__class__.__name__, self._minlength, ncoords, @@ -53,7 +58,7 @@ class LineString(LinearGeometryMixin, GEOSGeometry): if numpy_coords: shape = coords.shape # Using numpy's shape. if len(shape) != 2: - raise TypeError('Too many dimensions.') + raise TypeError("Too many dimensions.") self._checkdim(shape[1]) ndim = shape[1] else: @@ -63,13 +68,15 @@ class LineString(LinearGeometryMixin, GEOSGeometry): # Incrementing through each of the coordinates and verifying for coord in coords: if not isinstance(coord, (tuple, list, Point)): - raise TypeError('Each coordinate should be a sequence (list or tuple)') + raise TypeError( + "Each coordinate should be a sequence (list or tuple)" + ) if ndim is None: ndim = len(coord) self._checkdim(ndim) elif len(coord) != ndim: - raise TypeError('Dimension mismatch.') + raise TypeError("Dimension mismatch.") # Creating a coordinate sequence object because it is easier to # set the points using its methods. @@ -122,20 +129,21 @@ class LineString(LinearGeometryMixin, GEOSGeometry): self._post_init() else: # can this happen? - raise GEOSException('Geometry resulting from slice deletion was invalid.') + raise GEOSException("Geometry resulting from slice deletion was invalid.") def _set_single(self, index, value): self._cs[index] = value def _checkdim(self, dim): if dim not in (2, 3): - raise TypeError('Dimension mismatch.') + raise TypeError("Dimension mismatch.") # #### Sequence Properties #### @property def tuple(self): "Return a tuple version of the geometry from the coordinate sequence." return self._cs.tuple + coords = tuple def _listarr(self, func): @@ -181,7 +189,5 @@ class LinearRing(LineString): @property def is_counterclockwise(self): if self.empty: - raise ValueError( - 'Orientation of an empty LinearRing cannot be determined.' - ) + raise ValueError("Orientation of an empty LinearRing cannot be determined.") return self._cs.is_counterclockwise diff --git a/django/contrib/gis/geos/mutable_list.py b/django/contrib/gis/geos/mutable_list.py index b04a2128af..36131fe9ce 100644 --- a/django/contrib/gis/geos/mutable_list.py +++ b/django/contrib/gis/geos/mutable_list.py @@ -60,10 +60,10 @@ class ListMixin: # ### Python initialization and special list interface methods ### def __init__(self, *args, **kwargs): - if not hasattr(self, '_get_single_internal'): + if not hasattr(self, "_get_single_internal"): self._get_single_internal = self._get_single_external - if not hasattr(self, '_set_single'): + if not hasattr(self, "_set_single"): self._set_single = self._set_single_rebuild self._assign_extended_slice = self._assign_extended_slice_rebuild @@ -72,7 +72,9 @@ class ListMixin: def __getitem__(self, index): "Get the item(s) at the specified index/slice." if isinstance(index, slice): - return [self._get_single_external(i) for i in range(*index.indices(len(self)))] + return [ + self._get_single_external(i) for i in range(*index.indices(len(self))) + ] else: index = self._checkindex(index) return self._get_single_external(index) @@ -91,9 +93,9 @@ class ListMixin: indexRange = range(*index.indices(origLen)) newLen = origLen - len(indexRange) - newItems = (self._get_single_internal(i) - for i in range(origLen) - if i not in indexRange) + newItems = ( + self._get_single_internal(i) for i in range(origLen) if i not in indexRange + ) self._rebuild(newLen, newItems) @@ -108,28 +110,28 @@ class ListMixin: # ### Special methods for arithmetic operations ### def __add__(self, other): - 'add another list-like object' + "add another list-like object" return self.__class__([*self, *other]) def __radd__(self, other): - 'add to another list-like object' + "add to another list-like object" return other.__class__([*other, *self]) def __iadd__(self, other): - 'add another list-like object to self' + "add another list-like object to self" self.extend(other) return self def __mul__(self, n): - 'multiply' + "multiply" return self.__class__(list(self) * n) def __rmul__(self, n): - 'multiply' + "multiply" return self.__class__(list(self) * n) def __imul__(self, n): - 'multiply' + "multiply" if n <= 0: del self[:] else: @@ -179,16 +181,16 @@ class ListMixin: for i in range(0, len(self)): if self[i] == val: return i - raise ValueError('%s not found in object' % val) + raise ValueError("%s not found in object" % val) # ## Mutating ## def append(self, val): "Standard list append method" - self[len(self):] = [val] + self[len(self) :] = [val] def extend(self, vals): "Standard list extend method" - self[len(self):] = vals + self[len(self) :] = vals def insert(self, index, val): "Standard list insert method" @@ -217,9 +219,9 @@ class ListMixin: # ### Private routines ### def _rebuild(self, newLen, newItems): if newLen and newLen < self._minlength: - raise ValueError('Must have at least %d items' % self._minlength) + raise ValueError("Must have at least %d items" % self._minlength) if self._maxlength is not None and newLen > self._maxlength: - raise ValueError('Cannot have more than %d items' % self._maxlength) + raise ValueError("Cannot have more than %d items" % self._maxlength) self._set_list(newLen, newItems) @@ -232,19 +234,19 @@ class ListMixin: return index if -length <= index < 0: return index + length - raise IndexError('invalid index: %s' % index) + raise IndexError("invalid index: %s" % index) def _check_allowed(self, items): - if hasattr(self, '_allowed'): + if hasattr(self, "_allowed"): if False in [isinstance(val, self._allowed) for val in items]: - raise TypeError('Invalid type encountered in the arguments.') + raise TypeError("Invalid type encountered in the arguments.") def _set_slice(self, index, values): "Assign values to a slice of the object" try: valueList = list(values) except TypeError: - raise TypeError('can only assign an iterable to a slice') + raise TypeError("can only assign an iterable to a slice") self._check_allowed(valueList) @@ -259,13 +261,14 @@ class ListMixin: self._assign_extended_slice(start, stop, step, valueList) def _assign_extended_slice_rebuild(self, start, stop, step, valueList): - 'Assign an extended slice by rebuilding entire list' + "Assign an extended slice by rebuilding entire list" indexList = range(start, stop, step) # extended slice, only allow assigning slice of same size if len(valueList) != len(indexList): - raise ValueError('attempt to assign sequence of size %d ' - 'to extended slice of size %d' - % (len(valueList), len(indexList))) + raise ValueError( + "attempt to assign sequence of size %d " + "to extended slice of size %d" % (len(valueList), len(indexList)) + ) # we're not changing the length of the sequence newLen = len(self) @@ -281,19 +284,20 @@ class ListMixin: self._rebuild(newLen, newItems()) def _assign_extended_slice(self, start, stop, step, valueList): - 'Assign an extended slice by re-assigning individual items' + "Assign an extended slice by re-assigning individual items" indexList = range(start, stop, step) # extended slice, only allow assigning slice of same size if len(valueList) != len(indexList): - raise ValueError('attempt to assign sequence of size %d ' - 'to extended slice of size %d' - % (len(valueList), len(indexList))) + raise ValueError( + "attempt to assign sequence of size %d " + "to extended slice of size %d" % (len(valueList), len(indexList)) + ) for i, val in zip(indexList, valueList): self._set_single(i, val) def _assign_simple_slice(self, start, stop, valueList): - 'Assign a simple slice; Can assign slice of any length' + "Assign a simple slice; Can assign slice of any length" origLen = len(self) stop = max(start, stop) newLen = origLen - stop + start + len(valueList) diff --git a/django/contrib/gis/geos/point.py b/django/contrib/gis/geos/point.py index 00fdd96a27..06b2668621 100644 --- a/django/contrib/gis/geos/point.py +++ b/django/contrib/gis/geos/point.py @@ -32,7 +32,7 @@ class Point(GEOSGeometry): else: coords = [x, y] else: - raise TypeError('Invalid parameters given for Point initialization.') + raise TypeError("Invalid parameters given for Point initialization.") point = self._create_point(len(coords), coords) @@ -47,7 +47,9 @@ class Point(GEOSGeometry): return self._create_empty() if wkb is None else super()._from_pickle_wkb(wkb) def _ogr_ptr(self): - return gdal.geometries.Point._create_empty() if self.empty else super()._ogr_ptr() + return ( + gdal.geometries.Point._create_empty() if self.empty else super()._ogr_ptr() + ) @classmethod def _create_empty(cls): @@ -62,7 +64,7 @@ class Point(GEOSGeometry): return capi.create_point(None) if ndim < 2 or ndim > 3: - raise TypeError('Invalid point dimension: %s' % ndim) + raise TypeError("Invalid point dimension: %s" % ndim) cs = capi.create_cs(c_uint(1), c_uint(ndim)) i = iter(coords) @@ -84,7 +86,7 @@ class Point(GEOSGeometry): self._post_init() else: # can this happen? - raise GEOSException('Geometry resulting from slice deletion was invalid.') + raise GEOSException("Geometry resulting from slice deletion was invalid.") def _set_single(self, index, value): self._cs.setOrdinate(index, 0, value) @@ -142,7 +144,7 @@ class Point(GEOSGeometry): def z(self, value): "Set the Z component of the Point." if not self.hasz: - raise GEOSException('Cannot set Z on 2D Point.') + raise GEOSException("Cannot set Z on 2D Point.") self._cs.setOrdinate(2, 0, value) # ### Tuple setting and retrieval routines. ### diff --git a/django/contrib/gis/geos/polygon.py b/django/contrib/gis/geos/polygon.py index d75106a9a8..452e72fcb6 100644 --- a/django/contrib/gis/geos/polygon.py +++ b/django/contrib/gis/geos/polygon.py @@ -59,8 +59,10 @@ class Polygon(GEOSGeometry): x0, y0, x1, y1 = bbox for z in bbox: if not isinstance(z, (float, int)): - return GEOSGeometry('POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % - (x0, y0, x0, y1, x1, y1, x1, y0, x0, y0)) + return GEOSGeometry( + "POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))" + % (x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) + ) return Polygon(((x0, y0), (x0, y1), (x1, y1), (x1, y0), (x0, y0))) # ### These routines are needed for list-like operation w/ListMixin ### @@ -95,8 +97,13 @@ class Polygon(GEOSGeometry): else: return capi.geom_clone(g.ptr) - def _construct_ring(self, param, msg=( - 'Parameter must be a sequence of LinearRings or objects that can initialize to LinearRings')): + def _construct_ring( + self, + param, + msg=( + "Parameter must be a sequence of LinearRings or objects that can initialize to LinearRings" + ), + ): "Try to construct a ring from the given parameter." if isinstance(param, LinearRing): return param @@ -135,7 +142,9 @@ class Polygon(GEOSGeometry): return capi.get_intring(self.ptr, index - 1) def _get_single_external(self, index): - return GEOSGeometry(capi.geom_clone(self._get_single_internal(index)), srid=self.srid) + return GEOSGeometry( + capi.geom_clone(self._get_single_internal(index)), srid=self.srid + ) _set_single = GEOSGeometry._set_single_rebuild _assign_extended_slice = GEOSGeometry._assign_extended_slice_rebuild @@ -163,13 +172,17 @@ class Polygon(GEOSGeometry): def tuple(self): "Get the tuple for each ring in this Polygon." return tuple(self[i].tuple for i in range(len(self))) + coords = tuple @property def kml(self): "Return the KML representation of this Polygon." - inner_kml = ''.join( + inner_kml = "".join( "<innerBoundaryIs>%s</innerBoundaryIs>" % self[i + 1].kml for i in range(self.num_interior_rings) ) - return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml) + return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % ( + self[0].kml, + inner_kml, + ) diff --git a/django/contrib/gis/geos/prepared.py b/django/contrib/gis/geos/prepared.py index 789432f81a..9c77d8a533 100644 --- a/django/contrib/gis/geos/prepared.py +++ b/django/contrib/gis/geos/prepared.py @@ -8,6 +8,7 @@ class PreparedGeometry(GEOSBase): At the moment this includes the contains covers, and intersects operations. """ + ptr_type = capi.PREPGEOM_PTR destructor = capi.prepared_destroy @@ -17,6 +18,7 @@ class PreparedGeometry(GEOSBase): # See #21662 self._base_geom = geom from .geometry import GEOSGeometry + if not isinstance(geom, GEOSGeometry): raise TypeError self.ptr = capi.geos_prepare(geom.ptr) diff --git a/django/contrib/gis/geos/prototypes/__init__.py b/django/contrib/gis/geos/prototypes/__init__.py index a79533fa14..8fa98f98e7 100644 --- a/django/contrib/gis/geos/prototypes/__init__.py +++ b/django/contrib/gis/geos/prototypes/__init__.py @@ -5,22 +5,62 @@ """ from django.contrib.gis.geos.prototypes.coordseq import ( # NOQA - create_cs, cs_clone, cs_getdims, cs_getordinate, cs_getsize, cs_getx, - cs_gety, cs_getz, cs_is_ccw, cs_setordinate, cs_setx, cs_sety, cs_setz, + create_cs, + cs_clone, + cs_getdims, + cs_getordinate, + cs_getsize, + cs_getx, + cs_gety, + cs_getz, + cs_is_ccw, + cs_setordinate, + cs_setx, + cs_sety, + cs_setz, get_cs, ) from django.contrib.gis.geos.prototypes.geom import ( # NOQA - create_collection, create_empty_polygon, create_linearring, - create_linestring, create_point, create_polygon, destroy_geom, geom_clone, - geos_get_srid, geos_makevalid, geos_normalize, geos_set_srid, geos_type, - geos_typeid, get_dims, get_extring, get_geomn, get_intring, get_nrings, - get_num_coords, get_num_geoms, + create_collection, + create_empty_polygon, + create_linearring, + create_linestring, + create_point, + create_polygon, + destroy_geom, + geom_clone, + geos_get_srid, + geos_makevalid, + geos_normalize, + geos_set_srid, + geos_type, + geos_typeid, + get_dims, + get_extring, + get_geomn, + get_intring, + get_nrings, + get_num_coords, + get_num_geoms, ) from django.contrib.gis.geos.prototypes.misc import * # NOQA from django.contrib.gis.geos.prototypes.predicates import ( # NOQA - geos_contains, geos_covers, geos_crosses, geos_disjoint, geos_equals, - geos_equalsexact, geos_hasz, geos_intersects, geos_isclosed, geos_isempty, - geos_isring, geos_issimple, geos_isvalid, geos_overlaps, - geos_relatepattern, geos_touches, geos_within, + geos_contains, + geos_covers, + geos_crosses, + geos_disjoint, + geos_equals, + geos_equalsexact, + geos_hasz, + geos_intersects, + geos_isclosed, + geos_isempty, + geos_isring, + geos_issimple, + geos_isvalid, + geos_overlaps, + geos_relatepattern, + geos_touches, + geos_within, ) from django.contrib.gis.geos.prototypes.topology import * # NOQA diff --git a/django/contrib/gis/geos/prototypes/coordseq.py b/django/contrib/gis/geos/prototypes/coordseq.py index aab5d3e75e..ed05de99f4 100644 --- a/django/contrib/gis/geos/prototypes/coordseq.py +++ b/django/contrib/gis/geos/prototypes/coordseq.py @@ -1,16 +1,14 @@ from ctypes import POINTER, c_byte, c_double, c_int, c_uint from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR, GEOSFuncFactory -from django.contrib.gis.geos.prototypes.errcheck import ( - GEOSException, last_arg_byref, -) +from django.contrib.gis.geos.prototypes.errcheck import GEOSException, last_arg_byref # ## Error-checking routines specific to coordinate sequences. ## def check_cs_op(result, func, cargs): "Check the status code of a coordinate sequence operation." if result == 0: - raise GEOSException('Could not set value on coordinate sequence') + raise GEOSException("Could not set value on coordinate sequence") else: return result @@ -49,7 +47,9 @@ class CsOperation(GEOSFuncFactory): else: argtypes = [CS_PTR, c_uint, dbl_param] - super().__init__(*args, **{**kwargs, 'errcheck': errcheck, 'argtypes': argtypes}) + super().__init__( + *args, **{**kwargs, "errcheck": errcheck, "argtypes": argtypes} + ) class CsOutput(GEOSFuncFactory): @@ -59,7 +59,7 @@ class CsOutput(GEOSFuncFactory): def errcheck(result, func, cargs): if not result: raise GEOSException( - 'Error encountered checking Coordinate Sequence returned from GEOS ' + "Error encountered checking Coordinate Sequence returned from GEOS " 'C function "%s".' % func.__name__ ) return result @@ -68,26 +68,28 @@ class CsOutput(GEOSFuncFactory): # ## Coordinate Sequence ctypes prototypes ## # Coordinate Sequence constructors & cloning. -cs_clone = CsOutput('GEOSCoordSeq_clone', argtypes=[CS_PTR]) -create_cs = CsOutput('GEOSCoordSeq_create', argtypes=[c_uint, c_uint]) -get_cs = CsOutput('GEOSGeom_getCoordSeq', argtypes=[GEOM_PTR]) +cs_clone = CsOutput("GEOSCoordSeq_clone", argtypes=[CS_PTR]) +create_cs = CsOutput("GEOSCoordSeq_create", argtypes=[c_uint, c_uint]) +get_cs = CsOutput("GEOSGeom_getCoordSeq", argtypes=[GEOM_PTR]) # Getting, setting ordinate -cs_getordinate = CsOperation('GEOSCoordSeq_getOrdinate', ordinate=True, get=True) -cs_setordinate = CsOperation('GEOSCoordSeq_setOrdinate', ordinate=True) +cs_getordinate = CsOperation("GEOSCoordSeq_getOrdinate", ordinate=True, get=True) +cs_setordinate = CsOperation("GEOSCoordSeq_setOrdinate", ordinate=True) # For getting, x, y, z -cs_getx = CsOperation('GEOSCoordSeq_getX', get=True) -cs_gety = CsOperation('GEOSCoordSeq_getY', get=True) -cs_getz = CsOperation('GEOSCoordSeq_getZ', get=True) +cs_getx = CsOperation("GEOSCoordSeq_getX", get=True) +cs_gety = CsOperation("GEOSCoordSeq_getY", get=True) +cs_getz = CsOperation("GEOSCoordSeq_getZ", get=True) # For setting, x, y, z -cs_setx = CsOperation('GEOSCoordSeq_setX') -cs_sety = CsOperation('GEOSCoordSeq_setY') -cs_setz = CsOperation('GEOSCoordSeq_setZ') +cs_setx = CsOperation("GEOSCoordSeq_setX") +cs_sety = CsOperation("GEOSCoordSeq_setY") +cs_setz = CsOperation("GEOSCoordSeq_setZ") # These routines return size & dimensions. -cs_getsize = CsInt('GEOSCoordSeq_getSize') -cs_getdims = CsInt('GEOSCoordSeq_getDimensions') +cs_getsize = CsInt("GEOSCoordSeq_getSize") +cs_getdims = CsInt("GEOSCoordSeq_getDimensions") -cs_is_ccw = GEOSFuncFactory('GEOSCoordSeq_isCCW', restype=c_int, argtypes=[CS_PTR, POINTER(c_byte)]) +cs_is_ccw = GEOSFuncFactory( + "GEOSCoordSeq_isCCW", restype=c_int, argtypes=[CS_PTR, POINTER(c_byte)] +) diff --git a/django/contrib/gis/geos/prototypes/errcheck.py b/django/contrib/gis/geos/prototypes/errcheck.py index 7d5f8422e0..a527f513a7 100644 --- a/django/contrib/gis/geos/prototypes/errcheck.py +++ b/django/contrib/gis/geos/prototypes/errcheck.py @@ -8,7 +8,7 @@ from django.contrib.gis.geos.libgeos import GEOSFuncFactory # Getting the `free` routine used to free the memory allocated for # string pointers returned by GEOS. -free = GEOSFuncFactory('GEOSFree') +free = GEOSFuncFactory("GEOSFree") free.argtypes = [c_void_p] @@ -29,14 +29,19 @@ def check_dbl(result, func, cargs): def check_geom(result, func, cargs): "Error checking on routines that return Geometries." if not result: - raise GEOSException('Error encountered checking Geometry returned from GEOS C function "%s".' % func.__name__) + raise GEOSException( + 'Error encountered checking Geometry returned from GEOS C function "%s".' + % func.__name__ + ) return result def check_minus_one(result, func, cargs): "Error checking on routines that should not return -1." if result == -1: - raise GEOSException('Error encountered in GEOS C function "%s".' % func.__name__) + raise GEOSException( + 'Error encountered in GEOS C function "%s".' % func.__name__ + ) else: return result @@ -48,7 +53,9 @@ def check_predicate(result, func, cargs): elif result == 0: return False else: - raise GEOSException('Error encountered on GEOS C predicate function "%s".' % func.__name__) + raise GEOSException( + 'Error encountered on GEOS C predicate function "%s".' % func.__name__ + ) def check_sized_string(result, func, cargs): @@ -58,7 +65,9 @@ def check_sized_string(result, func, cargs): This frees the memory allocated by GEOS at the result pointer. """ if not result: - raise GEOSException('Invalid string pointer returned by GEOS C function "%s"' % func.__name__) + raise GEOSException( + 'Invalid string pointer returned by GEOS C function "%s"' % func.__name__ + ) # A c_size_t object is passed in by reference for the second # argument on these routines, and its needed to determine the # correct size. @@ -75,7 +84,10 @@ def check_string(result, func, cargs): This frees the memory allocated by GEOS at the result pointer. """ if not result: - raise GEOSException('Error encountered checking string return value in GEOS C function "%s".' % func.__name__) + raise GEOSException( + 'Error encountered checking string return value in GEOS C function "%s".' + % func.__name__ + ) # Getting the string value at the pointer address. s = string_at(result) # Freeing the memory allocated within GEOS diff --git a/django/contrib/gis/geos/prototypes/geom.py b/django/contrib/gis/geos/prototypes/geom.py index bddf7b55b2..a456acd0c1 100644 --- a/django/contrib/gis/geos/prototypes/geom.py +++ b/django/contrib/gis/geos/prototypes/geom.py @@ -2,7 +2,9 @@ from ctypes import POINTER, c_char_p, c_int, c_ubyte, c_uint from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR, GEOSFuncFactory from django.contrib.gis.geos.prototypes.errcheck import ( - check_geom, check_minus_one, check_string, + check_geom, + check_minus_one, + check_string, ) # This is the return type used by binary output (WKB, HEX) routines. @@ -44,44 +46,46 @@ class StringFromGeom(GEOSFuncFactory): # ### ctypes prototypes ### # The GEOS geometry type, typeid, num_coordinates and number of geometries -geos_makevalid = GeomOutput('GEOSMakeValid', argtypes=[GEOM_PTR]) -geos_normalize = IntFromGeom('GEOSNormalize') -geos_type = StringFromGeom('GEOSGeomType') -geos_typeid = IntFromGeom('GEOSGeomTypeId') -get_dims = GEOSFuncFactory('GEOSGeom_getDimensions', argtypes=[GEOM_PTR], restype=c_int) -get_num_coords = IntFromGeom('GEOSGetNumCoordinates') -get_num_geoms = IntFromGeom('GEOSGetNumGeometries') +geos_makevalid = GeomOutput("GEOSMakeValid", argtypes=[GEOM_PTR]) +geos_normalize = IntFromGeom("GEOSNormalize") +geos_type = StringFromGeom("GEOSGeomType") +geos_typeid = IntFromGeom("GEOSGeomTypeId") +get_dims = GEOSFuncFactory("GEOSGeom_getDimensions", argtypes=[GEOM_PTR], restype=c_int) +get_num_coords = IntFromGeom("GEOSGetNumCoordinates") +get_num_geoms = IntFromGeom("GEOSGetNumGeometries") # Geometry creation factories -create_point = GeomOutput('GEOSGeom_createPoint', argtypes=[CS_PTR]) -create_linestring = GeomOutput('GEOSGeom_createLineString', argtypes=[CS_PTR]) -create_linearring = GeomOutput('GEOSGeom_createLinearRing', argtypes=[CS_PTR]) +create_point = GeomOutput("GEOSGeom_createPoint", argtypes=[CS_PTR]) +create_linestring = GeomOutput("GEOSGeom_createLineString", argtypes=[CS_PTR]) +create_linearring = GeomOutput("GEOSGeom_createLinearRing", argtypes=[CS_PTR]) # Polygon and collection creation routines need argument types defined # for compatibility with some platforms, e.g. macOS ARM64. With argtypes # defined, arrays are automatically cast and byref() calls are not needed. create_polygon = GeomOutput( - 'GEOSGeom_createPolygon', argtypes=[GEOM_PTR, POINTER(GEOM_PTR), c_uint], + "GEOSGeom_createPolygon", + argtypes=[GEOM_PTR, POINTER(GEOM_PTR), c_uint], ) -create_empty_polygon = GeomOutput('GEOSGeom_createEmptyPolygon', argtypes=[]) +create_empty_polygon = GeomOutput("GEOSGeom_createEmptyPolygon", argtypes=[]) create_collection = GeomOutput( - 'GEOSGeom_createCollection', argtypes=[c_int, POINTER(GEOM_PTR), c_uint], + "GEOSGeom_createCollection", + argtypes=[c_int, POINTER(GEOM_PTR), c_uint], ) # Ring routines -get_extring = GeomOutput('GEOSGetExteriorRing', argtypes=[GEOM_PTR]) -get_intring = GeomOutput('GEOSGetInteriorRingN', argtypes=[GEOM_PTR, c_int]) -get_nrings = IntFromGeom('GEOSGetNumInteriorRings') +get_extring = GeomOutput("GEOSGetExteriorRing", argtypes=[GEOM_PTR]) +get_intring = GeomOutput("GEOSGetInteriorRingN", argtypes=[GEOM_PTR, c_int]) +get_nrings = IntFromGeom("GEOSGetNumInteriorRings") # Collection Routines -get_geomn = GeomOutput('GEOSGetGeometryN', argtypes=[GEOM_PTR, c_int]) +get_geomn = GeomOutput("GEOSGetGeometryN", argtypes=[GEOM_PTR, c_int]) # Cloning -geom_clone = GEOSFuncFactory('GEOSGeom_clone', argtypes=[GEOM_PTR], restype=GEOM_PTR) +geom_clone = GEOSFuncFactory("GEOSGeom_clone", argtypes=[GEOM_PTR], restype=GEOM_PTR) # Destruction routine. -destroy_geom = GEOSFuncFactory('GEOSGeom_destroy', argtypes=[GEOM_PTR]) +destroy_geom = GEOSFuncFactory("GEOSGeom_destroy", argtypes=[GEOM_PTR]) # SRID routines -geos_get_srid = GEOSFuncFactory('GEOSGetSRID', argtypes=[GEOM_PTR], restype=c_int) -geos_set_srid = GEOSFuncFactory('GEOSSetSRID', argtypes=[GEOM_PTR, c_int]) +geos_get_srid = GEOSFuncFactory("GEOSGetSRID", argtypes=[GEOM_PTR], restype=c_int) +geos_set_srid = GEOSFuncFactory("GEOSSetSRID", argtypes=[GEOM_PTR, c_int]) diff --git a/django/contrib/gis/geos/prototypes/io.py b/django/contrib/gis/geos/prototypes/io.py index 4a1180ad7a..555d0e3a25 100644 --- a/django/contrib/gis/geos/prototypes/io.py +++ b/django/contrib/gis/geos/prototypes/io.py @@ -3,10 +3,14 @@ from ctypes import POINTER, Structure, byref, c_byte, c_char_p, c_int, c_size_t from django.contrib.gis.geos.base import GEOSBase from django.contrib.gis.geos.libgeos import ( - GEOM_PTR, GEOSFuncFactory, geos_version_tuple, + GEOM_PTR, + GEOSFuncFactory, + geos_version_tuple, ) from django.contrib.gis.geos.prototypes.errcheck import ( - check_geom, check_sized_string, check_string, + check_geom, + check_sized_string, + check_string, ) from django.contrib.gis.geos.prototypes.geom import c_uchar_p, geos_char_p from django.utils.encoding import force_bytes @@ -35,33 +39,43 @@ WKB_READ_PTR = POINTER(WKBReader_st) WKB_WRITE_PTR = POINTER(WKBReader_st) # WKTReader routines -wkt_reader_create = GEOSFuncFactory('GEOSWKTReader_create', restype=WKT_READ_PTR) -wkt_reader_destroy = GEOSFuncFactory('GEOSWKTReader_destroy', argtypes=[WKT_READ_PTR]) +wkt_reader_create = GEOSFuncFactory("GEOSWKTReader_create", restype=WKT_READ_PTR) +wkt_reader_destroy = GEOSFuncFactory("GEOSWKTReader_destroy", argtypes=[WKT_READ_PTR]) wkt_reader_read = GEOSFuncFactory( - 'GEOSWKTReader_read', argtypes=[WKT_READ_PTR, c_char_p], restype=GEOM_PTR, errcheck=check_geom + "GEOSWKTReader_read", + argtypes=[WKT_READ_PTR, c_char_p], + restype=GEOM_PTR, + errcheck=check_geom, ) # WKTWriter routines -wkt_writer_create = GEOSFuncFactory('GEOSWKTWriter_create', restype=WKT_WRITE_PTR) -wkt_writer_destroy = GEOSFuncFactory('GEOSWKTWriter_destroy', argtypes=[WKT_WRITE_PTR]) +wkt_writer_create = GEOSFuncFactory("GEOSWKTWriter_create", restype=WKT_WRITE_PTR) +wkt_writer_destroy = GEOSFuncFactory("GEOSWKTWriter_destroy", argtypes=[WKT_WRITE_PTR]) wkt_writer_write = GEOSFuncFactory( - 'GEOSWKTWriter_write', argtypes=[WKT_WRITE_PTR, GEOM_PTR], restype=geos_char_p, errcheck=check_string + "GEOSWKTWriter_write", + argtypes=[WKT_WRITE_PTR, GEOM_PTR], + restype=geos_char_p, + errcheck=check_string, ) wkt_writer_get_outdim = GEOSFuncFactory( - 'GEOSWKTWriter_getOutputDimension', argtypes=[WKT_WRITE_PTR], restype=c_int + "GEOSWKTWriter_getOutputDimension", argtypes=[WKT_WRITE_PTR], restype=c_int ) wkt_writer_set_outdim = GEOSFuncFactory( - 'GEOSWKTWriter_setOutputDimension', argtypes=[WKT_WRITE_PTR, c_int] + "GEOSWKTWriter_setOutputDimension", argtypes=[WKT_WRITE_PTR, c_int] ) -wkt_writer_set_trim = GEOSFuncFactory('GEOSWKTWriter_setTrim', argtypes=[WKT_WRITE_PTR, c_byte]) -wkt_writer_set_precision = GEOSFuncFactory('GEOSWKTWriter_setRoundingPrecision', argtypes=[WKT_WRITE_PTR, c_int]) +wkt_writer_set_trim = GEOSFuncFactory( + "GEOSWKTWriter_setTrim", argtypes=[WKT_WRITE_PTR, c_byte] +) +wkt_writer_set_precision = GEOSFuncFactory( + "GEOSWKTWriter_setRoundingPrecision", argtypes=[WKT_WRITE_PTR, c_int] +) # WKBReader routines -wkb_reader_create = GEOSFuncFactory('GEOSWKBReader_create', restype=WKB_READ_PTR) -wkb_reader_destroy = GEOSFuncFactory('GEOSWKBReader_destroy', argtypes=[WKB_READ_PTR]) +wkb_reader_create = GEOSFuncFactory("GEOSWKBReader_create", restype=WKB_READ_PTR) +wkb_reader_destroy = GEOSFuncFactory("GEOSWKBReader_destroy", argtypes=[WKB_READ_PTR]) class WKBReadFunc(GEOSFuncFactory): @@ -75,12 +89,12 @@ class WKBReadFunc(GEOSFuncFactory): errcheck = staticmethod(check_geom) -wkb_reader_read = WKBReadFunc('GEOSWKBReader_read') -wkb_reader_read_hex = WKBReadFunc('GEOSWKBReader_readHEX') +wkb_reader_read = WKBReadFunc("GEOSWKBReader_read") +wkb_reader_read_hex = WKBReadFunc("GEOSWKBReader_readHEX") # WKBWriter routines -wkb_writer_create = GEOSFuncFactory('GEOSWKBWriter_create', restype=WKB_WRITE_PTR) -wkb_writer_destroy = GEOSFuncFactory('GEOSWKBWriter_destroy', argtypes=[WKB_WRITE_PTR]) +wkb_writer_create = GEOSFuncFactory("GEOSWKBWriter_create", restype=WKB_WRITE_PTR) +wkb_writer_destroy = GEOSFuncFactory("GEOSWKBWriter_destroy", argtypes=[WKB_WRITE_PTR]) # WKB Writing prototypes. @@ -90,8 +104,8 @@ class WKBWriteFunc(GEOSFuncFactory): errcheck = staticmethod(check_sized_string) -wkb_writer_write = WKBWriteFunc('GEOSWKBWriter_write') -wkb_writer_write_hex = WKBWriteFunc('GEOSWKBWriter_writeHEX') +wkb_writer_write = WKBWriteFunc("GEOSWKBWriter_write") +wkb_writer_write_hex = WKBWriteFunc("GEOSWKBWriter_writeHEX") # WKBWriter property getter/setter prototypes. @@ -104,17 +118,22 @@ class WKBWriterSet(GEOSFuncFactory): argtypes = [WKB_WRITE_PTR, c_int] -wkb_writer_get_byteorder = WKBWriterGet('GEOSWKBWriter_getByteOrder') -wkb_writer_set_byteorder = WKBWriterSet('GEOSWKBWriter_setByteOrder') -wkb_writer_get_outdim = WKBWriterGet('GEOSWKBWriter_getOutputDimension') -wkb_writer_set_outdim = WKBWriterSet('GEOSWKBWriter_setOutputDimension') -wkb_writer_get_include_srid = WKBWriterGet('GEOSWKBWriter_getIncludeSRID', restype=c_byte) -wkb_writer_set_include_srid = WKBWriterSet('GEOSWKBWriter_setIncludeSRID', argtypes=[WKB_WRITE_PTR, c_byte]) +wkb_writer_get_byteorder = WKBWriterGet("GEOSWKBWriter_getByteOrder") +wkb_writer_set_byteorder = WKBWriterSet("GEOSWKBWriter_setByteOrder") +wkb_writer_get_outdim = WKBWriterGet("GEOSWKBWriter_getOutputDimension") +wkb_writer_set_outdim = WKBWriterSet("GEOSWKBWriter_setOutputDimension") +wkb_writer_get_include_srid = WKBWriterGet( + "GEOSWKBWriter_getIncludeSRID", restype=c_byte +) +wkb_writer_set_include_srid = WKBWriterSet( + "GEOSWKBWriter_setIncludeSRID", argtypes=[WKB_WRITE_PTR, c_byte] +) # ### Base I/O Class ### class IOBase(GEOSBase): "Base class for GEOS I/O objects." + def __init__(self): # Getting the pointer with the constructor. self.ptr = self._constructor() @@ -122,6 +141,7 @@ class IOBase(GEOSBase): # __del__ is too late (import error). self.destructor.func + # ### Base WKB/WKT Reading and Writing objects ### @@ -183,7 +203,7 @@ class WKTWriter(IOBase): @outdim.setter def outdim(self, new_dim): if new_dim not in (2, 3): - raise ValueError('WKT output dimension must be 2 or 3') + raise ValueError("WKT output dimension must be 2 or 3") wkt_writer_set_outdim(self.ptr, new_dim) @property @@ -203,7 +223,9 @@ class WKTWriter(IOBase): @precision.setter def precision(self, precision): if (not isinstance(precision, int) or precision < 0) and precision is not None: - raise AttributeError('WKT output rounding precision must be non-negative integer or None.') + raise AttributeError( + "WKT output rounding precision must be non-negative integer or None." + ) if precision != self._precision: self._precision = precision wkt_writer_set_precision(self.ptr, -1 if precision is None else precision) @@ -221,34 +243,37 @@ class WKBWriter(IOBase): def _handle_empty_point(self, geom): from django.contrib.gis.geos import Point + if isinstance(geom, Point) and geom.empty: if self.srid: # PostGIS uses POINT(NaN NaN) for WKB representation of empty # points. Use it for EWKB as it's a PostGIS specific format. # https://trac.osgeo.org/postgis/ticket/3181 - geom = Point(float('NaN'), float('NaN'), srid=geom.srid) + geom = Point(float("NaN"), float("NaN"), srid=geom.srid) else: - raise ValueError('Empty point is not representable in WKB.') + raise ValueError("Empty point is not representable in WKB.") return geom def write(self, geom): "Return the WKB representation of the given geometry." from django.contrib.gis.geos import Polygon + geom = self._handle_empty_point(geom) wkb = wkb_writer_write(self.ptr, geom.ptr, byref(c_size_t())) if self.geos_version < (3, 6, 1) and isinstance(geom, Polygon) and geom.empty: # Fix GEOS output for empty polygon. # See https://trac.osgeo.org/geos/ticket/680. - wkb = wkb[:-8] + b'\0' * 4 + wkb = wkb[:-8] + b"\0" * 4 return memoryview(wkb) def write_hex(self, geom): "Return the HEXEWKB representation of the given geometry." from django.contrib.gis.geos.polygon import Polygon + geom = self._handle_empty_point(geom) wkb = wkb_writer_write_hex(self.ptr, geom.ptr, byref(c_size_t())) if self.geos_version < (3, 6, 1) and isinstance(geom, Polygon) and geom.empty: - wkb = wkb[:-16] + b'0' * 8 + wkb = wkb[:-16] + b"0" * 8 return wkb # ### WKBWriter Properties ### @@ -259,7 +284,9 @@ class WKBWriter(IOBase): def _set_byteorder(self, order): if order not in (0, 1): - raise ValueError('Byte order parameter must be 0 (Big Endian) or 1 (Little Endian).') + raise ValueError( + "Byte order parameter must be 0 (Big Endian) or 1 (Little Endian)." + ) wkb_writer_set_byteorder(self.ptr, order) byteorder = property(_get_byteorder, _set_byteorder) @@ -272,7 +299,7 @@ class WKBWriter(IOBase): @outdim.setter def outdim(self, new_dim): if new_dim not in (2, 3): - raise ValueError('WKB output dimension must be 2 or 3') + raise ValueError("WKB output dimension must be 2 or 3") wkb_writer_set_outdim(self.ptr, new_dim) # Property for getting/setting the include srid flag. diff --git a/django/contrib/gis/geos/prototypes/misc.py b/django/contrib/gis/geos/prototypes/misc.py index 016c8328cb..fccd0ecc9e 100644 --- a/django/contrib/gis/geos/prototypes/misc.py +++ b/django/contrib/gis/geos/prototypes/misc.py @@ -8,7 +8,7 @@ from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOSFuncFactory from django.contrib.gis.geos.prototypes.errcheck import check_dbl, check_string from django.contrib.gis.geos.prototypes.geom import geos_char_p -__all__ = ['geos_area', 'geos_distance', 'geos_length', 'geos_isvalidreason'] +__all__ = ["geos_area", "geos_distance", "geos_length", "geos_isvalidreason"] class DblFromGeom(GEOSFuncFactory): @@ -16,6 +16,7 @@ class DblFromGeom(GEOSFuncFactory): Argument is a Geometry, return type is double that is passed in by reference as the last argument. """ + restype = c_int # Status code returned errcheck = staticmethod(check_dbl) @@ -23,9 +24,11 @@ class DblFromGeom(GEOSFuncFactory): # ### ctypes prototypes ### # Area, distance, and length prototypes. -geos_area = DblFromGeom('GEOSArea', argtypes=[GEOM_PTR, POINTER(c_double)]) -geos_distance = DblFromGeom('GEOSDistance', argtypes=[GEOM_PTR, GEOM_PTR, POINTER(c_double)]) -geos_length = DblFromGeom('GEOSLength', argtypes=[GEOM_PTR, POINTER(c_double)]) +geos_area = DblFromGeom("GEOSArea", argtypes=[GEOM_PTR, POINTER(c_double)]) +geos_distance = DblFromGeom( + "GEOSDistance", argtypes=[GEOM_PTR, GEOM_PTR, POINTER(c_double)] +) +geos_length = DblFromGeom("GEOSLength", argtypes=[GEOM_PTR, POINTER(c_double)]) geos_isvalidreason = GEOSFuncFactory( - 'GEOSisValidReason', restype=geos_char_p, errcheck=check_string, argtypes=[GEOM_PTR] + "GEOSisValidReason", restype=geos_char_p, errcheck=check_string, argtypes=[GEOM_PTR] ) diff --git a/django/contrib/gis/geos/prototypes/predicates.py b/django/contrib/gis/geos/prototypes/predicates.py index d4681c1689..d2e113a734 100644 --- a/django/contrib/gis/geos/prototypes/predicates.py +++ b/django/contrib/gis/geos/prototypes/predicates.py @@ -22,22 +22,26 @@ class BinaryPredicate(UnaryPredicate): # ## Unary Predicates ## -geos_hasz = UnaryPredicate('GEOSHasZ') -geos_isclosed = UnaryPredicate('GEOSisClosed') -geos_isempty = UnaryPredicate('GEOSisEmpty') -geos_isring = UnaryPredicate('GEOSisRing') -geos_issimple = UnaryPredicate('GEOSisSimple') -geos_isvalid = UnaryPredicate('GEOSisValid') +geos_hasz = UnaryPredicate("GEOSHasZ") +geos_isclosed = UnaryPredicate("GEOSisClosed") +geos_isempty = UnaryPredicate("GEOSisEmpty") +geos_isring = UnaryPredicate("GEOSisRing") +geos_issimple = UnaryPredicate("GEOSisSimple") +geos_isvalid = UnaryPredicate("GEOSisValid") # ## Binary Predicates ## -geos_contains = BinaryPredicate('GEOSContains') -geos_covers = BinaryPredicate('GEOSCovers') -geos_crosses = BinaryPredicate('GEOSCrosses') -geos_disjoint = BinaryPredicate('GEOSDisjoint') -geos_equals = BinaryPredicate('GEOSEquals') -geos_equalsexact = BinaryPredicate('GEOSEqualsExact', argtypes=[GEOM_PTR, GEOM_PTR, c_double]) -geos_intersects = BinaryPredicate('GEOSIntersects') -geos_overlaps = BinaryPredicate('GEOSOverlaps') -geos_relatepattern = BinaryPredicate('GEOSRelatePattern', argtypes=[GEOM_PTR, GEOM_PTR, c_char_p]) -geos_touches = BinaryPredicate('GEOSTouches') -geos_within = BinaryPredicate('GEOSWithin') +geos_contains = BinaryPredicate("GEOSContains") +geos_covers = BinaryPredicate("GEOSCovers") +geos_crosses = BinaryPredicate("GEOSCrosses") +geos_disjoint = BinaryPredicate("GEOSDisjoint") +geos_equals = BinaryPredicate("GEOSEquals") +geos_equalsexact = BinaryPredicate( + "GEOSEqualsExact", argtypes=[GEOM_PTR, GEOM_PTR, c_double] +) +geos_intersects = BinaryPredicate("GEOSIntersects") +geos_overlaps = BinaryPredicate("GEOSOverlaps") +geos_relatepattern = BinaryPredicate( + "GEOSRelatePattern", argtypes=[GEOM_PTR, GEOM_PTR, c_char_p] +) +geos_touches = BinaryPredicate("GEOSTouches") +geos_within = BinaryPredicate("GEOSWithin") diff --git a/django/contrib/gis/geos/prototypes/prepared.py b/django/contrib/gis/geos/prototypes/prepared.py index 52c31563d8..4fdfab30ed 100644 --- a/django/contrib/gis/geos/prototypes/prepared.py +++ b/django/contrib/gis/geos/prototypes/prepared.py @@ -1,13 +1,11 @@ from ctypes import c_byte -from django.contrib.gis.geos.libgeos import ( - GEOM_PTR, PREPGEOM_PTR, GEOSFuncFactory, -) +from django.contrib.gis.geos.libgeos import GEOM_PTR, PREPGEOM_PTR, GEOSFuncFactory from django.contrib.gis.geos.prototypes.errcheck import check_predicate # Prepared geometry constructor and destructors. -geos_prepare = GEOSFuncFactory('GEOSPrepare', argtypes=[GEOM_PTR], restype=PREPGEOM_PTR) -prepared_destroy = GEOSFuncFactory('GEOSPreparedGeom_destroy', argtypes=[PREPGEOM_PTR]) +geos_prepare = GEOSFuncFactory("GEOSPrepare", argtypes=[GEOM_PTR], restype=PREPGEOM_PTR) +prepared_destroy = GEOSFuncFactory("GEOSPreparedGeom_destroy", argtypes=[PREPGEOM_PTR]) # Prepared geometry binary predicate support. @@ -17,12 +15,12 @@ class PreparedPredicate(GEOSFuncFactory): errcheck = staticmethod(check_predicate) -prepared_contains = PreparedPredicate('GEOSPreparedContains') -prepared_contains_properly = PreparedPredicate('GEOSPreparedContainsProperly') -prepared_covers = PreparedPredicate('GEOSPreparedCovers') -prepared_crosses = PreparedPredicate('GEOSPreparedCrosses') -prepared_disjoint = PreparedPredicate('GEOSPreparedDisjoint') -prepared_intersects = PreparedPredicate('GEOSPreparedIntersects') -prepared_overlaps = PreparedPredicate('GEOSPreparedOverlaps') -prepared_touches = PreparedPredicate('GEOSPreparedTouches') -prepared_within = PreparedPredicate('GEOSPreparedWithin') +prepared_contains = PreparedPredicate("GEOSPreparedContains") +prepared_contains_properly = PreparedPredicate("GEOSPreparedContainsProperly") +prepared_covers = PreparedPredicate("GEOSPreparedCovers") +prepared_crosses = PreparedPredicate("GEOSPreparedCrosses") +prepared_disjoint = PreparedPredicate("GEOSPreparedDisjoint") +prepared_intersects = PreparedPredicate("GEOSPreparedIntersects") +prepared_overlaps = PreparedPredicate("GEOSPreparedOverlaps") +prepared_touches = PreparedPredicate("GEOSPreparedTouches") +prepared_within = PreparedPredicate("GEOSPreparedWithin") diff --git a/django/contrib/gis/geos/prototypes/threadsafe.py b/django/contrib/gis/geos/prototypes/threadsafe.py index 500f6a6019..d4f7ffb8ac 100644 --- a/django/contrib/gis/geos/prototypes/threadsafe.py +++ b/django/contrib/gis/geos/prototypes/threadsafe.py @@ -1,13 +1,12 @@ import threading from django.contrib.gis.geos.base import GEOSBase -from django.contrib.gis.geos.libgeos import ( - CONTEXT_PTR, error_h, lgeos, notice_h, -) +from django.contrib.gis.geos.libgeos import CONTEXT_PTR, error_h, lgeos, notice_h class GEOSContextHandle(GEOSBase): """Represent a GEOS context handle.""" + ptr_type = CONTEXT_PTR destructor = lgeos.finishGEOS_r @@ -31,10 +30,11 @@ class GEOSFunc: Serve as a wrapper for GEOS C Functions. Use thread-safe function variants when available. """ + def __init__(self, func_name): # GEOS thread-safe function signatures end with '_r' and take an # additional context handle parameter. - self.cfunc = getattr(lgeos, func_name + '_r') + self.cfunc = getattr(lgeos, func_name + "_r") # Create a reference to thread_context so it's not garbage-collected # before an attempt to call this object. self.thread_context = thread_context diff --git a/django/contrib/gis/geos/prototypes/topology.py b/django/contrib/gis/geos/prototypes/topology.py index 9ca0dce695..e61eae964a 100644 --- a/django/contrib/gis/geos/prototypes/topology.py +++ b/django/contrib/gis/geos/prototypes/topology.py @@ -6,7 +6,9 @@ from ctypes import c_double, c_int from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOSFuncFactory from django.contrib.gis.geos.prototypes.errcheck import ( - check_geom, check_minus_one, check_string, + check_geom, + check_minus_one, + check_string, ) from django.contrib.gis.geos.prototypes.geom import geos_char_p @@ -19,35 +21,52 @@ class Topology(GEOSFuncFactory): # Topology Routines -geos_boundary = Topology('GEOSBoundary') -geos_buffer = Topology('GEOSBuffer', argtypes=[GEOM_PTR, c_double, c_int]) -geos_bufferwithstyle = Topology('GEOSBufferWithStyle', argtypes=[GEOM_PTR, c_double, c_int, c_int, c_int, c_double]) -geos_centroid = Topology('GEOSGetCentroid') -geos_convexhull = Topology('GEOSConvexHull') -geos_difference = Topology('GEOSDifference', argtypes=[GEOM_PTR, GEOM_PTR]) -geos_envelope = Topology('GEOSEnvelope') -geos_intersection = Topology('GEOSIntersection', argtypes=[GEOM_PTR, GEOM_PTR]) -geos_linemerge = Topology('GEOSLineMerge') -geos_pointonsurface = Topology('GEOSPointOnSurface') -geos_preservesimplify = Topology('GEOSTopologyPreserveSimplify', argtypes=[GEOM_PTR, c_double]) -geos_simplify = Topology('GEOSSimplify', argtypes=[GEOM_PTR, c_double]) -geos_symdifference = Topology('GEOSSymDifference', argtypes=[GEOM_PTR, GEOM_PTR]) -geos_union = Topology('GEOSUnion', argtypes=[GEOM_PTR, GEOM_PTR]) - -geos_unary_union = GEOSFuncFactory('GEOSUnaryUnion', argtypes=[GEOM_PTR], restype=GEOM_PTR) +geos_boundary = Topology("GEOSBoundary") +geos_buffer = Topology("GEOSBuffer", argtypes=[GEOM_PTR, c_double, c_int]) +geos_bufferwithstyle = Topology( + "GEOSBufferWithStyle", argtypes=[GEOM_PTR, c_double, c_int, c_int, c_int, c_double] +) +geos_centroid = Topology("GEOSGetCentroid") +geos_convexhull = Topology("GEOSConvexHull") +geos_difference = Topology("GEOSDifference", argtypes=[GEOM_PTR, GEOM_PTR]) +geos_envelope = Topology("GEOSEnvelope") +geos_intersection = Topology("GEOSIntersection", argtypes=[GEOM_PTR, GEOM_PTR]) +geos_linemerge = Topology("GEOSLineMerge") +geos_pointonsurface = Topology("GEOSPointOnSurface") +geos_preservesimplify = Topology( + "GEOSTopologyPreserveSimplify", argtypes=[GEOM_PTR, c_double] +) +geos_simplify = Topology("GEOSSimplify", argtypes=[GEOM_PTR, c_double]) +geos_symdifference = Topology("GEOSSymDifference", argtypes=[GEOM_PTR, GEOM_PTR]) +geos_union = Topology("GEOSUnion", argtypes=[GEOM_PTR, GEOM_PTR]) + +geos_unary_union = GEOSFuncFactory( + "GEOSUnaryUnion", argtypes=[GEOM_PTR], restype=GEOM_PTR +) # GEOSRelate returns a string, not a geometry. geos_relate = GEOSFuncFactory( - 'GEOSRelate', argtypes=[GEOM_PTR, GEOM_PTR], restype=geos_char_p, errcheck=check_string + "GEOSRelate", + argtypes=[GEOM_PTR, GEOM_PTR], + restype=geos_char_p, + errcheck=check_string, ) # Linear referencing routines geos_project = GEOSFuncFactory( - 'GEOSProject', argtypes=[GEOM_PTR, GEOM_PTR], restype=c_double, errcheck=check_minus_one + "GEOSProject", + argtypes=[GEOM_PTR, GEOM_PTR], + restype=c_double, + errcheck=check_minus_one, ) -geos_interpolate = Topology('GEOSInterpolate', argtypes=[GEOM_PTR, c_double]) +geos_interpolate = Topology("GEOSInterpolate", argtypes=[GEOM_PTR, c_double]) geos_project_normalized = GEOSFuncFactory( - 'GEOSProjectNormalized', argtypes=[GEOM_PTR, GEOM_PTR], restype=c_double, errcheck=check_minus_one + "GEOSProjectNormalized", + argtypes=[GEOM_PTR, GEOM_PTR], + restype=c_double, + errcheck=check_minus_one, +) +geos_interpolate_normalized = Topology( + "GEOSInterpolateNormalized", argtypes=[GEOM_PTR, c_double] ) -geos_interpolate_normalized = Topology('GEOSInterpolateNormalized', argtypes=[GEOM_PTR, c_double]) diff --git a/django/contrib/gis/management/commands/inspectdb.py b/django/contrib/gis/management/commands/inspectdb.py index 8c6f62932a..1bcc1a0793 100644 --- a/django/contrib/gis/management/commands/inspectdb.py +++ b/django/contrib/gis/management/commands/inspectdb.py @@ -1,16 +1,18 @@ -from django.core.management.commands.inspectdb import ( - Command as InspectDBCommand, -) +from django.core.management.commands.inspectdb import Command as InspectDBCommand class Command(InspectDBCommand): - db_module = 'django.contrib.gis.db' + db_module = "django.contrib.gis.db" def get_field_type(self, connection, table_name, row): - field_type, field_params, field_notes = super().get_field_type(connection, table_name, row) - if field_type == 'GeometryField': + field_type, field_params, field_notes = super().get_field_type( + connection, table_name, row + ) + if field_type == "GeometryField": # Getting a more specific field type and any additional parameters # from the `get_geometry_type` routine for the spatial backend. - field_type, geo_params = connection.introspection.get_geometry_type(table_name, row) + field_type, geo_params = connection.introspection.get_geometry_type( + table_name, row + ) field_params.update(geo_params) return field_type, field_params, field_notes diff --git a/django/contrib/gis/management/commands/ogrinspect.py b/django/contrib/gis/management/commands/ogrinspect.py index 12c1db392f..133cec3e60 100644 --- a/django/contrib/gis/management/commands/ogrinspect.py +++ b/django/contrib/gis/management/commands/ogrinspect.py @@ -10,6 +10,7 @@ class LayerOptionAction(argparse.Action): Custom argparse action for the `ogrinspect` `layer_key` keyword option which may be an integer or a string. """ + def __call__(self, parser, namespace, value, option_string=None): try: setattr(namespace, self.dest, int(value)) @@ -23,80 +24,92 @@ class ListOptionAction(argparse.Action): a string list. If the string is 'True'/'true' then the option value will be a boolean instead. """ + def __call__(self, parser, namespace, value, option_string=None): - if value.lower() == 'true': + if value.lower() == "true": setattr(namespace, self.dest, True) else: - setattr(namespace, self.dest, value.split(',')) + setattr(namespace, self.dest, value.split(",")) class Command(BaseCommand): help = ( - 'Inspects the given OGR-compatible data source (e.g., a shapefile) and outputs\n' - 'a GeoDjango model with the given model name. For example:\n' - ' ./manage.py ogrinspect zipcode.shp Zipcode' + "Inspects the given OGR-compatible data source (e.g., a shapefile) and outputs\n" + "a GeoDjango model with the given model name. For example:\n" + " ./manage.py ogrinspect zipcode.shp Zipcode" ) requires_system_checks = [] def add_arguments(self, parser): - parser.add_argument('data_source', help='Path to the data source.') - parser.add_argument('model_name', help='Name of the model to create.') + parser.add_argument("data_source", help="Path to the data source.") + parser.add_argument("model_name", help="Name of the model to create.") parser.add_argument( - '--blank', - action=ListOptionAction, default=False, - help='Use a comma separated list of OGR field names to add ' - 'the `blank=True` option to the field definition. Set to `true` ' - 'to apply to all applicable fields.', + "--blank", + action=ListOptionAction, + default=False, + help="Use a comma separated list of OGR field names to add " + "the `blank=True` option to the field definition. Set to `true` " + "to apply to all applicable fields.", ) parser.add_argument( - '--decimal', - action=ListOptionAction, default=False, - help='Use a comma separated list of OGR float fields to ' - 'generate `DecimalField` instead of the default ' - '`FloatField`. Set to `true` to apply to all OGR float fields.', + "--decimal", + action=ListOptionAction, + default=False, + help="Use a comma separated list of OGR float fields to " + "generate `DecimalField` instead of the default " + "`FloatField`. Set to `true` to apply to all OGR float fields.", ) parser.add_argument( - '--geom-name', default='geom', - help='Specifies the model name for the Geometry Field (defaults to `geom`)' + "--geom-name", + default="geom", + help="Specifies the model name for the Geometry Field (defaults to `geom`)", ) parser.add_argument( - '--layer', dest='layer_key', - action=LayerOptionAction, default=0, - help='The key for specifying which layer in the OGR data ' - 'source to use. Defaults to 0 (the first layer). May be ' - 'an integer or a string identifier for the layer.', + "--layer", + dest="layer_key", + action=LayerOptionAction, + default=0, + help="The key for specifying which layer in the OGR data " + "source to use. Defaults to 0 (the first layer). May be " + "an integer or a string identifier for the layer.", ) parser.add_argument( - '--multi-geom', action='store_true', - help='Treat the geometry in the data source as a geometry collection.', + "--multi-geom", + action="store_true", + help="Treat the geometry in the data source as a geometry collection.", ) parser.add_argument( - '--name-field', - help='Specifies a field name to return for the __str__() method.', + "--name-field", + help="Specifies a field name to return for the __str__() method.", ) parser.add_argument( - '--no-imports', action='store_false', dest='imports', - help='Do not include `from django.contrib.gis.db import models` statement.', + "--no-imports", + action="store_false", + dest="imports", + help="Do not include `from django.contrib.gis.db import models` statement.", ) parser.add_argument( - '--null', action=ListOptionAction, default=False, - help='Use a comma separated list of OGR field names to add ' - 'the `null=True` option to the field definition. Set to `true` ' - 'to apply to all applicable fields.', + "--null", + action=ListOptionAction, + default=False, + help="Use a comma separated list of OGR field names to add " + "the `null=True` option to the field definition. Set to `true` " + "to apply to all applicable fields.", ) parser.add_argument( - '--srid', - help='The SRID to use for the Geometry Field. If it can be ' - 'determined, the SRID of the data source is used.', + "--srid", + help="The SRID to use for the Geometry Field. If it can be " + "determined, the SRID of the data source is used.", ) parser.add_argument( - '--mapping', action='store_true', - help='Generate mapping dictionary for use with `LayerMapping`.', + "--mapping", + action="store_true", + help="Generate mapping dictionary for use with `LayerMapping`.", ) def handle(self, *args, **options): - data_source, model_name = options.pop('data_source'), options.pop('model_name') + data_source, model_name = options.pop("data_source"), options.pop("model_name") # Getting the OGR DataSource from the string parameter. try: @@ -109,26 +122,43 @@ class Command(BaseCommand): from django.contrib.gis.utils.ogrinspect import _ogrinspect, mapping # Filter options to params accepted by `_ogrinspect` - ogr_options = {k: v for k, v in options.items() - if k in get_func_args(_ogrinspect) and v is not None} + ogr_options = { + k: v + for k, v in options.items() + if k in get_func_args(_ogrinspect) and v is not None + } output = [s for s in _ogrinspect(ds, model_name, **ogr_options)] - if options['mapping']: + if options["mapping"]: # Constructing the keyword arguments for `mapping`, and # calling it on the data source. kwargs = { - 'geom_name': options['geom_name'], - 'layer_key': options['layer_key'], - 'multi_geom': options['multi_geom'], + "geom_name": options["geom_name"], + "layer_key": options["layer_key"], + "multi_geom": options["multi_geom"], } mapping_dict = mapping(ds, **kwargs) # This extra legwork is so that the dictionary definition comes # out in the same order as the fields in the model definition. rev_mapping = {v: k for k, v in mapping_dict.items()} - output.extend(['', '', '# Auto-generated `LayerMapping` dictionary for %s model' % model_name, - '%s_mapping = {' % model_name.lower()]) - output.extend(" '%s': '%s'," % ( - rev_mapping[ogr_fld], ogr_fld) for ogr_fld in ds[options['layer_key']].fields + output.extend( + [ + "", + "", + "# Auto-generated `LayerMapping` dictionary for %s model" + % model_name, + "%s_mapping = {" % model_name.lower(), + ] + ) + output.extend( + " '%s': '%s'," % (rev_mapping[ogr_fld], ogr_fld) + for ogr_fld in ds[options["layer_key"]].fields + ) + output.extend( + [ + " '%s': '%s'," + % (options["geom_name"], mapping_dict[options["geom_name"]]), + "}", + ] ) - output.extend([" '%s': '%s'," % (options['geom_name'], mapping_dict[options['geom_name']]), '}']) - return '\n'.join(output) + return "\n".join(output) diff --git a/django/contrib/gis/measure.py b/django/contrib/gis/measure.py index 7e571f14dd..640f0e0068 100644 --- a/django/contrib/gis/measure.py +++ b/django/contrib/gis/measure.py @@ -38,7 +38,7 @@ and Geoff Biggs' PhD work on dimensioned units for robotics. from decimal import Decimal from functools import total_ordering -__all__ = ['A', 'Area', 'D', 'Distance'] +__all__ = ["A", "Area", "D", "Distance"] NUMERIC_TYPES = (int, float, Decimal) AREA_PREFIX = "sq_" @@ -73,13 +73,17 @@ class MeasureBase: if name in self.UNITS: return self.standard / self.UNITS[name] else: - raise AttributeError('Unknown unit type: %s' % name) + raise AttributeError("Unknown unit type: %s" % name) def __repr__(self): - return '%s(%s=%s)' % (pretty_name(self), self._default_unit, getattr(self, self._default_unit)) + return "%s(%s=%s)" % ( + pretty_name(self), + self._default_unit, + getattr(self, self._default_unit), + ) def __str__(self): - return '%s %s' % (getattr(self, self._default_unit), self._default_unit) + return "%s %s" % (getattr(self, self._default_unit), self._default_unit) # **** Comparison methods **** @@ -104,49 +108,65 @@ class MeasureBase: if isinstance(other, self.__class__): return self.__class__( default_unit=self._default_unit, - **{self.STANDARD_UNIT: (self.standard + other.standard)} + **{self.STANDARD_UNIT: (self.standard + other.standard)}, ) else: - raise TypeError('%(class)s must be added with %(class)s' % {"class": pretty_name(self)}) + raise TypeError( + "%(class)s must be added with %(class)s" % {"class": pretty_name(self)} + ) def __iadd__(self, other): if isinstance(other, self.__class__): self.standard += other.standard return self else: - raise TypeError('%(class)s must be added with %(class)s' % {"class": pretty_name(self)}) + raise TypeError( + "%(class)s must be added with %(class)s" % {"class": pretty_name(self)} + ) def __sub__(self, other): if isinstance(other, self.__class__): return self.__class__( default_unit=self._default_unit, - **{self.STANDARD_UNIT: (self.standard - other.standard)} + **{self.STANDARD_UNIT: (self.standard - other.standard)}, ) else: - raise TypeError('%(class)s must be subtracted from %(class)s' % {"class": pretty_name(self)}) + raise TypeError( + "%(class)s must be subtracted from %(class)s" + % {"class": pretty_name(self)} + ) def __isub__(self, other): if isinstance(other, self.__class__): self.standard -= other.standard return self else: - raise TypeError('%(class)s must be subtracted from %(class)s' % {"class": pretty_name(self)}) + raise TypeError( + "%(class)s must be subtracted from %(class)s" + % {"class": pretty_name(self)} + ) def __mul__(self, other): if isinstance(other, NUMERIC_TYPES): return self.__class__( default_unit=self._default_unit, - **{self.STANDARD_UNIT: (self.standard * other)} + **{self.STANDARD_UNIT: (self.standard * other)}, ) else: - raise TypeError('%(class)s must be multiplied with number' % {"class": pretty_name(self)}) + raise TypeError( + "%(class)s must be multiplied with number" + % {"class": pretty_name(self)} + ) def __imul__(self, other): if isinstance(other, NUMERIC_TYPES): self.standard *= float(other) return self else: - raise TypeError('%(class)s must be multiplied with number' % {"class": pretty_name(self)}) + raise TypeError( + "%(class)s must be multiplied with number" + % {"class": pretty_name(self)} + ) def __rmul__(self, other): return self * other @@ -157,17 +177,22 @@ class MeasureBase: if isinstance(other, NUMERIC_TYPES): return self.__class__( default_unit=self._default_unit, - **{self.STANDARD_UNIT: (self.standard / other)} + **{self.STANDARD_UNIT: (self.standard / other)}, ) else: - raise TypeError('%(class)s must be divided with number or %(class)s' % {"class": pretty_name(self)}) + raise TypeError( + "%(class)s must be divided with number or %(class)s" + % {"class": pretty_name(self)} + ) def __itruediv__(self, other): if isinstance(other, NUMERIC_TYPES): self.standard /= float(other) return self else: - raise TypeError('%(class)s must be divided with number' % {"class": pretty_name(self)}) + raise TypeError( + "%(class)s must be divided with number" % {"class": pretty_name(self)} + ) def __bool__(self): return bool(self.standard) @@ -199,7 +224,7 @@ class MeasureBase: val += self.UNITS[u] * value default_unit = u else: - raise AttributeError('Unknown unit type: %s' % unit) + raise AttributeError("Unknown unit type: %s" % unit) return val, default_unit @classmethod @@ -217,85 +242,87 @@ class MeasureBase: elif lower in cls.LALIAS: return cls.LALIAS[lower] else: - raise Exception('Could not find a unit keyword associated with "%s"' % unit_str) + raise Exception( + 'Could not find a unit keyword associated with "%s"' % unit_str + ) class Distance(MeasureBase): STANDARD_UNIT = "m" UNITS = { - 'chain': 20.1168, - 'chain_benoit': 20.116782, - 'chain_sears': 20.1167645, - 'british_chain_benoit': 20.1167824944, - 'british_chain_sears': 20.1167651216, - 'british_chain_sears_truncated': 20.116756, - 'cm': 0.01, - 'british_ft': 0.304799471539, - 'british_yd': 0.914398414616, - 'clarke_ft': 0.3047972654, - 'clarke_link': 0.201166195164, - 'fathom': 1.8288, - 'ft': 0.3048, - 'furlong': 201.168, - 'german_m': 1.0000135965, - 'gold_coast_ft': 0.304799710181508, - 'indian_yd': 0.914398530744, - 'inch': 0.0254, - 'km': 1000.0, - 'link': 0.201168, - 'link_benoit': 0.20116782, - 'link_sears': 0.20116765, - 'm': 1.0, - 'mi': 1609.344, - 'mm': 0.001, - 'nm': 1852.0, - 'nm_uk': 1853.184, - 'rod': 5.0292, - 'sears_yd': 0.91439841, - 'survey_ft': 0.304800609601, - 'um': 0.000001, - 'yd': 0.9144, + "chain": 20.1168, + "chain_benoit": 20.116782, + "chain_sears": 20.1167645, + "british_chain_benoit": 20.1167824944, + "british_chain_sears": 20.1167651216, + "british_chain_sears_truncated": 20.116756, + "cm": 0.01, + "british_ft": 0.304799471539, + "british_yd": 0.914398414616, + "clarke_ft": 0.3047972654, + "clarke_link": 0.201166195164, + "fathom": 1.8288, + "ft": 0.3048, + "furlong": 201.168, + "german_m": 1.0000135965, + "gold_coast_ft": 0.304799710181508, + "indian_yd": 0.914398530744, + "inch": 0.0254, + "km": 1000.0, + "link": 0.201168, + "link_benoit": 0.20116782, + "link_sears": 0.20116765, + "m": 1.0, + "mi": 1609.344, + "mm": 0.001, + "nm": 1852.0, + "nm_uk": 1853.184, + "rod": 5.0292, + "sears_yd": 0.91439841, + "survey_ft": 0.304800609601, + "um": 0.000001, + "yd": 0.9144, } # Unit aliases for `UNIT` terms encountered in Spatial Reference WKT. ALIAS = { - 'centimeter': 'cm', - 'foot': 'ft', - 'inches': 'inch', - 'kilometer': 'km', - 'kilometre': 'km', - 'meter': 'm', - 'metre': 'm', - 'micrometer': 'um', - 'micrometre': 'um', - 'millimeter': 'mm', - 'millimetre': 'mm', - 'mile': 'mi', - 'yard': 'yd', - 'British chain (Benoit 1895 B)': 'british_chain_benoit', - 'British chain (Sears 1922)': 'british_chain_sears', - 'British chain (Sears 1922 truncated)': 'british_chain_sears_truncated', - 'British foot (Sears 1922)': 'british_ft', - 'British foot': 'british_ft', - 'British yard (Sears 1922)': 'british_yd', - 'British yard': 'british_yd', - "Clarke's Foot": 'clarke_ft', - "Clarke's link": 'clarke_link', - 'Chain (Benoit)': 'chain_benoit', - 'Chain (Sears)': 'chain_sears', - 'Foot (International)': 'ft', - 'Furrow Long': 'furlong', - 'German legal metre': 'german_m', - 'Gold Coast foot': 'gold_coast_ft', - 'Indian yard': 'indian_yd', - 'Link (Benoit)': 'link_benoit', - 'Link (Sears)': 'link_sears', - 'Nautical Mile': 'nm', - 'Nautical Mile (UK)': 'nm_uk', - 'US survey foot': 'survey_ft', - 'U.S. Foot': 'survey_ft', - 'Yard (Indian)': 'indian_yd', - 'Yard (Sears)': 'sears_yd' + "centimeter": "cm", + "foot": "ft", + "inches": "inch", + "kilometer": "km", + "kilometre": "km", + "meter": "m", + "metre": "m", + "micrometer": "um", + "micrometre": "um", + "millimeter": "mm", + "millimetre": "mm", + "mile": "mi", + "yard": "yd", + "British chain (Benoit 1895 B)": "british_chain_benoit", + "British chain (Sears 1922)": "british_chain_sears", + "British chain (Sears 1922 truncated)": "british_chain_sears_truncated", + "British foot (Sears 1922)": "british_ft", + "British foot": "british_ft", + "British yard (Sears 1922)": "british_yd", + "British yard": "british_yd", + "Clarke's Foot": "clarke_ft", + "Clarke's link": "clarke_link", + "Chain (Benoit)": "chain_benoit", + "Chain (Sears)": "chain_sears", + "Foot (International)": "ft", + "Furrow Long": "furlong", + "German legal metre": "german_m", + "Gold Coast foot": "gold_coast_ft", + "Indian yard": "indian_yd", + "Link (Benoit)": "link_benoit", + "Link (Sears)": "link_sears", + "Nautical Mile": "nm", + "Nautical Mile (UK)": "nm_uk", + "US survey foot": "survey_ft", + "U.S. Foot": "survey_ft", + "Yard (Indian)": "indian_yd", + "Yard (Sears)": "sears_yd", } LALIAS = {k.lower(): v for k, v in ALIAS.items()} @@ -303,34 +330,39 @@ class Distance(MeasureBase): if isinstance(other, self.__class__): return Area( default_unit=AREA_PREFIX + self._default_unit, - **{AREA_PREFIX + self.STANDARD_UNIT: (self.standard * other.standard)} + **{AREA_PREFIX + self.STANDARD_UNIT: (self.standard * other.standard)}, ) elif isinstance(other, NUMERIC_TYPES): return self.__class__( default_unit=self._default_unit, - **{self.STANDARD_UNIT: (self.standard * other)} + **{self.STANDARD_UNIT: (self.standard * other)}, ) else: - raise TypeError('%(distance)s must be multiplied with number or %(distance)s' % { - "distance": pretty_name(self.__class__), - }) + raise TypeError( + "%(distance)s must be multiplied with number or %(distance)s" + % { + "distance": pretty_name(self.__class__), + } + ) class Area(MeasureBase): STANDARD_UNIT = AREA_PREFIX + Distance.STANDARD_UNIT # Getting the square units values and the alias dictionary. - UNITS = {'%s%s' % (AREA_PREFIX, k): v ** 2 for k, v in Distance.UNITS.items()} - ALIAS = {k: '%s%s' % (AREA_PREFIX, v) for k, v in Distance.ALIAS.items()} + UNITS = {"%s%s" % (AREA_PREFIX, k): v**2 for k, v in Distance.UNITS.items()} + ALIAS = {k: "%s%s" % (AREA_PREFIX, v) for k, v in Distance.ALIAS.items()} LALIAS = {k.lower(): v for k, v in ALIAS.items()} def __truediv__(self, other): if isinstance(other, NUMERIC_TYPES): return self.__class__( default_unit=self._default_unit, - **{self.STANDARD_UNIT: (self.standard / other)} + **{self.STANDARD_UNIT: (self.standard / other)}, ) else: - raise TypeError('%(class)s must be divided by a number' % {"class": pretty_name(self)}) + raise TypeError( + "%(class)s must be divided by a number" % {"class": pretty_name(self)} + ) # Shortcuts diff --git a/django/contrib/gis/ptr.py b/django/contrib/gis/ptr.py index a5a117a19a..6754701613 100644 --- a/django/contrib/gis/ptr.py +++ b/django/contrib/gis/ptr.py @@ -6,6 +6,7 @@ class CPointerBase: Base class for objects that have a pointer access property that controls access to the underlying C pointer. """ + _ptr = None # Initially the pointer is NULL. ptr_type = c_void_p destructor = None @@ -17,14 +18,16 @@ class CPointerBase: # aren't passed to routines -- that's very bad. if self._ptr: return self._ptr - raise self.null_ptr_exception_class('NULL %s pointer encountered.' % self.__class__.__name__) + raise self.null_ptr_exception_class( + "NULL %s pointer encountered." % self.__class__.__name__ + ) @ptr.setter def ptr(self, ptr): # Only allow the pointer to be set with pointers of the compatible # type or None (NULL). if not (ptr is None or isinstance(ptr, self.ptr_type)): - raise TypeError('Incompatible pointer type: %s.' % type(ptr)) + raise TypeError("Incompatible pointer type: %s." % type(ptr)) self._ptr = ptr def __del__(self): diff --git a/django/contrib/gis/serializers/geojson.py b/django/contrib/gis/serializers/geojson.py index 0e4f744774..f90d3544e7 100644 --- a/django/contrib/gis/serializers/geojson.py +++ b/django/contrib/gis/serializers/geojson.py @@ -9,12 +9,16 @@ class Serializer(JSONSerializer): """ Convert a queryset to GeoJSON, http://geojson.org/ """ + def _init_options(self): super()._init_options() - self.geometry_field = self.json_kwargs.pop('geometry_field', None) - self.srid = self.json_kwargs.pop('srid', 4326) - if (self.selected_fields is not None and self.geometry_field is not None and - self.geometry_field not in self.selected_fields): + self.geometry_field = self.json_kwargs.pop("geometry_field", None) + self.srid = self.json_kwargs.pop("srid", 4326) + if ( + self.selected_fields is not None + and self.geometry_field is not None + and self.geometry_field not in self.selected_fields + ): self.selected_fields = [*self.selected_fields, self.geometry_field] def start_serialization(self): @@ -22,10 +26,11 @@ class Serializer(JSONSerializer): self._cts = {} # cache of CoordTransform's self.stream.write( '{"type": "FeatureCollection", "crs": {"type": "name", "properties": {"name": "EPSG:%d"}},' - ' "features": [' % self.srid) + ' "features": [' % self.srid + ) def end_serialization(self): - self.stream.write(']}') + self.stream.write("]}") def start_object(self, obj): super().start_object(obj) @@ -33,7 +38,7 @@ class Serializer(JSONSerializer): if self.geometry_field is None: # Find the first declared geometry field for field in obj._meta.fields: - if hasattr(field, 'geom_type'): + if hasattr(field, "geom_type"): self.geometry_field = field.name break @@ -42,15 +47,18 @@ class Serializer(JSONSerializer): "type": "Feature", "properties": self._current, } - if ((self.selected_fields is None or 'pk' in self.selected_fields) and - 'pk' not in data["properties"]): + if ( + self.selected_fields is None or "pk" in self.selected_fields + ) and "pk" not in data["properties"]: data["properties"]["pk"] = obj._meta.pk.value_to_string(obj) if self._geometry: if self._geometry.srid != self.srid: # If needed, transform the geometry in the srid of the global geojson srid if self._geometry.srid not in self._cts: srs = SpatialReference(self.srid) - self._cts[self._geometry.srid] = CoordTransform(self._geometry.srs, srs) + self._cts[self._geometry.srid] = CoordTransform( + self._geometry.srs, srs + ) self._geometry.transform(self._cts[self._geometry.srid]) data["geometry"] = json.loads(self._geometry.geojson) else: diff --git a/django/contrib/gis/shortcuts.py b/django/contrib/gis/shortcuts.py index aa68140b62..33ff2acb06 100644 --- a/django/contrib/gis/shortcuts.py +++ b/django/contrib/gis/shortcuts.py @@ -15,8 +15,8 @@ except ImportError: def compress_kml(kml): "Return compressed KMZ from the given KML string." kmz = BytesIO() - with zipfile.ZipFile(kmz, 'a', zipfile.ZIP_DEFLATED) as zf: - zf.writestr('doc.kml', kml.encode(settings.DEFAULT_CHARSET)) + with zipfile.ZipFile(kmz, "a", zipfile.ZIP_DEFLATED) as zf: + zf.writestr("doc.kml", kml.encode(settings.DEFAULT_CHARSET)) kmz.seek(0) return kmz.read() @@ -25,7 +25,7 @@ def render_to_kml(*args, **kwargs): "Render the response as KML (using the correct MIME type)." return HttpResponse( loader.render_to_string(*args, **kwargs), - content_type='application/vnd.google-earth.kml+xml', + content_type="application/vnd.google-earth.kml+xml", ) @@ -36,5 +36,5 @@ def render_to_kmz(*args, **kwargs): """ return HttpResponse( compress_kml(loader.render_to_string(*args, **kwargs)), - content_type='application/vnd.google-earth.kmz', + content_type="application/vnd.google-earth.kmz", ) diff --git a/django/contrib/gis/sitemaps/__init__.py b/django/contrib/gis/sitemaps/__init__.py index 7337e65a9c..3654bfc80b 100644 --- a/django/contrib/gis/sitemaps/__init__.py +++ b/django/contrib/gis/sitemaps/__init__.py @@ -1,4 +1,4 @@ # Geo-enabled Sitemap classes. from django.contrib.gis.sitemaps.kml import KMLSitemap, KMZSitemap -__all__ = ['KMLSitemap', 'KMZSitemap'] +__all__ = ["KMLSitemap", "KMZSitemap"] diff --git a/django/contrib/gis/sitemaps/kml.py b/django/contrib/gis/sitemaps/kml.py index 103dc20408..5ec089d6f5 100644 --- a/django/contrib/gis/sitemaps/kml.py +++ b/django/contrib/gis/sitemaps/kml.py @@ -9,7 +9,8 @@ class KMLSitemap(Sitemap): """ A minimal hook to produce KML sitemaps. """ - geo_format = 'kml' + + geo_format = "kml" def __init__(self, locations=None): # If no locations specified, then we try to build for @@ -31,15 +32,21 @@ class KMLSitemap(Sitemap): if isinstance(source, models.base.ModelBase): for field in source._meta.fields: if isinstance(field, GeometryField): - kml_sources.append((source._meta.app_label, - source._meta.model_name, - field.name)) + kml_sources.append( + ( + source._meta.app_label, + source._meta.model_name, + field.name, + ) + ) elif isinstance(source, (list, tuple)): if len(source) != 3: - raise ValueError('Must specify a 3-tuple of (app_label, module_name, field_name).') + raise ValueError( + "Must specify a 3-tuple of (app_label, module_name, field_name)." + ) kml_sources.append(source) else: - raise TypeError('KML Sources must be a model or a 3-tuple.') + raise TypeError("KML Sources must be a model or a 3-tuple.") return kml_sources def get_urls(self, page=1, site=None, protocol=None): @@ -49,7 +56,7 @@ class KMLSitemap(Sitemap): """ urls = Sitemap.get_urls(self, page=page, site=site, protocol=protocol) for url in urls: - url['geo_format'] = self.geo_format + url["geo_format"] = self.geo_format return urls def items(self): @@ -57,14 +64,14 @@ class KMLSitemap(Sitemap): def location(self, obj): return reverse( - 'django.contrib.gis.sitemaps.views.%s' % self.geo_format, + "django.contrib.gis.sitemaps.views.%s" % self.geo_format, kwargs={ - 'label': obj[0], - 'model': obj[1], - 'field_name': obj[2], + "label": obj[0], + "model": obj[1], + "field_name": obj[2], }, ) class KMZSitemap(KMLSitemap): - geo_format = 'kmz' + geo_format = "kmz" diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py index be91d929f6..17eb54e60c 100644 --- a/django/contrib/gis/sitemaps/views.py +++ b/django/contrib/gis/sitemaps/views.py @@ -17,7 +17,10 @@ def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB try: klass = apps.get_model(label, model) except LookupError: - raise Http404('You must supply a valid app label and module name. Got "%s.%s"' % (label, model)) + raise Http404( + 'You must supply a valid app label and module name. Got "%s.%s"' + % (label, model) + ) if field_name: try: @@ -25,7 +28,7 @@ def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB if not isinstance(field, GeometryField): raise FieldDoesNotExist except FieldDoesNotExist: - raise Http404('Invalid geometry field.') + raise Http404("Invalid geometry field.") connection = connections[using] @@ -38,8 +41,9 @@ def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB placemarks = [] if connection.features.has_Transform_function: qs = klass._default_manager.using(using).annotate( - **{'%s_4326' % field_name: Transform(field_name, 4326)}) - field_name += '_4326' + **{"%s_4326" % field_name: Transform(field_name, 4326)} + ) + field_name += "_4326" else: qs = klass._default_manager.using(using).all() for mod in qs: @@ -51,7 +55,7 @@ def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB render = render_to_kmz else: render = render_to_kml - return render('gis/kml/placemarks.kml', {'places': placemarks}) + return render("gis/kml/placemarks.kml", {"places": placemarks}) def kmz(request, label, model, field_name=None, using=DEFAULT_DB_ALIAS): diff --git a/django/contrib/gis/utils/__init__.py b/django/contrib/gis/utils/__init__.py index c195ded932..26334fb6a4 100644 --- a/django/contrib/gis/utils/__init__.py +++ b/django/contrib/gis/utils/__init__.py @@ -10,7 +10,8 @@ try: # LayerMapping requires DJANGO_SETTINGS_MODULE to be set, # and ImproperlyConfigured is raised if that's not the case. from django.contrib.gis.utils.layermapping import ( # NOQA - LayerMapError, LayerMapping, + LayerMapError, + LayerMapping, ) except ImproperlyConfigured: pass diff --git a/django/contrib/gis/utils/layermapping.py b/django/contrib/gis/utils/layermapping.py index 3c78085489..bad4c05fb8 100644 --- a/django/contrib/gis/utils/layermapping.py +++ b/django/contrib/gis/utils/layermapping.py @@ -7,16 +7,26 @@ https://docs.djangoproject.com/en/dev/ref/contrib/gis/layermapping/ """ import sys -from decimal import Decimal, InvalidOperation as DecimalInvalidOperation +from decimal import Decimal +from decimal import InvalidOperation as DecimalInvalidOperation from pathlib import Path from django.contrib.gis.db.models import GeometryField from django.contrib.gis.gdal import ( - CoordTransform, DataSource, GDALException, OGRGeometry, OGRGeomType, + CoordTransform, + DataSource, + GDALException, + OGRGeometry, + OGRGeomType, SpatialReference, ) from django.contrib.gis.gdal.field import ( - OFTDate, OFTDateTime, OFTInteger, OFTInteger64, OFTReal, OFTString, + OFTDate, + OFTDateTime, + OFTInteger, + OFTInteger64, + OFTReal, + OFTString, OFTTime, ) from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist @@ -50,12 +60,12 @@ class LayerMapping: # Acceptable 'base' types for a multi-geometry type. MULTI_TYPES = { - 1: OGRGeomType('MultiPoint'), - 2: OGRGeomType('MultiLineString'), - 3: OGRGeomType('MultiPolygon'), - OGRGeomType('Point25D').num: OGRGeomType('MultiPoint25D'), - OGRGeomType('LineString25D').num: OGRGeomType('MultiLineString25D'), - OGRGeomType('Polygon25D').num: OGRGeomType('MultiPolygon25D'), + 1: OGRGeomType("MultiPoint"), + 2: OGRGeomType("MultiLineString"), + 3: OGRGeomType("MultiPolygon"), + OGRGeomType("Point25D").num: OGRGeomType("MultiPoint25D"), + OGRGeomType("LineString25D").num: OGRGeomType("MultiLineString25D"), + OGRGeomType("Polygon25D").num: OGRGeomType("MultiPolygon25D"), } # Acceptable Django field types and corresponding acceptable OGR # counterparts. @@ -83,10 +93,19 @@ class LayerMapping: models.PositiveSmallIntegerField: (OFTInteger, OFTReal, OFTString), } - def __init__(self, model, data, mapping, layer=0, - source_srs=None, encoding='utf-8', - transaction_mode='commit_on_success', - transform=True, unique=None, using=None): + def __init__( + self, + model, + data, + mapping, + layer=0, + source_srs=None, + encoding="utf-8", + transaction_mode="commit_on_success", + transform=True, + unique=None, + using=None, + ): """ A LayerMapping object is initialized using the given Model (not an instance), a DataSource (or string path to an OGR-supported data file), and a mapping @@ -133,6 +152,7 @@ class LayerMapping: # Making sure the encoding exists, if not a LookupError # exception will be thrown. from codecs import lookup + lookup(encoding) self.encoding = encoding else: @@ -140,7 +160,7 @@ class LayerMapping: if unique: self.check_unique(unique) - transaction_mode = 'autocommit' # Has to be set to autocommit. + transaction_mode = "autocommit" # Has to be set to autocommit. self.unique = unique else: self.unique = None @@ -148,12 +168,12 @@ class LayerMapping: # Setting the transaction decorator with the function in the # transaction modes dictionary. self.transaction_mode = transaction_mode - if transaction_mode == 'autocommit': + if transaction_mode == "autocommit": self.transaction_decorator = None - elif transaction_mode == 'commit_on_success': + elif transaction_mode == "commit_on_success": self.transaction_decorator = transaction.atomic else: - raise LayerMapError('Unrecognized transaction mode: %s' % transaction_mode) + raise LayerMapError("Unrecognized transaction mode: %s" % transaction_mode) # #### Checking routines used during initialization #### def check_fid_range(self, fid_range): @@ -190,7 +210,9 @@ class LayerMapping: try: idx = ogr_fields.index(ogr_map_fld) except ValueError: - raise LayerMapError('Given mapping OGR field "%s" not found in OGR Layer.' % ogr_map_fld) + raise LayerMapError( + 'Given mapping OGR field "%s" not found in OGR Layer.' % ogr_map_fld + ) return idx # No need to increment through each feature in the model, simply check @@ -201,32 +223,43 @@ class LayerMapping: try: model_field = self.model._meta.get_field(field_name) except FieldDoesNotExist: - raise LayerMapError('Given mapping field "%s" not in given Model fields.' % field_name) + raise LayerMapError( + 'Given mapping field "%s" not in given Model fields.' % field_name + ) # Getting the string name for the Django field class (e.g., 'PointField'). fld_name = model_field.__class__.__name__ if isinstance(model_field, GeometryField): if self.geom_field: - raise LayerMapError('LayerMapping does not support more than one GeometryField per model.') + raise LayerMapError( + "LayerMapping does not support more than one GeometryField per model." + ) # Getting the coordinate dimension of the geometry field. coord_dim = model_field.dim try: if coord_dim == 3: - gtype = OGRGeomType(ogr_name + '25D') + gtype = OGRGeomType(ogr_name + "25D") else: gtype = OGRGeomType(ogr_name) except GDALException: - raise LayerMapError('Invalid mapping for GeometryField "%s".' % field_name) + raise LayerMapError( + 'Invalid mapping for GeometryField "%s".' % field_name + ) # Making sure that the OGR Layer's Geometry is compatible. ltype = self.layer.geom_type - if not (ltype.name.startswith(gtype.name) or self.make_multi(ltype, model_field)): - raise LayerMapError('Invalid mapping geometry; model has %s%s, ' - 'layer geometry type is %s.' % - (fld_name, '(dim=3)' if coord_dim == 3 else '', ltype)) + if not ( + ltype.name.startswith(gtype.name) + or self.make_multi(ltype, model_field) + ): + raise LayerMapError( + "Invalid mapping geometry; model has %s%s, " + "layer geometry type is %s." + % (fld_name, "(dim=3)" if coord_dim == 3 else "", ltype) + ) # Setting the `geom_field` attribute w/the name of the model field # that is a Geometry. Also setting the coordinate dimension @@ -243,15 +276,19 @@ class LayerMapping: try: rel_model._meta.get_field(rel_name) except FieldDoesNotExist: - raise LayerMapError('ForeignKey mapping field "%s" not in %s fields.' % - (rel_name, rel_model.__class__.__name__)) + raise LayerMapError( + 'ForeignKey mapping field "%s" not in %s fields.' + % (rel_name, rel_model.__class__.__name__) + ) fields_val = rel_model else: - raise TypeError('ForeignKey mapping must be of dictionary type.') + raise TypeError("ForeignKey mapping must be of dictionary type.") else: # Is the model field type supported by LayerMapping? if model_field.__class__ not in self.FIELD_TYPES: - raise LayerMapError('Django field type "%s" has no OGR mapping (yet).' % fld_name) + raise LayerMapError( + 'Django field type "%s" has no OGR mapping (yet).' % fld_name + ) # Is the OGR field in the Layer? idx = check_ogr_fld(ogr_name) @@ -259,8 +296,10 @@ class LayerMapping: # Can the OGR field type be mapped to the Django field type? if not issubclass(ogr_field, self.FIELD_TYPES[model_field.__class__]): - raise LayerMapError('OGR field "%s" (of type %s) cannot be mapped to Django %s.' % - (ogr_field, ogr_field.__name__, fld_name)) + raise LayerMapError( + 'OGR field "%s" (of type %s) cannot be mapped to Django %s.' + % (ogr_field, ogr_field.__name__, fld_name) + ) fields_val = model_field self.fields[field_name] = fields_val @@ -279,7 +318,7 @@ class LayerMapping: sr = self.layer.srs if not sr: - raise LayerMapError('No source reference system defined.') + raise LayerMapError("No source reference system defined.") else: return sr @@ -295,7 +334,9 @@ class LayerMapping: if unique not in self.mapping: raise ValueError else: - raise TypeError('Unique keyword argument must be set with a tuple, list, or string.') + raise TypeError( + "Unique keyword argument must be set with a tuple, list, or string." + ) # Keyword argument retrieval routines #### def feature_kwargs(self, feat): @@ -316,7 +357,7 @@ class LayerMapping: try: val = self.verify_geom(feat.geom, model_field) except GDALException: - raise LayerMapError('Could not retrieve geometry from feature.') + raise LayerMapError("Could not retrieve geometry from feature.") elif isinstance(model_field, models.base.ModelBase): # The related _model_, not a field was passed in -- indicating # another mapping for the related Model. @@ -348,23 +389,34 @@ class LayerMapping: Verify if the OGR Field contents are acceptable to the model field. If they are, return the verified value, otherwise raise an exception. """ - if (isinstance(ogr_field, OFTString) and - isinstance(model_field, (models.CharField, models.TextField))): + if isinstance(ogr_field, OFTString) and isinstance( + model_field, (models.CharField, models.TextField) + ): if self.encoding and ogr_field.value is not None: # The encoding for OGR data sources may be specified here # (e.g., 'cp437' for Census Bureau boundary files). val = force_str(ogr_field.value, self.encoding) else: val = ogr_field.value - if model_field.max_length and val is not None and len(val) > model_field.max_length: - raise InvalidString('%s model field maximum string length is %s, given %s characters.' % - (model_field.name, model_field.max_length, len(val))) - elif isinstance(ogr_field, OFTReal) and isinstance(model_field, models.DecimalField): + if ( + model_field.max_length + and val is not None + and len(val) > model_field.max_length + ): + raise InvalidString( + "%s model field maximum string length is %s, given %s characters." + % (model_field.name, model_field.max_length, len(val)) + ) + elif isinstance(ogr_field, OFTReal) and isinstance( + model_field, models.DecimalField + ): try: # Creating an instance of the Decimal value to use. d = Decimal(str(ogr_field.value)) except DecimalInvalidOperation: - raise InvalidDecimal('Could not construct decimal from: %s' % ogr_field.value) + raise InvalidDecimal( + "Could not construct decimal from: %s" % ogr_field.value + ) # Getting the decimal value as a tuple. dtup = d.as_tuple() @@ -385,17 +437,21 @@ class LayerMapping: # InvalidDecimal exception. if n_prec > max_prec: raise InvalidDecimal( - 'A DecimalField with max_digits %d, decimal_places %d must ' - 'round to an absolute value less than 10^%d.' % - (model_field.max_digits, model_field.decimal_places, max_prec) + "A DecimalField with max_digits %d, decimal_places %d must " + "round to an absolute value less than 10^%d." + % (model_field.max_digits, model_field.decimal_places, max_prec) ) val = d - elif isinstance(ogr_field, (OFTReal, OFTString)) and isinstance(model_field, models.IntegerField): + elif isinstance(ogr_field, (OFTReal, OFTString)) and isinstance( + model_field, models.IntegerField + ): # Attempt to convert any OFTReal and OFTString value to an OFTInteger. try: val = int(ogr_field.value) except ValueError: - raise InvalidInteger('Could not construct integer from: %s' % ogr_field.value) + raise InvalidInteger( + "Could not construct integer from: %s" % ogr_field.value + ) else: val = ogr_field.value return val @@ -412,15 +468,17 @@ class LayerMapping: # Constructing and verifying the related model keyword arguments. fk_kwargs = {} for field_name, ogr_name in rel_mapping.items(): - fk_kwargs[field_name] = self.verify_ogr_field(feat[ogr_name], rel_model._meta.get_field(field_name)) + fk_kwargs[field_name] = self.verify_ogr_field( + feat[ogr_name], rel_model._meta.get_field(field_name) + ) # Attempting to retrieve and return the related model. try: return rel_model.objects.using(self.using).get(**fk_kwargs) except ObjectDoesNotExist: raise MissingForeignKey( - 'No ForeignKey %s model found with keyword arguments: %s' % - (rel_model.__name__, fk_kwargs) + "No ForeignKey %s model found with keyword arguments: %s" + % (rel_model.__name__, fk_kwargs) ) def verify_geom(self, geom, model_field): @@ -456,13 +514,17 @@ class LayerMapping: SpatialRefSys = self.spatial_backend.spatial_ref_sys() try: # Getting the target spatial reference system - target_srs = SpatialRefSys.objects.using(self.using).get(srid=self.geo_field.srid).srs + target_srs = ( + SpatialRefSys.objects.using(self.using) + .get(srid=self.geo_field.srid) + .srs + ) # Creating the CoordTransform object return CoordTransform(self.source_srs, target_srs) except Exception as exc: raise LayerMapError( - 'Could not translate between the data source and model geometry.' + "Could not translate between the data source and model geometry." ) from exc def geometry_field(self): @@ -477,11 +539,21 @@ class LayerMapping: Given the OGRGeomType for a geometry and its associated GeometryField, determine whether the geometry should be turned into a GeometryCollection. """ - return (geom_type.num in self.MULTI_TYPES and - model_field.__class__.__name__ == 'Multi%s' % geom_type.django) - - def save(self, verbose=False, fid_range=False, step=False, - progress=False, silent=False, stream=sys.stdout, strict=False): + return ( + geom_type.num in self.MULTI_TYPES + and model_field.__class__.__name__ == "Multi%s" % geom_type.django + ) + + def save( + self, + verbose=False, + fid_range=False, + step=False, + progress=False, + silent=False, + stream=sys.stdout, + strict=False, + ): """ Save the contents from the OGR DataSource Layer into the database according to the mapping dictionary given at initialization. @@ -547,7 +619,9 @@ class LayerMapping: if strict: raise elif not silent: - stream.write('Ignoring Feature ID %s because: %s\n' % (feat.fid, msg)) + stream.write( + "Ignoring Feature ID %s because: %s\n" % (feat.fid, msg) + ) else: # Constructing the model using the keyword args is_update = False @@ -585,23 +659,29 @@ class LayerMapping: m.save(using=self.using) num_saved += 1 if verbose: - stream.write('%s: %s\n' % ('Updated' if is_update else 'Saved', m)) + stream.write( + "%s: %s\n" % ("Updated" if is_update else "Saved", m) + ) except Exception as msg: if strict: # Bailing out if the `strict` keyword is set. if not silent: stream.write( - 'Failed to save the feature (id: %s) into the ' - 'model with the keyword arguments:\n' % feat.fid + "Failed to save the feature (id: %s) into the " + "model with the keyword arguments:\n" % feat.fid ) - stream.write('%s\n' % kwargs) + stream.write("%s\n" % kwargs) raise elif not silent: - stream.write('Failed to save %s:\n %s\nContinuing\n' % (kwargs, msg)) + stream.write( + "Failed to save %s:\n %s\nContinuing\n" % (kwargs, msg) + ) # Printing progress information, if requested. if progress and num_feat % progress_interval == 0: - stream.write('Processed %d features, saved %d ...\n' % (num_feat, num_saved)) + stream.write( + "Processed %d features, saved %d ...\n" % (num_feat, num_saved) + ) # Only used for status output purposes -- incremental saving uses the # values returned here. @@ -614,7 +694,9 @@ class LayerMapping: if step and isinstance(step, int) and step < nfeat: # Incremental saving is requested at the given interval (step) if default_range: - raise LayerMapError('The `step` keyword may not be used in conjunction with the `fid_range` keyword.') + raise LayerMapError( + "The `step` keyword may not be used in conjunction with the `fid_range` keyword." + ) beg, num_feat, num_saved = (0, 0, 0) indices = range(step, nfeat, step) n_i = len(indices) @@ -631,7 +713,9 @@ class LayerMapping: num_feat, num_saved = _save(step_slice, num_feat, num_saved) beg = end except Exception: # Deliberately catch everything - stream.write('%s\nFailed to save slice: %s\n' % ('=-' * 20, step_slice)) + stream.write( + "%s\nFailed to save slice: %s\n" % ("=-" * 20, step_slice) + ) raise else: # Otherwise, just calling the previously defined _save() function. diff --git a/django/contrib/gis/utils/ogrinfo.py b/django/contrib/gis/utils/ogrinfo.py index 324e41a0ad..eafa23ccae 100644 --- a/django/contrib/gis/utils/ogrinfo.py +++ b/django/contrib/gis/utils/ogrinfo.py @@ -20,7 +20,9 @@ def ogrinfo(data_source, num_features=10): elif isinstance(data_source, DataSource): pass else: - raise Exception('Data source parameter must be a string or a DataSource object.') + raise Exception( + "Data source parameter must be a string or a DataSource object." + ) for i, layer in enumerate(data_source): print("data source : %s" % data_source.name) @@ -44,8 +46,8 @@ def ogrinfo(data_source, num_features=10): if isinstance(val, str): val_fmt = ' ("%s")' else: - val_fmt = ' (%s)' + val_fmt = " (%s)" output += val_fmt % val else: - output += ' (None)' + output += " (None)" print(output) diff --git a/django/contrib/gis/utils/ogrinspect.py b/django/contrib/gis/utils/ogrinspect.py index 8c83fd5a43..c50b39ad14 100644 --- a/django/contrib/gis/utils/ogrinspect.py +++ b/django/contrib/gis/utils/ogrinspect.py @@ -5,12 +5,17 @@ models for GeoDjango and/or mapping dictionaries for use with the """ from django.contrib.gis.gdal import DataSource from django.contrib.gis.gdal.field import ( - OFTDate, OFTDateTime, OFTInteger, OFTInteger64, OFTReal, OFTString, + OFTDate, + OFTDateTime, + OFTInteger, + OFTInteger64, + OFTReal, + OFTString, OFTTime, ) -def mapping(data_source, geom_name='geom', layer_key=0, multi_geom=False): +def mapping(data_source, geom_name="geom", layer_key=0, multi_geom=False): """ Given a DataSource, generate a dictionary that may be used for invoking the LayerMapping utility. @@ -30,7 +35,9 @@ def mapping(data_source, geom_name='geom', layer_key=0, multi_geom=False): elif isinstance(data_source, DataSource): pass else: - raise TypeError('Data source parameter must be a string or a DataSource object.') + raise TypeError( + "Data source parameter must be a string or a DataSource object." + ) # Creating the dictionary. _mapping = {} @@ -38,8 +45,8 @@ def mapping(data_source, geom_name='geom', layer_key=0, multi_geom=False): # Generating the field name for each field in the layer. for field in data_source[layer_key].fields: mfield = field.lower() - if mfield[-1:] == '_': - mfield += 'field' + if mfield[-1:] == "_": + mfield += "field" _mapping[mfield] = field gtype = data_source[layer_key].geom_type if multi_geom: @@ -116,12 +123,22 @@ def ogrinspect(*args, **kwargs): Note: Call the _ogrinspect() helper to do the heavy lifting. """ - return '\n'.join(_ogrinspect(*args, **kwargs)) - - -def _ogrinspect(data_source, model_name, geom_name='geom', layer_key=0, srid=None, - multi_geom=False, name_field=None, imports=True, - decimal=False, blank=False, null=False): + return "\n".join(_ogrinspect(*args, **kwargs)) + + +def _ogrinspect( + data_source, + model_name, + geom_name="geom", + layer_key=0, + srid=None, + multi_geom=False, + name_field=None, + imports=True, + decimal=False, + blank=False, + null=False, +): """ Helper routine for `ogrinspect` that generates GeoDjango models corresponding to the given data source. See the `ogrinspect` docstring for more details. @@ -132,7 +149,9 @@ def _ogrinspect(data_source, model_name, geom_name='geom', layer_key=0, srid=Non elif isinstance(data_source, DataSource): pass else: - raise TypeError('Data source parameter must be a string or a DataSource object.') + raise TypeError( + "Data source parameter must be a string or a DataSource object." + ) # Getting the layer corresponding to the layer key and getting # a string listing of all OGR fields in the Layer. @@ -148,6 +167,7 @@ def _ogrinspect(data_source, model_name, geom_name='geom', layer_key=0, srid=Non return [s.lower() for s in ogr_fields] else: return [] + null_fields = process_kwarg(null) blank_fields = process_kwarg(blank) decimal_fields = process_kwarg(decimal) @@ -156,29 +176,30 @@ def _ogrinspect(data_source, model_name, geom_name='geom', layer_key=0, srid=Non def get_kwargs_str(field_name): kwlist = [] if field_name.lower() in null_fields: - kwlist.append('null=True') + kwlist.append("null=True") if field_name.lower() in blank_fields: - kwlist.append('blank=True') + kwlist.append("blank=True") if kwlist: - return ', ' + ', '.join(kwlist) + return ", " + ", ".join(kwlist) else: - return '' + return "" # For those wishing to disable the imports. if imports: - yield '# This is an auto-generated Django model module created by ogrinspect.' - yield 'from django.contrib.gis.db import models' - yield '' - yield '' + yield "# This is an auto-generated Django model module created by ogrinspect." + yield "from django.contrib.gis.db import models" + yield "" + yield "" - yield 'class %s(models.Model):' % model_name + yield "class %s(models.Model):" % model_name for field_name, width, precision, field_type in zip( - ogr_fields, layer.field_widths, layer.field_precisions, layer.field_types): + ogr_fields, layer.field_widths, layer.field_precisions, layer.field_types + ): # The model field name. mfield = field_name.lower() - if mfield[-1:] == '_': - mfield += 'field' + if mfield[-1:] == "_": + mfield += "field" # Getting the keyword args string. kwargs_str = get_kwargs_str(field_name) @@ -188,25 +209,32 @@ def _ogrinspect(data_source, model_name, geom_name='geom', layer_key=0, srid=Non # may also be mapped to `DecimalField` if specified in the # `decimal` keyword. if field_name.lower() in decimal_fields: - yield ' %s = models.DecimalField(max_digits=%d, decimal_places=%d%s)' % ( - mfield, width, precision, kwargs_str + yield " %s = models.DecimalField(max_digits=%d, decimal_places=%d%s)" % ( + mfield, + width, + precision, + kwargs_str, ) else: - yield ' %s = models.FloatField(%s)' % (mfield, kwargs_str[2:]) + yield " %s = models.FloatField(%s)" % (mfield, kwargs_str[2:]) elif field_type is OFTInteger: - yield ' %s = models.IntegerField(%s)' % (mfield, kwargs_str[2:]) + yield " %s = models.IntegerField(%s)" % (mfield, kwargs_str[2:]) elif field_type is OFTInteger64: - yield ' %s = models.BigIntegerField(%s)' % (mfield, kwargs_str[2:]) + yield " %s = models.BigIntegerField(%s)" % (mfield, kwargs_str[2:]) elif field_type is OFTString: - yield ' %s = models.CharField(max_length=%s%s)' % (mfield, width, kwargs_str) + yield " %s = models.CharField(max_length=%s%s)" % ( + mfield, + width, + kwargs_str, + ) elif field_type is OFTDate: - yield ' %s = models.DateField(%s)' % (mfield, kwargs_str[2:]) + yield " %s = models.DateField(%s)" % (mfield, kwargs_str[2:]) elif field_type is OFTDateTime: - yield ' %s = models.DateTimeField(%s)' % (mfield, kwargs_str[2:]) + yield " %s = models.DateTimeField(%s)" % (mfield, kwargs_str[2:]) elif field_type is OFTTime: - yield ' %s = models.TimeField(%s)' % (mfield, kwargs_str[2:]) + yield " %s = models.TimeField(%s)" % (mfield, kwargs_str[2:]) else: - raise TypeError('Unknown field type %s in %s' % (field_type, mfield)) + raise TypeError("Unknown field type %s in %s" % (field_type, mfield)) # TODO: Autodetection of multigeometry types (see #7218). gtype = layer.geom_type @@ -217,21 +245,21 @@ def _ogrinspect(data_source, model_name, geom_name='geom', layer_key=0, srid=Non # Setting up the SRID keyword string. if srid is None: if layer.srs is None: - srid_str = 'srid=-1' + srid_str = "srid=-1" else: srid = layer.srs.srid if srid is None: - srid_str = 'srid=-1' + srid_str = "srid=-1" elif srid == 4326: # WGS84 is already the default. - srid_str = '' + srid_str = "" else: - srid_str = 'srid=%s' % srid + srid_str = "srid=%s" % srid else: - srid_str = 'srid=%s' % srid + srid_str = "srid=%s" % srid - yield ' %s = models.%s(%s)' % (geom_name, geom_field, srid_str) + yield " %s = models.%s(%s)" % (geom_name, geom_field, srid_str) if name_field: - yield '' - yield ' def __str__(self): return self.%s' % name_field + yield "" + yield " def __str__(self): return self.%s" % name_field diff --git a/django/contrib/gis/utils/srs.py b/django/contrib/gis/utils/srs.py index d44d340383..2204767923 100644 --- a/django/contrib/gis/utils/srs.py +++ b/django/contrib/gis/utils/srs.py @@ -2,8 +2,9 @@ from django.contrib.gis.gdal import SpatialReference from django.db import DEFAULT_DB_ALIAS, connections -def add_srs_entry(srs, auth_name='EPSG', auth_srid=None, ref_sys_name=None, - database=None): +def add_srs_entry( + srs, auth_name="EPSG", auth_srid=None, ref_sys_name=None, database=None +): """ Take a GDAL SpatialReference system and add its information to the `spatial_ref_sys` table of the spatial backend. Doing this enables @@ -35,12 +36,10 @@ def add_srs_entry(srs, auth_name='EPSG', auth_srid=None, ref_sys_name=None, database = database or DEFAULT_DB_ALIAS connection = connections[database] - if not hasattr(connection.ops, 'spatial_version'): - raise Exception( - 'The `add_srs_entry` utility only works with spatial backends.' - ) + if not hasattr(connection.ops, "spatial_version"): + raise Exception("The `add_srs_entry` utility only works with spatial backends.") if not connection.features.supports_add_srs_entry: - raise Exception('This utility does not support your database backend.') + raise Exception("This utility does not support your database backend.") SpatialRefSys = connection.ops.spatial_ref_sys() # If argument is not a `SpatialReference` instance, use it as parameter @@ -49,24 +48,26 @@ def add_srs_entry(srs, auth_name='EPSG', auth_srid=None, ref_sys_name=None, srs = SpatialReference(srs) if srs.srid is None: - raise Exception('Spatial reference requires an SRID to be ' - 'compatible with the spatial backend.') + raise Exception( + "Spatial reference requires an SRID to be " + "compatible with the spatial backend." + ) # Initializing the keyword arguments dictionary for both PostGIS # and SpatiaLite. kwargs = { - 'srid': srs.srid, - 'auth_name': auth_name, - 'auth_srid': auth_srid or srs.srid, - 'proj4text': srs.proj4, + "srid": srs.srid, + "auth_name": auth_name, + "auth_srid": auth_srid or srs.srid, + "proj4text": srs.proj4, } # Backend-specific fields for the SpatialRefSys model. srs_field_names = {f.name for f in SpatialRefSys._meta.get_fields()} - if 'srtext' in srs_field_names: - kwargs['srtext'] = srs.wkt - if 'ref_sys_name' in srs_field_names: + if "srtext" in srs_field_names: + kwargs["srtext"] = srs.wkt + if "ref_sys_name" in srs_field_names: # SpatiaLite specific - kwargs['ref_sys_name'] = ref_sys_name or srs.name + kwargs["ref_sys_name"] = ref_sys_name or srs.name # Creating the spatial_ref_sys model. try: diff --git a/django/contrib/gis/views.py b/django/contrib/gis/views.py index 5b29db9fb2..346fb5b0c0 100644 --- a/django/contrib/gis/views.py +++ b/django/contrib/gis/views.py @@ -7,14 +7,16 @@ def feed(request, url, feed_dict=None): if not feed_dict: raise Http404(_("No feeds are registered.")) - slug = url.partition('/')[0] + slug = url.partition("/")[0] try: f = feed_dict[slug] except KeyError: - raise Http404(_('Slug %r isn’t registered.') % slug) + raise Http404(_("Slug %r isn’t registered.") % slug) instance = f() - instance.feed_url = getattr(f, 'feed_url', None) or request.path - instance.title_template = f.title_template or ('feeds/%s_title.html' % slug) - instance.description_template = f.description_template or ('feeds/%s_description.html' % slug) + instance.feed_url = getattr(f, "feed_url", None) or request.path + instance.title_template = f.title_template or ("feeds/%s_title.html" % slug) + instance.description_template = f.description_template or ( + "feeds/%s_description.html" % slug + ) return instance(request) |
