before send to remote
This commit is contained in:
0
env/lib/python3.8/site-packages/django/contrib/gis/__init__.py
vendored
Normal file
0
env/lib/python3.8/site-packages/django/contrib/gis/__init__.py
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/apps.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/apps.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/feeds.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/feeds.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/geometry.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/geometry.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/measure.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/measure.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/ptr.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/ptr.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/shortcuts.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/shortcuts.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/views.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/__pycache__/views.cpython-38.pyc
vendored
Normal file
Binary file not shown.
34
env/lib/python3.8/site-packages/django/contrib/gis/admin/__init__.py
vendored
Normal file
34
env/lib/python3.8/site-packages/django/contrib/gis/admin/__init__.py
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
from django.contrib.admin import (
|
||||
HORIZONTAL,
|
||||
VERTICAL,
|
||||
AdminSite,
|
||||
ModelAdmin,
|
||||
StackedInline,
|
||||
TabularInline,
|
||||
action,
|
||||
autodiscover,
|
||||
display,
|
||||
register,
|
||||
site,
|
||||
)
|
||||
from django.contrib.gis.admin.options import GeoModelAdmin, GISModelAdmin, OSMGeoAdmin
|
||||
from django.contrib.gis.admin.widgets import OpenLayersWidget
|
||||
|
||||
__all__ = [
|
||||
"HORIZONTAL",
|
||||
"VERTICAL",
|
||||
"AdminSite",
|
||||
"ModelAdmin",
|
||||
"StackedInline",
|
||||
"TabularInline",
|
||||
"action",
|
||||
"autodiscover",
|
||||
"display",
|
||||
"register",
|
||||
"site",
|
||||
"GISModelAdmin",
|
||||
# RemovedInDjango50Warning.
|
||||
"GeoModelAdmin",
|
||||
"OpenLayersWidget",
|
||||
"OSMGeoAdmin",
|
||||
]
|
BIN
env/lib/python3.8/site-packages/django/contrib/gis/admin/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/admin/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/admin/__pycache__/options.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/admin/__pycache__/options.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/admin/__pycache__/widgets.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/admin/__pycache__/widgets.cpython-38.pyc
vendored
Normal file
Binary file not shown.
180
env/lib/python3.8/site-packages/django/contrib/gis/admin/options.py
vendored
Normal file
180
env/lib/python3.8/site-packages/django/contrib/gis/admin/options.py
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
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
|
||||
default_lat = 0
|
||||
default_zoom = 4
|
||||
display_wkt = False
|
||||
display_srid = False
|
||||
extra_js = []
|
||||
num_zoom = 18
|
||||
max_zoom = False
|
||||
min_zoom = False
|
||||
units = False
|
||||
max_resolution = False
|
||||
max_extent = False
|
||||
modifiable = True
|
||||
mouse_position = True
|
||||
scale_text = True
|
||||
layerswitcher = True
|
||||
scrollable = True
|
||||
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"
|
||||
)
|
||||
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"}
|
||||
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."
|
||||
return super().media + Media(js=[self.openlayers_url] + self.extra_js)
|
||||
|
||||
def formfield_for_dbfield(self, db_field, request, **kwargs):
|
||||
"""
|
||||
Overloaded from ModelAdmin so that an OpenLayersWidget is used
|
||||
for viewing/editing 2D GeometryFields (OpenLayers 2 does not support
|
||||
3D editing).
|
||||
"""
|
||||
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)
|
||||
return db_field.formfield(**kwargs)
|
||||
else:
|
||||
return super().formfield_for_dbfield(db_field, request, **kwargs)
|
||||
|
||||
def get_map_widget(self, db_field):
|
||||
"""
|
||||
Return a subclass of the OpenLayersWidget (or whatever was specified
|
||||
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",
|
||||
)
|
||||
if is_collection:
|
||||
if db_field.geom_type == "GEOMETRYCOLLECTION":
|
||||
collection_type = "Any"
|
||||
else:
|
||||
collection_type = OGRGeomType(db_field.geom_type.replace("MULTI", ""))
|
||||
else:
|
||||
collection_type = "None"
|
||||
|
||||
class OLMap(self.widget):
|
||||
template_name = self.map_template
|
||||
geom_type = db_field.geom_type
|
||||
|
||||
wms_options = ""
|
||||
if self.wms_options:
|
||||
wms_options = ["%s: '%s'" % pair for pair in self.wms_options.items()]
|
||||
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,
|
||||
}
|
||||
|
||||
return OLMap
|
||||
|
||||
|
||||
# RemovedInDjango50Warning.
|
||||
class OSMGeoAdmin(GeoModelAdmin):
|
||||
map_template = "gis/admin/osm.html"
|
||||
num_zoom = 20
|
||||
map_srid = spherical_mercator_srid
|
||||
max_extent = "-20037508,-20037508,20037508,20037508"
|
||||
max_resolution = "156543.0339"
|
||||
point_zoom = num_zoom - 6
|
||||
units = "m"
|
135
env/lib/python3.8/site-packages/django/contrib/gis/admin/widgets.py
vendored
Normal file
135
env/lib/python3.8/site-packages/django/contrib/gis/admin/widgets.py
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
# RemovedInDjango50Warning.
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
from django.contrib.gis.gdal import GDALException
|
||||
from django.contrib.gis.geos import GEOSException, GEOSGeometry
|
||||
from django.forms.widgets import Textarea
|
||||
from django.utils import translation
|
||||
from django.utils.deprecation import RemovedInDjango50Warning
|
||||
|
||||
# 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")
|
||||
|
||||
|
||||
class OpenLayersWidget(Textarea):
|
||||
"""
|
||||
Render an OpenLayers map using the WKT of the geometry.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
warnings.warn(
|
||||
"django.contrib.gis.admin.OpenLayersWidget is deprecated.",
|
||||
RemovedInDjango50Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
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"]
|
||||
else:
|
||||
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"] = ""
|
||||
|
||||
# If a string reaches here (via a validation error on another
|
||||
# field) then just reconstruct the Geometry.
|
||||
if value and isinstance(value, str):
|
||||
try:
|
||||
value = GEOSGeometry(value)
|
||||
except (GEOSException, ValueError) as err:
|
||||
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"
|
||||
):
|
||||
value = None
|
||||
|
||||
# Constructing the dictionary of the 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
|
||||
# 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
|
||||
|
||||
if value:
|
||||
# Transforming the geometry to the projection used on the
|
||||
# OpenLayers map.
|
||||
srid = self.params["srid"]
|
||||
if value.srid != srid:
|
||||
try:
|
||||
ogr = value.ogr
|
||||
ogr.transform(srid)
|
||||
wkt = ogr.wkt
|
||||
except GDALException as err:
|
||||
logger.error(
|
||||
"Error transforming geometry from srid '%s' to srid '%s' (%s)",
|
||||
value.srid,
|
||||
srid,
|
||||
err,
|
||||
)
|
||||
wkt = ""
|
||||
else:
|
||||
wkt = value.wkt
|
||||
|
||||
# Setting the parameter WKT with that of the transformed
|
||||
# geometry.
|
||||
self.params["wkt"] = wkt
|
||||
|
||||
self.params.update(geo_context)
|
||||
return self.params
|
||||
|
||||
def map_options(self):
|
||||
"""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
|
||||
|
||||
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),
|
||||
]
|
||||
|
||||
# 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":
|
||||
value = ol_projection(self.params[param_name])
|
||||
elif option_type == "bounds":
|
||||
value = ol_bounds(self.params[param_name])
|
||||
elif option_type in (float, int):
|
||||
value = self.params[param_name]
|
||||
elif option_type in (str,):
|
||||
value = '"%s"' % self.params[param_name]
|
||||
else:
|
||||
raise TypeError
|
||||
map_options[js_name] = value
|
||||
return map_options
|
14
env/lib/python3.8/site-packages/django/contrib/gis/apps.py
vendored
Normal file
14
env/lib/python3.8/site-packages/django/contrib/gis/apps.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
from django.apps import AppConfig
|
||||
from django.core import serializers
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class GISConfig(AppConfig):
|
||||
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"
|
||||
)
|
0
env/lib/python3.8/site-packages/django/contrib/gis/db/__init__.py
vendored
Normal file
0
env/lib/python3.8/site-packages/django/contrib/gis/db/__init__.py
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/db/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/db/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
Binary file not shown.
0
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/__init__.py
vendored
Normal file
0
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/__init__.py
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/__pycache__/utils.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/__pycache__/utils.cpython-38.pyc
vendored
Normal file
Binary file not shown.
0
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/base/__init__.py
vendored
Normal file
0
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/base/__init__.py
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
26
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/base/adapter.py
vendored
Normal file
26
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/base/adapter.py
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
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
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.wkt, self.srid))
|
||||
|
||||
def __str__(self):
|
||||
return self.wkt
|
||||
|
||||
@classmethod
|
||||
def _fix_polygon(cls, poly):
|
||||
# Hook for Oracle.
|
||||
return poly
|
111
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/base/features.py
vendored
Normal file
111
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/base/features.py
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
import re
|
||||
|
||||
from django.contrib.gis.db import models
|
||||
|
||||
|
||||
class BaseSpatialFeatures:
|
||||
gis_enabled = True
|
||||
|
||||
# Does the database contain a SpatialRefSys model to store SRID information?
|
||||
has_spatialrefsys_table = True
|
||||
|
||||
# Does the backend support the django.contrib.gis.utils.add_srs_entry() utility?
|
||||
supports_add_srs_entry = True
|
||||
# Does the backend introspect GeometryField to its subtypes?
|
||||
supports_geometry_field_introspection = True
|
||||
|
||||
# Does the database have a geography type?
|
||||
supports_geography = False
|
||||
# Does the backend support storing 3D geometries?
|
||||
supports_3d_storage = False
|
||||
# Reference implementation of 3D functions is:
|
||||
# https://postgis.net/docs/PostGIS_Special_Functions_Index.html#PostGIS_3D_Functions
|
||||
supports_3d_functions = False
|
||||
# Does the database support SRID transform operations?
|
||||
supports_transform = True
|
||||
# Can geometry fields be null?
|
||||
supports_null_geometries = True
|
||||
# Are empty geometries supported?
|
||||
supports_empty_geometries = False
|
||||
# Can the function be applied on geodetic coordinate systems?
|
||||
supports_distance_geodetic = True
|
||||
supports_length_geodetic = True
|
||||
supports_perimeter_geodetic = False
|
||||
supports_area_geodetic = True
|
||||
# Is the database able to count vertices on polygons (with `num_points`)?
|
||||
supports_num_points_poly = True
|
||||
|
||||
# Does the backend support expressions for specifying distance in the
|
||||
# dwithin lookup?
|
||||
supports_dwithin_distance_expr = True
|
||||
|
||||
# Does the database have raster support?
|
||||
supports_raster = False
|
||||
|
||||
# Does the database support a unique index on geometry fields?
|
||||
supports_geometry_field_unique_index = True
|
||||
|
||||
# Can SchemaEditor alter geometry fields?
|
||||
can_alter_geometry_field = True
|
||||
|
||||
# Do the database functions/aggregates support the tolerance parameter?
|
||||
supports_tolerance_parameter = False
|
||||
|
||||
# Set of options that AsGeoJSON() doesn't support.
|
||||
unsupported_geojson_options = {}
|
||||
|
||||
# Does Intersection() return None (rather than an empty GeometryCollection)
|
||||
# for empty results?
|
||||
empty_intersection_returns_none = True
|
||||
|
||||
@property
|
||||
def supports_bbcontains_lookup(self):
|
||||
return "bbcontains" in self.connection.ops.gis_operators
|
||||
|
||||
@property
|
||||
def supports_contained_lookup(self):
|
||||
return "contained" in self.connection.ops.gis_operators
|
||||
|
||||
@property
|
||||
def supports_crosses_lookup(self):
|
||||
return "crosses" in self.connection.ops.gis_operators
|
||||
|
||||
@property
|
||||
def supports_distances_lookups(self):
|
||||
return self.has_Distance_function
|
||||
|
||||
@property
|
||||
def supports_dwithin_lookup(self):
|
||||
return "dwithin" in self.connection.ops.gis_operators
|
||||
|
||||
@property
|
||||
def supports_relate_lookup(self):
|
||||
return "relate" in self.connection.ops.gis_operators
|
||||
|
||||
@property
|
||||
def supports_isvalid_lookup(self):
|
||||
return self.has_IsValid_function
|
||||
|
||||
# Is the aggregate supported by the database?
|
||||
@property
|
||||
def supports_collect_aggr(self):
|
||||
return models.Collect not in self.connection.ops.disallowed_aggregates
|
||||
|
||||
@property
|
||||
def supports_extent_aggr(self):
|
||||
return models.Extent not in self.connection.ops.disallowed_aggregates
|
||||
|
||||
@property
|
||||
def supports_make_line_aggr(self):
|
||||
return models.MakeLine not in self.connection.ops.disallowed_aggregates
|
||||
|
||||
@property
|
||||
def supports_union_aggr(self):
|
||||
return models.Union not in self.connection.ops.disallowed_aggregates
|
||||
|
||||
def __getattr__(self, 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
|
||||
raise AttributeError
|
140
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/base/models.py
vendored
Normal file
140
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/base/models.py
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
from django.contrib.gis import gdal
|
||||
|
||||
|
||||
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"):
|
||||
# Returning a clone of the cached SpatialReference object.
|
||||
return self._srs.clone()
|
||||
else:
|
||||
# Attempting to cache a SpatialReference object.
|
||||
|
||||
# Trying to get from WKT first.
|
||||
try:
|
||||
self._srs = gdal.SpatialReference(self.wkt)
|
||||
return self.srs
|
||||
except Exception as e:
|
||||
msg = e
|
||||
|
||||
try:
|
||||
self._srs = gdal.SpatialReference(self.proj4text)
|
||||
return self.srs
|
||||
except Exception as e:
|
||||
msg = e
|
||||
|
||||
raise Exception(
|
||||
"Could not get OSR SpatialReference from WKT: %s\nError:\n%s"
|
||||
% (self.wkt, msg)
|
||||
)
|
||||
|
||||
@property
|
||||
def ellipsoid(self):
|
||||
"""
|
||||
Return a tuple of the ellipsoid parameters:
|
||||
(semimajor axis, semiminor axis, and inverse flattening).
|
||||
"""
|
||||
return self.srs.ellipsoid
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"Return the projection name."
|
||||
return self.srs.name
|
||||
|
||||
@property
|
||||
def spheroid(self):
|
||||
"Return the spheroid name for this spatial reference."
|
||||
return self.srs["spheroid"]
|
||||
|
||||
@property
|
||||
def datum(self):
|
||||
"Return the datum for this spatial reference."
|
||||
return self.srs["datum"]
|
||||
|
||||
@property
|
||||
def projected(self):
|
||||
"Is this Spatial Reference projected?"
|
||||
return self.srs.projected
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
"Is this Spatial Reference local?"
|
||||
return self.srs.local
|
||||
|
||||
@property
|
||||
def geographic(self):
|
||||
"Is this Spatial Reference geographic?"
|
||||
return self.srs.geographic
|
||||
|
||||
@property
|
||||
def linear_name(self):
|
||||
"Return the linear units name."
|
||||
return self.srs.linear_name
|
||||
|
||||
@property
|
||||
def linear_units(self):
|
||||
"Return the linear units."
|
||||
return self.srs.linear_units
|
||||
|
||||
@property
|
||||
def angular_name(self):
|
||||
"Return the name of the angular units."
|
||||
return self.srs.angular_name
|
||||
|
||||
@property
|
||||
def angular_units(self):
|
||||
"Return the angular units."
|
||||
return self.srs.angular_units
|
||||
|
||||
@property
|
||||
def units(self):
|
||||
"Return a tuple of the units and the name."
|
||||
if self.projected or self.local:
|
||||
return (self.linear_units, self.linear_name)
|
||||
elif self.geographic:
|
||||
return (self.angular_units, self.angular_name)
|
||||
else:
|
||||
return (None, None)
|
||||
|
||||
@classmethod
|
||||
def get_units(cls, wkt):
|
||||
"""
|
||||
Return a tuple of (unit_value, unit_name) for the given WKT without
|
||||
using any of the database fields.
|
||||
"""
|
||||
return gdal.SpatialReference(wkt).units
|
||||
|
||||
@classmethod
|
||||
def get_spheroid(cls, wkt, string=True):
|
||||
"""
|
||||
Class method used by GeometryField on initialization to
|
||||
retrieve the `SPHEROID[..]` parameters from the given WKT.
|
||||
"""
|
||||
srs = gdal.SpatialReference(wkt)
|
||||
sphere_params = srs.ellipsoid
|
||||
sphere_name = srs["spheroid"]
|
||||
|
||||
if not string:
|
||||
return sphere_name, sphere_params
|
||||
else:
|
||||
# `string` parameter used to place in format acceptable by PostGIS
|
||||
if len(sphere_params) == 3:
|
||||
radius, flattening = sphere_params[0], sphere_params[2]
|
||||
else:
|
||||
radius, flattening = sphere_params
|
||||
return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening)
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Return the string representation, a 'pretty' OGC WKT.
|
||||
"""
|
||||
return str(self.srs)
|
206
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/base/operations.py
vendored
Normal file
206
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/base/operations.py
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
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.db import NotSupportedError
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
class BaseSpatialOperations:
|
||||
# Quick booleans for the type of this spatial backend, and
|
||||
# an attribute for the spatial database version tuple (if applicable)
|
||||
postgis = False
|
||||
spatialite = False
|
||||
mariadb = False
|
||||
mysql = False
|
||||
oracle = False
|
||||
spatial_version = None
|
||||
|
||||
# How the geometry column should be selected.
|
||||
select = "%s"
|
||||
|
||||
@cached_property
|
||||
def select_extent(self):
|
||||
return self.select
|
||||
|
||||
# Aggregates
|
||||
disallowed_aggregates = ()
|
||||
|
||||
geom_func_prefix = ""
|
||||
|
||||
# Mapping between Django function names and backend names, when names do not
|
||||
# match; used in spatial_function_name().
|
||||
function_names = {}
|
||||
|
||||
# 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",
|
||||
}
|
||||
|
||||
# Constructors
|
||||
from_text = False
|
||||
|
||||
# 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."
|
||||
)
|
||||
|
||||
def convert_extent3d(self, box, srid):
|
||||
raise NotImplementedError(
|
||||
"Aggregate 3D extent not implemented for this spatial backend."
|
||||
)
|
||||
|
||||
# For quoting column values, rather than columns.
|
||||
def geo_quote_name(self, name):
|
||||
return "'%s'" % name
|
||||
|
||||
# GeometryField operations
|
||||
def geo_db_type(self, f):
|
||||
"""
|
||||
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"
|
||||
)
|
||||
|
||||
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."
|
||||
)
|
||||
|
||||
def get_geom_placeholder(self, f, value, compiler):
|
||||
"""
|
||||
Return the placeholder for the given geometry field with the given
|
||||
value. Depending on the spatial backend, the placeholder may contain a
|
||||
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"):
|
||||
return (
|
||||
"%s(%%s, %s)" % (self.spatial_function_name("Transform"), f.srid)
|
||||
if transform_value(value.output_field, f)
|
||||
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,
|
||||
)
|
||||
elif self.connection.features.has_spatialrefsys_table:
|
||||
return "%s(%%s,%s)" % (self.from_text, f.srid)
|
||||
else:
|
||||
# For backwards compatibility on MySQL (#27464).
|
||||
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
|
||||
)
|
||||
super().check_expression_support(expression)
|
||||
|
||||
def spatial_aggregate_name(self, agg_name):
|
||||
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
|
||||
)
|
||||
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."
|
||||
)
|
||||
|
||||
def spatial_ref_sys(self):
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseSpatialOperations must a provide spatial_ref_sys() "
|
||||
"method"
|
||||
)
|
||||
|
||||
distance_expr_for_lookup = staticmethod(Distance)
|
||||
|
||||
def get_db_converters(self, expression):
|
||||
converters = super().get_db_converters(expression)
|
||||
if isinstance(expression.output_field, GeometryField):
|
||||
converters.append(self.get_geometry_converter(expression))
|
||||
return converters
|
||||
|
||||
def get_geometry_converter(self, expression):
|
||||
raise NotImplementedError(
|
||||
"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."
|
||||
)
|
||||
else:
|
||||
units_name = field.units_name(self.connection)
|
||||
if units_name:
|
||||
return AreaMeasure.unit_attname(units_name)
|
||||
|
||||
def get_distance_att_for_field(self, field):
|
||||
dist_att = None
|
||||
if field.geodetic(self.connection):
|
||||
if self.connection.features.supports_distance_geodetic:
|
||||
dist_att = "m"
|
||||
else:
|
||||
units = field.units_name(self.connection)
|
||||
if units:
|
||||
dist_att = DistanceMeasure.unit_attname(units)
|
||||
return dist_att
|
0
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/mysql/__init__.py
vendored
Normal file
0
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/mysql/__init__.py
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/mysql/__pycache__/base.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/mysql/__pycache__/base.cpython-38.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
14
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/mysql/base.py
vendored
Normal file
14
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/mysql/base.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
from django.db.backends.mysql.base import DatabaseWrapper as MySQLDatabaseWrapper
|
||||
|
||||
from .features import DatabaseFeatures
|
||||
from .introspection import MySQLIntrospection
|
||||
from .operations import MySQLOperations
|
||||
from .schema import MySQLGISSchemaEditor
|
||||
|
||||
|
||||
class DatabaseWrapper(MySQLDatabaseWrapper):
|
||||
SchemaEditorClass = MySQLGISSchemaEditor
|
||||
# Classes instantiated in __init__().
|
||||
features_class = DatabaseFeatures
|
||||
introspection_class = MySQLIntrospection
|
||||
ops_class = MySQLOperations
|
44
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/mysql/features.py
vendored
Normal file
44
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/mysql/features.py
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures
|
||||
from django.db.backends.mysql.features import DatabaseFeatures as MySQLDatabaseFeatures
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
class DatabaseFeatures(BaseSpatialFeatures, MySQLDatabaseFeatures):
|
||||
has_spatialrefsys_table = False
|
||||
supports_add_srs_entry = False
|
||||
supports_distance_geodetic = False
|
||||
supports_length_geodetic = False
|
||||
supports_area_geodetic = False
|
||||
supports_transform = False
|
||||
supports_null_geometries = False
|
||||
supports_num_points_poly = False
|
||||
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)
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def supports_geometry_field_unique_index(self):
|
||||
# Not supported in MySQL since https://dev.mysql.com/worklog/task/?id=11808
|
||||
return self.connection.mysql_is_mariadb
|
||||
|
||||
@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,
|
||||
):
|
||||
skips.update(
|
||||
{
|
||||
"MySQL < 8 gives different results.": {
|
||||
"gis_tests.geoapp.tests.GeoLookupTest.test_disjoint_lookup",
|
||||
},
|
||||
}
|
||||
)
|
||||
return skips
|
37
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/mysql/introspection.py
vendored
Normal file
37
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/mysql/introspection.py
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
from MySQLdb.constants import FIELD_TYPE
|
||||
|
||||
from django.contrib.gis.gdal import OGRGeomType
|
||||
from django.db.backends.mysql.introspection import DatabaseIntrospection
|
||||
|
||||
|
||||
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"
|
||||
|
||||
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))
|
||||
# Increment over description info until we get to the geometry
|
||||
# column.
|
||||
for column, typ, null, key, default, extra in cursor.fetchall():
|
||||
if column == description.name:
|
||||
# Using OGRGeomType to convert from OGC name to Django field.
|
||||
# MySQL does not support 3D or SRIDs, so the field params
|
||||
# are empty.
|
||||
field_type = OGRGeomType(typ).django
|
||||
field_params = {}
|
||||
break
|
||||
return field_type, field_params
|
||||
|
||||
def supports_spatial_index(self, cursor, table_name):
|
||||
# Supported with MyISAM/Aria, or InnoDB on MySQL 5.7.5+/MariaDB.
|
||||
storage_engine = self.get_storage_engine(cursor, table_name)
|
||||
if storage_engine == "InnoDB":
|
||||
if self.connection.mysql_is_mariadb:
|
||||
return True
|
||||
return self.connection.mysql_version >= (5, 7, 5)
|
||||
return storage_engine in ("MyISAM", "Aria")
|
125
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/mysql/operations.py
vendored
Normal file
125
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/mysql/operations.py
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
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.utils import SpatialOperator
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometryBase
|
||||
from django.contrib.gis.geos.prototypes.io import wkb_r
|
||||
from django.contrib.gis.measure import Distance
|
||||
from django.db.backends.mysql.operations import DatabaseOperations
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
|
||||
name = "mysql"
|
||||
geom_func_prefix = "ST_"
|
||||
|
||||
Adapter = WKTAdapter
|
||||
|
||||
@cached_property
|
||||
def mariadb(self):
|
||||
return self.connection.mysql_is_mariadb
|
||||
|
||||
@cached_property
|
||||
def mysql(self):
|
||||
return not self.connection.mysql_is_mariadb
|
||||
|
||||
@cached_property
|
||||
def select(self):
|
||||
return self.geom_func_prefix + "AsBinary(%s)"
|
||||
|
||||
@cached_property
|
||||
def from_text(self):
|
||||
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"),
|
||||
}
|
||||
if self.connection.mysql_is_mariadb:
|
||||
operators["relate"] = SpatialOperator(func="ST_Relate")
|
||||
return operators
|
||||
|
||||
disallowed_aggregates = (
|
||||
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",
|
||||
}
|
||||
if self.connection.mysql_is_mariadb:
|
||||
unsupported.remove("PointOnSurface")
|
||||
unsupported.update({"GeoHash", "IsValid"})
|
||||
elif self.connection.mysql_version < (5, 7, 5):
|
||||
unsupported.update({"AsGeoJSON", "GeoHash", "IsValid"})
|
||||
return unsupported
|
||||
|
||||
def geo_db_type(self, f):
|
||||
return f.geom_type
|
||||
|
||||
def get_distance(self, f, value, lookup_type):
|
||||
value = value[0]
|
||||
if isinstance(value, Distance):
|
||||
if f.geodetic(self.connection):
|
||||
raise ValueError(
|
||||
"Only numeric values of degree units are allowed on "
|
||||
"geodetic distance queries."
|
||||
)
|
||||
dist_param = getattr(
|
||||
value, Distance.unit_attname(f.units_name(self.connection))
|
||||
)
|
||||
else:
|
||||
dist_param = value
|
||||
return [dist_param]
|
||||
|
||||
def get_geometry_converter(self, expression):
|
||||
read = wkb_r().read
|
||||
srid = expression.output_field.srid
|
||||
if srid == -1:
|
||||
srid = None
|
||||
geom_class = expression.output_field.geom_class
|
||||
|
||||
def converter(value, expression, connection):
|
||||
if value is not None:
|
||||
geom = GEOSGeometryBase(read(memoryview(value)), geom_class)
|
||||
if srid:
|
||||
geom.srid = srid
|
||||
return geom
|
||||
|
||||
return converter
|
88
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/mysql/schema.py
vendored
Normal file
88
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/mysql/schema.py
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
import logging
|
||||
|
||||
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")
|
||||
|
||||
|
||||
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"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.geometry_sql = []
|
||||
|
||||
def skip_default(self, field):
|
||||
# Geometry fields are stored as BLOB/TEXT, for which MySQL < 8.0.13
|
||||
# doesn't support defaults.
|
||||
if (
|
||||
isinstance(field, GeometryField)
|
||||
and not self._supports_limited_data_type_defaults
|
||||
):
|
||||
return True
|
||||
return super().skip_default(field)
|
||||
|
||||
def quote_value(self, value):
|
||||
if isinstance(value, self.connection.ops.Adapter):
|
||||
return super().quote_value(str(value))
|
||||
return super().quote_value(value)
|
||||
|
||||
def column_sql(self, model, field, include_default=False):
|
||||
column_sql = super().column_sql(model, field, include_default)
|
||||
# MySQL doesn't support spatial indexes on NULL columns
|
||||
if isinstance(field, GeometryField) and field.spatial_index and not field.null:
|
||||
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),
|
||||
}
|
||||
)
|
||||
return column_sql
|
||||
|
||||
def create_model(self, model):
|
||||
super().create_model(model)
|
||||
self.create_spatial_indexes()
|
||||
|
||||
def add_field(self, model, field):
|
||||
super().add_field(model, field)
|
||||
self.create_spatial_indexes()
|
||||
|
||||
def remove_field(self, model, field):
|
||||
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),
|
||||
}
|
||||
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,
|
||||
)
|
||||
|
||||
super().remove_field(model, field)
|
||||
|
||||
def _create_spatial_index_name(self, model, field):
|
||||
return "%s_%s_id" % (model._meta.db_table, field.column)
|
||||
|
||||
def create_spatial_indexes(self):
|
||||
for sql in self.geometry_sql:
|
||||
try:
|
||||
self.execute(sql)
|
||||
except OperationalError:
|
||||
logger.error(
|
||||
"Cannot create SPATIAL INDEX %s. Only MyISAM and (as of "
|
||||
"MySQL 5.7.5) InnoDB support them.",
|
||||
sql,
|
||||
)
|
||||
self.geometry_sql = []
|
0
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/__init__.py
vendored
Normal file
0
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/__init__.py
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
62
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/adapter.py
vendored
Normal file
62
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/adapter.py
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
from cx_Oracle import CLOB
|
||||
|
||||
from django.contrib.gis.db.backends.base.adapter import WKTAdapter
|
||||
from django.contrib.gis.geos import GeometryCollection, Polygon
|
||||
|
||||
|
||||
class OracleSpatialAdapter(WKTAdapter):
|
||||
input_size = CLOB
|
||||
|
||||
def __init__(self, geom):
|
||||
"""
|
||||
Oracle requires that polygon rings are in proper orientation. This
|
||||
affects spatial operations and an invalid orientation may cause
|
||||
failures. Correct orientations are:
|
||||
* Outer ring - counter clockwise
|
||||
* Inner ring(s) - clockwise
|
||||
"""
|
||||
if isinstance(geom, Polygon):
|
||||
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
|
||||
):
|
||||
geom = self._fix_geometry_collection(geom)
|
||||
|
||||
self.wkt = geom.wkt
|
||||
self.srid = geom.srid
|
||||
|
||||
@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)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _fix_polygon(cls, poly, clone=True):
|
||||
"""Fix single polygon orientation as described in __init__()."""
|
||||
if clone:
|
||||
poly = poly.clone()
|
||||
|
||||
if not poly.exterior_ring.is_counterclockwise:
|
||||
poly.exterior_ring = list(reversed(poly.exterior_ring))
|
||||
|
||||
for i in range(1, len(poly)):
|
||||
if poly[i].is_counterclockwise:
|
||||
poly[i] = list(reversed(poly[i]))
|
||||
|
||||
return poly
|
||||
|
||||
@classmethod
|
||||
def _fix_geometry_collection(cls, coll):
|
||||
"""
|
||||
Fix polygon orientations in geometry collections as described in
|
||||
__init__().
|
||||
"""
|
||||
coll = coll.clone()
|
||||
for i, geom in enumerate(coll):
|
||||
if isinstance(geom, Polygon):
|
||||
coll[i] = cls._fix_polygon(geom, clone=False)
|
||||
return coll
|
14
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/base.py
vendored
Normal file
14
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/base.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
from django.db.backends.oracle.base import DatabaseWrapper as OracleDatabaseWrapper
|
||||
|
||||
from .features import DatabaseFeatures
|
||||
from .introspection import OracleIntrospection
|
||||
from .operations import OracleOperations
|
||||
from .schema import OracleGISSchemaEditor
|
||||
|
||||
|
||||
class DatabaseWrapper(OracleDatabaseWrapper):
|
||||
SchemaEditorClass = OracleGISSchemaEditor
|
||||
# Classes instantiated in __init__().
|
||||
features_class = DatabaseFeatures
|
||||
introspection_class = OracleIntrospection
|
||||
ops_class = OracleOperations
|
28
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/features.py
vendored
Normal file
28
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/features.py
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures
|
||||
from django.db.backends.oracle.features import (
|
||||
DatabaseFeatures as OracleDatabaseFeatures,
|
||||
)
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
class DatabaseFeatures(BaseSpatialFeatures, OracleDatabaseFeatures):
|
||||
supports_add_srs_entry = False
|
||||
supports_geometry_field_introspection = False
|
||||
supports_geometry_field_unique_index = False
|
||||
supports_perimeter_geodetic = True
|
||||
supports_dwithin_distance_expr = False
|
||||
supports_tolerance_parameter = True
|
||||
unsupported_geojson_options = {"bbox", "crs", "precision"}
|
||||
|
||||
@cached_property
|
||||
def django_test_skips(self):
|
||||
skips = super().django_test_skips
|
||||
skips.update(
|
||||
{
|
||||
"Oracle doesn't support spatial operators in constraints.": {
|
||||
"gis_tests.gis_migrations.test_operations.OperationTests."
|
||||
"test_add_check_constraint",
|
||||
},
|
||||
}
|
||||
)
|
||||
return skips
|
47
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/introspection.py
vendored
Normal file
47
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/introspection.py
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
import cx_Oracle
|
||||
|
||||
from django.db.backends.oracle.introspection import DatabaseIntrospection
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
class OracleIntrospection(DatabaseIntrospection):
|
||||
# Associating any OBJECTVAR instances with GeometryField. This won't work
|
||||
# right on Oracle objects that aren't MDSYS.SDO_GEOMETRY, but it is the
|
||||
# only object type supported within Django anyways.
|
||||
@cached_property
|
||||
def data_types_reverse(self):
|
||||
return {
|
||||
**super().data_types_reverse,
|
||||
cx_Oracle.OBJECT: "GeometryField",
|
||||
}
|
||||
|
||||
def get_geometry_type(self, table_name, description):
|
||||
with self.connection.cursor() as cursor:
|
||||
# Querying USER_SDO_GEOM_METADATA to get the SRID and dimension information.
|
||||
try:
|
||||
cursor.execute(
|
||||
'SELECT "DIMINFO", "SRID" FROM "USER_SDO_GEOM_METADATA" '
|
||||
'WHERE "TABLE_NAME"=%s AND "COLUMN_NAME"=%s',
|
||||
(table_name.upper(), description.name.upper()),
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
except Exception as exc:
|
||||
raise Exception(
|
||||
"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"
|
||||
|
||||
# Getting the field parameters.
|
||||
field_params = {}
|
||||
dim, srid = row
|
||||
if srid != 4326:
|
||||
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
|
||||
return field_type, field_params
|
64
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/models.py
vendored
Normal file
64
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/models.py
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
The GeometryColumns and SpatialRefSys models for the Oracle spatial
|
||||
backend.
|
||||
|
||||
It should be noted that Oracle Spatial does not have database tables
|
||||
named according to the OGC standard, so the closest analogs are used.
|
||||
For example, the `USER_SDO_GEOM_METADATA` is used for the GeometryColumns
|
||||
model and the `SDO_COORD_REF_SYS` is used for the SpatialRefSys model.
|
||||
"""
|
||||
from django.contrib.gis.db import models
|
||||
from django.contrib.gis.db.backends.base.models import SpatialRefSysMixin
|
||||
|
||||
|
||||
class OracleGeometryColumns(models.Model):
|
||||
"Maps to the Oracle USER_SDO_GEOM_METADATA table."
|
||||
table_name = models.CharField(max_length=32)
|
||||
column_name = models.CharField(max_length=1024)
|
||||
srid = models.IntegerField(primary_key=True)
|
||||
# TODO: Add support for `diminfo` column (type MDSYS.SDO_DIM_ARRAY).
|
||||
|
||||
class Meta:
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
def table_name_col(cls):
|
||||
"""
|
||||
Return the name of the metadata column used to store the feature table
|
||||
name.
|
||||
"""
|
||||
return "table_name"
|
||||
|
||||
@classmethod
|
||||
def geom_col_name(cls):
|
||||
"""
|
||||
Return the name of the metadata column used to store the feature
|
||||
geometry column.
|
||||
"""
|
||||
return "column_name"
|
||||
|
||||
|
||||
class OracleSpatialRefSys(models.Model, SpatialRefSysMixin):
|
||||
"Maps to the Oracle MDSYS.CS_SRS table."
|
||||
cs_name = models.CharField(max_length=68)
|
||||
srid = models.IntegerField(primary_key=True)
|
||||
auth_srid = models.IntegerField()
|
||||
auth_name = models.CharField(max_length=256)
|
||||
wktext = models.CharField(max_length=2046)
|
||||
# Optional geometry representing the bounds of this coordinate
|
||||
# system. By default, all are NULL in the table.
|
||||
cs_bounds = models.PolygonField(null=True)
|
||||
|
||||
class Meta:
|
||||
app_label = "gis"
|
||||
db_table = "CS_SRS"
|
||||
managed = False
|
||||
|
||||
@property
|
||||
def wkt(self):
|
||||
return self.wktext
|
243
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/operations.py
vendored
Normal file
243
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/operations.py
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
"""
|
||||
This module contains the spatial lookup types, and the `get_geo_where_clause`
|
||||
routine for Oracle Spatial.
|
||||
|
||||
Please note that WKT support is broken on the XE version, and thus
|
||||
this backend will not work on such platforms. Specifically, XE lacks
|
||||
support for an internal JVM, and Java libraries are required to use
|
||||
the WKT constructors.
|
||||
"""
|
||||
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.oracle.adapter import OracleSpatialAdapter
|
||||
from django.contrib.gis.db.backends.utils import SpatialOperator
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry, GEOSGeometryBase
|
||||
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"
|
||||
|
||||
|
||||
class SDOOperator(SpatialOperator):
|
||||
sql_template = "%(func)s(%(lhs)s, %(rhs)s) = 'TRUE'"
|
||||
|
||||
|
||||
class SDODWithin(SpatialOperator):
|
||||
sql_template = "SDO_WITHIN_DISTANCE(%(lhs)s, %(rhs)s, %%s) = 'TRUE'"
|
||||
|
||||
|
||||
class SDODisjoint(SpatialOperator):
|
||||
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)
|
||||
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]
|
||||
return super().as_sql(connection, lookup, template_params, sql_params[:-1])
|
||||
|
||||
|
||||
class OracleOperations(BaseSpatialOperations, DatabaseOperations):
|
||||
|
||||
name = "oracle"
|
||||
oracle = True
|
||||
disallowed_aggregates = (models.Collect, models.Extent3D, models.MakeLine)
|
||||
|
||||
Adapter = OracleSpatialAdapter
|
||||
|
||||
extent = "SDO_AGGR_MBR"
|
||||
unionagg = "SDO_AGGR_UNION"
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
# We want to get SDO Geometries as WKT because it is much easier to
|
||||
# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
|
||||
# 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)"
|
||||
|
||||
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(),
|
||||
}
|
||||
|
||||
unsupported_functions = {
|
||||
"AsKML",
|
||||
"AsSVG",
|
||||
"Azimuth",
|
||||
"ForcePolygonCW",
|
||||
"GeoHash",
|
||||
"GeometryDistance",
|
||||
"LineLocatePoint",
|
||||
"MakeValid",
|
||||
"MemSize",
|
||||
"Scale",
|
||||
"SnapToGrid",
|
||||
"Translate",
|
||||
}
|
||||
|
||||
def geo_quote_name(self, name):
|
||||
return super().geo_quote_name(name).upper()
|
||||
|
||||
def convert_extent(self, clob):
|
||||
if clob:
|
||||
# Generally, Oracle returns a polygon for the extent -- however,
|
||||
# it can return a single point if there's only one Point in the
|
||||
# table.
|
||||
ext_geom = GEOSGeometry(memoryview(clob.read()))
|
||||
gtype = str(ext_geom.geom_type)
|
||||
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":
|
||||
ll = ext_geom.coords[:2]
|
||||
ur = ll
|
||||
else:
|
||||
raise Exception(
|
||||
"Unexpected geometry type returned for extent: %s" % gtype
|
||||
)
|
||||
xmin, ymin = ll
|
||||
xmax, ymax = ur
|
||||
return (xmin, ymin, xmax, ymax)
|
||||
else:
|
||||
return None
|
||||
|
||||
def geo_db_type(self, f):
|
||||
"""
|
||||
Return the geometry database type for Oracle. Unlike other spatial
|
||||
backends, no stored procedure is necessary and it's the same for all
|
||||
geometry types.
|
||||
"""
|
||||
return "MDSYS.SDO_GEOMETRY"
|
||||
|
||||
def get_distance(self, f, value, lookup_type):
|
||||
"""
|
||||
Return the distance parameters given the value and the lookup type.
|
||||
On Oracle, geometry columns with a geodetic coordinate system behave
|
||||
implicitly like a geography column, and thus meters will be used as
|
||||
the distance parameter on them.
|
||||
"""
|
||||
if not value:
|
||||
return []
|
||||
value = value[0]
|
||||
if isinstance(value, Distance):
|
||||
if f.geodetic(self.connection):
|
||||
dist_param = value.m
|
||||
else:
|
||||
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
|
||||
|
||||
return [dist_param]
|
||||
|
||||
def get_geom_placeholder(self, f, value, compiler):
|
||||
if value is None:
|
||||
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()
|
||||
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
|
||||
|
||||
return OracleGeometryColumns
|
||||
|
||||
def spatial_ref_sys(self):
|
||||
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":
|
||||
return []
|
||||
return super().modify_insert_params(placeholder, params)
|
||||
|
||||
def get_geometry_converter(self, expression):
|
||||
read = wkb_r().read
|
||||
srid = expression.output_field.srid
|
||||
if srid == -1:
|
||||
srid = None
|
||||
geom_class = expression.output_field.geom_class
|
||||
|
||||
def converter(value, expression, connection):
|
||||
if value is not None:
|
||||
geom = GEOSGeometryBase(read(memoryview(value.read())), geom_class)
|
||||
if srid:
|
||||
geom.srid = srid
|
||||
return geom
|
||||
|
||||
return converter
|
||||
|
||||
def get_area_att_for_field(self, field):
|
||||
return "sq_m"
|
121
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/schema.py
vendored
Normal file
121
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/oracle/schema.py
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
from django.contrib.gis.db.models import GeometryField
|
||||
from django.db.backends.oracle.schema import DatabaseSchemaEditor
|
||||
from django.db.backends.utils import strip_quotes, truncate_name
|
||||
|
||||
|
||||
class OracleGISSchemaEditor(DatabaseSchemaEditor):
|
||||
sql_add_geometry_metadata = """
|
||||
INSERT INTO USER_SDO_GEOM_METADATA
|
||||
("TABLE_NAME", "COLUMN_NAME", "DIMINFO", "SRID")
|
||||
VALUES (
|
||||
%(table)s,
|
||||
%(column)s,
|
||||
MDSYS.SDO_DIM_ARRAY(
|
||||
MDSYS.SDO_DIM_ELEMENT('LONG', %(dim0)s, %(dim2)s, %(tolerance)s),
|
||||
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_clear_geometry_field_metadata = (
|
||||
"DELETE FROM USER_SDO_GEOM_METADATA WHERE TABLE_NAME = %(table)s "
|
||||
"AND COLUMN_NAME = %(column)s"
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.geometry_sql = []
|
||||
|
||||
def geo_quote_name(self, name):
|
||||
return self.connection.ops.geo_quote_name(name)
|
||||
|
||||
def quote_value(self, value):
|
||||
if isinstance(value, self.connection.ops.Adapter):
|
||||
return super().quote_value(str(value))
|
||||
return super().quote_value(value)
|
||||
|
||||
def column_sql(self, model, field, include_default=False):
|
||||
column_sql = super().column_sql(model, field, include_default)
|
||||
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,
|
||||
}
|
||||
)
|
||||
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),
|
||||
}
|
||||
)
|
||||
return column_sql
|
||||
|
||||
def create_model(self, model):
|
||||
super().create_model(model)
|
||||
self.run_geometry_sql()
|
||||
|
||||
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),
|
||||
}
|
||||
)
|
||||
|
||||
def add_field(self, model, field):
|
||||
super().add_field(model, field)
|
||||
self.run_geometry_sql()
|
||||
|
||||
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),
|
||||
}
|
||||
)
|
||||
if field.spatial_index:
|
||||
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):
|
||||
for sql in self.geometry_sql:
|
||||
self.execute(sql)
|
||||
self.geometry_sql = []
|
||||
|
||||
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
|
||||
)
|
0
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/__init__.py
vendored
Normal file
0
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/__init__.py
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
71
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/adapter.py
vendored
Normal file
71
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/adapter.py
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
This object provides quoting for GEOS geometries into PostgreSQL/PostGIS.
|
||||
"""
|
||||
from psycopg2 import Binary
|
||||
from psycopg2.extensions import ISQLQuote
|
||||
|
||||
from django.contrib.gis.db.backends.postgis.pgraster import to_pgraster
|
||||
from django.contrib.gis.geos import GEOSGeometry
|
||||
|
||||
|
||||
class PostGISAdapter:
|
||||
def __init__(self, obj, geography=False):
|
||||
"""
|
||||
Initialize on the spatial object.
|
||||
"""
|
||||
self.is_geometry = isinstance(obj, (GEOSGeometry, PostGISAdapter))
|
||||
|
||||
# Getting the WKB (in string form, to allow easy pickling of
|
||||
# the adaptor) and the SRID from the geometry or raster.
|
||||
if self.is_geometry:
|
||||
self.ewkb = bytes(obj.ewkb)
|
||||
self._adapter = Binary(self.ewkb)
|
||||
else:
|
||||
self.ewkb = to_pgraster(obj)
|
||||
|
||||
self.srid = obj.srid
|
||||
self.geography = geography
|
||||
|
||||
def __conform__(self, proto):
|
||||
"""Does the given protocol conform to what Psycopg2 expects?"""
|
||||
if proto == ISQLQuote:
|
||||
return self
|
||||
else:
|
||||
raise Exception(
|
||||
"Error implementing psycopg2 protocol. Is psycopg2 installed?"
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, PostGISAdapter) and self.ewkb == other.ewkb
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.ewkb)
|
||||
|
||||
def __str__(self):
|
||||
return self.getquoted().decode()
|
||||
|
||||
@classmethod
|
||||
def _fix_polygon(cls, poly):
|
||||
return poly
|
||||
|
||||
def prepare(self, conn):
|
||||
"""
|
||||
This method allows escaping the binary in the style required by the
|
||||
server's `standard_conforming_string` setting.
|
||||
"""
|
||||
if self.is_geometry:
|
||||
self._adapter.prepare(conn)
|
||||
|
||||
def getquoted(self):
|
||||
"""
|
||||
Return a properly quoted string for use in PostgreSQL/PostGIS.
|
||||
"""
|
||||
if self.is_geometry:
|
||||
# Psycopg will figure out whether to use E'\\000' or '\000'.
|
||||
return b"%s(%s)" % (
|
||||
b"ST_GeogFromWKB" if self.geography else b"ST_GeomFromEWKB",
|
||||
self._adapter.getquoted(),
|
||||
)
|
||||
else:
|
||||
# For rasters, add explicit type cast to WKB string.
|
||||
return b"'%s'::raster" % self.ewkb.encode()
|
26
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/base.py
vendored
Normal file
26
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/base.py
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
from django.db.backends.base.base import NO_DB_ALIAS
|
||||
from django.db.backends.postgresql.base import (
|
||||
DatabaseWrapper as Psycopg2DatabaseWrapper,
|
||||
)
|
||||
|
||||
from .features import DatabaseFeatures
|
||||
from .introspection import PostGISIntrospection
|
||||
from .operations import PostGISOperations
|
||||
from .schema import PostGISSchemaEditor
|
||||
|
||||
|
||||
class DatabaseWrapper(Psycopg2DatabaseWrapper):
|
||||
SchemaEditorClass = PostGISSchemaEditor
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if kwargs.get("alias", "") != NO_DB_ALIAS:
|
||||
self.features = DatabaseFeatures(self)
|
||||
self.ops = PostGISOperations(self)
|
||||
self.introspection = PostGISIntrospection(self)
|
||||
|
||||
def prepare_database(self):
|
||||
super().prepare_database()
|
||||
# Check that postgis extension is installed.
|
||||
with self.cursor() as cursor:
|
||||
cursor.execute("CREATE EXTENSION IF NOT EXISTS postgis")
|
62
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/const.py
vendored
Normal file
62
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/const.py
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
PostGIS to GDAL conversion constant definitions
|
||||
"""
|
||||
# Lookup to convert pixel type values from GDAL to PostGIS
|
||||
GDAL_TO_POSTGIS = [None, 4, 6, 5, 8, 7, 10, 11, None, None, None, None]
|
||||
|
||||
# Lookup to convert pixel type values from PostGIS to GDAL
|
||||
POSTGIS_TO_GDAL = [1, 1, 1, 3, 1, 3, 2, 5, 4, None, 6, 7, None, None]
|
||||
|
||||
# Struct pack structure for raster header, the raster header has the
|
||||
# following structure:
|
||||
#
|
||||
# Endianness, PostGIS raster version, number of bands, scale, origin,
|
||||
# skew, srid, width, and height.
|
||||
#
|
||||
# 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"
|
||||
|
||||
# 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,
|
||||
]
|
||||
|
||||
# Size of the packed value in bytes for different numerical types.
|
||||
# This is needed to cut chunks of band data out of PostGIS raster strings
|
||||
# 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
|
||||
}
|
||||
|
||||
# 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
|
||||
BANDTYPE_PIXTYPE_MASK = 0x0F
|
||||
BANDTYPE_FLAG_HASNODATA = 1 << 6
|
13
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/features.py
vendored
Normal file
13
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/features.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures
|
||||
from django.db.backends.postgresql.features import (
|
||||
DatabaseFeatures as Psycopg2DatabaseFeatures,
|
||||
)
|
||||
|
||||
|
||||
class DatabaseFeatures(BaseSpatialFeatures, Psycopg2DatabaseFeatures):
|
||||
supports_geography = True
|
||||
supports_3d_storage = True
|
||||
supports_3d_functions = True
|
||||
supports_raster = True
|
||||
supports_empty_geometries = True
|
||||
empty_intersection_returns_none = False
|
71
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/introspection.py
vendored
Normal file
71
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/introspection.py
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
from django.contrib.gis.gdal import OGRGeomType
|
||||
from django.db.backends.postgresql.introspection import DatabaseIntrospection
|
||||
|
||||
|
||||
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",
|
||||
]
|
||||
|
||||
def get_field_type(self, data_type, description):
|
||||
if not self.postgis_oid_lookup:
|
||||
# Query PostgreSQL's pg_type table to determine the OID integers
|
||||
# for the PostGIS data types used in reverse lookup (the integers
|
||||
# may be different across versions). To prevent unnecessary
|
||||
# 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')"
|
||||
)
|
||||
self.postgis_oid_lookup = dict(cursor.fetchall())
|
||||
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):
|
||||
"""
|
||||
The geometry type OID used by PostGIS does not indicate the particular
|
||||
type of field that a geometry column is (e.g., whether it's a
|
||||
PointField or a PolygonField). Thus, this routine queries the PostGIS
|
||||
metadata tables to determine the geometry type.
|
||||
"""
|
||||
with self.connection.cursor() as cursor:
|
||||
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),
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
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 srid != 4326:
|
||||
field_params["srid"] = srid
|
||||
if dim != 2:
|
||||
field_params["dim"] = dim
|
||||
return field_type, field_params
|
72
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/models.py
vendored
Normal file
72
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/models.py
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
The GeometryColumns and SpatialRefSys models for the PostGIS backend.
|
||||
"""
|
||||
from django.contrib.gis.db.backends.base.models import SpatialRefSysMixin
|
||||
from django.db import models
|
||||
|
||||
|
||||
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)
|
||||
f_geometry_column = models.CharField(max_length=256)
|
||||
coord_dimension = models.IntegerField()
|
||||
srid = models.IntegerField(primary_key=True)
|
||||
type = models.CharField(max_length=30)
|
||||
|
||||
class Meta:
|
||||
app_label = "gis"
|
||||
db_table = "geometry_columns"
|
||||
managed = False
|
||||
|
||||
def __str__(self):
|
||||
return "%s.%s - %dD %s field (SRID: %d)" % (
|
||||
self.f_table_name,
|
||||
self.f_geometry_column,
|
||||
self.coord_dimension,
|
||||
self.type,
|
||||
self.srid,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def table_name_col(cls):
|
||||
"""
|
||||
Return the name of the metadata column used to store the feature table
|
||||
name.
|
||||
"""
|
||||
return "f_table_name"
|
||||
|
||||
@classmethod
|
||||
def geom_col_name(cls):
|
||||
"""
|
||||
Return the name of the metadata column used to store the feature
|
||||
geometry column.
|
||||
"""
|
||||
return "f_geometry_column"
|
||||
|
||||
|
||||
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()
|
||||
srtext = models.CharField(max_length=2048)
|
||||
proj4text = models.CharField(max_length=2048)
|
||||
|
||||
class Meta:
|
||||
app_label = "gis"
|
||||
db_table = "spatial_ref_sys"
|
||||
managed = False
|
||||
|
||||
@property
|
||||
def wkt(self):
|
||||
return self.srtext
|
415
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/operations.py
vendored
Normal file
415
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/operations.py
vendored
Normal file
@@ -0,0 +1,415 @@
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
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
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometryBase
|
||||
from django.contrib.gis.geos.prototypes.io import wkb_r
|
||||
from django.contrib.gis.measure import Distance
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import NotSupportedError, ProgrammingError
|
||||
from django.db.backends.postgresql.operations import DatabaseOperations
|
||||
from django.db.models import Func, Value
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.version import get_version_tuple
|
||||
|
||||
from .adapter import PostGISAdapter
|
||||
from .models import PostGISGeometryColumns, PostGISSpatialRefSys
|
||||
from .pgraster import from_pgraster
|
||||
|
||||
# Identifier to mark raster lookups as bilateral.
|
||||
BILATERAL = "bilateral"
|
||||
|
||||
|
||||
class PostGISOperator(SpatialOperator):
|
||||
def __init__(self, geography=False, raster=False, **kwargs):
|
||||
# Only a subset of the operators and functions are available for the
|
||||
# geography type.
|
||||
self.geography = geography
|
||||
# Only a subset of the operators and functions are available for the
|
||||
# raster type. Lookups that don't support raster will be converted to
|
||||
# polygons. If the raster argument is set to BILATERAL, then the
|
||||
# operator cannot handle mixed geom-raster lookups.
|
||||
self.raster = raster
|
||||
super().__init__(**kwargs)
|
||||
|
||||
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,)
|
||||
)
|
||||
|
||||
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"
|
||||
|
||||
# Check which input is a 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,
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
# 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"]
|
||||
if rhs_is_raster:
|
||||
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"]
|
||||
elif rhs_is_raster and not lhs_is_raster:
|
||||
template_params["rhs"] = "ST_Polygon(%s)" % template_params["rhs"]
|
||||
|
||||
return template_params
|
||||
|
||||
|
||||
class ST_Polygon(Func):
|
||||
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)
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def output_field(self):
|
||||
return GeometryField(srid=self.source_expressions[0].field.srid)
|
||||
|
||||
|
||||
class PostGISOperations(BaseSpatialOperations, DatabaseOperations):
|
||||
name = "postgis"
|
||||
postgis = True
|
||||
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"
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
unsupported_functions = set()
|
||||
|
||||
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",
|
||||
}
|
||||
return function_names
|
||||
|
||||
@cached_property
|
||||
def spatial_version(self):
|
||||
"""Determine the version of the PostGIS library."""
|
||||
# Trying to get the PostGIS version because the function
|
||||
# signatures will depend on the version used. The cost
|
||||
# here is a database query to determine the version, which
|
||||
# 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"):
|
||||
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")
|
||||
|
||||
try:
|
||||
vtup = self.postgis_version_tuple()
|
||||
except ProgrammingError:
|
||||
raise ImproperlyConfigured(
|
||||
'Cannot determine PostGIS version for database "%s" '
|
||||
'using command "SELECT postgis_lib_version()". '
|
||||
"GeoDjango requires at least PostGIS version 2.5. "
|
||||
"Was the database created from a spatial database "
|
||||
"template?" % self.connection.settings_dict["NAME"]
|
||||
)
|
||||
version = vtup[1:]
|
||||
return version
|
||||
|
||||
def convert_extent(self, box):
|
||||
"""
|
||||
Return a 4-tuple extent for the `Extent` aggregate by converting
|
||||
the bounding box text returned by PostGIS (`box` argument), for
|
||||
example: "BOX(-90.0 30.0, -85.0 40.0)".
|
||||
"""
|
||||
if box is None:
|
||||
return None
|
||||
ll, ur = box[4:-1].split(",")
|
||||
xmin, ymin = map(float, ll.split())
|
||||
xmax, ymax = map(float, ur.split())
|
||||
return (xmin, ymin, xmax, ymax)
|
||||
|
||||
def convert_extent3d(self, box3d):
|
||||
"""
|
||||
Return a 6-tuple extent for the `Extent3D` aggregate by converting
|
||||
the 3d bounding-box text returned by PostGIS (`box3d` argument), for
|
||||
example: "BOX3D(-90.0 30.0 1, -85.0 40.0 2)".
|
||||
"""
|
||||
if box3d is None:
|
||||
return None
|
||||
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)
|
||||
|
||||
def geo_db_type(self, f):
|
||||
"""
|
||||
Return the database field type for the given spatial field.
|
||||
"""
|
||||
if f.geom_type == "RASTER":
|
||||
return "raster"
|
||||
|
||||
# Type-based geometries.
|
||||
# TODO: Support 'M' extension.
|
||||
if f.dim == 3:
|
||||
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."
|
||||
)
|
||||
|
||||
return "geography(%s,%d)" % (geom_type, f.srid)
|
||||
else:
|
||||
return "geometry(%s,%d)" % (geom_type, f.srid)
|
||||
|
||||
def get_distance(self, f, dist_val, lookup_type):
|
||||
"""
|
||||
Retrieve the distance parameters for the given geometry field,
|
||||
distance lookup value, and the distance lookup type.
|
||||
|
||||
This is the most complex implementation of the spatial backends due to
|
||||
what is supported on geodetic geometry columns vs. what's available on
|
||||
projected geometry columns. In addition, it has to take into account
|
||||
the geography column type.
|
||||
"""
|
||||
# Getting the distance parameter
|
||||
value = dist_val[0]
|
||||
|
||||
# Shorthand boolean flags.
|
||||
geodetic = f.geodetic(self.connection)
|
||||
geography = f.geography
|
||||
|
||||
if isinstance(value, Distance):
|
||||
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."
|
||||
)
|
||||
dist_param = value.m
|
||||
else:
|
||||
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
|
||||
|
||||
return [dist_param]
|
||||
|
||||
def get_geom_placeholder(self, f, value, compiler):
|
||||
"""
|
||||
Provide a proper substitution value for Geometries or rasters that are
|
||||
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"):
|
||||
if value.field.srid == f.srid:
|
||||
placeholder = "%s"
|
||||
else:
|
||||
placeholder = "%s(%%s, %s)" % (transform_func, f.srid)
|
||||
return placeholder
|
||||
|
||||
# Get the srid for this object
|
||||
if value is None:
|
||||
value_srid = None
|
||||
else:
|
||||
value_srid = value.srid
|
||||
|
||||
# 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"
|
||||
else:
|
||||
placeholder = "%s(%%s, %s)" % (transform_func, f.srid)
|
||||
|
||||
return placeholder
|
||||
|
||||
def _get_postgis_func(self, func):
|
||||
"""
|
||||
Helper routine for calling PostGIS functions and returning their result.
|
||||
"""
|
||||
# Close out the connection. See #9437.
|
||||
with self.connection.temporary_connection() as cursor:
|
||||
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")
|
||||
|
||||
def postgis_lib_version(self):
|
||||
"Return the version number of the PostGIS library used with PostgreSQL."
|
||||
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")
|
||||
|
||||
def postgis_version(self):
|
||||
"Return PostGIS version number and compile-time options."
|
||||
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")
|
||||
|
||||
def postgis_version_tuple(self):
|
||||
"""
|
||||
Return the PostGIS version as a tuple (version string, major,
|
||||
minor, subminor).
|
||||
"""
|
||||
version = self.postgis_lib_version()
|
||||
return (version,) + get_version_tuple(version)
|
||||
|
||||
def proj_version_tuple(self):
|
||||
"""
|
||||
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_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.")
|
||||
|
||||
def spatial_aggregate_name(self, agg_name):
|
||||
if agg_name == "Extent3D":
|
||||
return self.extent3d
|
||||
else:
|
||||
return self.geom_func_prefix + agg_name
|
||||
|
||||
# Routines for getting the OGC-compliant models.
|
||||
def geometry_columns(self):
|
||||
return PostGISGeometryColumns
|
||||
|
||||
def spatial_ref_sys(self):
|
||||
return PostGISSpatialRefSys
|
||||
|
||||
def parse_raster(self, value):
|
||||
"""Convert a PostGIS HEX String into a dict readable by GDALRaster."""
|
||||
return from_pgraster(value)
|
||||
|
||||
def distance_expr_for_lookup(self, lhs, rhs, **kwargs):
|
||||
return super().distance_expr_for_lookup(
|
||||
self._normalize_distance_lookup_arg(lhs),
|
||||
self._normalize_distance_lookup_arg(rhs),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _normalize_distance_lookup_arg(arg):
|
||||
is_raster = (
|
||||
arg.field.geom_type == "RASTER"
|
||||
if hasattr(arg, "field")
|
||||
else isinstance(arg, GDALRaster)
|
||||
)
|
||||
return ST_Polygon(arg) if is_raster else arg
|
||||
|
||||
def get_geometry_converter(self, expression):
|
||||
read = wkb_r().read
|
||||
geom_class = expression.output_field.geom_class
|
||||
|
||||
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"
|
153
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/pgraster.py
vendored
Normal file
153
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/pgraster.py
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
def pack(structure, data):
|
||||
"""
|
||||
Pack data into hex string with little endian format.
|
||||
"""
|
||||
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))
|
||||
|
||||
|
||||
def chunk(data, index):
|
||||
"""
|
||||
Split a string into two parts at the input index.
|
||||
"""
|
||||
return data[:index], data[index:]
|
||||
|
||||
|
||||
def from_pgraster(data):
|
||||
"""
|
||||
Convert a PostGIS HEX String into a dictionary.
|
||||
"""
|
||||
if data is None:
|
||||
return
|
||||
|
||||
# Split raster header from data
|
||||
header, data = chunk(data, 122)
|
||||
header = unpack(POSTGIS_HEADER_STRUCTURE, header)
|
||||
|
||||
# Parse band data
|
||||
bands = []
|
||||
pixeltypes = []
|
||||
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 = pixeltype_with_flags & BANDTYPE_PIXTYPE_MASK
|
||||
|
||||
# Convert datatype from PostGIS to GDAL & get pack type and size
|
||||
pixeltype = POSTGIS_TO_GDAL[pixeltype]
|
||||
pack_type = GDAL_TO_STRUCT[pixeltype]
|
||||
pack_size = 2 * STRUCT_SIZE[pack_type]
|
||||
|
||||
# Parse band nodata value. The nodata value is part of the
|
||||
# PGRaster string even if the nodata flag is True, so it always
|
||||
# has to be chunked off the data string.
|
||||
nodata, data = chunk(data, pack_size)
|
||||
nodata = unpack(pack_type, nodata)[0]
|
||||
|
||||
# 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)}
|
||||
|
||||
# Set the nodata value if the nodata flag is set.
|
||||
if pixeltype_with_flags & BANDTYPE_FLAG_HASNODATA:
|
||||
band_result["nodata_value"] = nodata
|
||||
|
||||
# Append band data to band list
|
||||
bands.append(band_result)
|
||||
|
||||
# Store pixeltype of this band in pixeltypes array
|
||||
pixeltypes.append(pixeltype)
|
||||
|
||||
# Check that all bands have the same pixeltype.
|
||||
# This is required by GDAL. PostGIS rasters could have different pixeltypes
|
||||
# for bands of the same raster.
|
||||
if len(set(pixeltypes)) != 1:
|
||||
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,
|
||||
}
|
||||
|
||||
|
||||
def to_pgraster(rast):
|
||||
"""
|
||||
Convert a GDALRaster into PostGIS Raster format.
|
||||
"""
|
||||
# Prepare the raster header data as a tuple. The first two numbers are
|
||||
# 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,
|
||||
)
|
||||
|
||||
# Pack raster header.
|
||||
result = pack(POSTGIS_HEADER_STRUCTURE, rasterheader)
|
||||
|
||||
for band in rast.bands:
|
||||
# The PostGIS raster band header has exactly two elements, a 8BUI byte
|
||||
# and the nodata value.
|
||||
#
|
||||
# The 8BUI stores both the PostGIS pixel data type and a nodata flag.
|
||||
# It is composed as the datatype with BANDTYPE_FLAG_HASNODATA (1 << 6)
|
||||
# for existing nodata values:
|
||||
# 8BUI_VALUE = PG_PIXEL_TYPE (0-11) | BANDTYPE_FLAG_HASNODATA
|
||||
#
|
||||
# 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()]
|
||||
|
||||
# Get band pixel type in PostGIS notation
|
||||
pixeltype = GDAL_TO_POSTGIS[band.datatype()]
|
||||
|
||||
# Set the nodata flag
|
||||
if band.nodata_value is not None:
|
||||
pixeltype |= BANDTYPE_FLAG_HASNODATA
|
||||
|
||||
# Pack band header
|
||||
bandheader = pack(structure, (pixeltype, band.nodata_value or 0))
|
||||
|
||||
# Add packed header and band data to result
|
||||
result += bandheader + band.data(as_memoryview=True)
|
||||
|
||||
# Convert raster to hex string before passing it to the DB.
|
||||
return result.hex()
|
76
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/schema.py
vendored
Normal file
76
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/postgis/schema.py
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
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)"
|
||||
|
||||
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):
|
||||
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"):
|
||||
return super()._create_index_sql(model, fields=fields, **kwargs)
|
||||
|
||||
field = fields[0]
|
||||
expressions = None
|
||||
opclasses = None
|
||||
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
|
||||
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")
|
||||
|
||||
return super()._create_index_sql(
|
||||
model,
|
||||
fields=fields,
|
||||
name=name,
|
||||
using=" USING %s" % self.geom_index_type,
|
||||
opclasses=opclasses,
|
||||
expressions=expressions,
|
||||
)
|
||||
|
||||
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"):
|
||||
return super()._alter_column_type_sql(table, old_field, new_field, new_type)
|
||||
|
||||
if old_field.dim == 2 and new_field.dim == 3:
|
||||
sql_alter = self.sql_alter_column_to_3d
|
||||
elif old_field.dim == 3 and new_field.dim == 2:
|
||||
sql_alter = self.sql_alter_column_to_2d
|
||||
else:
|
||||
sql_alter = self.sql_alter_column_type
|
||||
return (
|
||||
(
|
||||
sql_alter
|
||||
% {
|
||||
"column": self.quote_name(new_field.column),
|
||||
"type": new_type,
|
||||
},
|
||||
[],
|
||||
),
|
||||
[],
|
||||
)
|
0
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/__init__.py
vendored
Normal file
0
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/__init__.py
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
10
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/adapter.py
vendored
Normal file
10
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/adapter.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.contrib.gis.db.backends.base.adapter import WKTAdapter
|
||||
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)
|
79
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/base.py
vendored
Normal file
79
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/base.py
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
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 .client import SpatiaLiteClient
|
||||
from .features import DatabaseFeatures
|
||||
from .introspection import SpatiaLiteIntrospection
|
||||
from .operations import SpatiaLiteOperations
|
||||
from .schema import SpatialiteSchemaEditor
|
||||
|
||||
|
||||
class DatabaseWrapper(SQLiteDatabaseWrapper):
|
||||
SchemaEditorClass = SpatialiteSchemaEditor
|
||||
# Classes instantiated in __init__().
|
||||
client_class = SpatiaLiteClient
|
||||
features_class = DatabaseFeatures
|
||||
introspection_class = SpatiaLiteIntrospection
|
||||
ops_class = SpatiaLiteOperations
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Trying to find the location of the SpatiaLite library.
|
||||
# Here we are figuring out the path to the SpatiaLite library
|
||||
# (`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
|
||||
]
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_new_connection(self, conn_params):
|
||||
conn = super().get_new_connection(conn_params)
|
||||
# Enabling extension loading on the SQLite connection.
|
||||
try:
|
||||
conn.enable_load_extension(True)
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured(
|
||||
"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):
|
||||
raise ImproperlyConfigured(
|
||||
"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)
|
||||
)
|
||||
return conn
|
||||
|
||||
def prepare_database(self):
|
||||
super().prepare_database()
|
||||
# Check if spatial metadata have been initialized in the database
|
||||
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)")
|
5
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/client.py
vendored
Normal file
5
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/client.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.db.backends.sqlite3.client import DatabaseClient
|
||||
|
||||
|
||||
class SpatiaLiteClient(DatabaseClient):
|
||||
executable_name = "spatialite"
|
26
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/features.py
vendored
Normal file
26
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/features.py
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures
|
||||
from django.db.backends.sqlite3.features import (
|
||||
DatabaseFeatures as SQLiteDatabaseFeatures,
|
||||
)
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
class DatabaseFeatures(BaseSpatialFeatures, SQLiteDatabaseFeatures):
|
||||
can_alter_geometry_field = False # Not implemented
|
||||
supports_3d_storage = True
|
||||
|
||||
@cached_property
|
||||
def supports_area_geodetic(self):
|
||||
return bool(self.connection.ops.geom_lib_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",
|
||||
},
|
||||
}
|
||||
)
|
||||
return skips
|
82
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/introspection.py
vendored
Normal file
82
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/introspection.py
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
from django.contrib.gis.gdal import OGRGeomType
|
||||
from django.db.backends.sqlite3.introspection import (
|
||||
DatabaseIntrospection,
|
||||
FlexibleFieldLookupDict,
|
||||
)
|
||||
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
|
||||
class SpatiaLiteIntrospection(DatabaseIntrospection):
|
||||
data_types_reverse = GeoFlexibleFieldLookupDict()
|
||||
|
||||
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),
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
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.
|
||||
ogr_type = row[2]
|
||||
if isinstance(ogr_type, int) and ogr_type > 1000:
|
||||
# SpatiaLite uses SFSQL 1.2 offsets 1000 (Z), 2000 (M), and
|
||||
# 3000 (ZM) to indicate the presence of higher dimensional
|
||||
# coordinates (M not yet supported by Django).
|
||||
ogr_type = ogr_type % 1000 + OGRGeomType.wkb25bit
|
||||
field_type = OGRGeomType(ogr_type).django
|
||||
|
||||
# Getting any GeometryField keyword arguments that are not the default.
|
||||
dim = row[0]
|
||||
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
|
||||
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,),
|
||||
)
|
||||
for row in cursor.fetchall():
|
||||
constraints["%s__spatial__index" % row[0]] = {
|
||||
"columns": [row[0]],
|
||||
"primary_key": False,
|
||||
"unique": False,
|
||||
"foreign_key": None,
|
||||
"check": False,
|
||||
"index": True,
|
||||
}
|
||||
return constraints
|
70
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/models.py
vendored
Normal file
70
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/models.py
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
The GeometryColumns and SpatialRefSys models for the SpatiaLite backend.
|
||||
"""
|
||||
from django.contrib.gis.db.backends.base.models import SpatialRefSysMixin
|
||||
from django.db import models
|
||||
|
||||
|
||||
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")
|
||||
|
||||
class Meta:
|
||||
app_label = "gis"
|
||||
db_table = "geometry_columns"
|
||||
managed = False
|
||||
|
||||
def __str__(self):
|
||||
return "%s.%s - %dD %s field (SRID: %d)" % (
|
||||
self.f_table_name,
|
||||
self.f_geometry_column,
|
||||
self.coord_dimension,
|
||||
self.type,
|
||||
self.srid,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def table_name_col(cls):
|
||||
"""
|
||||
Return the name of the metadata column used to store the feature table
|
||||
name.
|
||||
"""
|
||||
return "f_table_name"
|
||||
|
||||
@classmethod
|
||||
def geom_col_name(cls):
|
||||
"""
|
||||
Return the name of the metadata column used to store the feature
|
||||
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()
|
||||
ref_sys_name = models.CharField(max_length=256)
|
||||
proj4text = models.CharField(max_length=2048)
|
||||
srtext = models.CharField(max_length=2048)
|
||||
|
||||
class Meta:
|
||||
app_label = "gis"
|
||||
db_table = "spatial_ref_sys"
|
||||
managed = False
|
||||
|
||||
@property
|
||||
def wkt(self):
|
||||
return self.srtext
|
225
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/operations.py
vendored
Normal file
225
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/operations.py
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
"""
|
||||
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.spatialite.adapter import SpatiaLiteAdapter
|
||||
from django.contrib.gis.db.backends.utils import SpatialOperator
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry, GEOSGeometryBase
|
||||
from django.contrib.gis.geos.prototypes.io import wkb_r
|
||||
from django.contrib.gis.measure import Distance
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db.backends.sqlite3.operations import DatabaseOperations
|
||||
from django.utils.functional import cached_property
|
||||
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
|
||||
|
||||
|
||||
class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
|
||||
name = "spatialite"
|
||||
spatialite = True
|
||||
|
||||
Adapter = SpatiaLiteAdapter
|
||||
|
||||
collect = "Collect"
|
||||
extent = "Extent"
|
||||
makeline = "MakeLine"
|
||||
unionagg = "GUnion"
|
||||
|
||||
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"),
|
||||
# Returns true if B's bounding box completely contains A's bounding box.
|
||||
"contained": SpatialOperator(func="MbrWithin"),
|
||||
# Returns true if A's bounding box completely contains B's bounding box.
|
||||
"bbcontains": SpatialOperator(func="MbrContains"),
|
||||
# Returns true if A's bounding box overlaps B's bounding box.
|
||||
"bboverlaps": SpatialOperator(func="MbrOverlaps"),
|
||||
# These are implemented here as synonyms for Equals
|
||||
"same_as": SpatialiteNullCheckOperator(func="Equals"),
|
||||
"exact": SpatialiteNullCheckOperator(func="Equals"),
|
||||
# Distance predicates
|
||||
"dwithin": SpatialOperator(func="PtDistWithin"),
|
||||
}
|
||||
|
||||
disallowed_aggregates = (models.Extent3D,)
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def unsupported_functions(self):
|
||||
unsupported = {"BoundingCircle", "GeometryDistance", "MemSize"}
|
||||
if not self.geom_lib_version():
|
||||
unsupported |= {"Azimuth", "GeoHash", "MakeValid"}
|
||||
return unsupported
|
||||
|
||||
@cached_property
|
||||
def spatial_version(self):
|
||||
"""Determine the version of the SpatiaLite library."""
|
||||
try:
|
||||
version = self.spatialite_version_tuple()[1:]
|
||||
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"],)
|
||||
) from exc
|
||||
if version < (4, 3, 0):
|
||||
raise ImproperlyConfigured("GeoDjango supports SpatiaLite 4.3.0 and above.")
|
||||
return version
|
||||
|
||||
def convert_extent(self, box):
|
||||
"""
|
||||
Convert the polygon data received from SpatiaLite to min/max values.
|
||||
"""
|
||||
if box is None:
|
||||
return None
|
||||
shell = GEOSGeometry(box).shell
|
||||
xmin, ymin = shell[0][:2]
|
||||
xmax, ymax = shell[2][:2]
|
||||
return (xmin, ymin, xmax, ymax)
|
||||
|
||||
def geo_db_type(self, f):
|
||||
"""
|
||||
Return None because geometry columns are added via the
|
||||
`AddGeometryColumn` stored procedure on SpatiaLite.
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_distance(self, f, value, lookup_type):
|
||||
"""
|
||||
Return the distance parameters for the given geometry field,
|
||||
lookup value, and lookup type.
|
||||
"""
|
||||
if not value:
|
||||
return []
|
||||
value = value[0]
|
||||
if isinstance(value, Distance):
|
||||
if f.geodetic(self.connection):
|
||||
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))
|
||||
)
|
||||
else:
|
||||
dist_param = value
|
||||
return [dist_param]
|
||||
|
||||
def _get_spatialite_func(self, func):
|
||||
"""
|
||||
Helper routine for calling SpatiaLite functions and returning
|
||||
their result.
|
||||
Any error occurring in this method should be handled by the caller.
|
||||
"""
|
||||
cursor = self.connection._cursor()
|
||||
try:
|
||||
cursor.execute("SELECT %s" % func)
|
||||
row = cursor.fetchone()
|
||||
finally:
|
||||
cursor.close()
|
||||
return row[0]
|
||||
|
||||
def geos_version(self):
|
||||
"Return the version of GEOS used by SpatiaLite as a string."
|
||||
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()")
|
||||
|
||||
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()
|
||||
|
||||
def spatialite_version(self):
|
||||
"Return the SpatiaLite library version as a string."
|
||||
return self._get_spatialite_func("spatialite_version()")
|
||||
|
||||
def spatialite_version_tuple(self):
|
||||
"""
|
||||
Return the SpatiaLite version as a tuple (version string, major,
|
||||
minor, subminor).
|
||||
"""
|
||||
version = self.spatialite_version()
|
||||
return (version,) + get_version_tuple(version)
|
||||
|
||||
def spatial_aggregate_name(self, agg_name):
|
||||
"""
|
||||
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()
|
||||
return getattr(self, agg_name)
|
||||
|
||||
# Routines for getting the OGC-compliant models.
|
||||
def geometry_columns(self):
|
||||
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):
|
||||
geom_class = expression.output_field.geom_class
|
||||
read = wkb_r().read
|
||||
|
||||
def converter(value, expression, connection):
|
||||
return None if value is None else GEOSGeometryBase(read(value), geom_class)
|
||||
|
||||
return converter
|
191
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/schema.py
vendored
Normal file
191
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/spatialite/schema.py
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
from django.db import DatabaseError
|
||||
from django.db.backends.sqlite3.schema import DatabaseSchemaEditor
|
||||
|
||||
|
||||
class SpatialiteSchemaEditor(DatabaseSchemaEditor):
|
||||
sql_add_geometry_column = (
|
||||
"SELECT AddGeometryColumn(%(table)s, %(column)s, %(srid)s, "
|
||||
"%(geom_type)s, %(dim)s, %(null)s)"
|
||||
)
|
||||
sql_add_spatial_index = "SELECT CreateSpatialIndex(%(table)s, %(column)s)"
|
||||
sql_drop_spatial_index = "DROP TABLE idx_%(table)s_%(column)s"
|
||||
sql_recover_geometry_metadata = (
|
||||
"SELECT RecoverGeometryColumn(%(table)s, %(column)s, %(srid)s, "
|
||||
"%(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_update_geometry_columns = (
|
||||
"UPDATE %(geom_table)s SET f_table_name = %(new_table)s "
|
||||
"WHERE f_table_name = %(old_table)s"
|
||||
)
|
||||
|
||||
geometry_tables = [
|
||||
"geometry_columns",
|
||||
"geometry_columns_auth",
|
||||
"geometry_columns_time",
|
||||
"geometry_columns_statistics",
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.geometry_sql = []
|
||||
|
||||
def geo_quote_name(self, name):
|
||||
return self.connection.ops.geo_quote_name(name)
|
||||
|
||||
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
|
||||
% {
|
||||
"table": self.geo_quote_name(model._meta.db_table),
|
||||
"column": self.geo_quote_name(field.column),
|
||||
"srid": field.srid,
|
||||
"geom_type": self.geo_quote_name(field.geom_type),
|
||||
"dim": field.dim,
|
||||
"null": int(not field.null),
|
||||
}
|
||||
)
|
||||
|
||||
if field.spatial_index:
|
||||
self.geometry_sql.append(
|
||||
self.sql_add_spatial_index
|
||||
% {
|
||||
"table": self.quote_name(model._meta.db_table),
|
||||
"column": self.quote_name(field.column),
|
||||
}
|
||||
)
|
||||
return None, None
|
||||
|
||||
def remove_geometry_metadata(self, model, field):
|
||||
self.execute(
|
||||
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
|
||||
% {
|
||||
"table": model._meta.db_table,
|
||||
"column": field.column,
|
||||
}
|
||||
)
|
||||
|
||||
def create_model(self, model):
|
||||
super().create_model(model)
|
||||
# Create geometry columns
|
||||
for sql in self.geometry_sql:
|
||||
self.execute(sql)
|
||||
self.geometry_sql = []
|
||||
|
||||
def delete_model(self, model, **kwargs):
|
||||
from django.contrib.gis.db.models import GeometryField
|
||||
|
||||
# Drop spatial metadata (dropping the table does not automatically remove them)
|
||||
for field in model._meta.local_fields:
|
||||
if isinstance(field, GeometryField):
|
||||
self.remove_geometry_metadata(model, field)
|
||||
# Make sure all geom stuff is gone
|
||||
for geom_table in self.geometry_tables:
|
||||
try:
|
||||
self.execute(
|
||||
self.sql_discard_geometry_columns
|
||||
% {
|
||||
"geom_table": geom_table,
|
||||
"table": self.quote_name(model._meta.db_table),
|
||||
}
|
||||
)
|
||||
except DatabaseError:
|
||||
pass
|
||||
super().delete_model(model, **kwargs)
|
||||
|
||||
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)
|
||||
for sql in self.geometry_sql:
|
||||
self.execute(sql)
|
||||
self.geometry_sql = []
|
||||
else:
|
||||
super().add_field(model, field)
|
||||
|
||||
def remove_field(self, model, field):
|
||||
from django.contrib.gis.db.models import GeometryField
|
||||
|
||||
# NOTE: If the field is a geometry field, the table is just recreated,
|
||||
# the parent's remove_field can't be used cause it will skip the
|
||||
# recreation if the field does not have a database type. Geometry fields
|
||||
# do not have a db type cause they are added and removed via stored
|
||||
# procedures.
|
||||
if isinstance(field, GeometryField):
|
||||
self._remake_table(model, delete_field=field)
|
||||
else:
|
||||
super().remove_field(model, field)
|
||||
|
||||
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
|
||||
% {
|
||||
"table": self.quote_name(old_db_table),
|
||||
"column": self.quote_name(field.column),
|
||||
}
|
||||
)
|
||||
# Alter table
|
||||
super().alter_db_table(model, old_db_table, new_db_table, disable_constraints)
|
||||
# Repoint any straggler names
|
||||
for geom_table in self.geometry_tables:
|
||||
try:
|
||||
self.execute(
|
||||
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),
|
||||
}
|
||||
)
|
||||
except DatabaseError:
|
||||
pass
|
||||
# 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)
|
||||
),
|
||||
}
|
||||
)
|
28
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/utils.py
vendored
Normal file
28
env/lib/python3.8/site-packages/django/contrib/gis/db/backends/utils.py
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
A collection of utility routines and classes used by the spatial
|
||||
backends.
|
||||
"""
|
||||
|
||||
|
||||
class SpatialOperator:
|
||||
"""
|
||||
Class encapsulating the behavior specific to a GIS operation (used by lookups).
|
||||
"""
|
||||
|
||||
sql_template = None
|
||||
|
||||
def __init__(self, op=None, func=None):
|
||||
self.op = op
|
||||
self.func = func
|
||||
|
||||
@property
|
||||
def default_template(self):
|
||||
if self.func:
|
||||
return "%(func)s(%(lhs)s, %(rhs)s)"
|
||||
else:
|
||||
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})
|
||||
return sql_template % template_params, sql_params
|
30
env/lib/python3.8/site-packages/django/contrib/gis/db/models/__init__.py
vendored
Normal file
30
env/lib/python3.8/site-packages/django/contrib/gis/db/models/__init__.py
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
from django.db.models import * # NOQA isort:skip
|
||||
from django.db.models import __all__ as models_all # isort:skip
|
||||
import django.contrib.gis.db.models.functions # NOQA
|
||||
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,
|
||||
)
|
||||
|
||||
__all__ = models_all + aggregates_all
|
||||
__all__ += [
|
||||
"GeometryCollectionField",
|
||||
"GeometryField",
|
||||
"LineStringField",
|
||||
"MultiLineStringField",
|
||||
"MultiPointField",
|
||||
"MultiPolygonField",
|
||||
"PointField",
|
||||
"PolygonField",
|
||||
"RasterField",
|
||||
]
|
BIN
env/lib/python3.8/site-packages/django/contrib/gis/db/models/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/db/models/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user