测试gitnore

This commit is contained in:
ladeng07
2022-05-06 15:45:57 +08:00
parent 12f390949b
commit 51552904f9
2347 changed files with 120102 additions and 53549 deletions
@@ -1,34 +1,12 @@
from django.contrib.admin import (
HORIZONTAL,
VERTICAL,
AdminSite,
ModelAdmin,
StackedInline,
TabularInline,
action,
autodiscover,
display,
register,
site,
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.options import GeoModelAdmin, OSMGeoAdmin
from django.contrib.gis.admin.widgets import OpenLayersWidget
__all__ = [
"HORIZONTAL",
"VERTICAL",
"AdminSite",
"ModelAdmin",
"StackedInline",
"TabularInline",
"action",
"autodiscover",
"display",
"register",
"site",
"GISModelAdmin",
"OpenLayersWidget",
# RemovedInDjango50Warning.
"GeoModelAdmin",
"OSMGeoAdmin",
'HORIZONTAL', 'VERTICAL', 'AdminSite', 'ModelAdmin', 'StackedInline',
'TabularInline', 'action', 'autodiscover', 'display', 'register', 'site',
'GeoModelAdmin', 'OSMGeoAdmin', 'OpenLayersWidget',
]
@@ -1,43 +1,17 @@
import warnings
from django.contrib.admin import ModelAdmin
from django.contrib.gis.admin.widgets import OpenLayersWidget
from django.contrib.gis.db import models
from django.contrib.gis.forms import OSMWidget
from django.contrib.gis.gdal import OGRGeomType
from django.forms import Media
from django.utils.deprecation import RemovedInDjango50Warning
class GeoModelAdminMixin:
gis_widget = OSMWidget
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
):
kwargs["widget"] = self.gis_widget(**self.gis_widget_kwargs)
return db_field.formfield(**kwargs)
else:
return super().formfield_for_dbfield(db_field, request, **kwargs)
class GISModelAdmin(GeoModelAdminMixin, ModelAdmin):
pass
# RemovedInDjango50Warning.
spherical_mercator_srid = 3857
# RemovedInDjango50Warning.
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,28 +34,16 @@ 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,
)
super().__init__(*args, **kwargs)
@property
def media(self):
"Injects OpenLayers JavaScript into the admin."
@@ -95,7 +57,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)
@@ -106,75 +68,67 @@ 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'
@@ -7,27 +7,26 @@ 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.
@@ -38,29 +37,26 @@ 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
@@ -69,17 +65,15 @@ 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
@@ -88,31 +82,30 @@ 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]
@@ -4,11 +4,9 @@ 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')
@@ -2,16 +2,14 @@ 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):
@@ -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
@@ -6,14 +6,13 @@ 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:
@@ -32,10 +31,7 @@ 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):
@@ -53,12 +49,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):
@@ -121,7 +117,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
@@ -1,7 +1,8 @@
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
from django.contrib.gis.measure import Distance as DistanceMeasure
from django.contrib.gis.measure import (
Area as AreaMeasure, Distance as DistanceMeasure,
)
from django.db import NotSupportedError
from django.utils.functional import cached_property
@@ -17,7 +18,7 @@ class BaseSpatialOperations:
spatial_version = None
# How the geometry column should be selected.
select = "%s"
select = '%s'
@cached_property
def select_extent(self):
@@ -26,7 +27,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().
@@ -34,36 +35,12 @@ 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
@@ -72,14 +49,10 @@ 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):
@@ -91,18 +64,14 @@ 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):
"""
@@ -111,62 +80,48 @@ 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)
@@ -178,17 +133,15 @@ 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:
@@ -198,7 +151,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:
@@ -1,4 +1,6 @@
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
@@ -1,5 +1,7 @@
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
@@ -12,13 +14,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
@@ -29,16 +31,13 @@ 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
@@ -8,13 +8,14 @@ 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():
@@ -30,8 +31,8 @@ class MySQLIntrospection(DatabaseIntrospection):
def supports_spatial_index(self, cursor, table_name):
# Supported with MyISAM/Aria, or InnoDB on MySQL 5.7.5+/MariaDB 10.2.2+
storage_engine = self.get_storage_engine(cursor, table_name)
if storage_engine == "InnoDB":
if storage_engine == 'InnoDB':
return self.connection.mysql_version >= (
(10, 2, 2) if self.connection.mysql_is_mariadb else (5, 7, 5)
)
return storage_engine in ("MyISAM", "Aria")
return storage_engine in ('MyISAM', 'Aria')
@@ -1,6 +1,8 @@
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
@@ -10,8 +12,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
@@ -25,71 +27,53 @@ 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'})
if self.connection.mysql_version < (10, 2, 4):
unsupported.add("AsGeoJSON")
unsupported.add('AsGeoJSON')
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):
@@ -100,12 +84,10 @@ 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]
@@ -123,5 +105,4 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
if srid:
geom.srid = srid
return geom
return converter
@@ -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,10 +18,7 @@ class MySQLGISSchemaEditor(DatabaseSchemaEditor):
def skip_default(self, field):
# Geometry fields are stored as BLOB/TEXT, for which MySQL < 8.0.13 and
# MariaDB < 10.2.1 don'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)
@@ -32,11 +29,10 @@ 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
@@ -53,22 +49,21 @@ 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,7 +72,6 @@ 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 = []
@@ -19,9 +19,7 @@ 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
@@ -29,9 +27,12 @@ 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
@@ -1,4 +1,6 @@
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
@@ -11,4 +11,4 @@ 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'}
@@ -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
@@ -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
@@ -10,7 +10,9 @@
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
@@ -18,7 +20,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):
@@ -30,63 +32,57 @@ 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
@@ -94,40 +90,28 @@ 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"),
# Oracle uses a different syntax, e.g., 'mask=inside+touch'
"relate": SDORelate(),
"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):
@@ -140,17 +124,15 @@ 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)
@@ -163,7 +145,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):
"""
@@ -179,47 +161,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)
@@ -236,8 +218,7 @@ 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'
@@ -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,18 +15,13 @@ 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,27 +36,23 @@ 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
@@ -72,12 +63,9 @@ 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)
@@ -85,22 +73,14 @@ 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):
@@ -111,6 +91,4 @@ 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)
@@ -31,9 +31,7 @@ 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
@@ -62,9 +60,9 @@ class PostGISAdapter:
"""
if self.is_geometry:
# Psycopg will figure out whether to use E'\\000' or '\000'.
return "%s(%s)" % (
"ST_GeogFromWKB" if self.geography else "ST_GeomFromEWKB",
self._adapter.getquoted().decode(),
return '%s(%s)' % (
'ST_GeogFromWKB' if self.geography else 'ST_GeomFromEWKB',
self._adapter.getquoted().decode()
)
else:
# For rasters, add explicit type cast to WKB string.
@@ -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)
@@ -15,23 +15,13 @@ 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.
@@ -39,24 +29,24 @@ 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
# whether the band data is stored as part of the datum or is to be found on the
# server's filesystem. There are currently 11 supported pixel value types, so 4
# bits are enough to account for all. Reserve the upper 4 bits for generic
# flags. See
# https://trac.osgeo.org/postgis/wiki/WKTRaster/RFC/RFC1_V0SerialFormat#Pixeltypeandstorageflag
# flags.
# See https://trac.osgeo.org/postgis/wiki/WKTRaster/RFC/RFC1_V0SerialFormat#Pixeltypeandstorageflag
BANDTYPE_PIXTYPE_MASK = 0x0F
BANDTYPE_FLAG_HASNODATA = 1 << 6
@@ -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,15 +21,9 @@ 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):
@@ -40,32 +34,27 @@ 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
@@ -10,7 +10,6 @@ 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)
@@ -20,12 +19,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,
@@ -39,7 +38,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):
@@ -47,7 +46,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):
@@ -55,7 +54,6 @@ 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()
@@ -63,8 +61,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
@@ -1,7 +1,9 @@
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
@@ -20,7 +22,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):
@@ -37,72 +39,56 @@ 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):
@@ -110,70 +96,64 @@ 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
@@ -185,13 +165,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()
@@ -199,9 +179,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.3. '
'Was the database created from a spatial database '
'template?' % self.connection.settings_dict['NAME']
)
version = vtup[1:]
return version
@@ -214,7 +194,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)
@@ -227,7 +207,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)
@@ -236,24 +216,22 @@ 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):
"""
@@ -276,16 +254,12 @@ 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
@@ -298,12 +272,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
@@ -315,9 +289,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
@@ -327,28 +301,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):
"""
@@ -363,16 +337,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
@@ -392,15 +366,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
@@ -410,8 +384,7 @@ 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'
@@ -3,13 +3,8 @@ 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,
)
@@ -17,14 +12,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):
@@ -51,7 +46,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
@@ -67,11 +62,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)
@@ -86,14 +81,13 @@ 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,
}
@@ -105,18 +99,9 @@ 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.
@@ -134,7 +119,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()]
@@ -1,60 +1,58 @@
from django.db.backends.ddl_references import Statement
from django.db.backends.postgresql.schema import DatabaseSchemaEditor
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_wrapper = 'ST_ConvexHull(%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":
field_column = self.quote_name(field.column)
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)
fields = None
field_column = self.rast_index_wrapper % field_column
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")
if not name:
name = self._create_index_name(model._meta.db_table, [field.column], "_id")
field_column = "%s %s" % (field_column, self.geom_index_ops_nd)
if kwargs.get('name') is None:
index_name = '%s_%s_id' % (model._meta.db_table, field.column)
else:
index_name = kwargs['name']
return super()._create_index_sql(
model,
fields=fields,
name=name,
using=" USING %s" % self.geom_index_type,
opclasses=opclasses,
expressions=expressions,
return Statement(
self.sql_create_index,
name=self.quote_name(index_name),
table=self.quote_name(model._meta.db_table),
using=' USING %s' % self.geom_index_type,
columns=field_column,
extra='',
condition='',
include='',
)
def _alter_column_type_sql(self, table, old_field, new_field, new_type):
"""
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:
@@ -65,8 +63,7 @@ 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,
},
@@ -4,7 +4,6 @@ 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)
@@ -2,7 +2,9 @@ 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
@@ -25,16 +27,12 @@ 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):
@@ -44,26 +42,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
@@ -73,7 +71,4 @@ class DatabaseWrapper(SQLiteDatabaseWrapper):
with self.cursor() as cursor:
cursor.execute("PRAGMA table_info(geometry_columns);")
if cursor.fetchall() == []:
if self.ops.spatial_version < (5,):
cursor.execute("SELECT InitSpatialMetaData(1)")
else:
cursor.execute("SELECT InitSpatialMetaDataFull(1)")
cursor.execute("SELECT InitSpatialMetaData(1)")
@@ -2,4 +2,4 @@ from django.db.backends.sqlite3.client import DatabaseClient
class SpatiaLiteClient(DatabaseClient):
executable_name = "spatialite"
executable_name = 'spatialite'
@@ -11,16 +11,14 @@ class DatabaseFeatures(BaseSpatialFeatures, SQLiteDatabaseFeatures):
@cached_property
def supports_area_geodetic(self):
return bool(self.connection.ops.geom_lib_version())
return bool(self.connection.ops.lwgeom_version())
@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
@@ -1,7 +1,6 @@
from django.contrib.gis.gdal import OGRGeomType
from django.db.backends.sqlite3.introspection import (
DatabaseIntrospection,
FlexibleFieldLookupDict,
DatabaseIntrospection, FlexibleFieldLookupDict,
)
@@ -10,16 +9,15 @@ 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',
}
@@ -29,18 +27,14 @@ 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.
@@ -57,21 +51,18 @@ 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,
@@ -9,21 +9,20 @@ 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,
@@ -37,7 +36,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):
@@ -45,14 +44,13 @@ 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()
@@ -61,8 +59,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
@@ -3,7 +3,9 @@ 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
@@ -18,69 +20,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"}
if not self.geom_lib_version():
unsupported |= {"Azimuth", "GeoHash", "MakeValid"}
unsupported = {'BoundingCircle', 'GeometryDistance', 'MemSize'}
if not self.lwgeom_version():
unsupported |= {'Azimuth', 'GeoHash', 'IsValid', 'MakeValid'}
return unsupported
@cached_property
@@ -91,11 +93,12 @@ 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):
@@ -126,16 +129,14 @@ 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]
@@ -148,7 +149,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()
@@ -156,33 +157,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()")
def rttopo_version(self):
"""Return the version of RTTOPO library used by SpatiaLite."""
return self._get_spatialite_func("rttopo_version()")
def geom_lib_version(self):
"""
Return the version of the version-dependant geom library used by
SpatiaLite.
"""
if self.spatial_version >= (5,):
return self.rttopo_version()
else:
return self.lwgeom_version()
return self._get_spatialite_func('lwgeom_version()')
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):
"""
@@ -197,7 +184,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.
@@ -205,14 +192,12 @@ 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):
@@ -221,5 +206,4 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
def converter(value, expression, connection):
return None if value is None else GEOSGeometryBase(read(value), geom_class)
return converter
@@ -14,9 +14,7 @@ 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"
@@ -38,14 +36,12 @@ 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,
@@ -57,8 +53,7 @@ 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),
}
@@ -67,15 +62,13 @@ 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,
}
@@ -99,8 +92,7 @@ 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),
}
@@ -111,7 +103,6 @@ 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)
@@ -134,17 +125,14 @@ 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),
}
@@ -155,8 +143,7 @@ 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),
@@ -167,25 +154,15 @@ 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)),
})
@@ -8,7 +8,6 @@ class SpatialOperator:
"""
Class encapsulating the behavior specific to a GIS operation (used by lookups).
"""
sql_template = None
def __init__(self, op=None, func=None):
@@ -18,11 +17,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
@@ -5,26 +5,14 @@ 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',
]
@@ -1,13 +1,10 @@
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):
@@ -26,45 +23,37 @@ 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)
@@ -74,8 +63,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)
@@ -85,10 +74,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
@@ -4,15 +4,8 @@ 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
@@ -24,9 +17,7 @@ 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):
@@ -36,7 +27,6 @@ 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:
@@ -46,14 +36,9 @@ 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)
@@ -61,8 +46,7 @@ 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,
)
@@ -77,7 +61,6 @@ 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
@@ -105,7 +88,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)
@@ -113,9 +96,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):
@@ -163,10 +146,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):
@@ -184,9 +167,7 @@ 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)
@@ -198,9 +179,7 @@ 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)
@@ -210,14 +189,9 @@ 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)
@@ -228,25 +202,14 @@ 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
@@ -281,36 +244,30 @@ 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):
@@ -326,49 +283,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")
@@ -393,18 +350,13 @@ 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)
@@ -423,13 +375,12 @@ 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
@@ -6,14 +6,8 @@ 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
@@ -38,21 +32,12 @@ 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):
@@ -76,11 +61,8 @@ 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__,
)
)
@@ -90,17 +72,15 @@ 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
@@ -120,17 +100,13 @@ 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)
@@ -138,13 +114,11 @@ 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)
@@ -158,18 +132,14 @@ 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)
@@ -185,7 +155,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
@@ -211,7 +181,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):
@@ -219,11 +189,7 @@ 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)
@@ -233,7 +199,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)
@@ -241,13 +207,11 @@ 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)
@@ -269,9 +233,7 @@ 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):
@@ -299,7 +261,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):
@@ -317,29 +279,21 @@ class Distance(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
)
if not geography and self.geo_field.geodetic(connection):
# Geometry fields with geodetic (lon/lat) coordinates need special
# distance functions.
# 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")
# DistanceSpheroid is more accurate and resource intensive than DistanceSphere
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)
@@ -357,7 +311,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):
@@ -371,8 +325,8 @@ class GeoHash(GeoFunc):
class GeometryDistance(GeoFunc):
output_field = FloatField()
arity = 2
function = ""
arg_joiner = " <-> "
function = ''
arg_joiner = ' <-> '
geom_param_pos = (0, 1)
@@ -383,7 +337,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):
@@ -397,13 +351,8 @@ 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):
@@ -413,20 +362,18 @@ 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)
@@ -461,9 +408,7 @@ 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
@@ -487,11 +432,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)
@@ -501,16 +446,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)
@@ -523,10 +468,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)
@@ -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,9 +64,7 @@ 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):
@@ -80,12 +78,7 @@ 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)
@@ -94,15 +87,13 @@ 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
@@ -111,8 +102,7 @@ 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
@@ -121,8 +111,7 @@ 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
@@ -131,8 +120,7 @@ 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
@@ -141,8 +129,7 @@ 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
@@ -151,8 +138,7 @@ 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
@@ -161,8 +147,7 @@ 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
@@ -171,8 +156,7 @@ 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
@@ -182,11 +166,10 @@ 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
@@ -195,18 +178,15 @@ 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
class BBOverlapsLookup(GISLookup):
"""
The 'bboverlaps' operator returns true if A's bounding box overlaps B's
bounding box.
The 'bboverlaps' operator returns true if A's bounding box overlaps B's bounding box.
"""
lookup_name = "bboverlaps"
lookup_name = 'bboverlaps'
@BaseSpatialField.register_lookup
@@ -215,71 +195,69 @@ 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)
@@ -289,107 +267,93 @@ 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 = '<='
@@ -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,10 +57,8 @@ 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))
):
# For raster fields, ensure input is None or a string, dict, or
if gtype == 'RASTER' and (value is None or isinstance(value, (str, dict, self._klass))):
# For raster fields, assure input is None or a string, dict, or
# raster instance.
pass
elif isinstance(value, self._klass):
@@ -73,10 +71,8 @@ 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
@@ -1,6 +1,7 @@
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',
]
@@ -10,14 +10,13 @@ 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):
@@ -38,12 +37,11 @@ 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
@@ -58,9 +56,7 @@ 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):
@@ -70,4 +66,4 @@ class DistanceField(models.FloatField):
return Distance(**{distance_att: value}) if distance_att else value
def get_internal_type(self):
return "DistanceField"
return 'DistanceField'
@@ -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,46 +52,36 @@ 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):
@@ -106,7 +96,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):
@@ -121,7 +111,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):
@@ -141,11 +131,10 @@ 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)}
@@ -1,13 +1,8 @@
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
@@ -13,18 +13,15 @@ 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):
@@ -32,7 +29,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."""
@@ -40,7 +37,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:
@@ -51,9 +48,7 @@ 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:
@@ -76,13 +71,8 @@ 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:
@@ -90,13 +80,12 @@ 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)
@@ -117,28 +106,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'
@@ -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,25 +15,24 @@ 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:
@@ -59,47 +58,40 @@ 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)
@@ -113,15 +105,14 @@ 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)
@@ -28,31 +28,22 @@
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',
)
@@ -44,15 +44,15 @@ from django.contrib.gis.gdal.prototypes import ds as capi
from django.utils.encoding import force_bytes, force_str
# For more information, see the OGR C API documentation:
# https://gdal.org/api/vector_c_api.html
# For more information, see the OGR C API source code:
# https://www.gdal.org/ogr__api_8h.html
#
# The OGR_DS_* routines are relevant here.
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,12 +73,10 @@ 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
@@ -93,17 +91,14 @@ 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):
@@ -112,7 +107,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):
@@ -2,35 +2,33 @@ 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
from django.contrib.gis.gdal.prototypes import raster as rcapi
from django.contrib.gis.gdal.prototypes import ds as vcapi, raster as rcapi
from django.utils.encoding import force_bytes, force_str
class Driver(GDALBase):
"""
Wrap a GDAL/OGR Data Source Driver.
For more information, see the C API documentation:
https://gdal.org/api/vector_c_api.html
https://gdal.org/api/raster_c_api.html
For more information, see the C API source code:
https://www.gdal.org/gdal_8h.html - https://www.gdal.org/ogr__api_8h.html
"""
# Case-insensitive aliases for some GDAL/OGR Drivers.
# For a complete list of original driver names see
# https://gdal.org/drivers/vector/
# https://gdal.org/drivers/raster/
# https://www.gdal.org/ogr_formats.html (vector)
# https://www.gdal.org/formats_list.html (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):
@@ -62,15 +60,11 @@ 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):
@@ -17,15 +17,14 @@ from django.contrib.gis.gdal.error import GDALException
# The OGR definition of an Envelope is a C structure containing four doubles.
# See the 'ogr_core.h' source file for more information:
# https://gdal.org/doxygen/ogr__core_8h_source.html
# https://www.gdal.org/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:
@@ -48,25 +47,23 @@ 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):
"""
@@ -74,21 +71,13 @@ 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."
@@ -115,16 +104,12 @@ 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:
@@ -136,11 +121,9 @@ 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]))
@@ -148,7 +131,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):
@@ -189,15 +172,7 @@ 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)
@@ -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
# https://www.gdal.org/cpl__error_8h.html
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
@@ -2,13 +2,12 @@ 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
from django.contrib.gis.gdal.prototypes import geom as geom_api
from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api
from django.utils.encoding import force_bytes, force_str
# For more information, see the OGR C API source code:
# https://gdal.org/api/vector_c_api.html
# https://www.gdal.org/ogr__api_8h.html
#
# The OGR_F_* routines are relevant here.
class Feature(GDALBase):
@@ -16,7 +15,6 @@ 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):
@@ -24,7 +22,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
@@ -40,9 +38,7 @@ 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):
@@ -51,7 +47,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."
@@ -85,9 +81,8 @@ 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
@@ -109,12 +104,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
@@ -8,7 +8,7 @@ from django.utils.encoding import force_str
# For more information, see the OGR C API source code:
# https://gdal.org/api/vector_c_api.html
# https://www.gdal.org/ogr__api_8h.html
#
# The OGR_Fld_* routines are relevant here.
class Field(GDALBase):
@@ -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,26 +41,14 @@ 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."
@@ -75,22 +63,12 @@ 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
@@ -1,7 +1,7 @@
"""
The OGRGeometry is a wrapper for using the OGR Geometry class
(see https://gdal.org/api/ogrgeometry_cpp.html#_CPPv411OGRGeometry).
OGRGeometry may be instantiated when reading geometries from OGR Data Sources
(see https://www.gdal.org/classOGRGeometry.html). OGRGeometry
may be instantiated when reading geometries from OGR Data Sources
(e.g. SHP files), or when given OGC WKT (a string).
While the 'full' API is not present yet, the API is "pythonic" unlike
@@ -28,7 +28,7 @@
>>> print(mpnt.proj)
+proj=longlat +ellps=clrk66 +datum=NAD27 +no_defs
>>> print(mpnt)
MULTIPOINT (-89.99993037860248 29.99979788655764,-89.99993037860248 29.99979788655764)
MULTIPOINT (-89.999930378602485 29.999797886557641,-89.999930378602485 29.999797886557641)
The OGRGeomType class is to make it easy to specify an OGR geometry type:
>>> from django.contrib.gis.gdal import OGRGeomType
@@ -46,20 +46,18 @@ 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
from django.contrib.gis.gdal.prototypes import srs as srs_api
from django.contrib.gis.gdal.prototypes import geom as capi, 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
# For more information, see the OGR C API source code:
# https://gdal.org/api/vector_c_api.html
# https://www.gdal.org/ogr__api_8h.html
#
# 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):
@@ -76,18 +74,16 @@ 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:
@@ -105,17 +101,12 @@ 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.
@@ -138,15 +129,13 @@ 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):
@@ -156,10 +145,8 @@ 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):
@@ -211,7 +198,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)
@@ -291,9 +278,7 @@ 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)
@@ -309,21 +294,19 @@ 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
@@ -342,7 +325,6 @@ class OGRGeometry(GDALBase):
Return the GeoJSON representation of this Geometry.
"""
return capi.to_json(self.ptr)
geojson = json
@property
@@ -358,7 +340,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
@@ -379,7 +361,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
@@ -420,19 +402,15 @@ 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.
@@ -522,14 +500,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):
@@ -554,11 +532,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:
@@ -572,9 +550,7 @@ 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."
@@ -584,7 +560,6 @@ 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):
@@ -617,6 +592,7 @@ class LinearRing(LineString):
class Polygon(OGRGeometry):
def __len__(self):
"Return the number of interior rings in this Polygon."
return self.geom_count
@@ -624,27 +600,21 @@ 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
@@ -657,7 +627,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
@@ -669,14 +639,9 @@ 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."
@@ -694,7 +659,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):
@@ -706,7 +671,6 @@ 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
@@ -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
@@ -7,22 +7,19 @@ 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
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.prototypes import (
ds as capi, geom as geom_api, srs as srs_api,
)
from django.contrib.gis.gdal.srs import SpatialReference
from django.utils.encoding import force_bytes, force_str
# For more information, see the OGR C API source code:
# https://gdal.org/api/vector_c_api.html
# https://www.gdal.org/ogr__api_8h.html
#
# The OGR_L_* routines are relevant here.
class Layer(GDALBase):
"""
A class that wraps an OGR Layer, needs to be instantiated from a DataSource
object.
"""
"A class that wraps an OGR Layer, needs to be instantiated from a DataSource object."
def __init__(self, layer_ptr, ds):
"""
@@ -32,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."
@@ -46,16 +43,14 @@ 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."
@@ -92,7 +87,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
@@ -138,14 +133,10 @@ 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):
@@ -154,26 +145,20 @@ 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:
@@ -186,7 +171,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)
@@ -194,10 +179,7 @@ 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)
@@ -208,7 +190,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):
@@ -218,7 +200,6 @@ 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]
@@ -7,45 +7,29 @@ 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",
"gdal201",
"gdal20",
'gdal302', 'gdal301', 'gdal300',
'gdal204', 'gdal203', 'gdal202', 'gdal201', 'gdal20',
]
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",
"gdal2.1.0",
"gdal2.0.0",
'gdal', 'GDAL',
'gdal3.2.0', 'gdal3.1.0', 'gdal3.0.0',
'gdal2.4.0', 'gdal2.3.0', 'gdal2.2.0', 'gdal2.1.0', 'gdal2.0.0',
]
else:
raise ImproperlyConfigured('GDAL is unsupported on OS "%s".' % os.name)
@@ -61,7 +45,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)
)
@@ -72,9 +56,8 @@ 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)
@@ -83,7 +66,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]
@@ -92,24 +75,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(rb"^(?P<major>\d+)\.(?P<minor>\d+)(?:\.(?P<subminor>\d+))?", ver)
m = re.match(br'^(?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()
@@ -123,7 +106,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)
@@ -136,5 +119,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)
@@ -8,15 +8,8 @@ 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 GDAL_VERSION, 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
@@ -25,13 +18,9 @@ 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)])
@@ -52,13 +41,10 @@ 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
@@ -78,20 +64,16 @@ 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])
if GDAL_VERSION >= (2, 2):
is_field_set = bool_output(lgdal.OGR_F_IsFieldSetAndNotNull, [c_void_p, c_int])
else:
is_field_set = bool_output(lgdal.OGR_F_IsFieldSet, [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
@@ -4,7 +4,9 @@
"""
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
@@ -61,7 +63,6 @@ def check_string(result, func, cargs, offset=-1, str_result=False):
lgdal.VSIFree(ptr)
return s
# ### DataSource, Layer error-checking ###
@@ -79,9 +80,7 @@ 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
@@ -97,9 +96,7 @@ 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
@@ -2,19 +2,14 @@
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,
)
@@ -60,7 +55,6 @@ 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
@@ -106,7 +100,6 @@ 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
@@ -136,7 +129,6 @@ 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
@@ -4,13 +4,8 @@ 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,
)
@@ -39,12 +34,8 @@ 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)
@@ -52,14 +43,8 @@ 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])
@@ -79,21 +64,13 @@ 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
@@ -101,23 +78,16 @@ 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)
@@ -5,20 +5,16 @@ related data structures.
from ctypes import POINTER, c_bool, c_char_p, c_double, c_int, c_void_p
from functools import partial
from django.contrib.gis.gdal.libgdal import std_call
from django.contrib.gis.gdal.libgdal import GDAL_VERSION, 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
# https://gdal.org/api/raster_c_api.html
# https://gdal.org/doxygen/gdalwarper_8h.html
# https://gdal.org/api/gdal_utils.html
# https://gdal.org/gdal_8h.html
# https://gdal.org/gdalwarper_8h.html
# https://www.gdal.org/gdal__utils_8h.html
# Prepare partial functions that use cpl error codes
void_output = partial(void_output, cpl=True)
@@ -26,152 +22,87 @@ 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_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)
if GDAL_VERSION >= (2, 1):
get_ds_info = const_string_output(std_call('GDALInfo'), [c_void_p, c_void_p])
else:
get_ds_info = None
# 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])
if GDAL_VERSION >= (2, 1):
delete_band_nodata_value = void_output(std_call('GDALDeleteRasterNoDataValue'), [c_void_p])
else:
delete_band_nodata_value = None
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])
# https://gdal.org/cpl__vsi_8h.html
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])
@@ -2,11 +2,7 @@ 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,
)
@@ -29,18 +25,14 @@ 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)
@@ -50,9 +42,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])
@@ -66,37 +58,20 @@ 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'
)
# Memory leak fixed in GDAL 1.5; still exists in 1.4.
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])
@@ -104,7 +79,5 @@ 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)
@@ -7,10 +7,7 @@ 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,
)
@@ -18,7 +15,6 @@ 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)
@@ -83,14 +79,8 @@ 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:
@@ -162,9 +152,11 @@ class GDALBand(GDALRasterBase):
Set the nodata value for this band.
"""
if value is None:
if not capi.delete_band_nodata_value:
raise ValueError('GDAL >= 2.1 required to delete nodata values.')
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()
@@ -198,10 +190,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])
@@ -216,28 +208,15 @@ 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:
@@ -270,4 +249,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)
@@ -6,7 +6,6 @@ class GDALRasterBase(GDALBase):
"""
Attributes that exist on both GDALRaster and GDALBand.
"""
@property
def metadata(self):
"""
@@ -16,7 +15,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)
@@ -39,7 +38,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
@@ -49,12 +48,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
@@ -66,12 +65,11 @@ 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,
)
@@ -1,22 +1,24 @@
"""
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
# See https://www.gdal.org/gdal_8h.html#a22e22ce0a55036a96f652765793fb7a4
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.
@@ -27,57 +29,44 @@ 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
# See https://www.gdal.org/gdal_8h.html#ace76452d94514561fffa8ea1d2a5968c
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', # Greyscale
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"
# Fixed base path for buffer-based GDAL in-memory files.
VSI_MEM_FILESYSTEM_BASE_PATH = "/vsimem/"
VSI_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.)
@@ -3,14 +3,7 @@ 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
@@ -19,11 +12,8 @@ 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_BASE_PATH, VSI_TAKE_BUFFER_OWNERSHIP,
)
from django.contrib.gis.gdal.srs import SpatialReference, SRSException
from django.contrib.gis.geometry import json_regex
@@ -33,9 +23,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):
@@ -70,7 +60,6 @@ class GDALRaster(GDALRasterBase):
"""
Wrap a raster GDAL Data Source object.
"""
destructor = capi.close_ds
def __init__(self, ds_input, write=False):
@@ -84,8 +73,9 @@ 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_BASE_PATH) and
not os.path.exists(ds_input)
):
raise GDALException(
'Unable to read raster source input "%s".' % ds_input
@@ -94,9 +84,7 @@ 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
@@ -107,7 +95,7 @@ class GDALRaster(GDALRasterBase):
# deleted.
self._ds_input = c_buffer(ds_input)
# Create random name to reference in vsimem filesystem.
vsi_path = os.path.join(VSI_MEM_FILESYSTEM_BASE_PATH, str(uuid.uuid4()))
vsi_path = os.path.join(VSI_FILESYSTEM_BASE_PATH, str(uuid.uuid4()))
# Create vsimem file from buffer.
capi.create_vsi_file_from_mem_buffer(
force_bytes(vsi_path),
@@ -121,36 +109,30 @@ 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)
@@ -160,54 +142,51 @@ 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:
@@ -222,7 +201,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):
"""
@@ -233,16 +212,12 @@ 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)
):
if not self.is_vsi_based:
return None
# Prepare an integer that will contain the buffer length.
out_length = c_int()
@@ -257,7 +232,7 @@ class GDALRaster(GDALRasterBase):
@cached_property
def is_vsi_based(self):
return self._ptr and self.name.startswith(VSI_FILESYSTEM_PREFIX)
return self._ptr and self.name.startswith(VSI_FILESYSTEM_BASE_PATH)
@property
def name(self):
@@ -298,7 +273,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
@@ -314,7 +289,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()
@@ -348,7 +323,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))
@@ -359,21 +334,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):
@@ -395,7 +370,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.
@@ -413,23 +388,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)
@@ -439,16 +414,10 @@ 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
@@ -460,10 +429,10 @@ 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()))
clone_name = os.path.join(VSI_FILESYSTEM_BASE_PATH, str(uuid.uuid4()))
return GDALRaster(
capi.copy_ds(
self.driver._ptr,
@@ -477,9 +446,8 @@ 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.
@@ -493,39 +461,35 @@ 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)
@@ -536,4 +500,6 @@ class GDALRaster(GDALRasterBase):
Return information about this raster in a string format equivalent
to the output of the gdalinfo command line utility.
"""
if not capi.get_ds_info:
raise ValueError('GDAL ≥ 2.1 is required for using the info property.')
return capi.get_ds_info(self.ptr, None).decode()
@@ -43,14 +43,13 @@ class AxisOrder(IntEnum):
class SpatialReference(GDALBase):
"""
A wrapper for the OGRSpatialReference object. According to the GDAL web site,
A wrapper for the OGRSpatialReference object. According to the GDAL Web site,
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
@@ -59,58 +58,56 @@ 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):
@@ -131,8 +128,7 @@ class SpatialReference(GDALBase):
4326
>>> print(srs['TOWGS84', 4]) # the fourth value in this wkt
0
>>> # For the units authority, have to use the pipe symbole.
>>> print(srs['UNIT|AUTHORITY'])
>>> print(srs['UNIT|AUTHORITY']) # For the units authority, have to use the pipe symbole.
EPSG
>>> print(srs['UNIT|AUTHORITY', 1]) # The authority value for the units
9122
@@ -192,11 +188,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
@@ -204,7 +200,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
@@ -337,7 +333,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))
@@ -348,10 +344,8 @@ 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
@@ -11,7 +11,7 @@ downloaded from MaxMind at http://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,6 +19,5 @@ except ImportError:
HAS_GEOIP2 = False
else:
from .base import GeoIP2, GeoIP2Exception
HAS_GEOIP2 = True
__all__ += ["GeoIP2", "GeoIP2Exception"]
__all__ += ['GeoIP2', 'GeoIP2Exception']
@@ -11,9 +11,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'),
}
@@ -33,13 +33,11 @@ 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
@@ -72,31 +70,29 @@ class GeoIP2:
if cache in self.cache_options:
self._cache = cache
else:
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
@@ -104,20 +100,18 @@ 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):
@@ -137,37 +131,27 @@ 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, country=False, 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 country and not self._country:
raise GeoIP2Exception(
"Invalid GeoIP country data file: %s" % self._country_file
)
raise GeoIP2Exception('Invalid GeoIP country data file: %s' % self._country_file)
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:
@@ -189,12 +173,12 @@ class GeoIP2:
def country_code(self, query):
"Return the country code for the given IP Address or FQDN."
enc_query = self._check_query(query, city_or_country=True)
return self.country(enc_query)["country_code"]
return self.country(enc_query)['country_code']
def country_name(self, query):
"Return the country name for the given IP Address or FQDN."
enc_query = self._check_query(query, city_or_country=True)
return self.country(enc_query)["country_name"]
return self.country(enc_query)['country_name']
def country(self, query):
"""
@@ -207,7 +191,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
@@ -220,14 +204,13 @@ 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."
ll = self.lon_lat(query)
if ll:
from django.contrib.gis.geos import Point
return Point(ll, srid=4326)
else:
return None
@@ -237,10 +220,7 @@ 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):
@@ -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,
}
@@ -4,14 +4,14 @@ 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)
# library. Not a substitute for good Web security programming practices.
hex_regex = _lazy_re_compile(r'^[0-9A-F]+$', re.I)
wkt_regex = _lazy_re_compile(
r"^(SRID=(?P<srid>\-?\d+);)?"
r"(?P<wkt>"
r"(?P<type>POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|"
r"MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)"
r"[ACEGIMLONPSRUTYZ\d,\.\-\+\(\) ]+)$",
re.I,
r'^(SRID=(?P<srid>\-?\d+);)?'
r'(?P<wkt>'
r'(?P<type>POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|'
r'MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)'
r'[ACEGIMLONPSRUTYZ\d,\.\-\+\(\) ]+)$',
re.I
)
json_regex = _lazy_re_compile(r"^(\s+)?\{.*}(\s+)?$", re.DOTALL)
json_regex = _lazy_re_compile(r'^(\s+)?\{.*}(\s+)?$', re.DOTALL)
@@ -3,10 +3,7 @@ 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
@@ -2,6 +2,8 @@
This module houses the Geometry Collection objects:
GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon
"""
from ctypes import byref, c_int, c_uint
from django.contrib.gis.geos import prototypes as capi
from django.contrib.gis.geos.geometry import GEOSGeometry, LinearGeometryMixin
from django.contrib.gis.geos.libgeos import GEOM_PTR
@@ -46,15 +48,12 @@ 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
]
)
return capi.create_collection(self._typeid, geoms, length)
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(c_int(self._typeid), byref(geoms), c_uint(length))
def _get_single_internal(self, index):
return capi.get_geomn(self.ptr, index)
@@ -62,9 +61,7 @@ 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."
@@ -81,13 +78,12 @@ 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
@@ -109,12 +105,4 @@ 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)
@@ -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,9 +50,7 @@ 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
@@ -61,7 +59,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)
@@ -69,7 +67,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."
@@ -182,13 +180,11 @@ 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):
@@ -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()
@@ -14,7 +14,9 @@ from django.contrib.gis.geos.error import GEOSException
from django.contrib.gis.geos.libgeos import GEOM_PTR
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
@@ -36,15 +38,12 @@ 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,
@@ -63,9 +62,7 @@ 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):
"""
@@ -88,7 +85,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):
@@ -107,7 +104,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
@@ -120,17 +117,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(rb"SRID=(?P<srid>\-?\d+)", srid_part)
match = re.match(br'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
@@ -152,11 +149,7 @@ 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))
@@ -321,7 +314,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):
@@ -360,7 +353,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):
@@ -393,7 +386,6 @@ class GEOSGeometryBase(GEOSBase):
Return GeoJSON representation of this Geometry.
"""
return self.ogr.json
geojson = json
@property
@@ -418,7 +410,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):
@@ -492,7 +484,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):
@@ -514,9 +506,7 @@ 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.
@@ -525,9 +515,7 @@ 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
@@ -618,7 +606,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
@@ -628,7 +616,6 @@ class GEOSGeometryBase(GEOSBase):
(xmin, ymin, xmax, ymax).
"""
from .point import Point
env = self.envelope
if isinstance(env, Point):
xmin, ymin = env.tuple
@@ -655,7 +642,6 @@ class LinearGeometryMixin:
"""
Used for LineString and MultiLineString.
"""
def interpolate(self, distance):
return self._topology(capi.geos_interpolate(self.ptr, distance))
@@ -664,16 +650,14 @@ 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
@@ -717,9 +701,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))
@@ -729,7 +713,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
@@ -739,14 +723,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.
@@ -5,13 +5,10 @@ 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
@@ -15,14 +15,13 @@ 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
@@ -30,12 +29,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)
@@ -52,7 +51,8 @@ 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,7 +135,6 @@ class GEOSFuncFactory:
"""
Lazy loading of GEOS functions.
"""
argtypes = None
restype = None
errcheck = None
@@ -155,7 +154,6 @@ 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
@@ -29,15 +29,11 @@ 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:
@@ -46,8 +42,7 @@ 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,
@@ -58,7 +53,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:
@@ -68,15 +63,13 @@ 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.
@@ -129,21 +122,20 @@ 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):
@@ -189,5 +181,7 @@ 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
@@ -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,9 +72,7 @@ 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)
@@ -93,9 +91,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)
@@ -110,28 +108,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:
@@ -181,16 +179,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"
@@ -219,9 +217,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)
@@ -234,19 +232,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)
@@ -261,14 +259,13 @@ 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)
@@ -284,20 +281,19 @@ 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)
@@ -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,9 +47,7 @@ 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):
@@ -64,7 +62,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)
@@ -86,7 +84,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)
@@ -144,7 +142,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. ###
@@ -1,3 +1,5 @@
from ctypes import byref, c_uint
from django.contrib.gis.geos import prototypes as capi
from django.contrib.gis.geos.geometry import GEOSGeometry
from django.contrib.gis.geos.libgeos import GEOM_PTR
@@ -32,8 +34,7 @@ class Polygon(GEOSGeometry):
ext_ring, *init_holes = args
n_holes = len(init_holes)
# If initialized as Polygon(shell, (LinearRing, LinearRing))
# [for backward-compatibility]
# If initialized as Polygon(shell, (LinearRing, LinearRing)) [for backward-compatibility]
if n_holes == 1 and isinstance(init_holes[0], (tuple, list)):
if not init_holes[0]:
init_holes = ()
@@ -60,10 +61,8 @@ 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 ###
@@ -86,11 +85,12 @@ class Polygon(GEOSGeometry):
n_holes = length - 1
if n_holes:
holes_param = (GEOM_PTR * n_holes)(*[self._clone(r) for r in rings])
holes = (GEOM_PTR * n_holes)(*[self._clone(r) for r in rings])
holes_param = byref(holes)
else:
holes_param = None
return capi.create_polygon(shell, holes_param, n_holes)
return capi.create_polygon(shell, holes_param, c_uint(n_holes))
def _clone(self, g):
if isinstance(g, GEOM_PTR):
@@ -98,14 +98,8 @@ 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
@@ -144,9 +138,7 @@ 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
@@ -174,17 +166,13 @@ 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)
@@ -8,7 +8,6 @@ class PreparedGeometry(GEOSBase):
At the moment this includes the contains covers, and intersects
operations.
"""
ptr_type = capi.PREPGEOM_PTR
destructor = capi.prepared_destroy
@@ -18,7 +17,6 @@ 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)
@@ -5,61 +5,22 @@
"""
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_normalize,
geos_set_srid,
geos_type,
geos_typeid,
get_dims,
get_extring,
get_geomn,
get_intring,
get_nrings,
get_num_coords,
create_collection, create_empty_polygon, create_linearring,
create_linestring, create_point, create_polygon, destroy_geom, geom_clone,
geos_get_srid, 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
@@ -1,14 +1,16 @@
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
@@ -47,9 +49,7 @@ 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,28 +68,26 @@ 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)])
@@ -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,19 +29,14 @@ 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
@@ -53,9 +48,7 @@ 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):
@@ -65,9 +58,7 @@ 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.
@@ -84,10 +75,7 @@ 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
@@ -1,10 +1,8 @@
from ctypes import POINTER, c_char_p, c_int, c_ubyte, c_uint
from ctypes import POINTER, c_char_p, c_int, c_ubyte
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.
@@ -46,45 +44,38 @@ class StringFromGeom(GEOSFuncFactory):
# ### ctypes prototypes ###
# The GEOS geometry type, typeid, num_coordinates and number of geometries
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_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],
)
create_empty_polygon = GeomOutput("GEOSGeom_createEmptyPolygon", argtypes=[])
create_collection = GeomOutput(
"GEOSGeom_createCollection",
argtypes=[c_int, POINTER(GEOM_PTR), c_uint],
)
# Polygon and collection creation routines are special and will not
# have their argument types defined.
create_polygon = GeomOutput('GEOSGeom_createPolygon')
create_empty_polygon = GeomOutput('GEOSGeom_createEmptyPolygon')
create_collection = GeomOutput('GEOSGeom_createCollection')
# 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])
@@ -3,14 +3,10 @@ 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
@@ -39,43 +35,33 @@ 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):
@@ -89,12 +75,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.
@@ -104,8 +90,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.
@@ -118,22 +104,17 @@ 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()
@@ -141,7 +122,6 @@ class IOBase(GEOSBase):
# __del__ is too late (import error).
self.destructor.func
# ### Base WKB/WKT Reading and Writing objects ###
@@ -203,7 +183,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
@@ -223,9 +203,7 @@ 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)
@@ -243,37 +221,34 @@ 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 ###
@@ -284,9 +259,7 @@ 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)
@@ -299,7 +272,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.
@@ -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,7 +16,6 @@ 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)
@@ -24,11 +23,9 @@ 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]
)
@@ -22,26 +22,22 @@ 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')
@@ -1,11 +1,13 @@
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.
@@ -15,12 +17,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')
@@ -1,12 +1,13 @@
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
@@ -30,11 +31,10 @@ 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
@@ -6,9 +6,7 @@ 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
@@ -21,52 +19,35 @@ 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_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_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,
)
geos_interpolate_normalized = Topology(
"GEOSInterpolateNormalized", argtypes=[GEOM_PTR, c_double]
'GEOSProjectNormalized', argtypes=[GEOM_PTR, GEOM_PTR], restype=c_double, errcheck=check_minus_one
)
geos_interpolate_normalized = Topology('GEOSInterpolateNormalized', argtypes=[GEOM_PTR, c_double])
@@ -4,7 +4,6 @@
# Anastasiadis Stavros <anastasiadis.st00@gmail.com>, 2014
# Dimitris Glezos <glezos@transifex.com>, 2011
# Elena Andreou <helenaandreou.ha@gmail.com>, 2016
# Fotis Athineos <fotis@transifex.com>, 2021
# Kostas Papadimitriou <vinilios@gmail.com>, 2012
# Nick Mavrakis <mavrakis.n@gmail.com>, 2016
# Pãnoș <panos.laganakos@gmail.com>, 2016
@@ -12,9 +11,9 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2021-08-04 06:23+0000\n"
"Last-Translator: Fotis Athineos <fotis@transifex.com>\n"
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
"PO-Revision-Date: 2017-09-23 18:54+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Greek (http://www.transifex.com/django/django/language/el/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -29,9 +28,9 @@ msgid "The base GIS field."
msgstr "Το βασικό GIS πεδίο."
msgid ""
"The base Geometry field maps to the OpenGIS Specification Geometry type."
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
msgstr ""
"Το βασικό Γεωμετρικό πεδίο αντιστοιχεί στον τύπο της Γεωμετρικής "
"Το βασικό Γεωμετρικό πεδίο -- αντιστοιχεί στον τύπο της Γεωμετρικής "
"Προδιαγραφής OpenGIS."
msgid "Point"
@@ -90,5 +89,5 @@ msgid "No feeds are registered."
msgstr "Δεν υπάρχουν εγγεγραμμένες ροές ειδήσεων."
#, python-format
msgid "Slug %r isnt registered."
msgid "Slug %r isn't registered."
msgstr "Το slug %r δεν έχει καταχωρηθεί."
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ka\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "GIS"
msgstr ""

Some files were not shown because too many files have changed in this diff Show More