änderung1
This commit is contained in:
1 parent
cc1d096bc3
commit
cbdda613b7
2098 files changed
+274524
No files matched your search
@@ -0,0 +1 @@
|
||||
default_app_config = 'django.contrib.gis.apps.GISConfig'
|
||||
@@ -0,0 +1,12 @@
|
||||
from django.contrib.admin import (
|
||||
HORIZONTAL, VERTICAL, AdminSite, ModelAdmin, StackedInline, TabularInline,
|
||||
autodiscover, register, site,
|
||||
)
|
||||
from django.contrib.gis.admin.options import GeoModelAdmin, OSMGeoAdmin
|
||||
from django.contrib.gis.admin.widgets import OpenLayersWidget
|
||||
|
||||
__all__ = [
|
||||
'HORIZONTAL', 'VERTICAL', 'AdminSite', 'ModelAdmin', 'StackedInline',
|
||||
'TabularInline', 'autodiscover', 'register', 'site',
|
||||
'GeoModelAdmin', 'OSMGeoAdmin', 'OpenLayersWidget',
|
||||
]
|
||||
Whitespace-only changes.
@@ -0,0 +1,98 @@
|
||||
import re
|
||||
|
||||
from django.contrib.gis.db.models import aggregates
|
||||
|
||||
|
||||
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 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
|
||||
# Do geometric relationship operations operate on real shapes (or only on bounding boxes)?
|
||||
supports_real_shape_operations = 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
|
||||
|
||||
# The following properties indicate if the database backend support
|
||||
# certain lookups (dwithin, left and right, relate, ...)
|
||||
supports_left_right_lookups = False
|
||||
|
||||
# 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
|
||||
|
||||
@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 aggregates.Collect not in self.connection.ops.disallowed_aggregates
|
||||
|
||||
@property
|
||||
def supports_extent_aggr(self):
|
||||
return aggregates.Extent not in self.connection.ops.disallowed_aggregates
|
||||
|
||||
@property
|
||||
def supports_make_line_aggr(self):
|
||||
return aggregates.MakeLine not in self.connection.ops.disallowed_aggregates
|
||||
|
||||
@property
|
||||
def supports_union_aggr(self):
|
||||
return aggregates.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.group(1)
|
||||
return func_name not in self.connection.ops.unsupported_functions
|
||||
raise AttributeError
|
||||
@@ -0,0 +1,136 @@
|
||||
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)
|
||||
@@ -0,0 +1,162 @@
|
||||
from django.contrib.gis.db.models import GeometryField
|
||||
from django.contrib.gis.db.models.functions import Distance
|
||||
from django.contrib.gis.measure import (
|
||||
Area as AreaMeasure, Distance as DistanceMeasure,
|
||||
)
|
||||
from django.db.utils 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
|
||||
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
|
||||
|
||||
# Does the spatial database have a geometry or geography type?
|
||||
geography = False
|
||||
geometry = False
|
||||
|
||||
# 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 = {}
|
||||
|
||||
# Blacklist/set of known unsupported functions of the backend
|
||||
unsupported_functions = {
|
||||
'Area', 'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG', 'Azimuth',
|
||||
'BoundingCircle', 'Centroid', 'Difference', 'Distance', 'Envelope',
|
||||
'ForceRHR', 'GeoHash', '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,0 +1,77 @@
|
||||
import logging
|
||||
|
||||
from django.contrib.gis.db.models.fields import GeometryField
|
||||
from django.db.backends.mysql.schema import DatabaseSchemaEditor
|
||||
from django.db.utils import OperationalError
|
||||
|
||||
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):
|
||||
return (
|
||||
super().skip_default(field) or
|
||||
# Geometry fields are stored as BLOB/TEXT and can't have defaults.
|
||||
isinstance(field, GeometryField)
|
||||
)
|
||||
|
||||
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,0 +1,57 @@
|
||||
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):
|
||||
self._fix_polygon(geom)
|
||||
elif isinstance(geom, GeometryCollection):
|
||||
self._fix_geometry_collection(geom)
|
||||
|
||||
self.wkt = geom.wkt
|
||||
self.srid = geom.srid
|
||||
|
||||
def _fix_polygon(self, poly):
|
||||
"""Fix single polygon orientation as described in __init__()."""
|
||||
if self._isClockwise(poly.exterior_ring):
|
||||
poly.exterior_ring = list(reversed(poly.exterior_ring))
|
||||
|
||||
for i in range(1, len(poly)):
|
||||
if not self._isClockwise(poly[i]):
|
||||
poly[i] = list(reversed(poly[i]))
|
||||
|
||||
return poly
|
||||
|
||||
def _fix_geometry_collection(self, coll):
|
||||
"""
|
||||
Fix polygon orientations in geometry collections as described in
|
||||
__init__().
|
||||
"""
|
||||
for i, geom in enumerate(coll):
|
||||
if isinstance(geom, Polygon):
|
||||
coll[i] = self._fix_polygon(geom)
|
||||
|
||||
def _isClockwise(self, coords):
|
||||
"""
|
||||
A modified shoelace algorithm to determine polygon orientation.
|
||||
See https://en.wikipedia.org/wiki/Shoelace_formula.
|
||||
"""
|
||||
n = len(coords)
|
||||
area = 0.0
|
||||
for i in range(n):
|
||||
j = (i + 1) % n
|
||||
area += coords[i][0] * coords[j][1]
|
||||
area -= coords[j][0] * coords[i][1]
|
||||
return area < 0.0
|
||||
@@ -0,0 +1,16 @@
|
||||
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
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import cx_Oracle
|
||||
|
||||
from django.db.backends.oracle.introspection import DatabaseIntrospection
|
||||
|
||||
|
||||
class OracleIntrospection(DatabaseIntrospection):
|
||||
# Associating any OBJECTVAR instances with GeometryField. Of course,
|
||||
# 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.
|
||||
data_types_reverse = DatabaseIntrospection.data_types_reverse.copy()
|
||||
data_types_reverse[cx_Oracle.OBJECT] = 'GeometryField'
|
||||
|
||||
def get_geometry_type(self, table_name, geo_col):
|
||||
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(), geo_col.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, geo_col)
|
||||
) 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
|
||||
@@ -0,0 +1,217 @@
|
||||
"""
|
||||
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.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.db.models import aggregates
|
||||
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.pop()
|
||||
return super().as_sql(connection, lookup, template_params, sql_params)
|
||||
|
||||
|
||||
class OracleOperations(BaseSpatialOperations, DatabaseOperations):
|
||||
|
||||
name = 'oracle'
|
||||
oracle = True
|
||||
disallowed_aggregates = (aggregates.Collect, aggregates.Extent3D, aggregates.MakeLine)
|
||||
|
||||
Adapter = OracleSpatialAdapter
|
||||
|
||||
extent = 'SDO_AGGR_MBR'
|
||||
unionagg = 'SDO_AGGR_UNION'
|
||||
|
||||
from_text = 'SDO_GEOMETRY'
|
||||
|
||||
function_names = {
|
||||
'Area': 'SDO_GEOM.SDO_AREA',
|
||||
'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'),
|
||||
'relate': SDORelate(), # Oracle uses a different syntax, e.g., 'mask=inside+touch'
|
||||
'touches': SDOOperator(func='SDO_TOUCH'),
|
||||
'within': SDOOperator(func='SDO_INSIDE'),
|
||||
'dwithin': SDODWithin(),
|
||||
}
|
||||
|
||||
unsupported_functions = {
|
||||
'AsGeoJSON', 'AsKML', 'AsSVG', 'Azimuth',
|
||||
'ForcePolygonCW', 'ForceRHR', 'GeoHash', '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'
|
||||
@@ -0,0 +1,94 @@
|
||||
from django.contrib.gis.db.models.fields 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 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)
|
||||
Whitespace-only changes.
@@ -0,0 +1,65 @@
|
||||
"""
|
||||
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()
|
||||
|
||||
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 '%s(%s)' % (
|
||||
'ST_GeogFromWKB' if self.geography else 'ST_GeomFromEWKB',
|
||||
self._adapter.getquoted().decode()
|
||||
)
|
||||
else:
|
||||
# For rasters, add explicit type cast to WKB string.
|
||||
return "'%s'::raster" % self.ewkb
|
||||
@@ -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")
|
||||
@@ -0,0 +1,12 @@
|
||||
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_3d_storage = True
|
||||
supports_3d_functions = True
|
||||
supports_left_right_lookups = True
|
||||
supports_raster = True
|
||||
supports_empty_geometries = True
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
from django.contrib.gis.gdal import OGRGeomType
|
||||
from django.db.backends.postgresql.introspection import DatabaseIntrospection
|
||||
|
||||
|
||||
class GeoIntrospectionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PostGISIntrospection(DatabaseIntrospection):
|
||||
# Reverse dictionary for PostGIS geometry types not populated until
|
||||
# introspection is actually performed.
|
||||
postgis_types_reverse = {}
|
||||
|
||||
ignored_tables = DatabaseIntrospection.ignored_tables + [
|
||||
'geography_columns',
|
||||
'geometry_columns',
|
||||
'raster_columns',
|
||||
'spatial_ref_sys',
|
||||
'raster_overviews',
|
||||
]
|
||||
|
||||
def get_postgis_types(self):
|
||||
"""
|
||||
Return a dictionary with keys that are the PostgreSQL object
|
||||
identification integers for the PostGIS geometry and/or
|
||||
geography types (if supported).
|
||||
"""
|
||||
field_types = [
|
||||
('geometry', 'GeometryField'),
|
||||
# The value for the geography type is actually a tuple
|
||||
# to pass in the `geography=True` keyword to the field
|
||||
# definition.
|
||||
('geography', ('GeometryField', {'geography': True})),
|
||||
]
|
||||
postgis_types = {}
|
||||
|
||||
# The OID integers associated with the geometry type may
|
||||
# be different across versions; hence, this is why we have
|
||||
# to query the PostgreSQL pg_type table corresponding to the
|
||||
# PostGIS custom data types.
|
||||
oid_sql = 'SELECT "oid" FROM "pg_type" WHERE "typname" = %s'
|
||||
with self.connection.cursor() as cursor:
|
||||
for field_type in field_types:
|
||||
cursor.execute(oid_sql, (field_type[0],))
|
||||
for result in cursor.fetchall():
|
||||
postgis_types[result[0]] = field_type[1]
|
||||
return postgis_types
|
||||
|
||||
def get_field_type(self, data_type, description):
|
||||
if not self.postgis_types_reverse:
|
||||
# If the PostGIS types reverse dictionary is not populated, do so
|
||||
# now. In order to prevent unnecessary requests upon connection
|
||||
# initialization, the `data_types_reverse` dictionary is not updated
|
||||
# with the PostGIS custom types until introspection is actually
|
||||
# performed -- in other words, when this function is called.
|
||||
self.postgis_types_reverse = self.get_postgis_types()
|
||||
self.data_types_reverse.update(self.postgis_types_reverse)
|
||||
return super().get_field_type(data_type, description)
|
||||
|
||||
def get_geometry_type(self, table_name, geo_col):
|
||||
"""
|
||||
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:
|
||||
try:
|
||||
# First seeing if this geometry column is in the `geometry_columns`
|
||||
cursor.execute('SELECT "coord_dimension", "srid", "type" '
|
||||
'FROM "geometry_columns" '
|
||||
'WHERE "f_table_name"=%s AND "f_geometry_column"=%s',
|
||||
(table_name, geo_col))
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
raise GeoIntrospectionError
|
||||
except GeoIntrospectionError:
|
||||
cursor.execute('SELECT "coord_dimension", "srid", "type" '
|
||||
'FROM "geography_columns" '
|
||||
'WHERE "f_table_name"=%s AND "f_geography_column"=%s',
|
||||
(table_name, geo_col))
|
||||
row = cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
raise Exception('Could not find a geometry or geography column for "%s"."%s"' %
|
||||
(table_name, geo_col))
|
||||
|
||||
# OGRGeomType does not require GDAL and makes it easy to convert
|
||||
# from OGC geom type name to Django field.
|
||||
field_type = OGRGeomType(row[2]).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 dim != 2:
|
||||
field_params['dim'] = dim
|
||||
return field_type, field_params
|
||||
+396
@@ -0,0 +1,396 @@
|
||||
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.backends.postgresql.operations import DatabaseOperations
|
||||
from django.db.models import Func, Value
|
||||
from django.db.utils import NotSupportedError, ProgrammingError
|
||||
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
|
||||
geography = 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 = {
|
||||
'BoundingCircle': 'ST_MinimumBoundingCircle',
|
||||
'NumPoints': 'ST_NPoints',
|
||||
}
|
||||
if self.spatial_version < (2, 2, 0):
|
||||
function_names.update({
|
||||
'DistanceSphere': 'ST_distance_sphere',
|
||||
'DistanceSpheroid': 'ST_distance_spheroid',
|
||||
'LengthSpheroid': 'ST_length_spheroid',
|
||||
'MemSize': 'ST_mem_size',
|
||||
})
|
||||
if self.spatial_version < (2, 4, 0):
|
||||
function_names['ForcePolygonCW'] = 'ST_ForceRHR'
|
||||
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.1. '
|
||||
'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.4 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.4 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.group(1), m.group(2), m.group(3)]))
|
||||
else:
|
||||
raise Exception('Could not determine PROJ.4 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'
|
||||
@@ -0,0 +1,141 @@
|
||||
import struct
|
||||
|
||||
from django.forms import ValidationError
|
||||
|
||||
from .const import (
|
||||
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, data = chunk(data, 2)
|
||||
pixeltype = unpack('B', pixeltype)[0]
|
||||
|
||||
# Subtract nodata byte from band nodata value if it exists
|
||||
has_nodata = pixeltype >= 64
|
||||
if has_nodata:
|
||||
pixeltype -= 64
|
||||
|
||||
# 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)}
|
||||
|
||||
# If the nodata flag is True, set the nodata value.
|
||||
if has_nodata:
|
||||
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 integer plus 64 as a flag for existing
|
||||
# nodata values:
|
||||
# 8BUI_VALUE = PG_PIXEL_TYPE (0-11) + FLAG (0 or 64)
|
||||
#
|
||||
# For example, if the byte value is 71, then the datatype is
|
||||
# 71-64 = 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 += 64
|
||||
|
||||
# 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()
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.db.backends.sqlite3.client import DatabaseClient
|
||||
|
||||
|
||||
class SpatiaLiteClient(DatabaseClient):
|
||||
executable_name = 'spatialite'
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
from django.contrib.gis.gdal import OGRGeomType
|
||||
from django.db.backends.sqlite3.introspection import (
|
||||
DatabaseIntrospection, FlexibleFieldLookupDict,
|
||||
)
|
||||
|
||||
|
||||
class GeoFlexibleFieldLookupDict(FlexibleFieldLookupDict):
|
||||
"""
|
||||
Sublcass 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, geo_col):
|
||||
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, geo_col))
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
raise Exception('Could not find a geometry column for "%s"."%s"' %
|
||||
(table_name, geo_col))
|
||||
|
||||
# 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
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
"""
|
||||
SQL functions reference lists:
|
||||
https://www.gaia-gis.it/gaia-sins/spatialite-sql-4.2.1.html
|
||||
"""
|
||||
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.db.models import aggregates
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry, GEOSGeometryBase
|
||||
from django.contrib.gis.geos.prototypes.io import wkb_r, wkt_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 = (aggregates.Extent3D,)
|
||||
|
||||
@cached_property
|
||||
def select(self):
|
||||
return 'CAST (AsEWKB(%s) AS BLOB)' if self.spatial_version >= (4, 3, 0) else 'AsText(%s)'
|
||||
|
||||
function_names = {
|
||||
'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', 'ForceRHR', 'MemSize'}
|
||||
if not self.lwgeom_version():
|
||||
unsupported |= {'Azimuth', 'GeoHash', 'IsValid', '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, 1, 0):
|
||||
raise ImproperlyConfigured('GeoDjango only supports SpatiaLite versions 4.1.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 proj4_version(self):
|
||||
"Return the version of the PROJ.4 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 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
|
||||
if self.spatial_version >= (4, 3, 0):
|
||||
read = wkb_r().read
|
||||
|
||||
def converter(value, expression, connection):
|
||||
return None if value is None else GEOSGeometryBase(read(value), geom_class)
|
||||
else:
|
||||
read = wkt_r().read
|
||||
srid = expression.output_field.srid
|
||||
if srid == -1:
|
||||
srid = None
|
||||
|
||||
def converter(value, expression, connection):
|
||||
if value is not None:
|
||||
geom = GEOSGeometryBase(read(value), geom_class)
|
||||
if srid:
|
||||
geom.srid = srid
|
||||
return geom
|
||||
return converter
|
||||
@@ -0,0 +1,165 @@
|
||||
from django.db.backends.sqlite3.schema import DatabaseSchemaEditor
|
||||
from django.db.utils import DatabaseError
|
||||
|
||||
|
||||
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.fields 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.fields 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.fields 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.fields 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.fields 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)),
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
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',
|
||||
]
|
||||
@@ -0,0 +1,76 @@
|
||||
from django.contrib.gis.db.models.fields import (
|
||||
ExtentField, GeometryCollectionField, GeometryField, LineStringField,
|
||||
)
|
||||
from django.db.models.aggregates import Aggregate
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
__all__ = ['Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union']
|
||||
|
||||
|
||||
class GeoAggregate(Aggregate):
|
||||
function = None
|
||||
is_extent = False
|
||||
|
||||
@cached_property
|
||||
def output_field(self):
|
||||
return self.output_field_class(self.source_expressions[0].output_field.srid)
|
||||
|
||||
def as_sql(self, compiler, connection, function=None, **extra_context):
|
||||
# this will be called again in parent, but it's needed now - before
|
||||
# we get the spatial_aggregate_name
|
||||
connection.ops.check_expression_support(self)
|
||||
return super().as_sql(
|
||||
compiler,
|
||||
connection,
|
||||
function=function or connection.ops.spatial_aggregate_name(self.name),
|
||||
**extra_context
|
||||
)
|
||||
|
||||
def as_oracle(self, compiler, connection, **extra_context):
|
||||
tolerance = self.extra.get('tolerance') or getattr(self, 'tolerance', 0.05)
|
||||
template = None if self.is_extent else '%(function)s(SDOAGGRTYPE(%(expressions)s,%(tolerance)s))'
|
||||
return self.as_sql(compiler, connection, template=template, tolerance=tolerance, **extra_context)
|
||||
|
||||
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
|
||||
c = super().resolve_expression(query, allow_joins, reuse, summarize, for_save)
|
||||
for expr in c.get_source_expressions():
|
||||
if not hasattr(expr.field, 'geom_type'):
|
||||
raise ValueError('Geospatial aggregates only allowed on geometry fields.')
|
||||
return c
|
||||
|
||||
|
||||
class Collect(GeoAggregate):
|
||||
name = 'Collect'
|
||||
output_field_class = GeometryCollectionField
|
||||
|
||||
|
||||
class Extent(GeoAggregate):
|
||||
name = 'Extent'
|
||||
is_extent = '2D'
|
||||
|
||||
def __init__(self, expression, **extra):
|
||||
super().__init__(expression, output_field=ExtentField(), **extra)
|
||||
|
||||
def convert_value(self, value, expression, connection):
|
||||
return connection.ops.convert_extent(value)
|
||||
|
||||
|
||||
class Extent3D(GeoAggregate):
|
||||
name = 'Extent3D'
|
||||
is_extent = '3D'
|
||||
|
||||
def __init__(self, expression, **extra):
|
||||
super().__init__(expression, output_field=ExtentField(), **extra)
|
||||
|
||||
def convert_value(self, value, expression, connection):
|
||||
return connection.ops.convert_extent3d(value)
|
||||
|
||||
|
||||
class MakeLine(GeoAggregate):
|
||||
name = 'MakeLine'
|
||||
output_field_class = LineStringField
|
||||
|
||||
|
||||
class Union(GeoAggregate):
|
||||
name = 'Union'
|
||||
output_field_class = GeometryField
|
||||
@@ -0,0 +1,471 @@
|
||||
import warnings
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.gis.db.models.fields import BaseSpatialField, GeometryField
|
||||
from django.contrib.gis.db.models.sql import AreaField, DistanceField
|
||||
from django.contrib.gis.geos import GEOSGeometry
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db.models import (
|
||||
BooleanField, FloatField, IntegerField, TextField, Transform,
|
||||
)
|
||||
from django.db.models.expressions import Func, Value
|
||||
from django.db.models.functions import Cast
|
||||
from django.db.utils import NotSupportedError
|
||||
from django.utils.deprecation import RemovedInDjango30Warning
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
NUMERIC_TYPES = (int, float, Decimal)
|
||||
|
||||
|
||||
class GeoFuncMixin:
|
||||
function = None
|
||||
geom_param_pos = (0,)
|
||||
|
||||
def __init__(self, *expressions, **extra):
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
# Ensure that value expressions are geometric.
|
||||
for pos in self.geom_param_pos:
|
||||
expr = self.source_expressions[pos]
|
||||
if not isinstance(expr, Value):
|
||||
continue
|
||||
try:
|
||||
output_field = expr.output_field
|
||||
except FieldError:
|
||||
output_field = None
|
||||
geom = expr.value
|
||||
if not isinstance(geom, GEOSGeometry) or output_field and not isinstance(output_field, GeometryField):
|
||||
raise TypeError("%s function requires a geometric argument in position %d." % (self.name, pos + 1))
|
||||
if not geom.srid and not output_field:
|
||||
raise ValueError("SRID is required for all geometries.")
|
||||
if not output_field:
|
||||
self.source_expressions[pos] = Value(geom, output_field=GeometryField(srid=geom.srid))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
@cached_property
|
||||
def geo_field(self):
|
||||
return self.source_expressions[self.geom_param_pos[0]].field
|
||||
|
||||
def as_sql(self, compiler, connection, function=None, **extra_context):
|
||||
if not self.function and not function:
|
||||
function = connection.ops.spatial_function_name(self.name)
|
||||
return super().as_sql(compiler, connection, function=function, **extra_context)
|
||||
|
||||
def resolve_expression(self, *args, **kwargs):
|
||||
res = super().resolve_expression(*args, **kwargs)
|
||||
|
||||
# Ensure that expressions are geometric.
|
||||
source_fields = res.get_source_fields()
|
||||
for pos in self.geom_param_pos:
|
||||
field = source_fields[pos]
|
||||
if not isinstance(field, GeometryField):
|
||||
raise TypeError(
|
||||
"%s function requires a GeometryField in position %s, got %s." % (
|
||||
self.name, pos + 1, type(field).__name__,
|
||||
)
|
||||
)
|
||||
|
||||
base_srid = res.geo_field.srid
|
||||
for pos in self.geom_param_pos[1:]:
|
||||
expr = res.source_expressions[pos]
|
||||
expr_srid = expr.output_field.srid
|
||||
if expr_srid != base_srid:
|
||||
# Automatic SRID conversion so objects are comparable.
|
||||
res.source_expressions[pos] = Transform(expr, base_srid).resolve_expression(*args, **kwargs)
|
||||
return res
|
||||
|
||||
def _handle_param(self, value, param_name='', check_types=None):
|
||||
if not hasattr(value, 'resolve_expression'):
|
||||
if check_types and not isinstance(value, check_types):
|
||||
raise TypeError(
|
||||
"The %s parameter has the wrong type: should be %s." % (
|
||||
param_name, check_types)
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
class GeoFunc(GeoFuncMixin, Func):
|
||||
pass
|
||||
|
||||
|
||||
class GeomOutputGeoFunc(GeoFunc):
|
||||
@cached_property
|
||||
def output_field(self):
|
||||
return GeometryField(srid=self.geo_field.srid)
|
||||
|
||||
|
||||
class SQLiteDecimalToFloatMixin:
|
||||
"""
|
||||
By default, Decimal values are converted to str by the SQLite backend, which
|
||||
is not acceptable by the GIS functions expecting numeric values.
|
||||
"""
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
for expr in self.get_source_expressions():
|
||||
if hasattr(expr, 'value') and isinstance(expr.value, Decimal):
|
||||
expr.value = float(expr.value)
|
||||
return super().as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class OracleToleranceMixin:
|
||||
tolerance = 0.05
|
||||
|
||||
def as_oracle(self, compiler, connection, **extra_context):
|
||||
tol = self.extra.get('tolerance', self.tolerance)
|
||||
return self.as_sql(
|
||||
compiler, connection,
|
||||
template="%%(function)s(%%(expressions)s, %s)" % tol,
|
||||
**extra_context
|
||||
)
|
||||
|
||||
|
||||
class Area(OracleToleranceMixin, GeoFunc):
|
||||
arity = 1
|
||||
|
||||
@cached_property
|
||||
def output_field(self):
|
||||
return AreaField(self.geo_field)
|
||||
|
||||
def as_sql(self, compiler, connection, **extra_context):
|
||||
if not connection.features.supports_area_geodetic and self.geo_field.geodetic(connection):
|
||||
raise NotSupportedError('Area on geodetic coordinate systems not supported.')
|
||||
return super().as_sql(compiler, connection, **extra_context)
|
||||
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
if self.geo_field.geodetic(connection):
|
||||
extra_context['template'] = '%(function)s(%(expressions)s, %(spheroid)d)'
|
||||
extra_context['spheroid'] = True
|
||||
return self.as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class Azimuth(GeoFunc):
|
||||
output_field = FloatField()
|
||||
arity = 2
|
||||
geom_param_pos = (0, 1)
|
||||
|
||||
|
||||
class AsGeoJSON(GeoFunc):
|
||||
output_field = TextField()
|
||||
|
||||
def __init__(self, expression, bbox=False, crs=False, precision=8, **extra):
|
||||
expressions = [expression]
|
||||
if precision is not None:
|
||||
expressions.append(self._handle_param(precision, 'precision', int))
|
||||
options = 0
|
||||
if crs and bbox:
|
||||
options = 3
|
||||
elif bbox:
|
||||
options = 1
|
||||
elif crs:
|
||||
options = 2
|
||||
if options:
|
||||
expressions.append(options)
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
|
||||
class AsGML(GeoFunc):
|
||||
geom_param_pos = (1,)
|
||||
output_field = TextField()
|
||||
|
||||
def __init__(self, expression, version=2, precision=8, **extra):
|
||||
expressions = [version, expression]
|
||||
if precision is not None:
|
||||
expressions.append(self._handle_param(precision, 'precision', int))
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_oracle(self, compiler, connection, **extra_context):
|
||||
source_expressions = self.get_source_expressions()
|
||||
version = source_expressions[0]
|
||||
clone = self.copy()
|
||||
clone.set_source_expressions([source_expressions[1]])
|
||||
extra_context['function'] = 'SDO_UTIL.TO_GML311GEOMETRY' if version.value == 3 else 'SDO_UTIL.TO_GMLGEOMETRY'
|
||||
return super(AsGML, clone).as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class AsKML(AsGML):
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
# No version parameter
|
||||
clone = self.copy()
|
||||
clone.set_source_expressions(self.get_source_expressions()[1:])
|
||||
return clone.as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class AsSVG(GeoFunc):
|
||||
output_field = TextField()
|
||||
|
||||
def __init__(self, expression, relative=False, precision=8, **extra):
|
||||
relative = relative if hasattr(relative, 'resolve_expression') else int(relative)
|
||||
expressions = [
|
||||
expression,
|
||||
relative,
|
||||
self._handle_param(precision, 'precision', int),
|
||||
]
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
|
||||
class BoundingCircle(OracleToleranceMixin, GeoFunc):
|
||||
def __init__(self, expression, num_seg=48, **extra):
|
||||
super().__init__(expression, num_seg, **extra)
|
||||
|
||||
def as_oracle(self, compiler, connection, **extra_context):
|
||||
clone = self.copy()
|
||||
clone.set_source_expressions([self.get_source_expressions()[0]])
|
||||
return super(BoundingCircle, clone).as_oracle(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class Centroid(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||
arity = 1
|
||||
|
||||
|
||||
class Difference(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||
arity = 2
|
||||
geom_param_pos = (0, 1)
|
||||
|
||||
|
||||
class DistanceResultMixin:
|
||||
@cached_property
|
||||
def output_field(self):
|
||||
return DistanceField(self.geo_field)
|
||||
|
||||
def source_is_geography(self):
|
||||
return self.geo_field.geography and self.geo_field.srid == 4326
|
||||
|
||||
|
||||
class Distance(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
|
||||
geom_param_pos = (0, 1)
|
||||
spheroid = None
|
||||
|
||||
def __init__(self, expr1, expr2, spheroid=None, **extra):
|
||||
expressions = [expr1, expr2]
|
||||
if spheroid is not None:
|
||||
self.spheroid = self._handle_param(spheroid, 'spheroid', bool)
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_postgresql(self, compiler, connection, **extra_context):
|
||||
clone = self.copy()
|
||||
function = None
|
||||
expr2 = clone.source_expressions[1]
|
||||
geography = self.source_is_geography()
|
||||
if expr2.output_field.geography != geography:
|
||||
if isinstance(expr2, Value):
|
||||
expr2.output_field.geography = geography
|
||||
else:
|
||||
clone.source_expressions[1] = Cast(
|
||||
expr2,
|
||||
GeometryField(srid=expr2.output_field.srid, geography=geography),
|
||||
)
|
||||
|
||||
if not geography and self.geo_field.geodetic(connection):
|
||||
# Geometry fields with geodetic (lon/lat) coordinates need special distance functions
|
||||
if self.spheroid:
|
||||
# DistanceSpheroid is more accurate and resource intensive than DistanceSphere
|
||||
function = connection.ops.spatial_function_name('DistanceSpheroid')
|
||||
# Replace boolean param by the real spheroid of the base field
|
||||
clone.source_expressions.append(Value(self.geo_field.spheroid(connection)))
|
||||
else:
|
||||
function = connection.ops.spatial_function_name('DistanceSphere')
|
||||
return super(Distance, clone).as_sql(compiler, connection, function=function, **extra_context)
|
||||
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
if self.geo_field.geodetic(connection):
|
||||
# SpatiaLite returns NULL instead of zero on geodetic coordinates
|
||||
extra_context['template'] = 'COALESCE(%(function)s(%(expressions)s, %(spheroid)s), 0)'
|
||||
extra_context['spheroid'] = int(bool(self.spheroid))
|
||||
return super().as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class Envelope(GeomOutputGeoFunc):
|
||||
arity = 1
|
||||
|
||||
|
||||
class ForcePolygonCW(GeomOutputGeoFunc):
|
||||
arity = 1
|
||||
|
||||
|
||||
class ForceRHR(GeomOutputGeoFunc):
|
||||
arity = 1
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
warnings.warn(
|
||||
'ForceRHR is deprecated in favor of ForcePolygonCW.',
|
||||
RemovedInDjango30Warning, stacklevel=2,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class GeoHash(GeoFunc):
|
||||
output_field = TextField()
|
||||
|
||||
def __init__(self, expression, precision=None, **extra):
|
||||
expressions = [expression]
|
||||
if precision is not None:
|
||||
expressions.append(self._handle_param(precision, 'precision', int))
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_mysql(self, compiler, connection, **extra_context):
|
||||
clone = self.copy()
|
||||
# If no precision is provided, set it to the maximum.
|
||||
if len(clone.source_expressions) < 2:
|
||||
clone.source_expressions.append(Value(100))
|
||||
return clone.as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class Intersection(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||
arity = 2
|
||||
geom_param_pos = (0, 1)
|
||||
|
||||
|
||||
@BaseSpatialField.register_lookup
|
||||
class IsValid(OracleToleranceMixin, GeoFuncMixin, Transform):
|
||||
lookup_name = 'isvalid'
|
||||
output_field = BooleanField()
|
||||
|
||||
def as_oracle(self, compiler, connection, **extra_context):
|
||||
sql, params = super().as_oracle(compiler, connection, **extra_context)
|
||||
return "CASE %s WHEN 'TRUE' THEN 1 ELSE 0 END" % sql, params
|
||||
|
||||
|
||||
class Length(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
|
||||
def __init__(self, expr1, spheroid=True, **extra):
|
||||
self.spheroid = spheroid
|
||||
super().__init__(expr1, **extra)
|
||||
|
||||
def as_sql(self, compiler, connection, **extra_context):
|
||||
if self.geo_field.geodetic(connection) and not connection.features.supports_length_geodetic:
|
||||
raise NotSupportedError("This backend doesn't support Length on geodetic fields")
|
||||
return super().as_sql(compiler, connection, **extra_context)
|
||||
|
||||
def as_postgresql(self, compiler, connection, **extra_context):
|
||||
clone = self.copy()
|
||||
function = None
|
||||
if self.source_is_geography():
|
||||
clone.source_expressions.append(Value(self.spheroid))
|
||||
elif self.geo_field.geodetic(connection):
|
||||
# Geometry fields with geodetic (lon/lat) coordinates need length_spheroid
|
||||
function = connection.ops.spatial_function_name('LengthSpheroid')
|
||||
clone.source_expressions.append(Value(self.geo_field.spheroid(connection)))
|
||||
else:
|
||||
dim = min(f.dim for f in self.get_source_fields() if f)
|
||||
if dim > 2:
|
||||
function = connection.ops.length3d
|
||||
return super(Length, clone).as_sql(compiler, connection, function=function, **extra_context)
|
||||
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
function = None
|
||||
if self.geo_field.geodetic(connection):
|
||||
function = 'GeodesicLength' if self.spheroid else 'GreatCircleLength'
|
||||
return super().as_sql(compiler, connection, function=function, **extra_context)
|
||||
|
||||
|
||||
class LineLocatePoint(GeoFunc):
|
||||
output_field = FloatField()
|
||||
arity = 2
|
||||
geom_param_pos = (0, 1)
|
||||
|
||||
|
||||
class MakeValid(GeoFunc):
|
||||
pass
|
||||
|
||||
|
||||
class MemSize(GeoFunc):
|
||||
output_field = IntegerField()
|
||||
arity = 1
|
||||
|
||||
|
||||
class NumGeometries(GeoFunc):
|
||||
output_field = IntegerField()
|
||||
arity = 1
|
||||
|
||||
|
||||
class NumPoints(GeoFunc):
|
||||
output_field = IntegerField()
|
||||
arity = 1
|
||||
|
||||
|
||||
class Perimeter(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
|
||||
arity = 1
|
||||
|
||||
def as_postgresql(self, compiler, connection, **extra_context):
|
||||
function = None
|
||||
if self.geo_field.geodetic(connection) and not self.source_is_geography():
|
||||
raise NotSupportedError("ST_Perimeter cannot use a non-projected non-geography field.")
|
||||
dim = min(f.dim for f in self.get_source_fields())
|
||||
if dim > 2:
|
||||
function = connection.ops.perimeter3d
|
||||
return super().as_sql(compiler, connection, function=function, **extra_context)
|
||||
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
if self.geo_field.geodetic(connection):
|
||||
raise NotSupportedError("Perimeter cannot use a non-projected field.")
|
||||
return super().as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class PointOnSurface(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||
arity = 1
|
||||
|
||||
|
||||
class Reverse(GeoFunc):
|
||||
arity = 1
|
||||
|
||||
|
||||
class Scale(SQLiteDecimalToFloatMixin, GeomOutputGeoFunc):
|
||||
def __init__(self, expression, x, y, z=0.0, **extra):
|
||||
expressions = [
|
||||
expression,
|
||||
self._handle_param(x, 'x', NUMERIC_TYPES),
|
||||
self._handle_param(y, 'y', NUMERIC_TYPES),
|
||||
]
|
||||
if z != 0.0:
|
||||
expressions.append(self._handle_param(z, 'z', NUMERIC_TYPES))
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
|
||||
class SnapToGrid(SQLiteDecimalToFloatMixin, GeomOutputGeoFunc):
|
||||
def __init__(self, expression, *args, **extra):
|
||||
nargs = len(args)
|
||||
expressions = [expression]
|
||||
if nargs in (1, 2):
|
||||
expressions.extend(
|
||||
[self._handle_param(arg, '', NUMERIC_TYPES) for arg in args]
|
||||
)
|
||||
elif nargs == 4:
|
||||
# Reverse origin and size param ordering
|
||||
expressions += [
|
||||
*(self._handle_param(arg, '', NUMERIC_TYPES) for arg in args[2:]),
|
||||
*(self._handle_param(arg, '', NUMERIC_TYPES) for arg in args[0:2]),
|
||||
]
|
||||
else:
|
||||
raise ValueError('Must provide 1, 2, or 4 arguments to `SnapToGrid`.')
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
|
||||
class SymDifference(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||
arity = 2
|
||||
geom_param_pos = (0, 1)
|
||||
|
||||
|
||||
class Transform(GeomOutputGeoFunc):
|
||||
def __init__(self, expression, srid, **extra):
|
||||
expressions = [
|
||||
expression,
|
||||
self._handle_param(srid, 'srid', int),
|
||||
]
|
||||
if 'output_field' not in extra:
|
||||
extra['output_field'] = GeometryField(srid=srid)
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
|
||||
class Translate(Scale):
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
clone = self.copy()
|
||||
if len(self.source_expressions) < 4:
|
||||
# Always provide the z parameter for ST_Translate
|
||||
clone.source_expressions.append(Value(0))
|
||||
return super(Translate, clone).as_sqlite(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class Union(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||
arity = 2
|
||||
geom_param_pos = (0, 1)
|
||||
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
The SpatialProxy object allows for lazy-geometries and lazy-rasters. The proxy
|
||||
uses Python descriptors for instantiating and setting Geometry or Raster
|
||||
objects corresponding to geographic model fields.
|
||||
|
||||
Thanks to Robert Coup for providing this functionality (see #4322).
|
||||
"""
|
||||
from django.db.models.query_utils import DeferredAttribute
|
||||
|
||||
|
||||
class SpatialProxy(DeferredAttribute):
|
||||
def __init__(self, klass, field, load_func=None):
|
||||
"""
|
||||
Initialize on the given Geometry or Raster class (not an instance)
|
||||
and the corresponding field.
|
||||
"""
|
||||
self._field = field
|
||||
self._klass = klass
|
||||
self._load_func = load_func or klass
|
||||
super().__init__(field.attname)
|
||||
|
||||
def __get__(self, instance, cls=None):
|
||||
"""
|
||||
Retrieve the geometry or raster, initializing it using the
|
||||
corresponding class specified during initialization and the value of
|
||||
the field. Currently, GEOS or OGR geometries as well as GDALRasters are
|
||||
supported.
|
||||
"""
|
||||
if instance is None:
|
||||
# Accessed on a class, not an instance
|
||||
return self
|
||||
|
||||
# Getting the value of the field.
|
||||
try:
|
||||
geo_value = instance.__dict__[self._field.attname]
|
||||
except KeyError:
|
||||
geo_value = super().__get__(instance, cls)
|
||||
|
||||
if isinstance(geo_value, self._klass):
|
||||
geo_obj = geo_value
|
||||
elif (geo_value is None) or (geo_value == ''):
|
||||
geo_obj = None
|
||||
else:
|
||||
# Otherwise, a geometry or raster object is built using the field's
|
||||
# contents, and the model's corresponding attribute is set.
|
||||
geo_obj = self._load_func(geo_value)
|
||||
setattr(instance, self._field.attname, geo_obj)
|
||||
return geo_obj
|
||||
|
||||
def __set__(self, instance, value):
|
||||
"""
|
||||
Retrieve the proxied geometry or raster with the corresponding class
|
||||
specified during initialization.
|
||||
|
||||
To set geometries, use values of None, HEXEWKB, or WKT.
|
||||
To set rasters, use JSON or dict values.
|
||||
"""
|
||||
# The geographic type of the field.
|
||||
gtype = self._field.geom_type
|
||||
|
||||
if gtype == 'RASTER' and (value is None or isinstance(value, (str, dict, self._klass))):
|
||||
# For raster fields, assure input is None or a string, dict, or
|
||||
# raster instance.
|
||||
pass
|
||||
elif isinstance(value, self._klass):
|
||||
# The geometry type must match that of the field -- unless the
|
||||
# general GeometryField is used.
|
||||
if value.srid is None:
|
||||
# Assigning the field SRID if the geometry has no SRID.
|
||||
value.srid = self._field.srid
|
||||
elif value is None or isinstance(value, (str, memoryview)):
|
||||
# Set geometries with None, WKT, HEX, or WKB
|
||||
pass
|
||||
else:
|
||||
raise TypeError('Cannot set %s SpatialProxy (%s) with value of type: %s' % (
|
||||
instance.__class__.__name__, gtype, type(value)))
|
||||
|
||||
# Setting the objects dictionary with the value, and returning.
|
||||
instance.__dict__[self._field.attname] = value
|
||||
return value
|
||||
@@ -0,0 +1,7 @@
|
||||
from django.contrib.gis.db.models.sql.conversion import (
|
||||
AreaField, DistanceField,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'AreaField', 'DistanceField',
|
||||
]
|
||||
@@ -0,0 +1,140 @@
|
||||
from django.contrib.syndication.views import Feed as BaseFeed
|
||||
from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
|
||||
|
||||
|
||||
class GeoFeedMixin:
|
||||
"""
|
||||
This mixin provides the necessary routines for SyndicationFeed subclasses
|
||||
to produce simple GeoRSS or W3C Geo elements.
|
||||
"""
|
||||
|
||||
def georss_coords(self, coords):
|
||||
"""
|
||||
In GeoRSS coordinate pairs are ordered by lat/lon and separated by
|
||||
a single white space. Given a tuple of coordinates, return a string
|
||||
GeoRSS representation.
|
||||
"""
|
||||
return ' '.join('%f %f' % (coord[1], coord[0]) for coord in coords)
|
||||
|
||||
def add_georss_point(self, handler, coords, w3c_geo=False):
|
||||
"""
|
||||
Adds a GeoRSS point with the given coords using the given handler.
|
||||
Handles the differences between simple GeoRSS and the more popular
|
||||
W3C Geo specification.
|
||||
"""
|
||||
if w3c_geo:
|
||||
lon, lat = coords[:2]
|
||||
handler.addQuickElement('geo:lat', '%f' % lat)
|
||||
handler.addQuickElement('geo:lon', '%f' % lon)
|
||||
else:
|
||||
handler.addQuickElement('georss:point', self.georss_coords((coords,)))
|
||||
|
||||
def add_georss_element(self, handler, item, w3c_geo=False):
|
||||
"""Add a GeoRSS XML element using the given item and handler."""
|
||||
# Getting the Geometry object.
|
||||
geom = item.get('geometry')
|
||||
if geom is not None:
|
||||
if isinstance(geom, (list, tuple)):
|
||||
# Special case if a tuple/list was passed in. The tuple may be
|
||||
# a point or a box
|
||||
box_coords = None
|
||||
if isinstance(geom[0], (list, tuple)):
|
||||
# Box: ( (X0, Y0), (X1, Y1) )
|
||||
if len(geom) == 2:
|
||||
box_coords = geom
|
||||
else:
|
||||
raise ValueError('Only should be two sets of coordinates.')
|
||||
else:
|
||||
if len(geom) == 2:
|
||||
# Point: (X, Y)
|
||||
self.add_georss_point(handler, geom, w3c_geo=w3c_geo)
|
||||
elif len(geom) == 4:
|
||||
# Box: (X0, Y0, X1, Y1)
|
||||
box_coords = (geom[:2], geom[2:])
|
||||
else:
|
||||
raise ValueError('Only should be 2 or 4 numeric elements.')
|
||||
# If a GeoRSS box was given via tuple.
|
||||
if box_coords is not None:
|
||||
if w3c_geo:
|
||||
raise ValueError('Cannot use simple GeoRSS box in W3C Geo feeds.')
|
||||
handler.addQuickElement('georss:box', self.georss_coords(box_coords))
|
||||
else:
|
||||
# Getting the lowercase geometry type.
|
||||
gtype = str(geom.geom_type).lower()
|
||||
if gtype == 'point':
|
||||
self.add_georss_point(handler, geom.coords, w3c_geo=w3c_geo)
|
||||
else:
|
||||
if w3c_geo:
|
||||
raise ValueError('W3C Geo only supports Point geometries.')
|
||||
# For formatting consistent w/the GeoRSS simple standard:
|
||||
# http://georss.org/1.0#simple
|
||||
if gtype in ('linestring', 'linearring'):
|
||||
handler.addQuickElement('georss:line', self.georss_coords(geom.coords))
|
||||
elif gtype in ('polygon',):
|
||||
# Only support the exterior ring.
|
||||
handler.addQuickElement('georss:polygon', self.georss_coords(geom[0].coords))
|
||||
else:
|
||||
raise ValueError('Geometry type "%s" not supported.' % geom.geom_type)
|
||||
|
||||
|
||||
# ### SyndicationFeed subclasses ###
|
||||
class GeoRSSFeed(Rss201rev2Feed, GeoFeedMixin):
|
||||
def rss_attributes(self):
|
||||
attrs = super().rss_attributes()
|
||||
attrs['xmlns:georss'] = 'http://www.georss.org/georss'
|
||||
return attrs
|
||||
|
||||
def add_item_elements(self, handler, item):
|
||||
super().add_item_elements(handler, item)
|
||||
self.add_georss_element(handler, item)
|
||||
|
||||
def add_root_elements(self, handler):
|
||||
super().add_root_elements(handler)
|
||||
self.add_georss_element(handler, self.feed)
|
||||
|
||||
|
||||
class GeoAtom1Feed(Atom1Feed, GeoFeedMixin):
|
||||
def root_attributes(self):
|
||||
attrs = super().root_attributes()
|
||||
attrs['xmlns:georss'] = 'http://www.georss.org/georss'
|
||||
return attrs
|
||||
|
||||
def add_item_elements(self, handler, item):
|
||||
super().add_item_elements(handler, item)
|
||||
self.add_georss_element(handler, item)
|
||||
|
||||
def add_root_elements(self, handler):
|
||||
super().add_root_elements(handler)
|
||||
self.add_georss_element(handler, self.feed)
|
||||
|
||||
|
||||
class W3CGeoFeed(Rss201rev2Feed, GeoFeedMixin):
|
||||
def rss_attributes(self):
|
||||
attrs = super().rss_attributes()
|
||||
attrs['xmlns:geo'] = 'http://www.w3.org/2003/01/geo/wgs84_pos#'
|
||||
return attrs
|
||||
|
||||
def add_item_elements(self, handler, item):
|
||||
super().add_item_elements(handler, item)
|
||||
self.add_georss_element(handler, item, w3c_geo=True)
|
||||
|
||||
def add_root_elements(self, handler):
|
||||
super().add_root_elements(handler)
|
||||
self.add_georss_element(handler, self.feed, w3c_geo=True)
|
||||
|
||||
|
||||
# ### Feed subclass ###
|
||||
class Feed(BaseFeed):
|
||||
"""
|
||||
This is a subclass of the `Feed` from `django.contrib.syndication`.
|
||||
This allows users to define a `geometry(obj)` and/or `item_geometry(item)`
|
||||
methods on their own subclasses so that geo-referenced information may
|
||||
placed in the feed.
|
||||
"""
|
||||
feed_type = GeoRSSFeed
|
||||
|
||||
def feed_extra_kwargs(self, obj):
|
||||
return {'geometry': self._get_dynamic_attr('geometry', obj)}
|
||||
|
||||
def item_extra_kwargs(self, item):
|
||||
return {'geometry': self._get_dynamic_attr('item_geometry', item)}
|
||||
@@ -0,0 +1,117 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.gis import gdal
|
||||
from django.contrib.gis.geometry import json_regex
|
||||
from django.contrib.gis.geos import GEOSException, GEOSGeometry
|
||||
from django.forms.widgets import Widget
|
||||
from django.utils import translation
|
||||
|
||||
logger = logging.getLogger('django.contrib.gis')
|
||||
|
||||
|
||||
class BaseGeometryWidget(Widget):
|
||||
"""
|
||||
The base class for rich geometry widgets.
|
||||
Render a map using the WKT of the geometry.
|
||||
"""
|
||||
geom_type = 'GEOMETRY'
|
||||
map_srid = 4326
|
||||
map_width = 600
|
||||
map_height = 400
|
||||
display_raw = False
|
||||
|
||||
supports_3d = False
|
||||
template_name = '' # set on subclasses
|
||||
|
||||
def __init__(self, attrs=None):
|
||||
self.attrs = {}
|
||||
for key in ('geom_type', 'map_srid', 'map_width', 'map_height', 'display_raw'):
|
||||
self.attrs[key] = getattr(self, key)
|
||||
if attrs:
|
||||
self.attrs.update(attrs)
|
||||
|
||||
def serialize(self, value):
|
||||
return value.wkt if value else ''
|
||||
|
||||
def deserialize(self, value):
|
||||
try:
|
||||
return GEOSGeometry(value)
|
||||
except (GEOSException, ValueError, TypeError) as err:
|
||||
logger.error("Error creating geometry from value '%s' (%s)", value, err)
|
||||
return None
|
||||
|
||||
def get_context(self, name, value, attrs):
|
||||
context = super().get_context(name, value, attrs)
|
||||
# If a string reaches here (via a validation error on another
|
||||
# field) then just reconstruct the Geometry.
|
||||
if value and isinstance(value, str):
|
||||
value = self.deserialize(value)
|
||||
|
||||
if value:
|
||||
# Check that srid of value and map match
|
||||
if value.srid and value.srid != self.map_srid:
|
||||
try:
|
||||
ogr = value.ogr
|
||||
ogr.transform(self.map_srid)
|
||||
value = ogr
|
||||
except gdal.GDALException as err:
|
||||
logger.error(
|
||||
"Error transforming geometry from srid '%s' to srid '%s' (%s)",
|
||||
value.srid, self.map_srid, err
|
||||
)
|
||||
|
||||
context.update(self.build_attrs(self.attrs, {
|
||||
'name': name,
|
||||
'module': 'geodjango_%s' % name.replace('-', '_'), # JS-safe
|
||||
'serialized': self.serialize(value),
|
||||
'geom_type': gdal.OGRGeomType(self.attrs['geom_type']),
|
||||
'STATIC_URL': settings.STATIC_URL,
|
||||
'LANGUAGE_BIDI': translation.get_language_bidi(),
|
||||
**(attrs or {}),
|
||||
}))
|
||||
return context
|
||||
|
||||
|
||||
class OpenLayersWidget(BaseGeometryWidget):
|
||||
template_name = 'gis/openlayers.html'
|
||||
map_srid = 3857
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
'all': (
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/ol3/4.6.5/ol.css',
|
||||
'gis/css/ol3.css',
|
||||
)
|
||||
}
|
||||
js = (
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/ol3/4.6.5/ol.js',
|
||||
'gis/js/OLMapWidget.js',
|
||||
)
|
||||
|
||||
def serialize(self, value):
|
||||
return value.json if value else ''
|
||||
|
||||
def deserialize(self, value):
|
||||
geom = super().deserialize(value)
|
||||
# GeoJSON assumes WGS84 (4326). Use the map's SRID instead.
|
||||
if geom and json_regex.match(value) and self.map_srid != 4326:
|
||||
geom.srid = self.map_srid
|
||||
return geom
|
||||
|
||||
|
||||
class OSMWidget(OpenLayersWidget):
|
||||
"""
|
||||
An OpenLayers/OpenStreetMap-based widget.
|
||||
"""
|
||||
template_name = 'gis/openlayers-osm.html'
|
||||
default_lon = 5
|
||||
default_lat = 47
|
||||
default_zoom = 12
|
||||
|
||||
def __init__(self, attrs=None):
|
||||
super().__init__()
|
||||
for key in ('default_lon', 'default_lat', 'default_zoom'):
|
||||
self.attrs[key] = getattr(self, key)
|
||||
if attrs:
|
||||
self.attrs.update(attrs)
|
||||
@@ -0,0 +1,28 @@
|
||||
Copyright (c) 2007-2009, Justin Bronn
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of OGRGeometry nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
This module houses ctypes interfaces for GDAL objects. The following GDAL
|
||||
objects are supported:
|
||||
|
||||
CoordTransform: Used for coordinate transformations from one spatial
|
||||
reference system to another.
|
||||
|
||||
Driver: Wraps an OGR data source driver.
|
||||
|
||||
DataSource: Wrapper for the OGR data source object, supports
|
||||
OGR-supported data sources.
|
||||
|
||||
Envelope: A ctypes structure for bounding boxes (GDAL library
|
||||
not required).
|
||||
|
||||
OGRGeometry: Object for accessing OGR Geometry functionality.
|
||||
|
||||
OGRGeomType: A class for representing the different OGR Geometry
|
||||
types (GDAL library not required).
|
||||
|
||||
SpatialReference: Represents OSR Spatial Reference objects.
|
||||
|
||||
The GDAL library will be imported from the system path using the default
|
||||
library name for the current OS. The default library path may be overridden
|
||||
by setting `GDAL_LIBRARY_PATH` in your settings with the path to the GDAL C
|
||||
library on your system.
|
||||
"""
|
||||
from django.contrib.gis.gdal.datasource import DataSource
|
||||
from django.contrib.gis.gdal.driver import Driver
|
||||
from django.contrib.gis.gdal.envelope import Envelope
|
||||
from django.contrib.gis.gdal.error import (
|
||||
GDALException, SRSException, check_err,
|
||||
)
|
||||
from django.contrib.gis.gdal.geometries import OGRGeometry
|
||||
from django.contrib.gis.gdal.geomtype import OGRGeomType
|
||||
from django.contrib.gis.gdal.libgdal import (
|
||||
GDAL_VERSION, gdal_full_version, gdal_version,
|
||||
)
|
||||
from django.contrib.gis.gdal.raster.source import GDALRaster
|
||||
from django.contrib.gis.gdal.srs import CoordTransform, SpatialReference
|
||||
|
||||
__all__ = (
|
||||
'Driver', 'DataSource', 'CoordTransform', 'Envelope', 'GDALException',
|
||||
'GDALRaster', 'GDAL_VERSION', 'OGRGeometry', 'OGRGeomType',
|
||||
'SpatialReference', 'SRSException', 'check_err', 'gdal_version',
|
||||
'gdal_full_version',
|
||||
)
|
||||
@@ -0,0 +1,120 @@
|
||||
"""
|
||||
DataSource is a wrapper for the OGR Data Source object, which provides
|
||||
an interface for reading vector geometry data from many different file
|
||||
formats (including ESRI shapefiles).
|
||||
|
||||
When instantiating a DataSource object, use the filename of a
|
||||
GDAL-supported data source. For example, a SHP file or a
|
||||
TIGER/Line file from the government.
|
||||
|
||||
The ds_driver keyword is used internally when a ctypes pointer
|
||||
is passed in directly.
|
||||
|
||||
Example:
|
||||
ds = DataSource('/home/foo/bar.shp')
|
||||
for layer in ds:
|
||||
for feature in layer:
|
||||
# Getting the geometry for the feature.
|
||||
g = feature.geom
|
||||
|
||||
# Getting the 'description' field for the feature.
|
||||
desc = feature['description']
|
||||
|
||||
# We can also increment through all of the fields
|
||||
# attached to this feature.
|
||||
for field in feature:
|
||||
# Get the name of the field (e.g. 'description')
|
||||
nm = field.name
|
||||
|
||||
# Get the type (integer) of the field, e.g. 0 => OFTInteger
|
||||
t = field.type
|
||||
|
||||
# Returns the value the field; OFTIntegers return ints,
|
||||
# OFTReal returns floats, all else returns string.
|
||||
val = field.value
|
||||
"""
|
||||
from ctypes import byref
|
||||
|
||||
from django.contrib.gis.gdal.base import GDALBase
|
||||
from django.contrib.gis.gdal.driver import Driver
|
||||
from django.contrib.gis.gdal.error import GDALException
|
||||
from django.contrib.gis.gdal.layer import Layer
|
||||
from django.contrib.gis.gdal.prototypes import ds as capi
|
||||
from django.utils.encoding import force_bytes, force_text
|
||||
|
||||
|
||||
# For more information, see the OGR C API source code:
|
||||
# https://www.gdal.org/ogr__api_8h.html
|
||||
#
|
||||
# The OGR_DS_* routines are relevant here.
|
||||
class DataSource(GDALBase):
|
||||
"Wraps an OGR Data Source object."
|
||||
destructor = capi.destroy_ds
|
||||
|
||||
def __init__(self, ds_input, ds_driver=False, write=False, encoding='utf-8'):
|
||||
# The write flag.
|
||||
if write:
|
||||
self._write = 1
|
||||
else:
|
||||
self._write = 0
|
||||
# See also https://trac.osgeo.org/gdal/wiki/rfc23_ogr_unicode
|
||||
self.encoding = encoding
|
||||
|
||||
Driver.ensure_registered()
|
||||
|
||||
if isinstance(ds_input, str):
|
||||
# The data source driver is a void pointer.
|
||||
ds_driver = Driver.ptr_type()
|
||||
try:
|
||||
# OGROpen will auto-detect the data source type.
|
||||
ds = capi.open_ds(force_bytes(ds_input), self._write, byref(ds_driver))
|
||||
except GDALException:
|
||||
# Making the error message more clear rather than something
|
||||
# like "Invalid pointer returned from OGROpen".
|
||||
raise GDALException('Could not open the datasource at "%s"' % ds_input)
|
||||
elif isinstance(ds_input, self.ptr_type) and isinstance(ds_driver, Driver.ptr_type):
|
||||
ds = ds_input
|
||||
else:
|
||||
raise GDALException('Invalid data source input type: %s' % type(ds_input))
|
||||
|
||||
if ds:
|
||||
self.ptr = ds
|
||||
self.driver = Driver(ds_driver)
|
||||
else:
|
||||
# Raise an exception if the returned pointer is NULL
|
||||
raise GDALException('Invalid data source file "%s"' % ds_input)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"Allows use of the index [] operator to get a layer at the index."
|
||||
if isinstance(index, str):
|
||||
try:
|
||||
layer = capi.get_layer_by_name(self.ptr, force_bytes(index))
|
||||
except GDALException:
|
||||
raise IndexError('Invalid OGR layer name given: %s.' % index)
|
||||
elif isinstance(index, int):
|
||||
if 0 <= index < self.layer_count:
|
||||
layer = capi.get_layer(self._ptr, index)
|
||||
else:
|
||||
raise IndexError('Index out of range when accessing layers in a datasource: %s.' % index)
|
||||
else:
|
||||
raise TypeError('Invalid index type: %s' % type(index))
|
||||
return Layer(layer, self)
|
||||
|
||||
def __len__(self):
|
||||
"Return the number of layers within the data source."
|
||||
return self.layer_count
|
||||
|
||||
def __str__(self):
|
||||
"Return OGR GetName and Driver for the Data Source."
|
||||
return '%s (%s)' % (self.name, self.driver)
|
||||
|
||||
@property
|
||||
def layer_count(self):
|
||||
"Return the number of layers in the data source."
|
||||
return capi.get_layer_count(self._ptr)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"Return the name of the data source."
|
||||
name = capi.get_ds_name(self._ptr)
|
||||
return force_text(name, self.encoding, strings_only=True)
|
||||
@@ -0,0 +1,97 @@
|
||||
from ctypes import c_void_p
|
||||
|
||||
from django.contrib.gis.gdal.base import GDALBase
|
||||
from django.contrib.gis.gdal.error import GDALException
|
||||
from django.contrib.gis.gdal.prototypes import ds as vcapi, raster as rcapi
|
||||
from django.utils.encoding import force_bytes, force_text
|
||||
|
||||
|
||||
class Driver(GDALBase):
|
||||
"""
|
||||
Wrap a GDAL/OGR Data Source Driver.
|
||||
For more information, see the C API source code:
|
||||
https://www.gdal.org/gdal_8h.html - https://www.gdal.org/ogr__api_8h.html
|
||||
"""
|
||||
|
||||
# Case-insensitive aliases for some GDAL/OGR Drivers.
|
||||
# For a complete list of original driver names see
|
||||
# https://www.gdal.org/ogr_formats.html (vector)
|
||||
# https://www.gdal.org/formats_list.html (raster)
|
||||
_alias = {
|
||||
# vector
|
||||
'esri': 'ESRI Shapefile',
|
||||
'shp': 'ESRI Shapefile',
|
||||
'shape': 'ESRI Shapefile',
|
||||
'tiger': 'TIGER',
|
||||
'tiger/line': 'TIGER',
|
||||
# raster
|
||||
'tiff': 'GTiff',
|
||||
'tif': 'GTiff',
|
||||
'jpeg': 'JPEG',
|
||||
'jpg': 'JPEG',
|
||||
}
|
||||
|
||||
def __init__(self, dr_input):
|
||||
"""
|
||||
Initialize an GDAL/OGR driver on either a string or integer input.
|
||||
"""
|
||||
if isinstance(dr_input, str):
|
||||
# If a string name of the driver was passed in
|
||||
self.ensure_registered()
|
||||
|
||||
# Checking the alias dictionary (case-insensitive) to see if an
|
||||
# alias exists for the given driver.
|
||||
if dr_input.lower() in self._alias:
|
||||
name = self._alias[dr_input.lower()]
|
||||
else:
|
||||
name = dr_input
|
||||
|
||||
# Attempting to get the GDAL/OGR driver by the string name.
|
||||
for iface in (vcapi, rcapi):
|
||||
driver = c_void_p(iface.get_driver_by_name(force_bytes(name)))
|
||||
if driver:
|
||||
break
|
||||
elif isinstance(dr_input, int):
|
||||
self.ensure_registered()
|
||||
for iface in (vcapi, rcapi):
|
||||
driver = iface.get_driver(dr_input)
|
||||
if driver:
|
||||
break
|
||||
elif isinstance(dr_input, c_void_p):
|
||||
driver = dr_input
|
||||
else:
|
||||
raise GDALException('Unrecognized input type for GDAL/OGR Driver: %s' % type(dr_input))
|
||||
|
||||
# Making sure we get a valid pointer to the OGR Driver
|
||||
if not driver:
|
||||
raise GDALException('Could not initialize GDAL/OGR Driver on input: %s' % dr_input)
|
||||
self.ptr = driver
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@classmethod
|
||||
def ensure_registered(cls):
|
||||
"""
|
||||
Attempt to register all the data source drivers.
|
||||
"""
|
||||
# Only register all if the driver counts are 0 (or else all drivers
|
||||
# will be registered over and over again)
|
||||
if not vcapi.get_driver_count():
|
||||
vcapi.register_all()
|
||||
if not rcapi.get_driver_count():
|
||||
rcapi.register_all()
|
||||
|
||||
@classmethod
|
||||
def driver_count(cls):
|
||||
"""
|
||||
Return the number of GDAL/OGR data source drivers registered.
|
||||
"""
|
||||
return vcapi.get_driver_count() + rcapi.get_driver_count()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
Return description/name string for this driver.
|
||||
"""
|
||||
return force_text(rcapi.get_driver_description(self.ptr))
|
||||
@@ -0,0 +1,178 @@
|
||||
"""
|
||||
The GDAL/OGR library uses an Envelope structure to hold the bounding
|
||||
box information for a geometry. The envelope (bounding box) contains
|
||||
two pairs of coordinates, one for the lower left coordinate and one
|
||||
for the upper right coordinate:
|
||||
|
||||
+----------o Upper right; (max_x, max_y)
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
Lower left (min_x, min_y) o----------+
|
||||
"""
|
||||
from ctypes import Structure, c_double
|
||||
|
||||
from django.contrib.gis.gdal.error import GDALException
|
||||
|
||||
|
||||
# The OGR definition of an Envelope is a C structure containing four doubles.
|
||||
# See the 'ogr_core.h' source file for more information:
|
||||
# https://www.gdal.org/ogr__core_8h_source.html
|
||||
class OGREnvelope(Structure):
|
||||
"Represent the OGREnvelope C Structure."
|
||||
_fields_ = [("MinX", c_double),
|
||||
("MaxX", c_double),
|
||||
("MinY", c_double),
|
||||
("MaxY", c_double),
|
||||
]
|
||||
|
||||
|
||||
class Envelope:
|
||||
"""
|
||||
The Envelope object is a C structure that contains the minimum and
|
||||
maximum X, Y coordinates for a rectangle bounding box. The naming
|
||||
of the variables is compatible with the OGR Envelope structure.
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""
|
||||
The initialization function may take an OGREnvelope structure, 4-element
|
||||
tuple or list, or 4 individual arguments.
|
||||
"""
|
||||
|
||||
if len(args) == 1:
|
||||
if isinstance(args[0], OGREnvelope):
|
||||
# OGREnvelope (a ctypes Structure) was passed in.
|
||||
self._envelope = args[0]
|
||||
elif isinstance(args[0], (tuple, list)):
|
||||
# A tuple was passed in.
|
||||
if len(args[0]) != 4:
|
||||
raise GDALException('Incorrect number of tuple elements (%d).' % len(args[0]))
|
||||
else:
|
||||
self._from_sequence(args[0])
|
||||
else:
|
||||
raise TypeError('Incorrect type of argument: %s' % type(args[0]))
|
||||
elif len(args) == 4:
|
||||
# Individual parameters passed in.
|
||||
# Thanks to ww for the help
|
||||
self._from_sequence([float(a) for a in args])
|
||||
else:
|
||||
raise GDALException('Incorrect number (%d) of arguments.' % len(args))
|
||||
|
||||
# Checking the x,y coordinates
|
||||
if self.min_x > self.max_x:
|
||||
raise GDALException('Envelope minimum X > maximum X.')
|
||||
if self.min_y > self.max_y:
|
||||
raise GDALException('Envelope minimum Y > maximum Y.')
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Return True if the envelopes are equivalent; can compare against
|
||||
other Envelopes and 4-tuples.
|
||||
"""
|
||||
if isinstance(other, Envelope):
|
||||
return (self.min_x == other.min_x) and (self.min_y == other.min_y) and \
|
||||
(self.max_x == other.max_x) and (self.max_y == other.max_y)
|
||||
elif isinstance(other, tuple) and len(other) == 4:
|
||||
return (self.min_x == other[0]) and (self.min_y == other[1]) and \
|
||||
(self.max_x == other[2]) and (self.max_y == other[3])
|
||||
else:
|
||||
raise GDALException('Equivalence testing only works with other Envelopes.')
|
||||
|
||||
def __str__(self):
|
||||
"Return a string representation of the tuple."
|
||||
return str(self.tuple)
|
||||
|
||||
def _from_sequence(self, seq):
|
||||
"Initialize the C OGR Envelope structure from the given sequence."
|
||||
self._envelope = OGREnvelope()
|
||||
self._envelope.MinX = seq[0]
|
||||
self._envelope.MinY = seq[1]
|
||||
self._envelope.MaxX = seq[2]
|
||||
self._envelope.MaxY = seq[3]
|
||||
|
||||
def expand_to_include(self, *args):
|
||||
"""
|
||||
Modify the envelope to expand to include the boundaries of
|
||||
the passed-in 2-tuple (a point), 4-tuple (an extent) or
|
||||
envelope.
|
||||
"""
|
||||
# We provide a number of different signatures for this method,
|
||||
# and the logic here is all about converting them into a
|
||||
# 4-tuple single parameter which does the actual work of
|
||||
# expanding the envelope.
|
||||
if len(args) == 1:
|
||||
if isinstance(args[0], Envelope):
|
||||
return self.expand_to_include(args[0].tuple)
|
||||
elif hasattr(args[0], 'x') and hasattr(args[0], 'y'):
|
||||
return self.expand_to_include(args[0].x, args[0].y, args[0].x, args[0].y)
|
||||
elif isinstance(args[0], (tuple, list)):
|
||||
# A tuple was passed in.
|
||||
if len(args[0]) == 2:
|
||||
return self.expand_to_include((args[0][0], args[0][1], args[0][0], args[0][1]))
|
||||
elif len(args[0]) == 4:
|
||||
(minx, miny, maxx, maxy) = args[0]
|
||||
if minx < self._envelope.MinX:
|
||||
self._envelope.MinX = minx
|
||||
if miny < self._envelope.MinY:
|
||||
self._envelope.MinY = miny
|
||||
if maxx > self._envelope.MaxX:
|
||||
self._envelope.MaxX = maxx
|
||||
if maxy > self._envelope.MaxY:
|
||||
self._envelope.MaxY = maxy
|
||||
else:
|
||||
raise GDALException('Incorrect number of tuple elements (%d).' % len(args[0]))
|
||||
else:
|
||||
raise TypeError('Incorrect type of argument: %s' % type(args[0]))
|
||||
elif len(args) == 2:
|
||||
# An x and an y parameter were passed in
|
||||
return self.expand_to_include((args[0], args[1], args[0], args[1]))
|
||||
elif len(args) == 4:
|
||||
# Individual parameters passed in.
|
||||
return self.expand_to_include(args)
|
||||
else:
|
||||
raise GDALException('Incorrect number (%d) of arguments.' % len(args[0]))
|
||||
|
||||
@property
|
||||
def min_x(self):
|
||||
"Return the value of the minimum X coordinate."
|
||||
return self._envelope.MinX
|
||||
|
||||
@property
|
||||
def min_y(self):
|
||||
"Return the value of the minimum Y coordinate."
|
||||
return self._envelope.MinY
|
||||
|
||||
@property
|
||||
def max_x(self):
|
||||
"Return the value of the maximum X coordinate."
|
||||
return self._envelope.MaxX
|
||||
|
||||
@property
|
||||
def max_y(self):
|
||||
"Return the value of the maximum Y coordinate."
|
||||
return self._envelope.MaxY
|
||||
|
||||
@property
|
||||
def ur(self):
|
||||
"Return the upper-right coordinate."
|
||||
return (self.max_x, self.max_y)
|
||||
|
||||
@property
|
||||
def ll(self):
|
||||
"Return the lower-left coordinate."
|
||||
return (self.min_x, self.min_y)
|
||||
|
||||
@property
|
||||
def tuple(self):
|
||||
"Return a tuple representing the envelope."
|
||||
return (self.min_x, self.min_y, self.max_x, self.max_y)
|
||||
|
||||
@property
|
||||
def wkt(self):
|
||||
"Return WKT representing a Polygon for this envelope."
|
||||
# TODO: Fix significant figures.
|
||||
return 'POLYGON((%s %s,%s %s,%s %s,%s %s,%s %s))' % \
|
||||
(self.min_x, self.min_y, self.min_x, self.max_y,
|
||||
self.max_x, self.max_y, self.max_x, self.min_y,
|
||||
self.min_x, self.min_y)
|
||||
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
This module houses the GDAL & SRS Exception objects, and the
|
||||
check_err() routine which checks the status code returned by
|
||||
GDAL/OGR methods.
|
||||
"""
|
||||
|
||||
|
||||
# #### GDAL & SRS Exceptions ####
|
||||
class GDALException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SRSException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# #### GDAL/OGR error checking codes and routine ####
|
||||
|
||||
# OGR Error Codes
|
||||
OGRERR_DICT = {
|
||||
1: (GDALException, 'Not enough data.'),
|
||||
2: (GDALException, 'Not enough memory.'),
|
||||
3: (GDALException, 'Unsupported geometry type.'),
|
||||
4: (GDALException, 'Unsupported operation.'),
|
||||
5: (GDALException, 'Corrupt data.'),
|
||||
6: (GDALException, 'OGR failure.'),
|
||||
7: (SRSException, 'Unsupported SRS.'),
|
||||
8: (GDALException, 'Invalid handle.'),
|
||||
}
|
||||
|
||||
# CPL Error Codes
|
||||
# https://www.gdal.org/cpl__error_8h.html
|
||||
CPLERR_DICT = {
|
||||
1: (GDALException, 'AppDefined'),
|
||||
2: (GDALException, 'OutOfMemory'),
|
||||
3: (GDALException, 'FileIO'),
|
||||
4: (GDALException, 'OpenFailed'),
|
||||
5: (GDALException, 'IllegalArg'),
|
||||
6: (GDALException, 'NotSupported'),
|
||||
7: (GDALException, 'AssertionFailed'),
|
||||
8: (GDALException, 'NoWriteAccess'),
|
||||
9: (GDALException, 'UserInterrupt'),
|
||||
10: (GDALException, 'ObjectNull'),
|
||||
}
|
||||
|
||||
ERR_NONE = 0
|
||||
|
||||
|
||||
def check_err(code, cpl=False):
|
||||
"""
|
||||
Check the given CPL/OGRERR and raise an exception where appropriate.
|
||||
"""
|
||||
err_dict = CPLERR_DICT if cpl else OGRERR_DICT
|
||||
|
||||
if code == ERR_NONE:
|
||||
return
|
||||
elif code in err_dict:
|
||||
e, msg = err_dict[code]
|
||||
raise e(msg)
|
||||
else:
|
||||
raise GDALException('Unknown error code: "%s"' % code)
|
||||
@@ -0,0 +1,95 @@
|
||||
from django.contrib.gis.gdal.error import GDALException
|
||||
|
||||
|
||||
class OGRGeomType:
|
||||
"Encapsulate OGR Geometry Types."
|
||||
|
||||
wkb25bit = -2147483648
|
||||
|
||||
# Dictionary of acceptable OGRwkbGeometryType s and their string names.
|
||||
_types = {
|
||||
0: 'Unknown',
|
||||
1: 'Point',
|
||||
2: 'LineString',
|
||||
3: 'Polygon',
|
||||
4: 'MultiPoint',
|
||||
5: 'MultiLineString',
|
||||
6: 'MultiPolygon',
|
||||
7: 'GeometryCollection',
|
||||
100: 'None',
|
||||
101: 'LinearRing',
|
||||
102: 'PointZ',
|
||||
1 + wkb25bit: 'Point25D',
|
||||
2 + wkb25bit: 'LineString25D',
|
||||
3 + wkb25bit: 'Polygon25D',
|
||||
4 + wkb25bit: 'MultiPoint25D',
|
||||
5 + wkb25bit: 'MultiLineString25D',
|
||||
6 + wkb25bit: 'MultiPolygon25D',
|
||||
7 + wkb25bit: 'GeometryCollection25D',
|
||||
}
|
||||
# Reverse type dictionary, keyed by lowercase of the name.
|
||||
_str_types = {v.lower(): k for k, v in _types.items()}
|
||||
|
||||
def __init__(self, type_input):
|
||||
"Figure out the correct OGR Type based upon the input."
|
||||
if isinstance(type_input, OGRGeomType):
|
||||
num = type_input.num
|
||||
elif isinstance(type_input, str):
|
||||
type_input = type_input.lower()
|
||||
if type_input == 'geometry':
|
||||
type_input = 'unknown'
|
||||
num = self._str_types.get(type_input)
|
||||
if num is None:
|
||||
raise GDALException('Invalid OGR String Type "%s"' % type_input)
|
||||
elif isinstance(type_input, int):
|
||||
if type_input not in self._types:
|
||||
raise GDALException('Invalid OGR Integer Type: %d' % type_input)
|
||||
num = type_input
|
||||
else:
|
||||
raise TypeError('Invalid OGR input type given.')
|
||||
|
||||
# Setting the OGR geometry type number.
|
||||
self.num = num
|
||||
|
||||
def __str__(self):
|
||||
"Return the value of the name property."
|
||||
return self.name
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Do an equivalence test on the OGR type with the given
|
||||
other OGRGeomType, the short-hand string, or the integer.
|
||||
"""
|
||||
if isinstance(other, OGRGeomType):
|
||||
return self.num == other.num
|
||||
elif isinstance(other, str):
|
||||
return self.name.lower() == other.lower()
|
||||
elif isinstance(other, int):
|
||||
return self.num == other
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"Return a short-hand string form of the OGR Geometry type."
|
||||
return self._types[self.num]
|
||||
|
||||
@property
|
||||
def django(self):
|
||||
"Return the Django GeometryField for this OGR Type."
|
||||
s = self.name.replace('25D', '')
|
||||
if s in ('LinearRing', 'None'):
|
||||
return None
|
||||
elif s == 'Unknown':
|
||||
s = 'Geometry'
|
||||
elif s == 'PointZ':
|
||||
s = 'Point'
|
||||
return s + 'Field'
|
||||
|
||||
def to_multi(self):
|
||||
"""
|
||||
Transform Point, LineString, Polygon, and their 25D equivalents
|
||||
to their Multi... counterpart.
|
||||
"""
|
||||
if self.name.startswith(('Point', 'LineString', 'Polygon')):
|
||||
self.num += 3
|
||||
@@ -0,0 +1,124 @@
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from ctypes import CDLL, CFUNCTYPE, c_char_p, c_int
|
||||
from ctypes.util import find_library
|
||||
|
||||
from django.contrib.gis.gdal.error import GDALException
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
logger = logging.getLogger('django.contrib.gis')
|
||||
|
||||
# Custom library path set?
|
||||
try:
|
||||
from django.conf import settings
|
||||
lib_path = settings.GDAL_LIBRARY_PATH
|
||||
except (AttributeError, EnvironmentError,
|
||||
ImportError, ImproperlyConfigured):
|
||||
lib_path = None
|
||||
|
||||
if lib_path:
|
||||
lib_names = None
|
||||
elif os.name == 'nt':
|
||||
# Windows NT shared libraries
|
||||
lib_names = ['gdal203', 'gdal202', 'gdal201', 'gdal20', 'gdal111']
|
||||
elif os.name == 'posix':
|
||||
# *NIX library names.
|
||||
lib_names = ['gdal', 'GDAL', 'gdal2.3.0', 'gdal2.2.0', 'gdal2.1.0', 'gdal2.0.0', 'gdal1.11.0']
|
||||
else:
|
||||
raise ImproperlyConfigured('GDAL is unsupported on OS "%s".' % os.name)
|
||||
|
||||
# Using the ctypes `find_library` utility to find the
|
||||
# path to the GDAL library from the list of library names.
|
||||
if lib_names:
|
||||
for lib_name in lib_names:
|
||||
lib_path = find_library(lib_name)
|
||||
if lib_path is not None:
|
||||
break
|
||||
|
||||
if lib_path is None:
|
||||
raise ImproperlyConfigured(
|
||||
'Could not find the GDAL library (tried "%s"). Is GDAL installed? '
|
||||
'If it is, try setting GDAL_LIBRARY_PATH in your settings.'
|
||||
% '", "'.join(lib_names)
|
||||
)
|
||||
|
||||
# This loads the GDAL/OGR C library
|
||||
lgdal = CDLL(lib_path)
|
||||
|
||||
# On Windows, the GDAL binaries have some OSR routines exported with
|
||||
# STDCALL, while others are not. Thus, the library will also need to
|
||||
# be loaded up as WinDLL for said OSR functions that require the
|
||||
# different calling convention.
|
||||
if os.name == 'nt':
|
||||
from ctypes import WinDLL
|
||||
lwingdal = WinDLL(lib_path)
|
||||
|
||||
|
||||
def std_call(func):
|
||||
"""
|
||||
Return the correct STDCALL function for certain OSR routines on Win32
|
||||
platforms.
|
||||
"""
|
||||
if os.name == 'nt':
|
||||
return lwingdal[func]
|
||||
else:
|
||||
return lgdal[func]
|
||||
|
||||
|
||||
# #### Version-information functions. ####
|
||||
|
||||
# Return GDAL library version information with the given key.
|
||||
_version_info = std_call('GDALVersionInfo')
|
||||
_version_info.argtypes = [c_char_p]
|
||||
_version_info.restype = c_char_p
|
||||
|
||||
|
||||
def gdal_version():
|
||||
"Return only the GDAL version number information."
|
||||
return _version_info(b'RELEASE_NAME')
|
||||
|
||||
|
||||
def gdal_full_version():
|
||||
"Return the full GDAL version information."
|
||||
return _version_info('')
|
||||
|
||||
|
||||
version_regex = re.compile(r'^(?P<major>\d+)\.(?P<minor>\d+)(\.(?P<subminor>\d+))?')
|
||||
|
||||
|
||||
def gdal_version_info():
|
||||
ver = gdal_version().decode()
|
||||
m = version_regex.match(ver)
|
||||
if not m:
|
||||
raise GDALException('Could not parse GDAL version string "%s"' % ver)
|
||||
return {key: m.group(key) for key in ('major', 'minor', 'subminor')}
|
||||
|
||||
|
||||
_verinfo = gdal_version_info()
|
||||
GDAL_MAJOR_VERSION = int(_verinfo['major'])
|
||||
GDAL_MINOR_VERSION = int(_verinfo['minor'])
|
||||
GDAL_SUBMINOR_VERSION = _verinfo['subminor'] and int(_verinfo['subminor'])
|
||||
GDAL_VERSION = (GDAL_MAJOR_VERSION, GDAL_MINOR_VERSION, GDAL_SUBMINOR_VERSION)
|
||||
del _verinfo
|
||||
|
||||
# Set library error handling so as errors are logged
|
||||
CPLErrorHandler = CFUNCTYPE(None, c_int, c_int, c_char_p)
|
||||
|
||||
|
||||
def err_handler(error_class, error_number, message):
|
||||
logger.error('GDAL_ERROR %d: %s', error_number, message)
|
||||
|
||||
|
||||
err_handler = CPLErrorHandler(err_handler)
|
||||
|
||||
|
||||
def function(name, args, restype):
|
||||
func = std_call(name)
|
||||
func.argtypes = args
|
||||
func.restype = restype
|
||||
return func
|
||||
|
||||
|
||||
set_error_handler = function('CPLSetErrorHandler', [CPLErrorHandler], CPLErrorHandler)
|
||||
set_error_handler(err_handler)
|
||||
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
This module houses the ctypes function prototypes for OGR DataSource
|
||||
related data structures. OGR_Dr_*, OGR_DS_*, OGR_L_*, OGR_F_*,
|
||||
OGR_Fld_* routines are relevant here.
|
||||
"""
|
||||
from ctypes import POINTER, c_char_p, c_double, c_int, c_long, c_void_p
|
||||
|
||||
from django.contrib.gis.gdal.envelope import OGREnvelope
|
||||
from django.contrib.gis.gdal.libgdal import GDAL_VERSION, lgdal
|
||||
from django.contrib.gis.gdal.prototypes.generation import (
|
||||
const_string_output, double_output, geom_output, int64_output, int_output,
|
||||
srs_output, void_output, voidptr_output,
|
||||
)
|
||||
|
||||
c_int_p = POINTER(c_int) # shortcut type
|
||||
|
||||
# Driver Routines
|
||||
register_all = void_output(lgdal.OGRRegisterAll, [], errcheck=False)
|
||||
cleanup_all = void_output(lgdal.OGRCleanupAll, [], errcheck=False)
|
||||
get_driver = voidptr_output(lgdal.OGRGetDriver, [c_int])
|
||||
get_driver_by_name = voidptr_output(lgdal.OGRGetDriverByName, [c_char_p], errcheck=False)
|
||||
get_driver_count = int_output(lgdal.OGRGetDriverCount, [])
|
||||
get_driver_name = const_string_output(lgdal.OGR_Dr_GetName, [c_void_p], decoding='ascii')
|
||||
|
||||
# DataSource
|
||||
open_ds = voidptr_output(lgdal.OGROpen, [c_char_p, c_int, POINTER(c_void_p)])
|
||||
destroy_ds = void_output(lgdal.OGR_DS_Destroy, [c_void_p], errcheck=False)
|
||||
release_ds = void_output(lgdal.OGRReleaseDataSource, [c_void_p])
|
||||
get_ds_name = const_string_output(lgdal.OGR_DS_GetName, [c_void_p])
|
||||
get_layer = voidptr_output(lgdal.OGR_DS_GetLayer, [c_void_p, c_int])
|
||||
get_layer_by_name = voidptr_output(lgdal.OGR_DS_GetLayerByName, [c_void_p, c_char_p])
|
||||
get_layer_count = int_output(lgdal.OGR_DS_GetLayerCount, [c_void_p])
|
||||
|
||||
# Layer Routines
|
||||
get_extent = void_output(lgdal.OGR_L_GetExtent, [c_void_p, POINTER(OGREnvelope), c_int])
|
||||
get_feature = voidptr_output(lgdal.OGR_L_GetFeature, [c_void_p, c_long])
|
||||
get_feature_count = int_output(lgdal.OGR_L_GetFeatureCount, [c_void_p, c_int])
|
||||
get_layer_defn = voidptr_output(lgdal.OGR_L_GetLayerDefn, [c_void_p])
|
||||
get_layer_srs = srs_output(lgdal.OGR_L_GetSpatialRef, [c_void_p])
|
||||
get_next_feature = voidptr_output(lgdal.OGR_L_GetNextFeature, [c_void_p])
|
||||
reset_reading = void_output(lgdal.OGR_L_ResetReading, [c_void_p], errcheck=False)
|
||||
test_capability = int_output(lgdal.OGR_L_TestCapability, [c_void_p, c_char_p])
|
||||
get_spatial_filter = geom_output(lgdal.OGR_L_GetSpatialFilter, [c_void_p])
|
||||
set_spatial_filter = void_output(lgdal.OGR_L_SetSpatialFilter, [c_void_p, c_void_p], errcheck=False)
|
||||
set_spatial_filter_rect = void_output(
|
||||
lgdal.OGR_L_SetSpatialFilterRect,
|
||||
[c_void_p, c_double, c_double, c_double, c_double], errcheck=False
|
||||
)
|
||||
|
||||
# Feature Definition Routines
|
||||
get_fd_geom_type = int_output(lgdal.OGR_FD_GetGeomType, [c_void_p])
|
||||
get_fd_name = const_string_output(lgdal.OGR_FD_GetName, [c_void_p])
|
||||
get_feat_name = const_string_output(lgdal.OGR_FD_GetName, [c_void_p])
|
||||
get_field_count = int_output(lgdal.OGR_FD_GetFieldCount, [c_void_p])
|
||||
get_field_defn = voidptr_output(lgdal.OGR_FD_GetFieldDefn, [c_void_p, c_int])
|
||||
|
||||
# Feature Routines
|
||||
clone_feature = voidptr_output(lgdal.OGR_F_Clone, [c_void_p])
|
||||
destroy_feature = void_output(lgdal.OGR_F_Destroy, [c_void_p], errcheck=False)
|
||||
feature_equal = int_output(lgdal.OGR_F_Equal, [c_void_p, c_void_p])
|
||||
get_feat_geom_ref = geom_output(lgdal.OGR_F_GetGeometryRef, [c_void_p])
|
||||
get_feat_field_count = int_output(lgdal.OGR_F_GetFieldCount, [c_void_p])
|
||||
get_feat_field_defn = voidptr_output(lgdal.OGR_F_GetFieldDefnRef, [c_void_p, c_int])
|
||||
get_fid = int_output(lgdal.OGR_F_GetFID, [c_void_p])
|
||||
get_field_as_datetime = int_output(
|
||||
lgdal.OGR_F_GetFieldAsDateTime,
|
||||
[c_void_p, c_int, c_int_p, c_int_p, c_int_p, c_int_p, c_int_p, c_int_p]
|
||||
)
|
||||
get_field_as_double = double_output(lgdal.OGR_F_GetFieldAsDouble, [c_void_p, c_int])
|
||||
get_field_as_integer = int_output(lgdal.OGR_F_GetFieldAsInteger, [c_void_p, c_int])
|
||||
if GDAL_VERSION >= (2, 0):
|
||||
get_field_as_integer64 = int64_output(lgdal.OGR_F_GetFieldAsInteger64, [c_void_p, c_int])
|
||||
get_field_as_string = const_string_output(lgdal.OGR_F_GetFieldAsString, [c_void_p, c_int])
|
||||
get_field_index = int_output(lgdal.OGR_F_GetFieldIndex, [c_void_p, c_char_p])
|
||||
|
||||
# Field Routines
|
||||
get_field_name = const_string_output(lgdal.OGR_Fld_GetNameRef, [c_void_p])
|
||||
get_field_precision = int_output(lgdal.OGR_Fld_GetPrecision, [c_void_p])
|
||||
get_field_type = int_output(lgdal.OGR_Fld_GetType, [c_void_p])
|
||||
get_field_type_name = const_string_output(lgdal.OGR_GetFieldTypeName, [c_int])
|
||||
get_field_width = int_output(lgdal.OGR_Fld_GetWidth, [c_void_p])
|
||||
@@ -0,0 +1,158 @@
|
||||
"""
|
||||
This module contains functions that generate ctypes prototypes for the
|
||||
GDAL routines.
|
||||
"""
|
||||
from ctypes import POINTER, c_char_p, c_double, c_int, c_int64, c_void_p
|
||||
from functools import partial
|
||||
|
||||
from django.contrib.gis.gdal.prototypes.errcheck import (
|
||||
check_arg_errcode, check_const_string, check_errcode, check_geom,
|
||||
check_geom_offset, check_pointer, check_srs, check_str_arg, check_string,
|
||||
)
|
||||
|
||||
|
||||
class gdal_char_p(c_char_p):
|
||||
pass
|
||||
|
||||
|
||||
def double_output(func, argtypes, errcheck=False, strarg=False, cpl=False):
|
||||
"Generate a ctypes function that returns a double value."
|
||||
func.argtypes = argtypes
|
||||
func.restype = c_double
|
||||
if errcheck:
|
||||
func.errcheck = partial(check_arg_errcode, cpl=cpl)
|
||||
if strarg:
|
||||
func.errcheck = check_str_arg
|
||||
return func
|
||||
|
||||
|
||||
def geom_output(func, argtypes, offset=None):
|
||||
"""
|
||||
Generate a function that returns a Geometry either by reference
|
||||
or directly (if the return_geom keyword is set to True).
|
||||
"""
|
||||
# Setting the argument types
|
||||
func.argtypes = argtypes
|
||||
|
||||
if not offset:
|
||||
# When a geometry pointer is directly returned.
|
||||
func.restype = c_void_p
|
||||
func.errcheck = check_geom
|
||||
else:
|
||||
# Error code returned, geometry is returned by-reference.
|
||||
func.restype = c_int
|
||||
|
||||
def geomerrcheck(result, func, cargs):
|
||||
return check_geom_offset(result, func, cargs, offset)
|
||||
func.errcheck = geomerrcheck
|
||||
|
||||
return func
|
||||
|
||||
|
||||
def int_output(func, argtypes, errcheck=None):
|
||||
"Generate a ctypes function that returns an integer value."
|
||||
func.argtypes = argtypes
|
||||
func.restype = c_int
|
||||
if errcheck:
|
||||
func.errcheck = errcheck
|
||||
return func
|
||||
|
||||
|
||||
def int64_output(func, argtypes):
|
||||
"Generate a ctypes function that returns a 64-bit integer value."
|
||||
func.argtypes = argtypes
|
||||
func.restype = c_int64
|
||||
return func
|
||||
|
||||
|
||||
def srs_output(func, argtypes):
|
||||
"""
|
||||
Generate a ctypes prototype for the given function with
|
||||
the given C arguments that returns a pointer to an OGR
|
||||
Spatial Reference System.
|
||||
"""
|
||||
func.argtypes = argtypes
|
||||
func.restype = c_void_p
|
||||
func.errcheck = check_srs
|
||||
return func
|
||||
|
||||
|
||||
def const_string_output(func, argtypes, offset=None, decoding=None, cpl=False):
|
||||
func.argtypes = argtypes
|
||||
if offset:
|
||||
func.restype = c_int
|
||||
else:
|
||||
func.restype = c_char_p
|
||||
|
||||
def _check_const(result, func, cargs):
|
||||
res = check_const_string(result, func, cargs, offset=offset, cpl=cpl)
|
||||
if res and decoding:
|
||||
res = res.decode(decoding)
|
||||
return res
|
||||
func.errcheck = _check_const
|
||||
|
||||
return func
|
||||
|
||||
|
||||
def string_output(func, argtypes, offset=-1, str_result=False, decoding=None):
|
||||
"""
|
||||
Generate a ctypes prototype for the given function with the
|
||||
given argument types that returns a string from a GDAL pointer.
|
||||
The `const` flag indicates whether the allocated pointer should
|
||||
be freed via the GDAL library routine VSIFree -- but only applies
|
||||
only when `str_result` is True.
|
||||
"""
|
||||
func.argtypes = argtypes
|
||||
if str_result:
|
||||
# Use subclass of c_char_p so the error checking routine
|
||||
# can free the memory at the pointer's address.
|
||||
func.restype = gdal_char_p
|
||||
else:
|
||||
# Error code is returned
|
||||
func.restype = c_int
|
||||
|
||||
# Dynamically defining our error-checking function with the
|
||||
# given offset.
|
||||
def _check_str(result, func, cargs):
|
||||
res = check_string(result, func, cargs, offset=offset, str_result=str_result)
|
||||
if res and decoding:
|
||||
res = res.decode(decoding)
|
||||
return res
|
||||
func.errcheck = _check_str
|
||||
return func
|
||||
|
||||
|
||||
def void_output(func, argtypes, errcheck=True, cpl=False):
|
||||
"""
|
||||
For functions that don't only return an error code that needs to
|
||||
be examined.
|
||||
"""
|
||||
if argtypes:
|
||||
func.argtypes = argtypes
|
||||
if errcheck:
|
||||
# `errcheck` keyword may be set to False for routines that
|
||||
# return void, rather than a status code.
|
||||
func.restype = c_int
|
||||
func.errcheck = partial(check_errcode, cpl=cpl)
|
||||
else:
|
||||
func.restype = None
|
||||
|
||||
return func
|
||||
|
||||
|
||||
def voidptr_output(func, argtypes, errcheck=True):
|
||||
"For functions that return c_void_p."
|
||||
func.argtypes = argtypes
|
||||
func.restype = c_void_p
|
||||
if errcheck:
|
||||
func.errcheck = check_pointer
|
||||
return func
|
||||
|
||||
|
||||
def chararray_output(func, argtypes, errcheck=True):
|
||||
"""For functions that return a c_char_p array."""
|
||||
func.argtypes = argtypes
|
||||
func.restype = POINTER(c_char_p)
|
||||
if errcheck:
|
||||
func.errcheck = check_pointer
|
||||
return func
|
||||
@@ -0,0 +1,80 @@
|
||||
from ctypes import POINTER, c_char_p, c_int, c_void_p
|
||||
|
||||
from django.contrib.gis.gdal.libgdal import lgdal, std_call
|
||||
from django.contrib.gis.gdal.prototypes.generation import (
|
||||
const_string_output, double_output, int_output, srs_output, string_output,
|
||||
void_output,
|
||||
)
|
||||
|
||||
|
||||
# Shortcut generation for routines with known parameters.
|
||||
def srs_double(f):
|
||||
"""
|
||||
Create a function prototype for the OSR routines that take
|
||||
the OSRSpatialReference object and return a double value.
|
||||
"""
|
||||
return double_output(f, [c_void_p, POINTER(c_int)], errcheck=True)
|
||||
|
||||
|
||||
def units_func(f):
|
||||
"""
|
||||
Create a ctypes function prototype for OSR units functions, e.g.,
|
||||
OSRGetAngularUnits, OSRGetLinearUnits.
|
||||
"""
|
||||
return double_output(f, [c_void_p, POINTER(c_char_p)], strarg=True)
|
||||
|
||||
|
||||
# Creation & destruction.
|
||||
clone_srs = srs_output(std_call('OSRClone'), [c_void_p])
|
||||
new_srs = srs_output(std_call('OSRNewSpatialReference'), [c_char_p])
|
||||
release_srs = void_output(lgdal.OSRRelease, [c_void_p], errcheck=False)
|
||||
destroy_srs = void_output(std_call('OSRDestroySpatialReference'), [c_void_p], errcheck=False)
|
||||
srs_validate = void_output(lgdal.OSRValidate, [c_void_p])
|
||||
|
||||
# Getting the semi_major, semi_minor, and flattening functions.
|
||||
semi_major = srs_double(lgdal.OSRGetSemiMajor)
|
||||
semi_minor = srs_double(lgdal.OSRGetSemiMinor)
|
||||
invflattening = srs_double(lgdal.OSRGetInvFlattening)
|
||||
|
||||
# WKT, PROJ, EPSG, XML importation routines.
|
||||
from_wkt = void_output(lgdal.OSRImportFromWkt, [c_void_p, POINTER(c_char_p)])
|
||||
from_proj = void_output(lgdal.OSRImportFromProj4, [c_void_p, c_char_p])
|
||||
from_epsg = void_output(std_call('OSRImportFromEPSG'), [c_void_p, c_int])
|
||||
from_xml = void_output(lgdal.OSRImportFromXML, [c_void_p, c_char_p])
|
||||
from_user_input = void_output(std_call('OSRSetFromUserInput'), [c_void_p, c_char_p])
|
||||
|
||||
# Morphing to/from ESRI WKT.
|
||||
morph_to_esri = void_output(lgdal.OSRMorphToESRI, [c_void_p])
|
||||
morph_from_esri = void_output(lgdal.OSRMorphFromESRI, [c_void_p])
|
||||
|
||||
# Identifying the EPSG
|
||||
identify_epsg = void_output(lgdal.OSRAutoIdentifyEPSG, [c_void_p])
|
||||
|
||||
# Getting the angular_units, linear_units functions
|
||||
linear_units = units_func(lgdal.OSRGetLinearUnits)
|
||||
angular_units = units_func(lgdal.OSRGetAngularUnits)
|
||||
|
||||
# For exporting to WKT, PROJ.4, "Pretty" WKT, and XML.
|
||||
to_wkt = string_output(std_call('OSRExportToWkt'), [c_void_p, POINTER(c_char_p)], decoding='utf-8')
|
||||
to_proj = string_output(std_call('OSRExportToProj4'), [c_void_p, POINTER(c_char_p)], decoding='ascii')
|
||||
to_pretty_wkt = string_output(
|
||||
std_call('OSRExportToPrettyWkt'),
|
||||
[c_void_p, POINTER(c_char_p), c_int], offset=-2, decoding='utf-8'
|
||||
)
|
||||
|
||||
# Memory leak fixed in GDAL 1.5; still exists in 1.4.
|
||||
to_xml = string_output(lgdal.OSRExportToXML, [c_void_p, POINTER(c_char_p), c_char_p], offset=-2, decoding='utf-8')
|
||||
|
||||
# String attribute retrival routines.
|
||||
get_attr_value = const_string_output(std_call('OSRGetAttrValue'), [c_void_p, c_char_p, c_int], decoding='utf-8')
|
||||
get_auth_name = const_string_output(lgdal.OSRGetAuthorityName, [c_void_p, c_char_p], decoding='ascii')
|
||||
get_auth_code = const_string_output(lgdal.OSRGetAuthorityCode, [c_void_p, c_char_p], decoding='ascii')
|
||||
|
||||
# SRS Properties
|
||||
isgeographic = int_output(lgdal.OSRIsGeographic, [c_void_p])
|
||||
islocal = int_output(lgdal.OSRIsLocal, [c_void_p])
|
||||
isprojected = int_output(lgdal.OSRIsProjected, [c_void_p])
|
||||
|
||||
# Coordinate transformation
|
||||
new_ct = srs_output(std_call('OCTNewCoordinateTransformation'), [c_void_p, c_void_p])
|
||||
destroy_ct = void_output(std_call('OCTDestroyCoordinateTransformation'), [c_void_p], errcheck=False)
|
||||
@@ -0,0 +1,252 @@
|
||||
from ctypes import byref, c_double, c_int, c_void_p
|
||||
|
||||
from django.contrib.gis.gdal.error import GDALException
|
||||
from django.contrib.gis.gdal.prototypes import raster as capi
|
||||
from django.contrib.gis.gdal.raster.base import GDALRasterBase
|
||||
from django.contrib.gis.shortcuts import numpy
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from .const import (
|
||||
GDAL_COLOR_TYPES, GDAL_INTEGER_TYPES, GDAL_PIXEL_TYPES, GDAL_TO_CTYPES,
|
||||
)
|
||||
|
||||
|
||||
class GDALBand(GDALRasterBase):
|
||||
"""
|
||||
Wrap a GDAL raster band, needs to be obtained from a GDALRaster object.
|
||||
"""
|
||||
def __init__(self, source, index):
|
||||
self.source = source
|
||||
self._ptr = capi.get_ds_raster_band(source._ptr, index)
|
||||
|
||||
def _flush(self):
|
||||
"""
|
||||
Call the flush method on the Band's parent raster and force a refresh
|
||||
of the statistics attribute when requested the next time.
|
||||
"""
|
||||
self.source._flush()
|
||||
self._stats_refresh = True
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""
|
||||
Return the description string of the band.
|
||||
"""
|
||||
return force_text(capi.get_band_description(self._ptr))
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
"""
|
||||
Width (X axis) in pixels of the band.
|
||||
"""
|
||||
return capi.get_band_xsize(self._ptr)
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
"""
|
||||
Height (Y axis) in pixels of the band.
|
||||
"""
|
||||
return capi.get_band_ysize(self._ptr)
|
||||
|
||||
@property
|
||||
def pixel_count(self):
|
||||
"""
|
||||
Return the total number of pixels in this band.
|
||||
"""
|
||||
return self.width * self.height
|
||||
|
||||
_stats_refresh = False
|
||||
|
||||
def statistics(self, refresh=False, approximate=False):
|
||||
"""
|
||||
Compute statistics on the pixel values of this band.
|
||||
|
||||
The return value is a tuple with the following structure:
|
||||
(minimum, maximum, mean, standard deviation).
|
||||
|
||||
If approximate=True, the statistics may be computed based on overviews
|
||||
or a subset of image tiles.
|
||||
|
||||
If refresh=True, the statistics will be computed from the data directly,
|
||||
and the cache will be updated where applicable.
|
||||
|
||||
For empty bands (where all pixel values are nodata), all statistics
|
||||
values are returned as None.
|
||||
|
||||
For raster formats using Persistent Auxiliary Metadata (PAM) services,
|
||||
the statistics might be cached in an auxiliary file.
|
||||
"""
|
||||
# Prepare array with arguments for capi function
|
||||
smin, smax, smean, sstd = c_double(), c_double(), c_double(), c_double()
|
||||
stats_args = [
|
||||
self._ptr, c_int(approximate), byref(smin), byref(smax),
|
||||
byref(smean), byref(sstd), c_void_p(), c_void_p(),
|
||||
]
|
||||
|
||||
if refresh or self._stats_refresh:
|
||||
func = capi.compute_band_statistics
|
||||
else:
|
||||
# Add additional argument to force computation if there is no
|
||||
# existing PAM file to take the values from.
|
||||
force = True
|
||||
stats_args.insert(2, c_int(force))
|
||||
func = capi.get_band_statistics
|
||||
|
||||
# Computation of statistics fails for empty bands.
|
||||
try:
|
||||
func(*stats_args)
|
||||
result = smin.value, smax.value, smean.value, sstd.value
|
||||
except GDALException:
|
||||
result = (None, None, None, None)
|
||||
|
||||
self._stats_refresh = False
|
||||
|
||||
return result
|
||||
|
||||
@property
|
||||
def min(self):
|
||||
"""
|
||||
Return the minimum pixel value for this band.
|
||||
"""
|
||||
return self.statistics()[0]
|
||||
|
||||
@property
|
||||
def max(self):
|
||||
"""
|
||||
Return the maximum pixel value for this band.
|
||||
"""
|
||||
return self.statistics()[1]
|
||||
|
||||
@property
|
||||
def mean(self):
|
||||
"""
|
||||
Return the mean of all pixel values of this band.
|
||||
"""
|
||||
return self.statistics()[2]
|
||||
|
||||
@property
|
||||
def std(self):
|
||||
"""
|
||||
Return the standard deviation of all pixel values of this band.
|
||||
"""
|
||||
return self.statistics()[3]
|
||||
|
||||
@property
|
||||
def nodata_value(self):
|
||||
"""
|
||||
Return the nodata value for this band, or None if it isn't set.
|
||||
"""
|
||||
# Get value and nodata exists flag
|
||||
nodata_exists = c_int()
|
||||
value = capi.get_band_nodata_value(self._ptr, nodata_exists)
|
||||
if not nodata_exists:
|
||||
value = None
|
||||
# If the pixeltype is an integer, convert to int
|
||||
elif self.datatype() in GDAL_INTEGER_TYPES:
|
||||
value = int(value)
|
||||
return value
|
||||
|
||||
@nodata_value.setter
|
||||
def nodata_value(self, value):
|
||||
"""
|
||||
Set the nodata value for this band.
|
||||
"""
|
||||
if value is None:
|
||||
if not capi.delete_band_nodata_value:
|
||||
raise ValueError('GDAL >= 2.1 required to delete nodata values.')
|
||||
capi.delete_band_nodata_value(self._ptr)
|
||||
elif not isinstance(value, (int, float)):
|
||||
raise ValueError('Nodata value must be numeric or None.')
|
||||
else:
|
||||
capi.set_band_nodata_value(self._ptr, value)
|
||||
self._flush()
|
||||
|
||||
def datatype(self, as_string=False):
|
||||
"""
|
||||
Return the GDAL Pixel Datatype for this band.
|
||||
"""
|
||||
dtype = capi.get_band_datatype(self._ptr)
|
||||
if as_string:
|
||||
dtype = GDAL_PIXEL_TYPES[dtype]
|
||||
return dtype
|
||||
|
||||
def color_interp(self, as_string=False):
|
||||
"""Return the GDAL color interpretation for this band."""
|
||||
color = capi.get_band_color_interp(self._ptr)
|
||||
if as_string:
|
||||
color = GDAL_COLOR_TYPES[color]
|
||||
return color
|
||||
|
||||
def data(self, data=None, offset=None, size=None, shape=None, as_memoryview=False):
|
||||
"""
|
||||
Read or writes pixel values for this band. Blocks of data can
|
||||
be accessed by specifying the width, height and offset of the
|
||||
desired block. The same specification can be used to update
|
||||
parts of a raster by providing an array of values.
|
||||
|
||||
Allowed input data types are bytes, memoryview, list, tuple, and array.
|
||||
"""
|
||||
offset = offset or (0, 0)
|
||||
size = size or (self.width - offset[0], self.height - offset[1])
|
||||
shape = shape or size
|
||||
if any(x <= 0 for x in size):
|
||||
raise ValueError('Offset too big for this raster.')
|
||||
|
||||
if size[0] > self.width or size[1] > self.height:
|
||||
raise ValueError('Size is larger than raster.')
|
||||
|
||||
# Create ctypes type array generator
|
||||
ctypes_array = GDAL_TO_CTYPES[self.datatype()] * (shape[0] * shape[1])
|
||||
|
||||
if data is None:
|
||||
# Set read mode
|
||||
access_flag = 0
|
||||
# Prepare empty ctypes array
|
||||
data_array = ctypes_array()
|
||||
else:
|
||||
# Set write mode
|
||||
access_flag = 1
|
||||
|
||||
# Instantiate ctypes array holding the input data
|
||||
if isinstance(data, (bytes, memoryview)) or (numpy and isinstance(data, numpy.ndarray)):
|
||||
data_array = ctypes_array.from_buffer_copy(data)
|
||||
else:
|
||||
data_array = ctypes_array(*data)
|
||||
|
||||
# Access band
|
||||
capi.band_io(self._ptr, access_flag, offset[0], offset[1],
|
||||
size[0], size[1], byref(data_array), shape[0],
|
||||
shape[1], self.datatype(), 0, 0)
|
||||
|
||||
# Return data as numpy array if possible, otherwise as list
|
||||
if data is None:
|
||||
if as_memoryview:
|
||||
return memoryview(data_array)
|
||||
elif numpy:
|
||||
# reshape() needs a reshape parameter with the height first.
|
||||
return numpy.frombuffer(
|
||||
data_array, dtype=numpy.dtype(data_array)
|
||||
).reshape(tuple(reversed(size)))
|
||||
else:
|
||||
return list(data_array)
|
||||
else:
|
||||
self._flush()
|
||||
|
||||
|
||||
class BandList(list):
|
||||
def __init__(self, source):
|
||||
self.source = source
|
||||
super().__init__()
|
||||
|
||||
def __iter__(self):
|
||||
for idx in range(1, len(self) + 1):
|
||||
yield GDALBand(self.source, idx)
|
||||
|
||||
def __len__(self):
|
||||
return capi.get_ds_raster_count(self.source._ptr)
|
||||
|
||||
def __getitem__(self, index):
|
||||
try:
|
||||
return GDALBand(self.source, index + 1)
|
||||
except GDALException:
|
||||
raise GDALException('Unable to get band index %d' % index)
|
||||
@@ -0,0 +1,75 @@
|
||||
from django.contrib.gis.gdal.base import GDALBase
|
||||
from django.contrib.gis.gdal.prototypes import raster as capi
|
||||
|
||||
|
||||
class GDALRasterBase(GDALBase):
|
||||
"""
|
||||
Attributes that exist on both GDALRaster and GDALBand.
|
||||
"""
|
||||
@property
|
||||
def metadata(self):
|
||||
"""
|
||||
Return the metadata for this raster or band. The return value is a
|
||||
nested dictionary, where the first-level key is the metadata domain and
|
||||
the second-level is the metadata item names and values for that domain.
|
||||
"""
|
||||
# The initial metadata domain list contains the default domain.
|
||||
# The default is returned if domain name is None.
|
||||
domain_list = ['DEFAULT']
|
||||
|
||||
# Get additional metadata domains from the raster.
|
||||
meta_list = capi.get_ds_metadata_domain_list(self._ptr)
|
||||
if meta_list:
|
||||
# The number of domains is unknown, so retrieve data until there
|
||||
# are no more values in the ctypes array.
|
||||
counter = 0
|
||||
domain = meta_list[counter]
|
||||
while domain:
|
||||
domain_list.append(domain.decode())
|
||||
counter += 1
|
||||
domain = meta_list[counter]
|
||||
|
||||
# Free domain list array.
|
||||
capi.free_dsl(meta_list)
|
||||
|
||||
# Retrieve metadata values for each domain.
|
||||
result = {}
|
||||
for domain in domain_list:
|
||||
# Get metadata for this domain.
|
||||
data = capi.get_ds_metadata(
|
||||
self._ptr,
|
||||
(None if domain == 'DEFAULT' else domain.encode()),
|
||||
)
|
||||
if not data:
|
||||
continue
|
||||
# The number of metadata items is unknown, so retrieve data until
|
||||
# there are no more values in the ctypes array.
|
||||
domain_meta = {}
|
||||
counter = 0
|
||||
item = data[counter]
|
||||
while item:
|
||||
key, val = item.decode().split('=')
|
||||
domain_meta[key] = val
|
||||
counter += 1
|
||||
item = data[counter]
|
||||
# The default domain values are returned if domain is None.
|
||||
result[domain or 'DEFAULT'] = domain_meta
|
||||
return result
|
||||
|
||||
@metadata.setter
|
||||
def metadata(self, value):
|
||||
"""
|
||||
Set the metadata. Update only the domains that are contained in the
|
||||
value dictionary.
|
||||
"""
|
||||
# Loop through domains.
|
||||
for domain, metadata in value.items():
|
||||
# Set the domain to None for the default, otherwise encode.
|
||||
domain = None if domain == 'DEFAULT' else domain.encode()
|
||||
# Set each metadata entry separately.
|
||||
for meta_name, meta_value in metadata.items():
|
||||
capi.set_ds_metadata_item(
|
||||
self._ptr, meta_name.encode(),
|
||||
meta_value.encode() if meta_value else None,
|
||||
domain,
|
||||
)
|
||||
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
GDAL - Constant definitions
|
||||
"""
|
||||
from ctypes import (
|
||||
c_double, c_float, c_int16, c_int32, c_ubyte, c_uint16, c_uint32,
|
||||
)
|
||||
|
||||
# See https://www.gdal.org/gdal_8h.html#a22e22ce0a55036a96f652765793fb7a4
|
||||
GDAL_PIXEL_TYPES = {
|
||||
0: 'GDT_Unknown', # Unknown or unspecified type
|
||||
1: 'GDT_Byte', # Eight bit unsigned integer
|
||||
2: 'GDT_UInt16', # Sixteen bit unsigned integer
|
||||
3: 'GDT_Int16', # Sixteen bit signed integer
|
||||
4: 'GDT_UInt32', # Thirty-two bit unsigned integer
|
||||
5: 'GDT_Int32', # Thirty-two bit signed integer
|
||||
6: 'GDT_Float32', # Thirty-two bit floating point
|
||||
7: 'GDT_Float64', # Sixty-four bit floating point
|
||||
8: 'GDT_CInt16', # Complex Int16
|
||||
9: 'GDT_CInt32', # Complex Int32
|
||||
10: 'GDT_CFloat32', # Complex Float32
|
||||
11: 'GDT_CFloat64', # Complex Float64
|
||||
}
|
||||
|
||||
# A list of gdal datatypes that are integers.
|
||||
GDAL_INTEGER_TYPES = [1, 2, 3, 4, 5]
|
||||
|
||||
# Lookup values to convert GDAL pixel type indices into ctypes objects.
|
||||
# The GDAL band-io works with ctypes arrays to hold data to be written
|
||||
# or to hold the space for data to be read into. The lookup below helps
|
||||
# selecting the right ctypes object for a given gdal pixel type.
|
||||
GDAL_TO_CTYPES = [
|
||||
None, c_ubyte, c_uint16, c_int16, c_uint32, c_int32,
|
||||
c_float, c_double, None, None, None, None
|
||||
]
|
||||
|
||||
# List of resampling algorithms that can be used to warp a GDALRaster.
|
||||
GDAL_RESAMPLE_ALGORITHMS = {
|
||||
'NearestNeighbour': 0,
|
||||
'Bilinear': 1,
|
||||
'Cubic': 2,
|
||||
'CubicSpline': 3,
|
||||
'Lanczos': 4,
|
||||
'Average': 5,
|
||||
'Mode': 6,
|
||||
}
|
||||
|
||||
# See https://www.gdal.org/gdal_8h.html#ace76452d94514561fffa8ea1d2a5968c
|
||||
GDAL_COLOR_TYPES = {
|
||||
0: 'GCI_Undefined', # Undefined, default value, i.e. not known
|
||||
1: 'GCI_GrayIndex', # Greyscale
|
||||
2: 'GCI_PaletteIndex', # Paletted
|
||||
3: 'GCI_RedBand', # Red band of RGBA image
|
||||
4: 'GCI_GreenBand', # Green band of RGBA image
|
||||
5: 'GCI_BlueBand', # Blue band of RGBA image
|
||||
6: 'GCI_AlphaBand', # Alpha (0=transparent, 255=opaque)
|
||||
7: 'GCI_HueBand', # Hue band of HLS image
|
||||
8: 'GCI_SaturationBand', # Saturation band of HLS image
|
||||
9: 'GCI_LightnessBand', # Lightness band of HLS image
|
||||
10: 'GCI_CyanBand', # Cyan band of CMYK image
|
||||
11: 'GCI_MagentaBand', # Magenta band of CMYK image
|
||||
12: 'GCI_YellowBand', # Yellow band of CMYK image
|
||||
13: 'GCI_BlackBand', # Black band of CMLY image
|
||||
14: 'GCI_YCbCr_YBand', # Y Luminance
|
||||
15: 'GCI_YCbCr_CbBand', # Cb Chroma
|
||||
16: 'GCI_YCbCr_CrBand', # Cr Chroma, also GCI_Max
|
||||
}
|
||||
|
||||
# Fixed base path for buffer-based GDAL in-memory files.
|
||||
VSI_FILESYSTEM_BASE_PATH = '/vsimem/'
|
||||
|
||||
# Should the memory file system take ownership of the buffer, freeing it when
|
||||
# the file is deleted? (No, GDALRaster.__del__() will delete the buffer.)
|
||||
VSI_TAKE_BUFFER_OWNERSHIP = False
|
||||
|
||||
# Should a VSI file be removed when retrieving its buffer?
|
||||
VSI_DELETE_BUFFER_ON_READ = False
|
||||
@@ -0,0 +1,335 @@
|
||||
"""
|
||||
The Spatial Reference class, represents OGR Spatial Reference objects.
|
||||
|
||||
Example:
|
||||
>>> from django.contrib.gis.gdal import SpatialReference
|
||||
>>> srs = SpatialReference('WGS84')
|
||||
>>> print(srs)
|
||||
GEOGCS["WGS 84",
|
||||
DATUM["WGS_1984",
|
||||
SPHEROID["WGS 84",6378137,298.257223563,
|
||||
AUTHORITY["EPSG","7030"]],
|
||||
TOWGS84[0,0,0,0,0,0,0],
|
||||
AUTHORITY["EPSG","6326"]],
|
||||
PRIMEM["Greenwich",0,
|
||||
AUTHORITY["EPSG","8901"]],
|
||||
UNIT["degree",0.01745329251994328,
|
||||
AUTHORITY["EPSG","9122"]],
|
||||
AUTHORITY["EPSG","4326"]]
|
||||
>>> print(srs.proj)
|
||||
+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs
|
||||
>>> print(srs.ellipsoid)
|
||||
(6378137.0, 6356752.3142451793, 298.25722356300003)
|
||||
>>> print(srs.projected, srs.geographic)
|
||||
False True
|
||||
>>> srs.import_epsg(32140)
|
||||
>>> print(srs.name)
|
||||
NAD83 / Texas South Central
|
||||
"""
|
||||
from ctypes import byref, c_char_p, c_int
|
||||
|
||||
from django.contrib.gis.gdal.base import GDALBase
|
||||
from django.contrib.gis.gdal.error import SRSException
|
||||
from django.contrib.gis.gdal.prototypes import srs as capi
|
||||
from django.utils.encoding import force_bytes, force_text
|
||||
|
||||
|
||||
class SpatialReference(GDALBase):
|
||||
"""
|
||||
A wrapper for the OGRSpatialReference object. According to the GDAL Web site,
|
||||
the SpatialReference object "provide[s] services to represent coordinate
|
||||
systems (projections and datums) and to transform between them."
|
||||
"""
|
||||
destructor = capi.release_srs
|
||||
|
||||
def __init__(self, srs_input='', srs_type='user'):
|
||||
"""
|
||||
Create a GDAL OSR Spatial Reference object from the given input.
|
||||
The input may be string of OGC Well Known Text (WKT), an integer
|
||||
EPSG code, a PROJ.4 string, and/or a projection "well known" shorthand
|
||||
string (one of 'WGS84', 'WGS72', 'NAD27', 'NAD83').
|
||||
"""
|
||||
|
||||
if srs_type == 'wkt':
|
||||
self.ptr = capi.new_srs(c_char_p(b''))
|
||||
self.import_wkt(srs_input)
|
||||
return
|
||||
elif isinstance(srs_input, str):
|
||||
try:
|
||||
# If SRID is a string, e.g., '4326', then make acceptable
|
||||
# as user input.
|
||||
srid = int(srs_input)
|
||||
srs_input = 'EPSG:%d' % srid
|
||||
except ValueError:
|
||||
pass
|
||||
elif isinstance(srs_input, int):
|
||||
# EPSG integer code was input.
|
||||
srs_type = 'epsg'
|
||||
elif isinstance(srs_input, self.ptr_type):
|
||||
srs = srs_input
|
||||
srs_type = 'ogr'
|
||||
else:
|
||||
raise TypeError('Invalid SRS type "%s"' % srs_type)
|
||||
|
||||
if srs_type == 'ogr':
|
||||
# Input is already an SRS pointer.
|
||||
srs = srs_input
|
||||
else:
|
||||
# Creating a new SRS pointer, using the string buffer.
|
||||
buf = c_char_p(b'')
|
||||
srs = capi.new_srs(buf)
|
||||
|
||||
# If the pointer is NULL, throw an exception.
|
||||
if not srs:
|
||||
raise SRSException('Could not create spatial reference from: %s' % srs_input)
|
||||
else:
|
||||
self.ptr = srs
|
||||
|
||||
# Importing from either the user input string or an integer SRID.
|
||||
if srs_type == 'user':
|
||||
self.import_user_input(srs_input)
|
||||
elif srs_type == 'epsg':
|
||||
self.import_epsg(srs_input)
|
||||
|
||||
def __getitem__(self, target):
|
||||
"""
|
||||
Return the value of the given string attribute node, None if the node
|
||||
doesn't exist. Can also take a tuple as a parameter, (target, child),
|
||||
where child is the index of the attribute in the WKT. For example:
|
||||
|
||||
>>> wkt = 'GEOGCS["WGS 84", DATUM["WGS_1984, ... AUTHORITY["EPSG","4326"]]'
|
||||
>>> srs = SpatialReference(wkt) # could also use 'WGS84', or 4326
|
||||
>>> print(srs['GEOGCS'])
|
||||
WGS 84
|
||||
>>> print(srs['DATUM'])
|
||||
WGS_1984
|
||||
>>> print(srs['AUTHORITY'])
|
||||
EPSG
|
||||
>>> print(srs['AUTHORITY', 1]) # The authority value
|
||||
4326
|
||||
>>> print(srs['TOWGS84', 4]) # the fourth value in this wkt
|
||||
0
|
||||
>>> print(srs['UNIT|AUTHORITY']) # For the units authority, have to use the pipe symbole.
|
||||
EPSG
|
||||
>>> print(srs['UNIT|AUTHORITY', 1]) # The authority value for the units
|
||||
9122
|
||||
"""
|
||||
if isinstance(target, tuple):
|
||||
return self.attr_value(*target)
|
||||
else:
|
||||
return self.attr_value(target)
|
||||
|
||||
def __str__(self):
|
||||
"Use 'pretty' WKT."
|
||||
return self.pretty_wkt
|
||||
|
||||
# #### SpatialReference Methods ####
|
||||
def attr_value(self, target, index=0):
|
||||
"""
|
||||
The attribute value for the given target node (e.g. 'PROJCS'). The index
|
||||
keyword specifies an index of the child node to return.
|
||||
"""
|
||||
if not isinstance(target, str) or not isinstance(index, int):
|
||||
raise TypeError
|
||||
return capi.get_attr_value(self.ptr, force_bytes(target), index)
|
||||
|
||||
def auth_name(self, target):
|
||||
"Return the authority name for the given string target node."
|
||||
return capi.get_auth_name(self.ptr, force_bytes(target))
|
||||
|
||||
def auth_code(self, target):
|
||||
"Return the authority code for the given string target node."
|
||||
return capi.get_auth_code(self.ptr, force_bytes(target))
|
||||
|
||||
def clone(self):
|
||||
"Return a clone of this SpatialReference object."
|
||||
return SpatialReference(capi.clone_srs(self.ptr))
|
||||
|
||||
def from_esri(self):
|
||||
"Morph this SpatialReference from ESRI's format to EPSG."
|
||||
capi.morph_from_esri(self.ptr)
|
||||
|
||||
def identify_epsg(self):
|
||||
"""
|
||||
This method inspects the WKT of this SpatialReference, and will
|
||||
add EPSG authority nodes where an EPSG identifier is applicable.
|
||||
"""
|
||||
capi.identify_epsg(self.ptr)
|
||||
|
||||
def to_esri(self):
|
||||
"Morph this SpatialReference to ESRI's format."
|
||||
capi.morph_to_esri(self.ptr)
|
||||
|
||||
def validate(self):
|
||||
"Check to see if the given spatial reference is valid."
|
||||
capi.srs_validate(self.ptr)
|
||||
|
||||
# #### Name & SRID properties ####
|
||||
@property
|
||||
def name(self):
|
||||
"Return the name of this Spatial Reference."
|
||||
if self.projected:
|
||||
return self.attr_value('PROJCS')
|
||||
elif self.geographic:
|
||||
return self.attr_value('GEOGCS')
|
||||
elif self.local:
|
||||
return self.attr_value('LOCAL_CS')
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def srid(self):
|
||||
"Return the SRID of top-level authority, or None if undefined."
|
||||
try:
|
||||
return int(self.attr_value('AUTHORITY', 1))
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
# #### Unit Properties ####
|
||||
@property
|
||||
def linear_name(self):
|
||||
"Return the name of the linear units."
|
||||
units, name = capi.linear_units(self.ptr, byref(c_char_p()))
|
||||
return name
|
||||
|
||||
@property
|
||||
def linear_units(self):
|
||||
"Return the value of the linear units."
|
||||
units, name = capi.linear_units(self.ptr, byref(c_char_p()))
|
||||
return units
|
||||
|
||||
@property
|
||||
def angular_name(self):
|
||||
"Return the name of the angular units."
|
||||
units, name = capi.angular_units(self.ptr, byref(c_char_p()))
|
||||
return name
|
||||
|
||||
@property
|
||||
def angular_units(self):
|
||||
"Return the value of the angular units."
|
||||
units, name = capi.angular_units(self.ptr, byref(c_char_p()))
|
||||
return units
|
||||
|
||||
@property
|
||||
def units(self):
|
||||
"""
|
||||
Return a 2-tuple of the units value and the units name. Automatically
|
||||
determine whether to return the linear or angular units.
|
||||
"""
|
||||
units, name = None, None
|
||||
if self.projected or self.local:
|
||||
units, name = capi.linear_units(self.ptr, byref(c_char_p()))
|
||||
elif self.geographic:
|
||||
units, name = capi.angular_units(self.ptr, byref(c_char_p()))
|
||||
if name is not None:
|
||||
name = force_text(name)
|
||||
return (units, name)
|
||||
|
||||
# #### Spheroid/Ellipsoid Properties ####
|
||||
@property
|
||||
def ellipsoid(self):
|
||||
"""
|
||||
Return a tuple of the ellipsoid parameters:
|
||||
(semimajor axis, semiminor axis, and inverse flattening)
|
||||
"""
|
||||
return (self.semi_major, self.semi_minor, self.inverse_flattening)
|
||||
|
||||
@property
|
||||
def semi_major(self):
|
||||
"Return the Semi Major Axis for this Spatial Reference."
|
||||
return capi.semi_major(self.ptr, byref(c_int()))
|
||||
|
||||
@property
|
||||
def semi_minor(self):
|
||||
"Return the Semi Minor Axis for this Spatial Reference."
|
||||
return capi.semi_minor(self.ptr, byref(c_int()))
|
||||
|
||||
@property
|
||||
def inverse_flattening(self):
|
||||
"Return the Inverse Flattening for this Spatial Reference."
|
||||
return capi.invflattening(self.ptr, byref(c_int()))
|
||||
|
||||
# #### Boolean Properties ####
|
||||
@property
|
||||
def geographic(self):
|
||||
"""
|
||||
Return True if this SpatialReference is geographic
|
||||
(root node is GEOGCS).
|
||||
"""
|
||||
return bool(capi.isgeographic(self.ptr))
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
"Return True if this SpatialReference is local (root node is LOCAL_CS)."
|
||||
return bool(capi.islocal(self.ptr))
|
||||
|
||||
@property
|
||||
def projected(self):
|
||||
"""
|
||||
Return True if this SpatialReference is a projected coordinate system
|
||||
(root node is PROJCS).
|
||||
"""
|
||||
return bool(capi.isprojected(self.ptr))
|
||||
|
||||
# #### Import Routines #####
|
||||
def import_epsg(self, epsg):
|
||||
"Import the Spatial Reference from the EPSG code (an integer)."
|
||||
capi.from_epsg(self.ptr, epsg)
|
||||
|
||||
def import_proj(self, proj):
|
||||
"Import the Spatial Reference from a PROJ.4 string."
|
||||
capi.from_proj(self.ptr, proj)
|
||||
|
||||
def import_user_input(self, user_input):
|
||||
"Import the Spatial Reference from the given user input string."
|
||||
capi.from_user_input(self.ptr, force_bytes(user_input))
|
||||
|
||||
def import_wkt(self, wkt):
|
||||
"Import the Spatial Reference from OGC WKT (string)"
|
||||
capi.from_wkt(self.ptr, byref(c_char_p(force_bytes(wkt))))
|
||||
|
||||
def import_xml(self, xml):
|
||||
"Import the Spatial Reference from an XML string."
|
||||
capi.from_xml(self.ptr, xml)
|
||||
|
||||
# #### Export Properties ####
|
||||
@property
|
||||
def wkt(self):
|
||||
"Return the WKT representation of this Spatial Reference."
|
||||
return capi.to_wkt(self.ptr, byref(c_char_p()))
|
||||
|
||||
@property
|
||||
def pretty_wkt(self, simplify=0):
|
||||
"Return the 'pretty' representation of the WKT."
|
||||
return capi.to_pretty_wkt(self.ptr, byref(c_char_p()), simplify)
|
||||
|
||||
@property
|
||||
def proj(self):
|
||||
"Return the PROJ.4 representation for this Spatial Reference."
|
||||
return capi.to_proj(self.ptr, byref(c_char_p()))
|
||||
|
||||
@property
|
||||
def proj4(self):
|
||||
"Alias for proj()."
|
||||
return self.proj
|
||||
|
||||
@property
|
||||
def xml(self, dialect=''):
|
||||
"Return the XML representation of this Spatial Reference."
|
||||
return capi.to_xml(self.ptr, byref(c_char_p()), force_bytes(dialect))
|
||||
|
||||
|
||||
class CoordTransform(GDALBase):
|
||||
"The coordinate system transformation object."
|
||||
destructor = capi.destroy_ct
|
||||
|
||||
def __init__(self, source, target):
|
||||
"Initialize on a source and target SpatialReference objects."
|
||||
if not isinstance(source, SpatialReference) or not isinstance(target, SpatialReference):
|
||||
raise TypeError('source and target must be of type SpatialReference')
|
||||
self.ptr = capi.new_ct(source._ptr, target._ptr)
|
||||
self._srs1_name = source.name
|
||||
self._srs2_name = target.name
|
||||
|
||||
def __str__(self):
|
||||
return 'Transform from "%s" to "%s"' % (self._srs1_name, self._srs2_name)
|
||||
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
This module houses the GeoIP2 object, a wrapper for the MaxMind GeoIP2(R)
|
||||
Python API (https://geoip2.readthedocs.io/). This is an alternative to the
|
||||
Python GeoIP2 interface provided by MaxMind.
|
||||
|
||||
GeoIP(R) is a registered trademark of MaxMind, Inc.
|
||||
|
||||
For IP-based geolocation, this module requires the GeoLite2 Country and City
|
||||
datasets, in binary format (CSV will not work!). The datasets may be
|
||||
downloaded from MaxMind at http://dev.maxmind.com/geoip/geoip2/geolite2/.
|
||||
Grab GeoLite2-Country.mmdb.gz and GeoLite2-City.mmdb.gz, and unzip them in the
|
||||
directory corresponding to settings.GEOIP_PATH.
|
||||
"""
|
||||
__all__ = ['HAS_GEOIP2']
|
||||
|
||||
try:
|
||||
import geoip2 # NOQA
|
||||
except ImportError:
|
||||
HAS_GEOIP2 = False
|
||||
else:
|
||||
from .base import GeoIP2, GeoIP2Exception
|
||||
HAS_GEOIP2 = True
|
||||
__all__ += ['GeoIP2', 'GeoIP2Exception']
|
||||
@@ -0,0 +1,229 @@
|
||||
import socket
|
||||
from pathlib import Path
|
||||
|
||||
import geoip2.database
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_ipv46_address
|
||||
|
||||
from .resources import City, Country
|
||||
|
||||
# Creating the settings dictionary with any settings, if needed.
|
||||
GEOIP_SETTINGS = {
|
||||
'GEOIP_PATH': getattr(settings, 'GEOIP_PATH', None),
|
||||
'GEOIP_CITY': getattr(settings, 'GEOIP_CITY', 'GeoLite2-City.mmdb'),
|
||||
'GEOIP_COUNTRY': getattr(settings, 'GEOIP_COUNTRY', 'GeoLite2-Country.mmdb'),
|
||||
}
|
||||
|
||||
|
||||
class GeoIP2Exception(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GeoIP2:
|
||||
# The flags for GeoIP memory caching.
|
||||
# Try MODE_MMAP_EXT, MODE_MMAP, MODE_FILE in that order.
|
||||
MODE_AUTO = 0
|
||||
# Use the C extension with memory map.
|
||||
MODE_MMAP_EXT = 1
|
||||
# Read from memory map. Pure Python.
|
||||
MODE_MMAP = 2
|
||||
# Read database as standard file. Pure Python.
|
||||
MODE_FILE = 4
|
||||
# Load database into memory. Pure Python.
|
||||
MODE_MEMORY = 8
|
||||
cache_options = frozenset((MODE_AUTO, MODE_MMAP_EXT, MODE_MMAP, MODE_FILE, MODE_MEMORY))
|
||||
|
||||
# Paths to the city & country binary databases.
|
||||
_city_file = ''
|
||||
_country_file = ''
|
||||
|
||||
# Initially, pointers to GeoIP file references are NULL.
|
||||
_city = None
|
||||
_country = None
|
||||
|
||||
def __init__(self, path=None, cache=0, country=None, city=None):
|
||||
"""
|
||||
Initialize the GeoIP object. No parameters are required to use default
|
||||
settings. Keyword arguments may be passed in to customize the locations
|
||||
of the GeoIP datasets.
|
||||
|
||||
* path: Base directory to where GeoIP data is located or the full path
|
||||
to where the city or country data files (*.mmdb) are located.
|
||||
Assumes that both the city and country data sets are located in
|
||||
this directory; overrides the GEOIP_PATH setting.
|
||||
|
||||
* cache: The cache settings when opening up the GeoIP datasets. May be
|
||||
an integer in (0, 1, 2, 4, 8) corresponding to the MODE_AUTO,
|
||||
MODE_MMAP_EXT, MODE_MMAP, MODE_FILE, and MODE_MEMORY,
|
||||
`GeoIPOptions` C API settings, respectively. Defaults to 0,
|
||||
meaning MODE_AUTO.
|
||||
|
||||
* country: The name of the GeoIP country data file. Defaults to
|
||||
'GeoLite2-Country.mmdb'; overrides the GEOIP_COUNTRY setting.
|
||||
|
||||
* city: The name of the GeoIP city data file. Defaults to
|
||||
'GeoLite2-City.mmdb'; overrides the GEOIP_CITY setting.
|
||||
"""
|
||||
# Checking the given cache option.
|
||||
if cache in self.cache_options:
|
||||
self._cache = cache
|
||||
else:
|
||||
raise GeoIP2Exception('Invalid GeoIP caching option: %s' % cache)
|
||||
|
||||
# Getting the GeoIP data path.
|
||||
path = path or GEOIP_SETTINGS['GEOIP_PATH']
|
||||
if not path:
|
||||
raise GeoIP2Exception('GeoIP path must be provided via parameter or the GEOIP_PATH setting.')
|
||||
if not isinstance(path, str):
|
||||
raise TypeError('Invalid path type: %s' % type(path).__name__)
|
||||
|
||||
path = Path(path)
|
||||
if path.is_dir():
|
||||
# Constructing the GeoIP database filenames using the settings
|
||||
# dictionary. If the database files for the GeoLite country
|
||||
# and/or city datasets exist, then try to open them.
|
||||
country_db = path / (country or GEOIP_SETTINGS['GEOIP_COUNTRY'])
|
||||
if country_db.is_file():
|
||||
self._country = geoip2.database.Reader(str(country_db), mode=cache)
|
||||
self._country_file = country_db
|
||||
|
||||
city_db = path / (city or GEOIP_SETTINGS['GEOIP_CITY'])
|
||||
if city_db.is_file():
|
||||
self._city = geoip2.database.Reader(str(city_db), mode=cache)
|
||||
self._city_file = city_db
|
||||
if not self._reader:
|
||||
raise GeoIP2Exception('Could not load a database from %s.' % path)
|
||||
elif path.is_file():
|
||||
# Otherwise, some detective work will be needed to figure out
|
||||
# whether the given database path is for the GeoIP country or city
|
||||
# databases.
|
||||
reader = geoip2.database.Reader(str(path), mode=cache)
|
||||
db_type = reader.metadata().database_type
|
||||
|
||||
if db_type.endswith('City'):
|
||||
# GeoLite City database detected.
|
||||
self._city = reader
|
||||
self._city_file = path
|
||||
elif db_type.endswith('Country'):
|
||||
# GeoIP Country database detected.
|
||||
self._country = reader
|
||||
self._country_file = path
|
||||
else:
|
||||
raise GeoIP2Exception('Unable to recognize database edition: %s' % db_type)
|
||||
else:
|
||||
raise GeoIP2Exception('GeoIP path must be a valid file or directory.')
|
||||
|
||||
@property
|
||||
def _reader(self):
|
||||
return self._country or self._city
|
||||
|
||||
@property
|
||||
def _country_or_city(self):
|
||||
if self._country:
|
||||
return self._country.country
|
||||
else:
|
||||
return self._city.city
|
||||
|
||||
def __del__(self):
|
||||
# Cleanup any GeoIP file handles lying around.
|
||||
if self._reader:
|
||||
self._reader.close()
|
||||
|
||||
def __repr__(self):
|
||||
meta = self._reader.metadata()
|
||||
version = '[v%s.%s]' % (meta.binary_format_major_version, meta.binary_format_minor_version)
|
||||
return '<%(cls)s %(version)s _country_file="%(country)s", _city_file="%(city)s">' % {
|
||||
'cls': self.__class__.__name__,
|
||||
'version': version,
|
||||
'country': self._country_file,
|
||||
'city': self._city_file,
|
||||
}
|
||||
|
||||
def _check_query(self, query, country=False, city=False, city_or_country=False):
|
||||
"Check the query and database availability."
|
||||
# Making sure a string was passed in for the query.
|
||||
if not isinstance(query, str):
|
||||
raise TypeError('GeoIP query must be a string, not type %s' % type(query).__name__)
|
||||
|
||||
# Extra checks for the existence of country and city databases.
|
||||
if city_or_country and not (self._country or self._city):
|
||||
raise GeoIP2Exception('Invalid GeoIP country and city data files.')
|
||||
elif country and not self._country:
|
||||
raise GeoIP2Exception('Invalid GeoIP country data file: %s' % self._country_file)
|
||||
elif city and not self._city:
|
||||
raise GeoIP2Exception('Invalid GeoIP city data file: %s' % self._city_file)
|
||||
|
||||
# Return the query string back to the caller. GeoIP2 only takes IP addresses.
|
||||
try:
|
||||
validate_ipv46_address(query)
|
||||
except ValidationError:
|
||||
query = socket.gethostbyname(query)
|
||||
|
||||
return query
|
||||
|
||||
def city(self, query):
|
||||
"""
|
||||
Return a dictionary of city information for the given IP address or
|
||||
Fully Qualified Domain Name (FQDN). Some information in the dictionary
|
||||
may be undefined (None).
|
||||
"""
|
||||
enc_query = self._check_query(query, city=True)
|
||||
return City(self._city.city(enc_query))
|
||||
|
||||
def country_code(self, query):
|
||||
"Return the country code for the given IP Address or FQDN."
|
||||
enc_query = self._check_query(query, city_or_country=True)
|
||||
return self.country(enc_query)['country_code']
|
||||
|
||||
def country_name(self, query):
|
||||
"Return the country name for the given IP Address or FQDN."
|
||||
enc_query = self._check_query(query, city_or_country=True)
|
||||
return self.country(enc_query)['country_name']
|
||||
|
||||
def country(self, query):
|
||||
"""
|
||||
Return a dictionary with the country code and name when given an
|
||||
IP address or a Fully Qualified Domain Name (FQDN). For example, both
|
||||
'24.124.1.80' and 'djangoproject.com' are valid parameters.
|
||||
"""
|
||||
# Returning the country code and name
|
||||
enc_query = self._check_query(query, city_or_country=True)
|
||||
return Country(self._country_or_city(enc_query))
|
||||
|
||||
# #### Coordinate retrieval routines ####
|
||||
def coords(self, query, ordering=('longitude', 'latitude')):
|
||||
cdict = self.city(query)
|
||||
if cdict is None:
|
||||
return None
|
||||
else:
|
||||
return tuple(cdict[o] for o in ordering)
|
||||
|
||||
def lon_lat(self, query):
|
||||
"Return a tuple of the (longitude, latitude) for the given query."
|
||||
return self.coords(query)
|
||||
|
||||
def lat_lon(self, query):
|
||||
"Return a tuple of the (latitude, longitude) for the given query."
|
||||
return self.coords(query, ('latitude', 'longitude'))
|
||||
|
||||
def geos(self, query):
|
||||
"Return a GEOS Point object for the given query."
|
||||
ll = self.lon_lat(query)
|
||||
if ll:
|
||||
from django.contrib.gis.geos import Point
|
||||
return Point(ll, srid=4326)
|
||||
else:
|
||||
return None
|
||||
|
||||
# #### GeoIP Database Information Routines ####
|
||||
@property
|
||||
def info(self):
|
||||
"Return information about the GeoIP library and databases in use."
|
||||
meta = self._reader.metadata()
|
||||
return 'GeoIP Library:\n\t%s.%s\n' % (meta.binary_format_major_version, meta.binary_format_minor_version)
|
||||
|
||||
@classmethod
|
||||
def open(cls, full_path, cache):
|
||||
return GeoIP2(full_path, cache)
|
||||
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2007-2009 Justin Bronn
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of GEOSGeometry nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
The GeoDjango GEOS module. Please consult the GeoDjango documentation
|
||||
for more details: https://docs.djangoproject.com/en/dev/ref/contrib/gis/geos/
|
||||
"""
|
||||
from .collections import ( # NOQA
|
||||
GeometryCollection, MultiLineString, MultiPoint, MultiPolygon,
|
||||
)
|
||||
from .error import GEOSException # NOQA
|
||||
from .factory import fromfile, fromstr # NOQA
|
||||
from .geometry import GEOSGeometry, hex_regex, wkt_regex # NOQA
|
||||
from .io import WKBReader, WKBWriter, WKTReader, WKTWriter # NOQA
|
||||
from .libgeos import geos_version # NOQA
|
||||
from .linestring import LinearRing, LineString # NOQA
|
||||
from .point import Point # NOQA
|
||||
from .polygon import Polygon # NOQA
|
||||
@@ -0,0 +1,196 @@
|
||||
"""
|
||||
This module houses the GEOSCoordSeq object, which is used internally
|
||||
by GEOSGeometry to house the actual coordinates of the Point,
|
||||
LineString, and LinearRing geometries.
|
||||
"""
|
||||
from ctypes import byref, c_double, c_uint
|
||||
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
from django.contrib.gis.geos.base import GEOSBase
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from django.contrib.gis.geos.libgeos import CS_PTR
|
||||
from django.contrib.gis.shortcuts import numpy
|
||||
|
||||
|
||||
class GEOSCoordSeq(GEOSBase):
|
||||
"The internal representation of a list of coordinates inside a Geometry."
|
||||
|
||||
ptr_type = CS_PTR
|
||||
|
||||
def __init__(self, ptr, z=False):
|
||||
"Initialize from a GEOS pointer."
|
||||
if not isinstance(ptr, CS_PTR):
|
||||
raise TypeError('Coordinate sequence should initialize with a CS_PTR.')
|
||||
self._ptr = ptr
|
||||
self._z = z
|
||||
|
||||
def __iter__(self):
|
||||
"Iterate over each point in the coordinate sequence."
|
||||
for i in range(self.size):
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Return the number of points in the coordinate sequence."
|
||||
return int(self.size)
|
||||
|
||||
def __str__(self):
|
||||
"Return the string representation of the coordinate sequence."
|
||||
return str(self.tuple)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"Return the coordinate sequence value at the given index."
|
||||
self._checkindex(index)
|
||||
return self._point_getter(index)
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
"Set the coordinate sequence value at the given index."
|
||||
# Checking the input value
|
||||
if isinstance(value, (list, tuple)):
|
||||
pass
|
||||
elif numpy and isinstance(value, numpy.ndarray):
|
||||
pass
|
||||
else:
|
||||
raise TypeError('Must set coordinate with a sequence (list, tuple, or numpy array).')
|
||||
# Checking the dims of the input
|
||||
if self.dims == 3 and self._z:
|
||||
n_args = 3
|
||||
point_setter = self._set_point_3d
|
||||
else:
|
||||
n_args = 2
|
||||
point_setter = self._set_point_2d
|
||||
if len(value) != n_args:
|
||||
raise TypeError('Dimension of value does not match.')
|
||||
self._checkindex(index)
|
||||
point_setter(index, value)
|
||||
|
||||
# #### Internal Routines ####
|
||||
def _checkindex(self, index):
|
||||
"Check the given index."
|
||||
if not (0 <= index < self.size):
|
||||
raise IndexError('invalid GEOS Geometry index: %s' % index)
|
||||
|
||||
def _checkdim(self, dim):
|
||||
"Check the given dimension."
|
||||
if dim < 0 or dim > 2:
|
||||
raise GEOSException('invalid ordinate dimension "%d"' % dim)
|
||||
|
||||
def _get_x(self, index):
|
||||
return capi.cs_getx(self.ptr, index, byref(c_double()))
|
||||
|
||||
def _get_y(self, index):
|
||||
return capi.cs_gety(self.ptr, index, byref(c_double()))
|
||||
|
||||
def _get_z(self, index):
|
||||
return capi.cs_getz(self.ptr, index, byref(c_double()))
|
||||
|
||||
def _set_x(self, index, value):
|
||||
capi.cs_setx(self.ptr, index, value)
|
||||
|
||||
def _set_y(self, index, value):
|
||||
capi.cs_sety(self.ptr, index, value)
|
||||
|
||||
def _set_z(self, index, value):
|
||||
capi.cs_setz(self.ptr, index, value)
|
||||
|
||||
@property
|
||||
def _point_getter(self):
|
||||
return self._get_point_3d if self.dims == 3 and self._z else self._get_point_2d
|
||||
|
||||
def _get_point_2d(self, index):
|
||||
return (self._get_x(index), self._get_y(index))
|
||||
|
||||
def _get_point_3d(self, index):
|
||||
return (self._get_x(index), self._get_y(index), self._get_z(index))
|
||||
|
||||
def _set_point_2d(self, index, value):
|
||||
x, y = value
|
||||
self._set_x(index, x)
|
||||
self._set_y(index, y)
|
||||
|
||||
def _set_point_3d(self, index, value):
|
||||
x, y, z = value
|
||||
self._set_x(index, x)
|
||||
self._set_y(index, y)
|
||||
self._set_z(index, z)
|
||||
|
||||
# #### Ordinate getting and setting routines ####
|
||||
def getOrdinate(self, dimension, index):
|
||||
"Return the value for the given dimension and index."
|
||||
self._checkindex(index)
|
||||
self._checkdim(dimension)
|
||||
return capi.cs_getordinate(self.ptr, index, dimension, byref(c_double()))
|
||||
|
||||
def setOrdinate(self, dimension, index, value):
|
||||
"Set the value for the given dimension and index."
|
||||
self._checkindex(index)
|
||||
self._checkdim(dimension)
|
||||
capi.cs_setordinate(self.ptr, index, dimension, value)
|
||||
|
||||
def getX(self, index):
|
||||
"Get the X value at the index."
|
||||
return self.getOrdinate(0, index)
|
||||
|
||||
def setX(self, index, value):
|
||||
"Set X with the value at the given index."
|
||||
self.setOrdinate(0, index, value)
|
||||
|
||||
def getY(self, index):
|
||||
"Get the Y value at the given index."
|
||||
return self.getOrdinate(1, index)
|
||||
|
||||
def setY(self, index, value):
|
||||
"Set Y with the value at the given index."
|
||||
self.setOrdinate(1, index, value)
|
||||
|
||||
def getZ(self, index):
|
||||
"Get Z with the value at the given index."
|
||||
return self.getOrdinate(2, index)
|
||||
|
||||
def setZ(self, index, value):
|
||||
"Set Z with the value at the given index."
|
||||
self.setOrdinate(2, index, value)
|
||||
|
||||
# ### Dimensions ###
|
||||
@property
|
||||
def size(self):
|
||||
"Return the size of this coordinate sequence."
|
||||
return capi.cs_getsize(self.ptr, byref(c_uint()))
|
||||
|
||||
@property
|
||||
def dims(self):
|
||||
"Return the dimensions of this coordinate sequence."
|
||||
return capi.cs_getdims(self.ptr, byref(c_uint()))
|
||||
|
||||
@property
|
||||
def hasz(self):
|
||||
"""
|
||||
Return whether this coordinate sequence is 3D. This property value is
|
||||
inherited from the parent Geometry.
|
||||
"""
|
||||
return self._z
|
||||
|
||||
# ### Other Methods ###
|
||||
def clone(self):
|
||||
"Clone this coordinate sequence."
|
||||
return GEOSCoordSeq(capi.cs_clone(self.ptr), self.hasz)
|
||||
|
||||
@property
|
||||
def kml(self):
|
||||
"Return the KML representation for the coordinates."
|
||||
# Getting the substitution string depending on whether the coordinates have
|
||||
# a Z dimension.
|
||||
if self.hasz:
|
||||
substr = '%s,%s,%s '
|
||||
else:
|
||||
substr = '%s,%s,0 '
|
||||
return '<coordinates>%s</coordinates>' % \
|
||||
''.join(substr % self[i] for i in range(len(self))).strip()
|
||||
|
||||
@property
|
||||
def tuple(self):
|
||||
"Return a tuple version of this coordinate sequence."
|
||||
n = self.size
|
||||
get_point = self._point_getter
|
||||
if n == 1:
|
||||
return get_point(0)
|
||||
return tuple(get_point(i) for i in range(n))
|
||||
@@ -0,0 +1,175 @@
|
||||
"""
|
||||
This module houses the ctypes initialization procedures, as well
|
||||
as the notice and error handler function callbacks (get called
|
||||
when an error occurs in GEOS).
|
||||
|
||||
This module also houses GEOS Pointer utilities, including
|
||||
get_pointer_arr(), and GEOM_PTR.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
from ctypes import CDLL, CFUNCTYPE, POINTER, Structure, c_char_p
|
||||
from ctypes.util import find_library
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils.functional import SimpleLazyObject, cached_property
|
||||
from django.utils.version import get_version_tuple
|
||||
|
||||
logger = logging.getLogger('django.contrib.gis')
|
||||
|
||||
|
||||
def load_geos():
|
||||
# Custom library path set?
|
||||
try:
|
||||
from django.conf import settings
|
||||
lib_path = settings.GEOS_LIBRARY_PATH
|
||||
except (AttributeError, EnvironmentError,
|
||||
ImportError, ImproperlyConfigured):
|
||||
lib_path = None
|
||||
|
||||
# Setting the appropriate names for the GEOS-C library.
|
||||
if lib_path:
|
||||
lib_names = None
|
||||
elif os.name == 'nt':
|
||||
# Windows NT libraries
|
||||
lib_names = ['geos_c', 'libgeos_c-1']
|
||||
elif os.name == 'posix':
|
||||
# *NIX libraries
|
||||
lib_names = ['geos_c', 'GEOS']
|
||||
else:
|
||||
raise ImportError('Unsupported OS "%s"' % os.name)
|
||||
|
||||
# Using the ctypes `find_library` utility to find the path to the GEOS
|
||||
# shared library. This is better than manually specifying each library name
|
||||
# and extension (e.g., libgeos_c.[so|so.1|dylib].).
|
||||
if lib_names:
|
||||
for lib_name in lib_names:
|
||||
lib_path = find_library(lib_name)
|
||||
if lib_path is not None:
|
||||
break
|
||||
|
||||
# No GEOS library could be found.
|
||||
if lib_path is None:
|
||||
raise ImportError(
|
||||
'Could not find the GEOS library (tried "%s"). '
|
||||
'Try setting GEOS_LIBRARY_PATH in your settings.' %
|
||||
'", "'.join(lib_names)
|
||||
)
|
||||
# Getting the GEOS C library. The C interface (CDLL) is used for
|
||||
# both *NIX and Windows.
|
||||
# See the GEOS C API source code for more details on the library function calls:
|
||||
# http://geos.refractions.net/ro/doxygen_docs/html/geos__c_8h-source.html
|
||||
_lgeos = CDLL(lib_path)
|
||||
# Here we set up the prototypes for the initGEOS_r and finishGEOS_r
|
||||
# routines. These functions aren't actually called until they are
|
||||
# attached to a GEOS context handle -- this actually occurs in
|
||||
# geos/prototypes/threadsafe.py.
|
||||
_lgeos.initGEOS_r.restype = CONTEXT_PTR
|
||||
_lgeos.finishGEOS_r.argtypes = [CONTEXT_PTR]
|
||||
# Set restype for compatibility across 32 and 64-bit platforms.
|
||||
_lgeos.GEOSversion.restype = c_char_p
|
||||
return _lgeos
|
||||
|
||||
|
||||
# The notice and error handler C function callback definitions.
|
||||
# Supposed to mimic the GEOS message handler (C below):
|
||||
# typedef void (*GEOSMessageHandler)(const char *fmt, ...);
|
||||
NOTICEFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
|
||||
|
||||
|
||||
def notice_h(fmt, lst):
|
||||
fmt, lst = fmt.decode(), lst.decode()
|
||||
try:
|
||||
warn_msg = fmt % lst
|
||||
except TypeError:
|
||||
warn_msg = fmt
|
||||
logger.warning('GEOS_NOTICE: %s\n', warn_msg)
|
||||
|
||||
|
||||
notice_h = NOTICEFUNC(notice_h)
|
||||
|
||||
ERRORFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
|
||||
|
||||
|
||||
def error_h(fmt, lst):
|
||||
fmt, lst = fmt.decode(), lst.decode()
|
||||
try:
|
||||
err_msg = fmt % lst
|
||||
except TypeError:
|
||||
err_msg = fmt
|
||||
logger.error('GEOS_ERROR: %s\n', err_msg)
|
||||
|
||||
|
||||
error_h = ERRORFUNC(error_h)
|
||||
|
||||
# #### GEOS Geometry C data structures, and utility functions. ####
|
||||
|
||||
|
||||
# Opaque GEOS geometry structures, used for GEOM_PTR and CS_PTR
|
||||
class GEOSGeom_t(Structure):
|
||||
pass
|
||||
|
||||
|
||||
class GEOSPrepGeom_t(Structure):
|
||||
pass
|
||||
|
||||
|
||||
class GEOSCoordSeq_t(Structure):
|
||||
pass
|
||||
|
||||
|
||||
class GEOSContextHandle_t(Structure):
|
||||
pass
|
||||
|
||||
|
||||
# Pointers to opaque GEOS geometry structures.
|
||||
GEOM_PTR = POINTER(GEOSGeom_t)
|
||||
PREPGEOM_PTR = POINTER(GEOSPrepGeom_t)
|
||||
CS_PTR = POINTER(GEOSCoordSeq_t)
|
||||
CONTEXT_PTR = POINTER(GEOSContextHandle_t)
|
||||
|
||||
|
||||
lgeos = SimpleLazyObject(load_geos)
|
||||
|
||||
|
||||
class GEOSFuncFactory:
|
||||
"""
|
||||
Lazy loading of GEOS functions.
|
||||
"""
|
||||
argtypes = None
|
||||
restype = None
|
||||
errcheck = None
|
||||
|
||||
def __init__(self, func_name, *args, restype=None, errcheck=None, argtypes=None, **kwargs):
|
||||
self.func_name = func_name
|
||||
if restype is not None:
|
||||
self.restype = restype
|
||||
if errcheck is not None:
|
||||
self.errcheck = errcheck
|
||||
if argtypes is not None:
|
||||
self.argtypes = argtypes
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.func(*args, **kwargs)
|
||||
|
||||
@cached_property
|
||||
def func(self):
|
||||
from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
|
||||
func = GEOSFunc(self.func_name)
|
||||
func.argtypes = self.argtypes or []
|
||||
func.restype = self.restype
|
||||
if self.errcheck:
|
||||
func.errcheck = self.errcheck
|
||||
return func
|
||||
|
||||
|
||||
def geos_version():
|
||||
"""Return the string version of the GEOS library."""
|
||||
return lgeos.GEOSversion()
|
||||
|
||||
|
||||
def geos_version_tuple():
|
||||
"""Return the GEOS version as a tuple (major, minor, subminor)."""
|
||||
return get_version_tuple(geos_version().decode())
|
||||
@@ -0,0 +1,157 @@
|
||||
from ctypes import c_uint
|
||||
|
||||
from django.contrib.gis import gdal
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry
|
||||
|
||||
|
||||
class Point(GEOSGeometry):
|
||||
_minlength = 2
|
||||
_maxlength = 3
|
||||
has_cs = True
|
||||
|
||||
def __init__(self, x=None, y=None, z=None, srid=None):
|
||||
"""
|
||||
The Point object may be initialized with either a tuple, or individual
|
||||
parameters.
|
||||
|
||||
For example:
|
||||
>>> p = Point((5, 23)) # 2D point, passed in as a tuple
|
||||
>>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters
|
||||
"""
|
||||
if x is None:
|
||||
coords = []
|
||||
elif isinstance(x, (tuple, list)):
|
||||
# Here a tuple or list was passed in under the `x` parameter.
|
||||
coords = x
|
||||
elif isinstance(x, (float, int)) and isinstance(y, (float, int)):
|
||||
# Here X, Y, and (optionally) Z were passed in individually, as parameters.
|
||||
if isinstance(z, (float, int)):
|
||||
coords = [x, y, z]
|
||||
else:
|
||||
coords = [x, y]
|
||||
else:
|
||||
raise TypeError('Invalid parameters given for Point initialization.')
|
||||
|
||||
point = self._create_point(len(coords), coords)
|
||||
|
||||
# Initializing using the address returned from the GEOS
|
||||
# createPoint factory.
|
||||
super().__init__(point, srid=srid)
|
||||
|
||||
def _to_pickle_wkb(self):
|
||||
return None if self.empty else super()._to_pickle_wkb()
|
||||
|
||||
def _from_pickle_wkb(self, wkb):
|
||||
return self._create_empty() if wkb is None else super()._from_pickle_wkb(wkb)
|
||||
|
||||
def _ogr_ptr(self):
|
||||
return gdal.geometries.Point._create_empty() if self.empty else super()._ogr_ptr()
|
||||
|
||||
@classmethod
|
||||
def _create_empty(cls):
|
||||
return cls._create_point(None, None)
|
||||
|
||||
@classmethod
|
||||
def _create_point(cls, ndim, coords):
|
||||
"""
|
||||
Create a coordinate sequence, set X, Y, [Z], and create point
|
||||
"""
|
||||
if not ndim:
|
||||
return capi.create_point(None)
|
||||
|
||||
if ndim < 2 or ndim > 3:
|
||||
raise TypeError('Invalid point dimension: %s' % ndim)
|
||||
|
||||
cs = capi.create_cs(c_uint(1), c_uint(ndim))
|
||||
i = iter(coords)
|
||||
capi.cs_setx(cs, 0, next(i))
|
||||
capi.cs_sety(cs, 0, next(i))
|
||||
if ndim == 3:
|
||||
capi.cs_setz(cs, 0, next(i))
|
||||
|
||||
return capi.create_point(cs)
|
||||
|
||||
def _set_list(self, length, items):
|
||||
ptr = self._create_point(length, items)
|
||||
if ptr:
|
||||
capi.destroy_geom(self.ptr)
|
||||
self._ptr = ptr
|
||||
self._post_init()
|
||||
else:
|
||||
# can this happen?
|
||||
raise GEOSException('Geometry resulting from slice deletion was invalid.')
|
||||
|
||||
def _set_single(self, index, value):
|
||||
self._cs.setOrdinate(index, 0, value)
|
||||
|
||||
def __iter__(self):
|
||||
"Iterate over coordinates of this Point."
|
||||
for i in range(len(self)):
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Return the number of dimensions for this Point (either 0, 2 or 3)."
|
||||
if self.empty:
|
||||
return 0
|
||||
if self.hasz:
|
||||
return 3
|
||||
else:
|
||||
return 2
|
||||
|
||||
def _get_single_external(self, index):
|
||||
if index == 0:
|
||||
return self.x
|
||||
elif index == 1:
|
||||
return self.y
|
||||
elif index == 2:
|
||||
return self.z
|
||||
|
||||
_get_single_internal = _get_single_external
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
"Return the X component of the Point."
|
||||
return self._cs.getOrdinate(0, 0)
|
||||
|
||||
@x.setter
|
||||
def x(self, value):
|
||||
"Set the X component of the Point."
|
||||
self._cs.setOrdinate(0, 0, value)
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
"Return the Y component of the Point."
|
||||
return self._cs.getOrdinate(1, 0)
|
||||
|
||||
@y.setter
|
||||
def y(self, value):
|
||||
"Set the Y component of the Point."
|
||||
self._cs.setOrdinate(1, 0, value)
|
||||
|
||||
@property
|
||||
def z(self):
|
||||
"Return the Z component of the Point."
|
||||
return self._cs.getOrdinate(2, 0) if self.hasz else None
|
||||
|
||||
@z.setter
|
||||
def z(self, value):
|
||||
"Set the Z component of the Point."
|
||||
if not self.hasz:
|
||||
raise GEOSException('Cannot set Z on 2D Point.')
|
||||
self._cs.setOrdinate(2, 0, value)
|
||||
|
||||
# ### Tuple setting and retrieval routines. ###
|
||||
@property
|
||||
def tuple(self):
|
||||
"Return a tuple of the point."
|
||||
return self._cs.tuple
|
||||
|
||||
@tuple.setter
|
||||
def tuple(self, tup):
|
||||
"Set the coordinates of the point with the given tuple."
|
||||
self._cs[0] = tup
|
||||
|
||||
# The tuple and coords properties
|
||||
coords = tuple
|
||||
@@ -0,0 +1,178 @@
|
||||
from ctypes import byref, c_uint
|
||||
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry
|
||||
from django.contrib.gis.geos.libgeos import GEOM_PTR
|
||||
from django.contrib.gis.geos.linestring import LinearRing
|
||||
|
||||
|
||||
class Polygon(GEOSGeometry):
|
||||
_minlength = 1
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize on an exterior ring and a sequence of holes (both
|
||||
instances may be either LinearRing instances, or a tuple/list
|
||||
that may be constructed into a LinearRing).
|
||||
|
||||
Examples of initialization, where shell, hole1, and hole2 are
|
||||
valid LinearRing geometries:
|
||||
>>> from django.contrib.gis.geos import LinearRing, Polygon
|
||||
>>> shell = hole1 = hole2 = LinearRing()
|
||||
>>> poly = Polygon(shell, hole1, hole2)
|
||||
>>> poly = Polygon(shell, (hole1, hole2))
|
||||
|
||||
>>> # Example where a tuple parameters are used:
|
||||
>>> poly = Polygon(((0, 0), (0, 10), (10, 10), (0, 10), (0, 0)),
|
||||
... ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4)))
|
||||
"""
|
||||
if not args:
|
||||
super().__init__(self._create_polygon(0, None), **kwargs)
|
||||
return
|
||||
|
||||
# Getting the ext_ring and init_holes parameters from the argument list
|
||||
ext_ring, *init_holes = args
|
||||
n_holes = len(init_holes)
|
||||
|
||||
# If initialized as Polygon(shell, (LinearRing, LinearRing)) [for backward-compatibility]
|
||||
if n_holes == 1 and isinstance(init_holes[0], (tuple, list)):
|
||||
if not init_holes[0]:
|
||||
init_holes = ()
|
||||
n_holes = 0
|
||||
elif isinstance(init_holes[0][0], LinearRing):
|
||||
init_holes = init_holes[0]
|
||||
n_holes = len(init_holes)
|
||||
|
||||
polygon = self._create_polygon(n_holes + 1, [ext_ring, *init_holes])
|
||||
super().__init__(polygon, **kwargs)
|
||||
|
||||
def __iter__(self):
|
||||
"Iterate over each ring in the polygon."
|
||||
for i in range(len(self)):
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Return the number of rings in this Polygon."
|
||||
return self.num_interior_rings + 1
|
||||
|
||||
@classmethod
|
||||
def from_bbox(cls, bbox):
|
||||
"Construct a Polygon from a bounding box (4-tuple)."
|
||||
x0, y0, x1, y1 = bbox
|
||||
for z in bbox:
|
||||
if not isinstance(z, (float, int)):
|
||||
return GEOSGeometry('POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' %
|
||||
(x0, y0, x0, y1, x1, y1, x1, y0, x0, y0))
|
||||
return Polygon(((x0, y0), (x0, y1), (x1, y1), (x1, y0), (x0, y0)))
|
||||
|
||||
# ### These routines are needed for list-like operation w/ListMixin ###
|
||||
def _create_polygon(self, length, items):
|
||||
# Instantiate LinearRing objects if necessary, but don't clone them yet
|
||||
# _construct_ring will throw a TypeError if a parameter isn't a valid ring
|
||||
# If we cloned the pointers here, we wouldn't be able to clean up
|
||||
# in case of error.
|
||||
if not length:
|
||||
return capi.create_empty_polygon()
|
||||
|
||||
rings = []
|
||||
for r in items:
|
||||
if isinstance(r, GEOM_PTR):
|
||||
rings.append(r)
|
||||
else:
|
||||
rings.append(self._construct_ring(r))
|
||||
|
||||
shell = self._clone(rings.pop(0))
|
||||
|
||||
n_holes = length - 1
|
||||
if n_holes:
|
||||
holes = (GEOM_PTR * n_holes)(*[self._clone(r) for r in rings])
|
||||
holes_param = byref(holes)
|
||||
else:
|
||||
holes_param = None
|
||||
|
||||
return capi.create_polygon(shell, holes_param, c_uint(n_holes))
|
||||
|
||||
def _clone(self, g):
|
||||
if isinstance(g, GEOM_PTR):
|
||||
return capi.geom_clone(g)
|
||||
else:
|
||||
return capi.geom_clone(g.ptr)
|
||||
|
||||
def _construct_ring(self, param, msg=(
|
||||
'Parameter must be a sequence of LinearRings or objects that can initialize to LinearRings')):
|
||||
"Try to construct a ring from the given parameter."
|
||||
if isinstance(param, LinearRing):
|
||||
return param
|
||||
try:
|
||||
ring = LinearRing(param)
|
||||
return ring
|
||||
except TypeError:
|
||||
raise TypeError(msg)
|
||||
|
||||
def _set_list(self, length, items):
|
||||
# Getting the current pointer, replacing with the newly constructed
|
||||
# geometry, and destroying the old geometry.
|
||||
prev_ptr = self.ptr
|
||||
srid = self.srid
|
||||
self.ptr = self._create_polygon(length, items)
|
||||
if srid:
|
||||
self.srid = srid
|
||||
capi.destroy_geom(prev_ptr)
|
||||
|
||||
def _get_single_internal(self, index):
|
||||
"""
|
||||
Return the ring at the specified index. The first index, 0, will
|
||||
always return the exterior ring. Indices > 0 will return the
|
||||
interior ring at the given index (e.g., poly[1] and poly[2] would
|
||||
return the first and second interior ring, respectively).
|
||||
|
||||
CAREFUL: Internal/External are not the same as Interior/Exterior!
|
||||
Return a pointer from the existing geometries for use internally by the
|
||||
object's methods. _get_single_external() returns a clone of the same
|
||||
geometry for use by external code.
|
||||
"""
|
||||
if index == 0:
|
||||
return capi.get_extring(self.ptr)
|
||||
else:
|
||||
# Getting the interior ring, have to subtract 1 from the index.
|
||||
return capi.get_intring(self.ptr, index - 1)
|
||||
|
||||
def _get_single_external(self, index):
|
||||
return GEOSGeometry(capi.geom_clone(self._get_single_internal(index)), srid=self.srid)
|
||||
|
||||
_set_single = GEOSGeometry._set_single_rebuild
|
||||
_assign_extended_slice = GEOSGeometry._assign_extended_slice_rebuild
|
||||
|
||||
# #### Polygon Properties ####
|
||||
@property
|
||||
def num_interior_rings(self):
|
||||
"Return the number of interior rings."
|
||||
# Getting the number of rings
|
||||
return capi.get_nrings(self.ptr)
|
||||
|
||||
def _get_ext_ring(self):
|
||||
"Get the exterior ring of the Polygon."
|
||||
return self[0]
|
||||
|
||||
def _set_ext_ring(self, ring):
|
||||
"Set the exterior ring of the Polygon."
|
||||
self[0] = ring
|
||||
|
||||
# Properties for the exterior ring/shell.
|
||||
exterior_ring = property(_get_ext_ring, _set_ext_ring)
|
||||
shell = exterior_ring
|
||||
|
||||
@property
|
||||
def tuple(self):
|
||||
"Get the tuple for each ring in this Polygon."
|
||||
return tuple(self[i].tuple for i in range(len(self)))
|
||||
coords = tuple
|
||||
|
||||
@property
|
||||
def kml(self):
|
||||
"Return the KML representation of this Polygon."
|
||||
inner_kml = ''.join(
|
||||
"<innerBoundaryIs>%s</innerBoundaryIs>" % self[i + 1].kml
|
||||
for i in range(self.num_interior_rings)
|
||||
)
|
||||
return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml)
|
||||
@@ -0,0 +1,49 @@
|
||||
from .base import GEOSBase
|
||||
from .prototypes import prepared as capi
|
||||
|
||||
|
||||
class PreparedGeometry(GEOSBase):
|
||||
"""
|
||||
A geometry that is prepared for performing certain operations.
|
||||
At the moment this includes the contains covers, and intersects
|
||||
operations.
|
||||
"""
|
||||
ptr_type = capi.PREPGEOM_PTR
|
||||
destructor = capi.prepared_destroy
|
||||
|
||||
def __init__(self, geom):
|
||||
# Keeping a reference to the original geometry object to prevent it
|
||||
# from being garbage collected which could then crash the prepared one
|
||||
# See #21662
|
||||
self._base_geom = geom
|
||||
from .geometry import GEOSGeometry
|
||||
if not isinstance(geom, GEOSGeometry):
|
||||
raise TypeError
|
||||
self.ptr = capi.geos_prepare(geom.ptr)
|
||||
|
||||
def contains(self, other):
|
||||
return capi.prepared_contains(self.ptr, other.ptr)
|
||||
|
||||
def contains_properly(self, other):
|
||||
return capi.prepared_contains_properly(self.ptr, other.ptr)
|
||||
|
||||
def covers(self, other):
|
||||
return capi.prepared_covers(self.ptr, other.ptr)
|
||||
|
||||
def intersects(self, other):
|
||||
return capi.prepared_intersects(self.ptr, other.ptr)
|
||||
|
||||
def crosses(self, other):
|
||||
return capi.prepared_crosses(self.ptr, other.ptr)
|
||||
|
||||
def disjoint(self, other):
|
||||
return capi.prepared_disjoint(self.ptr, other.ptr)
|
||||
|
||||
def overlaps(self, other):
|
||||
return capi.prepared_overlaps(self.ptr, other.ptr)
|
||||
|
||||
def touches(self, other):
|
||||
return capi.prepared_touches(self.ptr, other.ptr)
|
||||
|
||||
def within(self, other):
|
||||
return capi.prepared_within(self.ptr, other.ptr)
|
||||
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
This module contains all of the GEOS ctypes function prototypes. Each
|
||||
prototype handles the interaction between the GEOS library and Python
|
||||
via ctypes.
|
||||
"""
|
||||
|
||||
from django.contrib.gis.geos.prototypes.coordseq import ( # NOQA
|
||||
create_cs, cs_clone, cs_getdims, cs_getordinate, cs_getsize, cs_getx,
|
||||
cs_gety, cs_getz, cs_setordinate, cs_setx, cs_sety, cs_setz, get_cs,
|
||||
)
|
||||
from django.contrib.gis.geos.prototypes.geom import ( # NOQA
|
||||
create_collection, create_empty_polygon, create_linearring,
|
||||
create_linestring, create_point, create_polygon, destroy_geom, geom_clone,
|
||||
geos_get_srid, geos_normalize, geos_set_srid, geos_type, geos_typeid,
|
||||
get_dims, get_extring, get_geomn, get_intring, get_nrings, get_num_coords,
|
||||
get_num_geoms,
|
||||
)
|
||||
from django.contrib.gis.geos.prototypes.misc import * # NOQA
|
||||
from django.contrib.gis.geos.prototypes.predicates import ( # NOQA
|
||||
geos_contains, geos_covers, geos_crosses, geos_disjoint, geos_equals,
|
||||
geos_equalsexact, geos_hasz, geos_intersects, geos_isclosed, geos_isempty,
|
||||
geos_isring, geos_issimple, geos_isvalid, geos_overlaps,
|
||||
geos_relatepattern, geos_touches, geos_within,
|
||||
)
|
||||
from django.contrib.gis.geos.prototypes.topology import * # NOQA
|
||||
@@ -0,0 +1,91 @@
|
||||
from ctypes import POINTER, c_double, c_int, c_uint
|
||||
|
||||
from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR, GEOSFuncFactory
|
||||
from django.contrib.gis.geos.prototypes.errcheck import (
|
||||
GEOSException, last_arg_byref,
|
||||
)
|
||||
|
||||
|
||||
# ## Error-checking routines specific to coordinate sequences. ##
|
||||
def check_cs_op(result, func, cargs):
|
||||
"Check the status code of a coordinate sequence operation."
|
||||
if result == 0:
|
||||
raise GEOSException('Could not set value on coordinate sequence')
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
def check_cs_get(result, func, cargs):
|
||||
"Check the coordinate sequence retrieval."
|
||||
check_cs_op(result, func, cargs)
|
||||
# Object in by reference, return its value.
|
||||
return last_arg_byref(cargs)
|
||||
|
||||
|
||||
# ## Coordinate sequence prototype factory classes. ##
|
||||
class CsInt(GEOSFuncFactory):
|
||||
"For coordinate sequence routines that return an integer."
|
||||
argtypes = [CS_PTR, POINTER(c_uint)]
|
||||
restype = c_int
|
||||
errcheck = staticmethod(check_cs_get)
|
||||
|
||||
|
||||
class CsOperation(GEOSFuncFactory):
|
||||
"For coordinate sequence operations."
|
||||
restype = c_int
|
||||
|
||||
def __init__(self, *args, ordinate=False, get=False, **kwargs):
|
||||
if get:
|
||||
# Get routines have double parameter passed-in by reference.
|
||||
errcheck = check_cs_get
|
||||
dbl_param = POINTER(c_double)
|
||||
else:
|
||||
errcheck = check_cs_op
|
||||
dbl_param = c_double
|
||||
|
||||
if ordinate:
|
||||
# Get/Set ordinate routines have an extra uint parameter.
|
||||
argtypes = [CS_PTR, c_uint, c_uint, dbl_param]
|
||||
else:
|
||||
argtypes = [CS_PTR, c_uint, dbl_param]
|
||||
|
||||
super().__init__(*args, **{**kwargs, 'errcheck': errcheck, 'argtypes': argtypes})
|
||||
|
||||
|
||||
class CsOutput(GEOSFuncFactory):
|
||||
restype = CS_PTR
|
||||
|
||||
@staticmethod
|
||||
def errcheck(result, func, cargs):
|
||||
if not result:
|
||||
raise GEOSException(
|
||||
'Error encountered checking Coordinate Sequence returned from GEOS '
|
||||
'C function "%s".' % func.__name__
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
# ## Coordinate Sequence ctypes prototypes ##
|
||||
|
||||
# Coordinate Sequence constructors & cloning.
|
||||
cs_clone = CsOutput('GEOSCoordSeq_clone', argtypes=[CS_PTR])
|
||||
create_cs = CsOutput('GEOSCoordSeq_create', argtypes=[c_uint, c_uint])
|
||||
get_cs = CsOutput('GEOSGeom_getCoordSeq', argtypes=[GEOM_PTR])
|
||||
|
||||
# Getting, setting ordinate
|
||||
cs_getordinate = CsOperation('GEOSCoordSeq_getOrdinate', ordinate=True, get=True)
|
||||
cs_setordinate = CsOperation('GEOSCoordSeq_setOrdinate', ordinate=True)
|
||||
|
||||
# For getting, x, y, z
|
||||
cs_getx = CsOperation('GEOSCoordSeq_getX', get=True)
|
||||
cs_gety = CsOperation('GEOSCoordSeq_getY', get=True)
|
||||
cs_getz = CsOperation('GEOSCoordSeq_getZ', get=True)
|
||||
|
||||
# For setting, x, y, z
|
||||
cs_setx = CsOperation('GEOSCoordSeq_setX')
|
||||
cs_sety = CsOperation('GEOSCoordSeq_setY')
|
||||
cs_setz = CsOperation('GEOSCoordSeq_setZ')
|
||||
|
||||
# These routines return size & dimensions.
|
||||
cs_getsize = CsInt('GEOSCoordSeq_getSize')
|
||||
cs_getdims = CsInt('GEOSCoordSeq_getDimensions')
|
||||
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
Error checking functions for GEOS ctypes prototype functions.
|
||||
"""
|
||||
from ctypes import c_void_p, string_at
|
||||
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from django.contrib.gis.geos.libgeos import GEOSFuncFactory
|
||||
|
||||
# Getting the `free` routine used to free the memory allocated for
|
||||
# string pointers returned by GEOS.
|
||||
free = GEOSFuncFactory('GEOSFree')
|
||||
free.argtypes = [c_void_p]
|
||||
|
||||
|
||||
def last_arg_byref(args):
|
||||
"Return the last C argument's value by reference."
|
||||
return args[-1]._obj.value
|
||||
|
||||
|
||||
def check_dbl(result, func, cargs):
|
||||
"Check the status code and returns the double value passed in by reference."
|
||||
# Checking the status code
|
||||
if result != 1:
|
||||
return None
|
||||
# Double passed in by reference, return its value.
|
||||
return last_arg_byref(cargs)
|
||||
|
||||
|
||||
def check_geom(result, func, cargs):
|
||||
"Error checking on routines that return Geometries."
|
||||
if not result:
|
||||
raise GEOSException('Error encountered checking Geometry returned from GEOS C function "%s".' % func.__name__)
|
||||
return result
|
||||
|
||||
|
||||
def check_minus_one(result, func, cargs):
|
||||
"Error checking on routines that should not return -1."
|
||||
if result == -1:
|
||||
raise GEOSException('Error encountered in GEOS C function "%s".' % func.__name__)
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
def check_predicate(result, func, cargs):
|
||||
"Error checking for unary/binary predicate functions."
|
||||
if result == 1:
|
||||
return True
|
||||
elif result == 0:
|
||||
return False
|
||||
else:
|
||||
raise GEOSException('Error encountered on GEOS C predicate function "%s".' % func.__name__)
|
||||
|
||||
|
||||
def check_sized_string(result, func, cargs):
|
||||
"""
|
||||
Error checking for routines that return explicitly sized strings.
|
||||
|
||||
This frees the memory allocated by GEOS at the result pointer.
|
||||
"""
|
||||
if not result:
|
||||
raise GEOSException('Invalid string pointer returned by GEOS C function "%s"' % func.__name__)
|
||||
# A c_size_t object is passed in by reference for the second
|
||||
# argument on these routines, and its needed to determine the
|
||||
# correct size.
|
||||
s = string_at(result, last_arg_byref(cargs))
|
||||
# Freeing the memory allocated within GEOS
|
||||
free(result)
|
||||
return s
|
||||
|
||||
|
||||
def check_string(result, func, cargs):
|
||||
"""
|
||||
Error checking for routines that return strings.
|
||||
|
||||
This frees the memory allocated by GEOS at the result pointer.
|
||||
"""
|
||||
if not result:
|
||||
raise GEOSException('Error encountered checking string return value in GEOS C function "%s".' % func.__name__)
|
||||
# Getting the string value at the pointer address.
|
||||
s = string_at(result)
|
||||
# Freeing the memory allocated within GEOS
|
||||
free(result)
|
||||
return s
|
||||
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
This module is for the miscellaneous GEOS routines, particularly the
|
||||
ones that return the area, distance, and length.
|
||||
"""
|
||||
from ctypes import POINTER, c_double, c_int
|
||||
|
||||
from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOSFuncFactory
|
||||
from django.contrib.gis.geos.prototypes.errcheck import check_dbl, check_string
|
||||
from django.contrib.gis.geos.prototypes.geom import geos_char_p
|
||||
|
||||
__all__ = ['geos_area', 'geos_distance', 'geos_length', 'geos_isvalidreason']
|
||||
|
||||
|
||||
class DblFromGeom(GEOSFuncFactory):
|
||||
"""
|
||||
Argument is a Geometry, return type is double that is passed
|
||||
in by reference as the last argument.
|
||||
"""
|
||||
restype = c_int # Status code returned
|
||||
errcheck = staticmethod(check_dbl)
|
||||
|
||||
|
||||
# ### ctypes prototypes ###
|
||||
|
||||
# Area, distance, and length prototypes.
|
||||
geos_area = DblFromGeom('GEOSArea', argtypes=[GEOM_PTR, POINTER(c_double)])
|
||||
geos_distance = DblFromGeom('GEOSDistance', argtypes=[GEOM_PTR, GEOM_PTR, POINTER(c_double)])
|
||||
geos_length = DblFromGeom('GEOSLength', argtypes=[GEOM_PTR, POINTER(c_double)])
|
||||
geos_isvalidreason = GEOSFuncFactory(
|
||||
'GEOSisValidReason', restype=geos_char_p, errcheck=check_string, argtypes=[GEOM_PTR]
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
from ctypes import c_byte
|
||||
|
||||
from django.contrib.gis.geos.libgeos import (
|
||||
GEOM_PTR, PREPGEOM_PTR, GEOSFuncFactory,
|
||||
)
|
||||
from django.contrib.gis.geos.prototypes.errcheck import check_predicate
|
||||
|
||||
# Prepared geometry constructor and destructors.
|
||||
geos_prepare = GEOSFuncFactory('GEOSPrepare', argtypes=[GEOM_PTR], restype=PREPGEOM_PTR)
|
||||
prepared_destroy = GEOSFuncFactory('GEOSPreparedGeom_destroy', argtypes=[PREPGEOM_PTR])
|
||||
|
||||
|
||||
# Prepared geometry binary predicate support.
|
||||
class PreparedPredicate(GEOSFuncFactory):
|
||||
argtypes = [PREPGEOM_PTR, GEOM_PTR]
|
||||
restype = c_byte
|
||||
errcheck = staticmethod(check_predicate)
|
||||
|
||||
|
||||
prepared_contains = PreparedPredicate('GEOSPreparedContains')
|
||||
prepared_contains_properly = PreparedPredicate('GEOSPreparedContainsProperly')
|
||||
prepared_covers = PreparedPredicate('GEOSPreparedCovers')
|
||||
prepared_crosses = PreparedPredicate('GEOSPreparedCrosses')
|
||||
prepared_disjoint = PreparedPredicate('GEOSPreparedDisjoint')
|
||||
prepared_intersects = PreparedPredicate('GEOSPreparedIntersects')
|
||||
prepared_overlaps = PreparedPredicate('GEOSPreparedOverlaps')
|
||||
prepared_touches = PreparedPredicate('GEOSPreparedTouches')
|
||||
prepared_within = PreparedPredicate('GEOSPreparedWithin')
|
||||
@@ -0,0 +1,77 @@
|
||||
import threading
|
||||
|
||||
from django.contrib.gis.geos.base import GEOSBase
|
||||
from django.contrib.gis.geos.libgeos import (
|
||||
CONTEXT_PTR, error_h, lgeos, notice_h,
|
||||
)
|
||||
|
||||
|
||||
class GEOSContextHandle(GEOSBase):
|
||||
"""Represent a GEOS context handle."""
|
||||
ptr_type = CONTEXT_PTR
|
||||
destructor = lgeos.finishGEOS_r
|
||||
|
||||
def __init__(self):
|
||||
# Initializing the context handler for this thread with
|
||||
# the notice and error handler.
|
||||
self.ptr = lgeos.initGEOS_r(notice_h, error_h)
|
||||
|
||||
|
||||
# Defining a thread-local object and creating an instance
|
||||
# to hold a reference to GEOSContextHandle for this thread.
|
||||
class GEOSContext(threading.local):
|
||||
handle = None
|
||||
|
||||
|
||||
thread_context = GEOSContext()
|
||||
|
||||
|
||||
class GEOSFunc:
|
||||
"""
|
||||
Serve as a wrapper for GEOS C Functions. Use thread-safe function
|
||||
variants when available.
|
||||
"""
|
||||
def __init__(self, func_name):
|
||||
# GEOS thread-safe function signatures end with '_r' and take an
|
||||
# additional context handle parameter.
|
||||
self.cfunc = getattr(lgeos, func_name + '_r')
|
||||
# Create a reference to thread_context so it's not garbage-collected
|
||||
# before an attempt to call this object.
|
||||
self.thread_context = thread_context
|
||||
|
||||
def __call__(self, *args):
|
||||
# Create a context handle if one doesn't exist for this thread.
|
||||
self.thread_context.handle = self.thread_context.handle or GEOSContextHandle()
|
||||
# Call the threaded GEOS routine with the pointer of the context handle
|
||||
# as the first argument.
|
||||
return self.cfunc(self.thread_context.handle.ptr, *args)
|
||||
|
||||
def __str__(self):
|
||||
return self.cfunc.__name__
|
||||
|
||||
# argtypes property
|
||||
def _get_argtypes(self):
|
||||
return self.cfunc.argtypes
|
||||
|
||||
def _set_argtypes(self, argtypes):
|
||||
self.cfunc.argtypes = [CONTEXT_PTR, *argtypes]
|
||||
|
||||
argtypes = property(_get_argtypes, _set_argtypes)
|
||||
|
||||
# restype property
|
||||
def _get_restype(self):
|
||||
return self.cfunc.restype
|
||||
|
||||
def _set_restype(self, restype):
|
||||
self.cfunc.restype = restype
|
||||
|
||||
restype = property(_get_restype, _set_restype)
|
||||
|
||||
# errcheck property
|
||||
def _get_errcheck(self):
|
||||
return self.cfunc.errcheck
|
||||
|
||||
def _set_errcheck(self, errcheck):
|
||||
self.cfunc.errcheck = errcheck
|
||||
|
||||
errcheck = property(_get_errcheck, _set_errcheck)
|
||||
@@ -0,0 +1,80 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-03-18 09:16+0100\n"
|
||||
"PO-Revision-Date: 2015-03-18 08:35+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Afrikaans (http://www.transifex.com/projects/p/django/"
|
||||
"language/af/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: af\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr ""
|
||||
|
||||
msgid "The base GIS field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
|
||||
msgid "Point"
|
||||
msgstr ""
|
||||
|
||||
msgid "Line string"
|
||||
msgstr ""
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr ""
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr ""
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Google Maps via GeoDjango"
|
||||
msgstr ""
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr ""
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr ""
|
||||
Binary file not shown.
@@ -0,0 +1,90 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Bashar Al-Abdulhadi, 2015-2016
|
||||
# Bashar Al-Abdulhadi, 2014
|
||||
# Eyad Toma <d.eyad.t@gmail.com>, 2013
|
||||
# Jannis Leidel <jannis@leidel.info>, 2011
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-19 16:40+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Arabic (http://www.transifex.com/django/django/language/ar/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ar\n"
|
||||
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
|
||||
"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr "نظم المعلومات الجغرافية GIS"
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr "حقل نظم المعلومات الجغرافية الرئيسي"
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
"قاعدة الحقل الهندسي ترتكز لمواصفات نوع OpenGIS (نظم المعلومات الجغرافية "
|
||||
"المفتوحة) الهندسية."
|
||||
|
||||
msgid "Point"
|
||||
msgstr "نقطة إحداثية"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "سطر تسلسل أحرف"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "مُضلّع إحداثي"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "نقاط إحداثية"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "تسلسل أحرف متعدد الأسطر"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "مجموعة مُضلعات إحداثية"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "مجموعة إحداثية"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr "حقل مجموع التحصيل"
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr "حقل خطوط المسح التسامتي"
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "لم تُدخل أي أحداثيات."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "الإحداثيات غير صحيحة."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "نوع الإحداثيات غير صحيح."
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr "حدث خطأ أثناء تحويل geometry إلى حقل SRID."
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr "حذف جميع المميزات"
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr "نافذة تدقيق WKT:"
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr "نافذة التدقيق (قيمة تسلسلية)"
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "لا موجز مسجّل"
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "Slug %r غير مسجّل"
|
||||
Binary file not shown.
@@ -0,0 +1,85 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Ḷḷumex03 <tornes@opmbx.org>, 2014
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-23 19:51+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Asturian (http://www.transifex.com/django/django/language/"
|
||||
"ast/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ast\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr ""
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
|
||||
msgid "Point"
|
||||
msgstr "Puntu"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr ""
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "Polígonu"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr ""
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "Nun s'apurrió'l valor de xeometría."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "Valor de xeometría inválidu."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "Triba de xeometría inválida."
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr ""
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr ""
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "Nun hai feeds rexistraos"
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr ""
|
||||
@@ -0,0 +1,88 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Ali Ismayilov <ali@ismailov.info>, 2011
|
||||
# Emin Mastizada <emin@linux.com>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2018-04-27 17:21+0000\n"
|
||||
"Last-Translator: Emin Mastizada <emin@linux.com>\n"
|
||||
"Language-Team: Azerbaijani (http://www.transifex.com/django/django/language/"
|
||||
"az/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: az\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr "GIS"
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr "Təməl GIS sahəsi (field)."
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
"Təməl Həndəsə sahəsi -- OpenGIS Specification Geometry növünə işarətlənir."
|
||||
|
||||
msgid "Point"
|
||||
msgstr "Nöqtə"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "Xətt"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "Poliqon"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "Nöqtələr"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "Xətlər"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "Poliqonlar"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "Fiqurlar çoxluğu"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr "Aqreqat Sahəsini Genişləndir"
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr "Rastr Sahəsi"
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "Həndəsi qiyməti verilməyib."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "Həndəsi qiyməti düzgün deyil."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "Həndəsi tipi düzgün deyil."
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
"Fiquru vərəqənin fiqur sahəsinin SRID qiymətinə çevirərkən xəta baş verdi."
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr "Bütün Xüsusiyyətləri sil"
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr "WKT sazlama pəncərəsi:"
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr "Sazlama pəncərəsi (seriallaşdırılmış dəyər)"
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "Qeyd edilmiş axın yoxdur."
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "%r slug-ı qeyd edilməyib."
|
||||
@@ -0,0 +1,90 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Viktar Palstsiuk <vipals@gmail.com>, 2014-2015
|
||||
# znotdead <zhirafchik@gmail.com>, 2016
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-19 16:40+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Belarusian (http://www.transifex.com/django/django/language/"
|
||||
"be/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: be\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
|
||||
"%100>=11 && n%100<=14)? 2 : 3);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr "ГІС"
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr "Галоўнае поле ГІС."
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
"Галоўнае геаметрычнае поле - адлюстроўвае геамэтрычныя віды паводле "
|
||||
"спэцыфікацыі «OpenGIS»."
|
||||
|
||||
msgid "Point"
|
||||
msgstr "Пункт"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "Ломаная"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "Шматкутнік"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "Набор пунктаў"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "Набор ломаных"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "Набор шматкутнікаў"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "Набор ґеамэтрычных аб’ектаў"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr "Поле аб'яднанай плошчы"
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr "Растравае поле"
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "Не пазначылі значэньне ґеамэтрыі."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "Хібнае значэньне ґеамэтрыі."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "Хібны від ґеамэтрыі."
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr "Не ўдалося ператварыць ґеамэтрыю ў SRID."
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr "Выдаліць усе аб’екты"
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr "Акно адладкі WKT:"
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr "Акно адладкі (сэрыялізаванае значэньне)"
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "Няма запісаных стужак."
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "Біркі «%r» няма ў запісаных."
|
||||
@@ -0,0 +1,90 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Georgi Kostadinov <grgkostadinov@gmail.com>, 2012
|
||||
# Todor Lubenov <tgl.sysdev@gmail.com>, 2016
|
||||
# Todor Lubenov <tgl.sysdev@gmail.com>, 2011,2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-19 16:40+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Bulgarian (http://www.transifex.com/django/django/language/"
|
||||
"bg/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: bg\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr "GIS"
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr "Базово ГИС поле."
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
"Основното ГИС поле - карти за спецификация от OpenGIS геометричен тип."
|
||||
|
||||
msgid "Point"
|
||||
msgstr "Точка"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "Линеен елемент"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "Полигон"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "Комплексна-точка"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "Комплексен-линеен елемент"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "Комплексен полигон"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "Геометрична колекция"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr "Разшири Полето за обединяване"
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr "Растерно Поле"
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "Няма предоставена геометрична стойност."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "Невалидна геометрична стойност."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "Невалиден геометричен тип."
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
"Възникна грешка при трансформиране на геометрията на SRID от полето "
|
||||
"геометрия."
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr "Изтрий всички обекти"
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr "WKT прозорец за проверка:"
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr "Debugging прозорец (сериализирана стойност)"
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "Няма регистрирани фийда."
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "Слъгът %r не е регистриран."
|
||||
Binary file not shown.
@@ -0,0 +1,86 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Jannis Leidel <jannis@leidel.info>, 2011
|
||||
# Tahmid Rafi <rafi.tahmid@gmail.com>, 2014
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-19 16:40+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Bengali (http://www.transifex.com/django/django/language/"
|
||||
"bn/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: bn\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr "জিআইএস"
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
|
||||
msgid "Point"
|
||||
msgstr "বিন্দু"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr ""
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "বহুভুজ"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr ""
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "কোন জ্যামিতিক মান দেয়া হয়নি।"
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "জ্যামিতিক মানটি বৈধ নয়।"
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "জ্যামিতিক নমুনাটি বৈধ নয়।"
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr ""
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr ""
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "%r স্লাগটি রেজিস্টারকৃত নয়।"
|
||||
Binary file not shown.
@@ -0,0 +1,88 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Jannis Leidel <jannis@leidel.info>, 2011
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-19 16:40+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Bosnian (http://www.transifex.com/django/django/language/"
|
||||
"bs/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: bs\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr ""
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
|
||||
msgid "Point"
|
||||
msgstr "Tačka"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "Linijska nit"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "Poligon"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "Multi-point"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "Višelinijska nit"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "Multi poligon"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "Geometrijska kolekcija"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "Niste zadali parametre za geometriju."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "Neispravan parametar za geometriju."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "Nepostojeći tip geometrije."
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
"Došlo je do greške tokom pretvaranje geometrije u SRID geometrijskom polja "
|
||||
"obrazca."
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr ""
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr ""
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr ""
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,89 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Erik Wognsen <r4mses@gmail.com>, 2012,2014-2015
|
||||
# Finn Gruwier Larsen, 2011
|
||||
# Jannis Leidel <jannis@leidel.info>, 2011
|
||||
# Kristian Øllegaard <kristian@oellegaard.com>, 2012
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-19 16:40+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Danish (http://www.transifex.com/django/django/language/da/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: da\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr "GIS"
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr "Basis-GIS-feltet."
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
"Basisgeometrifeltet -- mapper til OpenGIS Specification Geometry-typen."
|
||||
|
||||
msgid "Point"
|
||||
msgstr "Punkt"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "Linjesegment"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "Polygon"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "Multipunkt"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "Multilinjesegment"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "Multipolygon"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "Geometrisamling"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr "Extent Aggregate-felt"
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr "Raster-felt"
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "Ingen værdi givet for geometri."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "Ugyldig geometriværdi."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "Ugyldig gemometritype."
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
"Der opstod en fejl ved transformation af geometrien til formularfeltets SRID"
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr "Slet alle Features"
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr "WKT-fejlsøgningsvindue:"
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr "Fejlsøgninsvindue (serialiseret værdi)"
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "Ingen feeds registrerede."
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "\"Slug\" %r er ikke registreret."
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,89 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Michael Wolf <milupo@sorbzilla.de>, 2016
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-23 00:02+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Lower Sorbian (http://www.transifex.com/django/django/"
|
||||
"language/dsb/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: dsb\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n"
|
||||
"%100==4 ? 2 : 3);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr "GIS"
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr "Bazowe pólo GIS."
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
"Bazowe geometrijowe pólo -- wužywa geometrijowy typ specifikacije OpenGIS."
|
||||
|
||||
msgid "Point"
|
||||
msgstr "Dypk"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "Cera"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "Wjelerožk"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "Někotare dypki"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "Někotare cery"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "Někotare wjelerožki"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "Geometrijowa zběrka"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr "Pólo „Extent Aggregate"
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr "Rasterowe pólo"
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "Žedna geometrijowa gódnota pódana."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "Njepłaśiwa geometrijowa gódnota."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "Njepłaśiwy geometrijowy typ."
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
"Zmólka jo se nastała, gaž geometrija jo se do SRID póla geometrijowego "
|
||||
"formulara pśetwóriła."
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr "Wšykne funkcije lašowaś"
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr "Wokno pytanja zmólkow WKT:"
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr "Wokno pytanja zmólkow (serializěrowana gódnota)"
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "Žedne kanale njejsu zregistrěrowane."
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "Adresowe mě %r njejo zregistrěrowane."
|
||||
@@ -0,0 +1,93 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Anastasiadis Stavros <anastasiadis.st00@gmail.com>, 2014
|
||||
# Dimitris Glezos <glezos@transifex.com>, 2011
|
||||
# Elena Andreou <helenaandreou.ha@gmail.com>, 2016
|
||||
# Kostas Papadimitriou <vinilios@gmail.com>, 2012
|
||||
# Nick Mavrakis <mavrakis.n@gmail.com>, 2016
|
||||
# Pãnoș <panos.laganakos@gmail.com>, 2016
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-23 18:54+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Greek (http://www.transifex.com/django/django/language/el/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: el\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr " Γεωγραφικά Συστήματα Πληροφορίας"
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr "Το βασικό GIS πεδίο."
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
"Το βασικό Γεωμετρικό πεδίο -- αντιστοιχεί στον τύπο της Γεωμετρικής "
|
||||
"Προδιαγραφής OpenGIS."
|
||||
|
||||
msgid "Point"
|
||||
msgstr "Σημείο"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "Γραμμή string"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "Πολύγωνο"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "Πολλαπλό σημείο"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "Multi-line string"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "Πολλαπλό πολύγωνο"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "Συλλογή γεωμετριών"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr "Πεδίο Extent Aggregate"
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr "Πεδίο Raster"
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "Δε δόθηκε τιμή γεωμετρίας."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "Άκυρη γεωμετρική τιμή."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "Άκυρος γεωμετρικός τύπος."
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
"Παρουσιάστηκε σφάλμα κατά τη μετατροπή της γεωμετρίας στο SRID του πεδίου "
|
||||
"της φόρμας γεωμετρίας."
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr "Διαγραφή όλων των αντικειμένων"
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr "Παράθυρο αποσφαλμάτωσης WKT"
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr "Παράθυρο αποσφαλμάτωσης (σειριακή τιμή)"
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "Δεν υπάρχουν εγγεγραμμένες ροές ειδήσεων."
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "Το slug %r δεν έχει καταχωρηθεί."
|
||||
@@ -0,0 +1,103 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
|
||||
"Last-Translator: Django team\n"
|
||||
"Language-Team: English <en@li.org>\n"
|
||||
"Language: en\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: contrib/gis/apps.py:8
|
||||
msgid "GIS"
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/db/models/fields.py:78
|
||||
msgid "The base GIS field."
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/db/models/fields.py:165
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/db/models/fields.py:352
|
||||
msgid "Point"
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/db/models/fields.py:358
|
||||
msgid "Line string"
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/db/models/fields.py:364
|
||||
msgid "Polygon"
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/db/models/fields.py:370
|
||||
msgid "Multi-point"
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/db/models/fields.py:376
|
||||
msgid "Multi-line string"
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/db/models/fields.py:382
|
||||
msgid "Multi polygon"
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/db/models/fields.py:388
|
||||
msgid "Geometry collection"
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/db/models/fields.py:394
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/db/models/fields.py:405
|
||||
msgid "Raster Field"
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/forms/fields.py:20
|
||||
msgid "No geometry value provided."
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/forms/fields.py:21
|
||||
msgid "Invalid geometry value."
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/forms/fields.py:22
|
||||
msgid "Invalid geometry type."
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/forms/fields.py:23
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/templates/gis/admin/openlayers.html:35
|
||||
#: contrib/gis/templates/gis/openlayers.html:18
|
||||
msgid "Delete all Features"
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/templates/gis/admin/openlayers.html:37
|
||||
msgid "WKT debugging window:"
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/templates/gis/openlayers.html:13
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/views.py:10
|
||||
msgid "No feeds are registered."
|
||||
msgstr ""
|
||||
|
||||
#: contrib/gis/views.py:16
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr ""
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-03-18 09:16+0100\n"
|
||||
"PO-Revision-Date: 2015-03-18 08:35+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: English (Australia) (http://www.transifex.com/projects/p/"
|
||||
"django/language/en_AU/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: en_AU\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr ""
|
||||
|
||||
msgid "The base GIS field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
|
||||
msgid "Point"
|
||||
msgstr ""
|
||||
|
||||
msgid "Line string"
|
||||
msgstr ""
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr ""
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr ""
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Google Maps via GeoDjango"
|
||||
msgstr ""
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr ""
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr ""
|
||||
Binary file not shown.
+90
@@ -0,0 +1,90 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Jannis Leidel <jannis@leidel.info>, 2011
|
||||
# Ramiro Morales, 2012,2014-2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-23 18:54+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Spanish (Argentina) (http://www.transifex.com/django/django/"
|
||||
"language/es_AR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: es_AR\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr "SIG"
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr "El campo GIS base"
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
"El campo Geometry base -- corresponde al tipo Geometry de la especificación "
|
||||
"OpenGIS."
|
||||
|
||||
msgid "Point"
|
||||
msgstr "Punto"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "Secuencia de líneas"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "Polígono"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "Multi-punto"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "Cadena multi-línea"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "Multi polígono"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "Colección de Geometry's"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr "Campo Extent Aggregate"
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr "Campo Raster"
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "No se ha proporcionado un valor de geometría."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "Valor de geometría no válido."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "Tipo de geometría no válido."
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
"Ha ocurrido un error mientras se transformaba la geometría al SRID del campo "
|
||||
"de formulario de la misma."
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr "Eliminar todas las Features"
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr "Ventana de depuración WTK:"
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr "Ventana de depuración (valor serializado)"
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "No se han registrado feeds."
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "El slug %r no está registrado."
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Abraham Estrada, 2011-2012
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-19 16:40+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Spanish (Mexico) (http://www.transifex.com/django/django/"
|
||||
"language/es_MX/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: es_MX\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr ""
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
|
||||
msgid "Point"
|
||||
msgstr "Punto"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "Secuencia de líneas"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "Polígono"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "Multi-punto"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "Cadena multi-línea"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "Multi polígonos"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "Colección de geometrías"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "No se ha proporcionado un valor de geometría."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "Valor de geometría no válido."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "Tipo de geometría no válido."
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
"Ha ocurrido un error mientras se transformaba la geometría al SRID del campo "
|
||||
"de formulario de la misma."
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr ""
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr ""
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "No hay feeds registrados."
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "El slug %r no está registrado."
|
||||
BIN
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,88 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Aitzol Naberan <anaberan@codesyntax.com>, 2011-2012
|
||||
# Eneko Illarramendi <eneko@illarra.com>, 2017
|
||||
# Urtzi Odriozola <urtzi.odriozola@gmail.com>, 2017
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-25 08:06+0000\n"
|
||||
"Last-Translator: Urtzi Odriozola <urtzi.odriozola@gmail.com>\n"
|
||||
"Language-Team: Basque (http://www.transifex.com/django/django/language/eu/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: eu\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr "GIS"
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr "Oinarrizko GIS eremua."
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
"Oinarrizko Geometria eremua -- OpenGIS zehaztapeneko Geomtry motarentzat "
|
||||
"mapak"
|
||||
|
||||
msgid "Point"
|
||||
msgstr "Puntua"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "Lerro string-a"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "Poligonoa"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "Puntu anitz"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "Lerro-anitzeko string-a"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "Multi poligonoa"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "Geometria bilduma"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr "Eremu agregatu hedatua"
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr "Raster eremua"
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "Ez fa geometria baliorik eman."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "Geometria balio okera."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "Geometria mota okerra."
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr "Errore bat gertatu da geometria bere form eremuaren SRIDra biurtzean."
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr "Ezabatu ezaugarri guztiak"
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr "WKT arazketa leihoa:"
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr "Arazketa leihoa (balio serializatua)"
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "Ez dago jariorik erregistratuta."
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "%r sluga ez dago erregistratuta."
|
||||
@@ -0,0 +1,90 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Ali Nikneshan <ali@nikneshan.com>, 2012-2013
|
||||
# Alireza Savand <alireza.savand@gmail.com>, 2013
|
||||
# Ali Vakilzade <ali.vakilzade@gmail.com>, 2015
|
||||
# Jannis Leidel <jannis@leidel.info>, 2011
|
||||
# Pouya Abbassi, 2016
|
||||
# Saeed <sd.javadi@gmail.com>, 2011
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-23 18:54+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Persian (http://www.transifex.com/django/django/language/"
|
||||
"fa/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: fa\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr "جیآیاس"
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr "پایهی سیستم اطلاعات جغرافیایی میدانی."
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr "پایهی فیلد هندسی --نوع خاص هندسهی نقشههای نوع OpenGIS"
|
||||
|
||||
msgid "Point"
|
||||
msgstr "نقطه"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "رشته خط"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "چندضلعی"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "چند نقطه ای"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "چند خط رشته"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "چندین چند ضلعی "
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "مجموعه هندسی"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr "تراکم وسعت حوزهی "
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr "حوزهی شطرنجی"
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "مقدار جغرافیاییای مقرر نشده است."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "مقدار جغرافیایی نامعتبر"
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "نوعِ جغرافیایی نامعتبر"
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr "مشکلی در هنگام انتقال مختصات هندسی از فیلد به SRID رخ داد."
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr "حذف همهی اشیاء"
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr "پنجرهی اشکال زدایی «متن قابل درک»"
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr "پنجرهی اشکال زدایی (ارزش متوالی)"
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "هیچ فیدی ثبت شده است."
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "Slug %r ثبت نشده"
|
||||
Binary file not shown.
@@ -0,0 +1,80 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-03-18 09:16+0100\n"
|
||||
"PO-Revision-Date: 2015-03-18 08:35+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Western Frisian (http://www.transifex.com/projects/p/django/"
|
||||
"language/fy/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: fy\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr ""
|
||||
|
||||
msgid "The base GIS field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
|
||||
msgid "Point"
|
||||
msgstr ""
|
||||
|
||||
msgid "Line string"
|
||||
msgstr ""
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr ""
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr ""
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Google Maps via GeoDjango"
|
||||
msgstr ""
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr ""
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr ""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,86 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# Chandan kumar <chandankumar.093047@gmail.com>, 2012
|
||||
# Sandeep Satavlekar <sandysat@gmail.com>, 2011
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-19 16:40+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Hindi (http://www.transifex.com/django/django/language/hi/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: hi\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr ""
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
|
||||
msgid "Point"
|
||||
msgstr "बिंदु"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "पंक्ति शृंखला"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "बहुभुज"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "बहु बिंदु"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "बहु - पंक्ति शृंखला"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "बहु बहुभुज"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "ज्यामिति संग्रह"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "कोई ज्यामिति मूल्य प्रदान नहीं की है."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "अवैध ज्यामिति मूल्य."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "अवैध ज्यामिति प्रकार"
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
"ज्यामिति को ज्यामिति प्रपत्र फ़ील्ड के SRID में परिवर्तित करते वक़्त एक ग़लती हो गयी ."
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr ""
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr ""
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "कोई फ़ीड पंजीकृत नहीं हैं."
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "स्लग %r पंजीकृत नहीं है."
|
||||
@@ -0,0 +1,91 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# berislavlopac <berislav.lopac@gmail.com>, 2012
|
||||
# Davor Lučić <r.dav.lc@gmail.com>, 2012
|
||||
# Filip Cuk <filipcuk2@gmail.com>, 2016
|
||||
# Jannis Leidel <jannis@leidel.info>, 2011
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-19 16:40+0000\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: Croatian (http://www.transifex.com/django/django/language/"
|
||||
"hr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: hr\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr ""
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
|
||||
msgid "Point"
|
||||
msgstr "Točka"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "Linija (Line string)"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "Poligon"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "Više točaka"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "Više linija (Line string)"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "Više poligona"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "Geometrijska kolekcija"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr ""
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "Geometrijska vrijednost nije priložena."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "Neispravna geometrijska vrijednost."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "Neispravan geometrijski tip."
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr ""
|
||||
"Došlo je do greške pri transformaciji geometrije na SRID geometrijskog polja "
|
||||
"forme."
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr "Izbriši sve značajke"
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr ""
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "Nema registriranih izvora."
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "Slug %r nije registriran."
|
||||
Binary file not shown.
@@ -0,0 +1,90 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
# András Veres-Szentkirályi, 2016
|
||||
# Jannis Leidel <jannis@leidel.info>, 2011
|
||||
# Kristóf Gruber <>, 2012
|
||||
# Szilveszter Farkas <szilveszter.farkas@gmail.com>, 2011
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2017-09-23 18:54+0000\n"
|
||||
"Last-Translator: János R (Hangya)\n"
|
||||
"Language-Team: Hungarian (http://www.transifex.com/django/django/language/"
|
||||
"hu/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: hu\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr "GIS"
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr "Az alap GIS mező."
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
"Az alap Geometry mező -- az OpenGIS specifikáció geometria típusának "
|
||||
"megfelelően"
|
||||
|
||||
msgid "Point"
|
||||
msgstr "Pont"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "Vonallánc"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "Poligon"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "Multi-pont"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "Többes vonallánc"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "Multi-poligon"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "Geometria gyűjtemény"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr "Terület összegző mező"
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr "Raszter mező"
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "Geometriai adat nem került megadásra."
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "Érvénytelen geometriai érték."
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "Érvénytelen geometriai típus."
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr "Hiba történt a geometriai transzformáció során."
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr "Minden Feature törlése"
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr "WKT debug ablak:"
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr "Debug ablak (szerializált érték)"
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "Nincs regisztrált feed."
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "%r slug nincs regisztrálva."
|
||||
Binary file not shown.
@@ -0,0 +1,86 @@
|
||||
# This file is distributed under the same license as the Django package.
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-01-19 16:49+0100\n"
|
||||
"PO-Revision-Date: 2018-11-01 20:36+0000\n"
|
||||
"Last-Translator: Ruben Harutyunov <rharutyunov@mail.ru>\n"
|
||||
"Language-Team: Armenian (http://www.transifex.com/django/django/language/"
|
||||
"hy/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: hy\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "GIS"
|
||||
msgstr "ԱՏՀ"
|
||||
|
||||
msgid "The base GIS field."
|
||||
msgstr "ԱՏՀ֊ի գլխավոր դաշտ"
|
||||
|
||||
msgid ""
|
||||
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
|
||||
msgstr ""
|
||||
"Հիմնական երկրաչափական դաշտը: Համապատասխանում է մուտքագրել OpenGIS "
|
||||
"սպեցիֆիկացիայի երկրաչափության տիպին։"
|
||||
|
||||
msgid "Point"
|
||||
msgstr "Կետ"
|
||||
|
||||
msgid "Line string"
|
||||
msgstr "Թեք"
|
||||
|
||||
msgid "Polygon"
|
||||
msgstr "Բազմանկյուն"
|
||||
|
||||
msgid "Multi-point"
|
||||
msgstr "Կետերի հավաքածու"
|
||||
|
||||
msgid "Multi-line string"
|
||||
msgstr "Թեքերի հավաքածու"
|
||||
|
||||
msgid "Multi polygon"
|
||||
msgstr "Բազմանկյունների հավաքածու"
|
||||
|
||||
msgid "Geometry collection"
|
||||
msgstr "Երկրաչափական օբյեկտների հավաքածու"
|
||||
|
||||
msgid "Extent Aggregate Field"
|
||||
msgstr "Մակերեսի կամ ծավալի խմբավորման դաշտ"
|
||||
|
||||
msgid "Raster Field"
|
||||
msgstr "Պատկերացանցի դաշտ"
|
||||
|
||||
msgid "No geometry value provided."
|
||||
msgstr "Երկարաչափական արժեք չի տրամադրվել։"
|
||||
|
||||
msgid "Invalid geometry value."
|
||||
msgstr "Երկարաչափական օյբեկտի սխալ արժեք։"
|
||||
|
||||
msgid "Invalid geometry type."
|
||||
msgstr "Երկարաչափական օյբեկտի տիպ։"
|
||||
|
||||
msgid ""
|
||||
"An error occurred when transforming the geometry to the SRID of the geometry "
|
||||
"form field."
|
||||
msgstr "Երկարաչափական օյբեկտի SRID ձևափոխելու ժամանակ առաջացել է սխալլ։"
|
||||
|
||||
msgid "Delete all Features"
|
||||
msgstr "Հեռացնել բոլոր օբյեկտները"
|
||||
|
||||
msgid "WKT debugging window:"
|
||||
msgstr "WKT֊ի կարգաբերման պատուհան․"
|
||||
|
||||
msgid "Debugging window (serialized value)"
|
||||
msgstr "Կարգաբերման պատուհան (սերիալիզացված արժեքներ)"
|
||||
|
||||
msgid "No feeds are registered."
|
||||
msgstr "Չկան գրանցված feed֊եր։"
|
||||
|
||||
#, python-format
|
||||
msgid "Slug %r isn't registered."
|
||||
msgstr "%r Slug֊ը գրանցված չէ։"
|
||||
Binary file not shown.
Loaded 100 of 2098 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user