This commit is contained in:
Mai Gillmann
2019-12-17 14:09:10 +01:00
parent 66e908fc8a
commit 4791d00a43
2122 changed files with 423791 additions and 0 deletions
@@ -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',
]
@@ -0,0 +1,19 @@
class WKTAdapter:
"""
An adaptor for Geometries sent to the MySQL and Oracle database backends.
"""
def __init__(self, geom):
self.wkt = geom.wkt
self.srid = geom.srid
def __eq__(self, other):
return (
isinstance(other, WKTAdapter) and
self.wkt == other.wkt and self.srid == other.srid
)
def __hash__(self):
return hash((self.wkt, self.srid))
def __str__(self):
return self.wkt
@@ -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,16 @@
from django.db.backends.mysql.base import (
DatabaseWrapper as MySQLDatabaseWrapper,
)
from .features import DatabaseFeatures
from .introspection import MySQLIntrospection
from .operations import MySQLOperations
from .schema import MySQLGISSchemaEditor
class DatabaseWrapper(MySQLDatabaseWrapper):
SchemaEditorClass = MySQLGISSchemaEditor
# Classes instantiated in __init__().
features_class = DatabaseFeatures
introspection_class = MySQLIntrospection
ops_class = MySQLOperations
@@ -0,0 +1,38 @@
from MySQLdb.constants import FIELD_TYPE
from django.contrib.gis.gdal import OGRGeomType
from django.db.backends.mysql.introspection import DatabaseIntrospection
class MySQLIntrospection(DatabaseIntrospection):
# Updating the data_types_reverse dictionary with the appropriate
# type for Geometry fields.
data_types_reverse = DatabaseIntrospection.data_types_reverse.copy()
data_types_reverse[FIELD_TYPE.GEOMETRY] = 'GeometryField'
def get_geometry_type(self, table_name, geo_col):
with self.connection.cursor() as cursor:
# In order to get the specific geometry type of the field,
# we introspect on the table definition using `DESCRIBE`.
cursor.execute('DESCRIBE %s' %
self.connection.ops.quote_name(table_name))
# Increment over description info until we get to the geometry
# column.
for column, typ, null, key, default, extra in cursor.fetchall():
if column == geo_col:
# Using OGRGeomType to convert from OGC name to Django field.
# MySQL does not support 3D or SRIDs, so the field params
# are empty.
field_type = OGRGeomType(typ).django
field_params = {}
break
return field_type, field_params
def supports_spatial_index(self, cursor, table_name):
# Supported with MyISAM/Aria, or InnoDB on MySQL 5.7.5+/MariaDB 10.2.2+
storage_engine = self.get_storage_engine(cursor, table_name)
if storage_engine == 'InnoDB':
return self.connection.mysql_version >= (
(10, 2, 2) if self.connection.mysql_is_mariadb else (5, 7, 5)
)
return storage_engine in ('MyISAM', 'Aria')
@@ -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,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,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,74 @@
from ctypes.util import find_library
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.db.backends.sqlite3.base import (
DatabaseWrapper as SQLiteDatabaseWrapper,
)
from .client import SpatiaLiteClient
from .features import DatabaseFeatures
from .introspection import SpatiaLiteIntrospection
from .operations import SpatiaLiteOperations
from .schema import SpatialiteSchemaEditor
class DatabaseWrapper(SQLiteDatabaseWrapper):
SchemaEditorClass = SpatialiteSchemaEditor
# Classes instantiated in __init__().
client_class = SpatiaLiteClient
features_class = DatabaseFeatures
introspection_class = SpatiaLiteIntrospection
ops_class = SpatiaLiteOperations
def __init__(self, *args, **kwargs):
# Trying to find the location of the SpatiaLite library.
# Here we are figuring out the path to the SpatiaLite library
# (`libspatialite`). If it's not in the system library path (e.g., it
# cannot be found by `ctypes.util.find_library`), then it may be set
# manually in the settings via the `SPATIALITE_LIBRARY_PATH` setting.
self.lib_spatialite_paths = [name for name in [
getattr(settings, 'SPATIALITE_LIBRARY_PATH', None),
'mod_spatialite.so',
'mod_spatialite',
find_library('spatialite'),
] if name is not None]
super().__init__(*args, **kwargs)
def get_new_connection(self, conn_params):
conn = super().get_new_connection(conn_params)
# Enabling extension loading on the SQLite connection.
try:
conn.enable_load_extension(True)
except AttributeError:
raise ImproperlyConfigured(
'SpatiaLite requires SQLite to be configured to allow '
'extension loading.'
)
# Load the SpatiaLite library extension on the connection.
for path in self.lib_spatialite_paths:
try:
conn.load_extension(path)
except Exception:
if getattr(settings, 'SPATIALITE_LIBRARY_PATH', None):
raise ImproperlyConfigured(
'Unable to load the SpatiaLite library extension '
'as specified in your SPATIALITE_LIBRARY_PATH setting.'
)
continue
else:
break
else:
raise ImproperlyConfigured(
'Unable to load the SpatiaLite library extension. '
'Library names tried: %s' % ', '.join(self.lib_spatialite_paths)
)
return conn
def prepare_database(self):
super().prepare_database()
# Check if spatial metadata have been initialized in the database
with self.cursor() as cursor:
cursor.execute("PRAGMA table_info(geometry_columns);")
if cursor.fetchall() == []:
cursor.execute("SELECT InitSpatialMetaData(1)")
@@ -0,0 +1,5 @@
from django.db.backends.sqlite3.client import DatabaseClient
class SpatiaLiteClient(DatabaseClient):
executable_name = 'spatialite'
@@ -0,0 +1,13 @@
from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures
from django.db.backends.sqlite3.features import (
DatabaseFeatures as SQLiteDatabaseFeatures,
)
from django.utils.functional import cached_property
class DatabaseFeatures(BaseSpatialFeatures, SQLiteDatabaseFeatures):
supports_3d_storage = True
@cached_property
def supports_area_geodetic(self):
return bool(self.connection.ops.lwgeom_version())
@@ -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
@@ -0,0 +1,381 @@
from collections import defaultdict, namedtuple
from django.contrib.gis import forms, gdal
from django.contrib.gis.db.models.proxy import SpatialProxy
from django.contrib.gis.gdal.error import GDALException
from django.contrib.gis.geos import (
GeometryCollection, GEOSException, GEOSGeometry, LineString,
MultiLineString, MultiPoint, MultiPolygon, Point, Polygon,
)
from django.core.exceptions import ImproperlyConfigured
from django.db.models.fields import Field
from django.utils.translation import gettext_lazy as _
# Local cache of the spatial_ref_sys table, which holds SRID data for each
# spatial database alias. This cache exists so that the database isn't queried
# for SRID info each time a distance query is constructed.
_srid_cache = defaultdict(dict)
SRIDCacheEntry = namedtuple('SRIDCacheEntry', ['units', 'units_name', 'spheroid', 'geodetic'])
def get_srid_info(srid, connection):
"""
Return the units, unit name, and spheroid WKT associated with the
given SRID from the `spatial_ref_sys` (or equivalent) spatial database
table for the given database connection. These results are cached.
"""
from django.contrib.gis.gdal import SpatialReference
global _srid_cache
try:
# The SpatialRefSys model for the spatial backend.
SpatialRefSys = connection.ops.spatial_ref_sys()
except NotImplementedError:
SpatialRefSys = None
alias, get_srs = (
(connection.alias, lambda srid: SpatialRefSys.objects.using(connection.alias).get(srid=srid).srs)
if SpatialRefSys else
(None, SpatialReference)
)
if srid not in _srid_cache[alias]:
srs = get_srs(srid)
units, units_name = srs.units
_srid_cache[alias][srid] = SRIDCacheEntry(
units=units,
units_name=units_name,
spheroid='SPHEROID["%s",%s,%s]' % (srs['spheroid'], srs.semi_major, srs.inverse_flattening),
geodetic=srs.geographic,
)
return _srid_cache[alias][srid]
class BaseSpatialField(Field):
"""
The Base GIS Field.
It's used as a base class for GeometryField and RasterField. Defines
properties that are common to all GIS fields such as the characteristics
of the spatial reference system of the field.
"""
description = _("The base GIS field.")
empty_strings_allowed = False
def __init__(self, verbose_name=None, srid=4326, spatial_index=True, **kwargs):
"""
The initialization function for base spatial fields. Takes the following
as keyword arguments:
srid:
The spatial reference system identifier, an OGC standard.
Defaults to 4326 (WGS84).
spatial_index:
Indicates whether to create a spatial index. Defaults to True.
Set this instead of 'db_index' for geographic fields since index
creation is different for geometry columns.
"""
# Setting the index flag with the value of the `spatial_index` keyword.
self.spatial_index = spatial_index
# Setting the SRID and getting the units. Unit information must be
# easily available in the field instance for distance queries.
self.srid = srid
# Setting the verbose_name keyword argument with the positional
# first parameter, so this works like normal fields.
kwargs['verbose_name'] = verbose_name
super().__init__(**kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
# Always include SRID for less fragility; include spatial index if it's
# not the default value.
kwargs['srid'] = self.srid
if self.spatial_index is not True:
kwargs['spatial_index'] = self.spatial_index
return name, path, args, kwargs
def db_type(self, connection):
return connection.ops.geo_db_type(self)
def spheroid(self, connection):
return get_srid_info(self.srid, connection).spheroid
def units(self, connection):
return get_srid_info(self.srid, connection).units
def units_name(self, connection):
return get_srid_info(self.srid, connection).units_name
def geodetic(self, connection):
"""
Return true if this field's SRID corresponds with a coordinate
system that uses non-projected units (e.g., latitude/longitude).
"""
return get_srid_info(self.srid, connection).geodetic
def get_placeholder(self, value, compiler, connection):
"""
Return the placeholder for the spatial column for the
given value.
"""
return connection.ops.get_geom_placeholder(self, value, compiler)
def get_srid(self, obj):
"""
Return the default SRID for the given geometry or raster, taking into
account the SRID set for the field. For example, if the input geometry
or raster doesn't have an SRID, then the SRID of the field will be
returned.
"""
srid = obj.srid # SRID of given geometry.
if srid is None or self.srid == -1 or (srid == -1 and self.srid != -1):
return self.srid
else:
return srid
def get_db_prep_value(self, value, connection, *args, **kwargs):
if value is None:
return None
return connection.ops.Adapter(
super().get_db_prep_value(value, connection, *args, **kwargs),
**({'geography': True} if self.geography and connection.ops.geography else {})
)
def get_raster_prep_value(self, value, is_candidate):
"""
Return a GDALRaster if conversion is successful, otherwise return None.
"""
if isinstance(value, gdal.GDALRaster):
return value
elif is_candidate:
try:
return gdal.GDALRaster(value)
except GDALException:
pass
elif isinstance(value, dict):
try:
return gdal.GDALRaster(value)
except GDALException:
raise ValueError("Couldn't create spatial object from lookup value '%s'." % value)
def get_prep_value(self, value):
obj = super().get_prep_value(value)
if obj is None:
return None
# When the input is not a geometry or raster, attempt to construct one
# from the given string input.
if isinstance(obj, GEOSGeometry):
pass
else:
# Check if input is a candidate for conversion to raster or geometry.
is_candidate = isinstance(obj, (bytes, str)) or hasattr(obj, '__geo_interface__')
# Try to convert the input to raster.
raster = self.get_raster_prep_value(obj, is_candidate)
if raster:
obj = raster
elif is_candidate:
try:
obj = GEOSGeometry(obj)
except (GEOSException, GDALException):
raise ValueError("Couldn't create spatial object from lookup value '%s'." % obj)
else:
raise ValueError('Cannot use object with type %s for a spatial lookup parameter.' % type(obj).__name__)
# Assigning the SRID value.
obj.srid = self.get_srid(obj)
return obj
class GeometryField(BaseSpatialField):
"""
The base Geometry field -- maps to the OpenGIS Specification Geometry type.
"""
description = _("The base Geometry field -- maps to the OpenGIS Specification Geometry type.")
form_class = forms.GeometryField
# The OpenGIS Geometry name.
geom_type = 'GEOMETRY'
geom_class = None
def __init__(self, verbose_name=None, dim=2, geography=False, *, extent=(-180.0, -90.0, 180.0, 90.0),
tolerance=0.05, **kwargs):
"""
The initialization function for geometry fields. In addition to the
parameters from BaseSpatialField, it takes the following as keyword
arguments:
dim:
The number of dimensions for this geometry. Defaults to 2.
extent:
Customize the extent, in a 4-tuple of WGS 84 coordinates, for the
geometry field entry in the `USER_SDO_GEOM_METADATA` table. Defaults
to (-180.0, -90.0, 180.0, 90.0).
tolerance:
Define the tolerance, in meters, to use for the geometry field
entry in the `USER_SDO_GEOM_METADATA` table. Defaults to 0.05.
"""
# Setting the dimension of the geometry field.
self.dim = dim
# Is this a geography rather than a geometry column?
self.geography = geography
# Oracle-specific private attributes for creating the entry in
# `USER_SDO_GEOM_METADATA`
self._extent = extent
self._tolerance = tolerance
super().__init__(verbose_name=verbose_name, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
# Include kwargs if they're not the default values.
if self.dim != 2:
kwargs['dim'] = self.dim
if self.geography is not False:
kwargs['geography'] = self.geography
if self._extent != (-180.0, -90.0, 180.0, 90.0):
kwargs['extent'] = self._extent
if self._tolerance != 0.05:
kwargs['tolerance'] = self._tolerance
return name, path, args, kwargs
def contribute_to_class(self, cls, name, **kwargs):
super().contribute_to_class(cls, name, **kwargs)
# Setup for lazy-instantiated Geometry object.
setattr(cls, self.attname, SpatialProxy(self.geom_class or GEOSGeometry, self, load_func=GEOSGeometry))
def formfield(self, **kwargs):
defaults = {
'form_class': self.form_class,
'geom_type': self.geom_type,
'srid': self.srid,
**kwargs,
}
if self.dim > 2 and not getattr(defaults['form_class'].widget, 'supports_3d', False):
defaults.setdefault('widget', forms.Textarea)
return super().formfield(**defaults)
def select_format(self, compiler, sql, params):
"""
Return the selection format string, depending on the requirements
of the spatial backend. For example, Oracle and MySQL require custom
selection formats in order to retrieve geometries in OGC WKB.
"""
return compiler.connection.ops.select % sql, params
# The OpenGIS Geometry Type Fields
class PointField(GeometryField):
geom_type = 'POINT'
geom_class = Point
form_class = forms.PointField
description = _("Point")
class LineStringField(GeometryField):
geom_type = 'LINESTRING'
geom_class = LineString
form_class = forms.LineStringField
description = _("Line string")
class PolygonField(GeometryField):
geom_type = 'POLYGON'
geom_class = Polygon
form_class = forms.PolygonField
description = _("Polygon")
class MultiPointField(GeometryField):
geom_type = 'MULTIPOINT'
geom_class = MultiPoint
form_class = forms.MultiPointField
description = _("Multi-point")
class MultiLineStringField(GeometryField):
geom_type = 'MULTILINESTRING'
geom_class = MultiLineString
form_class = forms.MultiLineStringField
description = _("Multi-line string")
class MultiPolygonField(GeometryField):
geom_type = 'MULTIPOLYGON'
geom_class = MultiPolygon
form_class = forms.MultiPolygonField
description = _("Multi polygon")
class GeometryCollectionField(GeometryField):
geom_type = 'GEOMETRYCOLLECTION'
geom_class = GeometryCollection
form_class = forms.GeometryCollectionField
description = _("Geometry collection")
class ExtentField(Field):
"Used as a return value from an extent aggregate"
description = _("Extent Aggregate Field")
def get_internal_type(self):
return "ExtentField"
def select_format(self, compiler, sql, params):
select = compiler.connection.ops.select_extent
return select % sql if select else sql, params
class RasterField(BaseSpatialField):
"""
Raster field for GeoDjango -- evaluates into GDALRaster objects.
"""
description = _("Raster Field")
geom_type = 'RASTER'
geography = False
def _check_connection(self, connection):
# Make sure raster fields are used only on backends with raster support.
if not connection.features.gis_enabled or not connection.features.supports_raster:
raise ImproperlyConfigured('Raster fields require backends with raster support.')
def db_type(self, connection):
self._check_connection(connection)
return super().db_type(connection)
def from_db_value(self, value, expression, connection):
return connection.ops.parse_raster(value)
def contribute_to_class(self, cls, name, **kwargs):
super().contribute_to_class(cls, name, **kwargs)
# Setup for lazy-instantiated Raster object. For large querysets, the
# instantiation of all GDALRasters can potentially be expensive. This
# delays the instantiation of the objects to the moment of evaluation
# of the raster attribute.
setattr(cls, self.attname, SpatialProxy(gdal.GDALRaster, self))
def get_transform(self, name):
from django.contrib.gis.db.models.lookups import RasterBandTransform
try:
band_index = int(name)
return type(
'SpecificRasterBandTransform',
(RasterBandTransform,),
{'band_index': band_index}
)
except ValueError:
pass
return super().get_transform(name)
@@ -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,69 @@
"""
This module holds simple classes to convert geospatial values from the
database.
"""
from decimal import Decimal
from django.contrib.gis.measure import Area, Distance
from django.db import models
class AreaField(models.FloatField):
"Wrapper for Area values."
def __init__(self, geo_field):
super().__init__()
self.geo_field = geo_field
def get_prep_value(self, value):
if not isinstance(value, Area):
raise ValueError('AreaField only accepts Area measurement objects.')
return value
def get_db_prep_value(self, value, connection, prepared=False):
if value is None:
return
area_att = connection.ops.get_area_att_for_field(self.geo_field)
return getattr(value, area_att) if area_att else value
def from_db_value(self, value, expression, connection):
if value is None:
return
# If the database returns a Decimal, convert it to a float as expected
# by the Python geometric objects.
if isinstance(value, Decimal):
value = float(value)
# If the units are known, convert value into area measure.
area_att = connection.ops.get_area_att_for_field(self.geo_field)
return Area(**{area_att: value}) if area_att else value
def get_internal_type(self):
return 'AreaField'
class DistanceField(models.FloatField):
"Wrapper for Distance values."
def __init__(self, geo_field):
super().__init__()
self.geo_field = geo_field
def get_prep_value(self, value):
if isinstance(value, Distance):
return value
return super().get_prep_value(value)
def get_db_prep_value(self, value, connection, prepared=False):
if not isinstance(value, Distance):
return value
distance_att = connection.ops.get_distance_att_for_field(self.geo_field)
if not distance_att:
raise ValueError('Distance measure is supplied, but units are unknown for result.')
return getattr(value, distance_att)
def from_db_value(self, value, expression, connection):
if value is None:
return
distance_att = connection.ops.get_distance_att_for_field(self.geo_field)
return Distance(**{distance_att: value}) if distance_att else value
def get_internal_type(self):
return 'DistanceField'
@@ -0,0 +1,132 @@
from django import forms
from django.contrib.gis.gdal import GDALException
from django.contrib.gis.geos import GEOSException, GEOSGeometry
from django.utils.translation import gettext_lazy as _
from .widgets import OpenLayersWidget
class GeometryField(forms.Field):
"""
This is the basic form field for a Geometry. Any textual input that is
accepted by GEOSGeometry is accepted by this form. By default,
this includes WKT, HEXEWKB, WKB (in a buffer), and GeoJSON.
"""
widget = OpenLayersWidget
geom_type = 'GEOMETRY'
default_error_messages = {
'required': _('No geometry value provided.'),
'invalid_geom': _('Invalid geometry value.'),
'invalid_geom_type': _('Invalid geometry type.'),
'transform_error': _('An error occurred when transforming the geometry '
'to the SRID of the geometry form field.'),
}
def __init__(self, *, srid=None, geom_type=None, **kwargs):
self.srid = srid
if geom_type is not None:
self.geom_type = geom_type
super().__init__(**kwargs)
self.widget.attrs['geom_type'] = self.geom_type
def to_python(self, value):
"""Transform the value to a Geometry object."""
if value in self.empty_values:
return None
if not isinstance(value, GEOSGeometry):
if hasattr(self.widget, 'deserialize'):
try:
value = self.widget.deserialize(value)
except GDALException:
value = None
else:
try:
value = GEOSGeometry(value)
except (GEOSException, ValueError, TypeError):
value = None
if value is None:
raise forms.ValidationError(self.error_messages['invalid_geom'], code='invalid_geom')
# Try to set the srid
if not value.srid:
try:
value.srid = self.widget.map_srid
except AttributeError:
if self.srid:
value.srid = self.srid
return value
def clean(self, value):
"""
Validate that the input value can be converted to a Geometry object
and return it. Raise a ValidationError if the value cannot be
instantiated as a Geometry.
"""
geom = super().clean(value)
if geom is None:
return geom
# Ensuring that the geometry is of the correct type (indicated
# using the OGC string label).
if str(geom.geom_type).upper() != self.geom_type and not self.geom_type == 'GEOMETRY':
raise forms.ValidationError(self.error_messages['invalid_geom_type'], code='invalid_geom_type')
# Transforming the geometry if the SRID was set.
if self.srid and self.srid != -1 and self.srid != geom.srid:
try:
geom.transform(self.srid)
except GEOSException:
raise forms.ValidationError(
self.error_messages['transform_error'], code='transform_error')
return geom
def has_changed(self, initial, data):
""" Compare geographic value of data with its initial value. """
try:
data = self.to_python(data)
initial = self.to_python(initial)
except forms.ValidationError:
return True
# Only do a geographic comparison if both values are available
if initial and data:
data.transform(initial.srid)
# If the initial value was not added by the browser, the geometry
# provided may be slightly different, the first time it is saved.
# The comparison is done with a very low tolerance.
return not initial.equals_exact(data, tolerance=0.000001)
else:
# Check for change of state of existence
return bool(initial) != bool(data)
class GeometryCollectionField(GeometryField):
geom_type = 'GEOMETRYCOLLECTION'
class PointField(GeometryField):
geom_type = 'POINT'
class MultiPointField(GeometryField):
geom_type = 'MULTIPOINT'
class LineStringField(GeometryField):
geom_type = 'LINESTRING'
class MultiLineStringField(GeometryField):
geom_type = 'MULTILINESTRING'
class PolygonField(GeometryField):
geom_type = 'POLYGON'
class MultiPolygonField(GeometryField):
geom_type = 'MULTIPOLYGON'
@@ -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,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,715 @@
"""
The OGRGeometry is a wrapper for using the OGR Geometry class
(see https://www.gdal.org/classOGRGeometry.html). OGRGeometry
may be instantiated when reading geometries from OGR Data Sources
(e.g. SHP files), or when given OGC WKT (a string).
While the 'full' API is not present yet, the API is "pythonic" unlike
the traditional and "next-generation" OGR Python bindings. One major
advantage OGR Geometries have over their GEOS counterparts is support
for spatial reference systems and their transformation.
Example:
>>> from django.contrib.gis.gdal import OGRGeometry, OGRGeomType, SpatialReference
>>> wkt1, wkt2 = 'POINT(-90 30)', 'POLYGON((0 0, 5 0, 5 5, 0 5)'
>>> pnt = OGRGeometry(wkt1)
>>> print(pnt)
POINT (-90 30)
>>> mpnt = OGRGeometry(OGRGeomType('MultiPoint'), SpatialReference('WGS84'))
>>> mpnt.add(wkt1)
>>> mpnt.add(wkt1)
>>> print(mpnt)
MULTIPOINT (-90 30,-90 30)
>>> print(mpnt.srs.name)
WGS 84
>>> print(mpnt.srs.proj)
+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs
>>> mpnt.transform(SpatialReference('NAD27'))
>>> print(mpnt.proj)
+proj=longlat +ellps=clrk66 +datum=NAD27 +no_defs
>>> print(mpnt)
MULTIPOINT (-89.999930378602485 29.999797886557641,-89.999930378602485 29.999797886557641)
The OGRGeomType class is to make it easy to specify an OGR geometry type:
>>> from django.contrib.gis.gdal import OGRGeomType
>>> gt1 = OGRGeomType(3) # Using an integer for the type
>>> gt2 = OGRGeomType('Polygon') # Using a string
>>> gt3 = OGRGeomType('POLYGON') # It's case-insensitive
>>> print(gt1 == 3, gt1 == 'Polygon') # Equivalence works w/non-OGRGeomType objects
True True
"""
import sys
from binascii import b2a_hex
from ctypes import byref, c_char_p, c_double, c_ubyte, c_void_p, string_at
from django.contrib.gis.gdal.base import GDALBase
from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope
from django.contrib.gis.gdal.error import GDALException, SRSException
from django.contrib.gis.gdal.geomtype import OGRGeomType
from django.contrib.gis.gdal.libgdal import GDAL_VERSION
from django.contrib.gis.gdal.prototypes import geom as capi, srs as srs_api
from django.contrib.gis.gdal.srs import CoordTransform, SpatialReference
from django.contrib.gis.geometry import hex_regex, json_regex, wkt_regex
from django.utils.encoding import force_bytes
# For more information, see the OGR C API source code:
# https://www.gdal.org/ogr__api_8h.html
#
# The OGR_G_* routines are relevant here.
class OGRGeometry(GDALBase):
"""Encapsulate an OGR geometry."""
destructor = capi.destroy_geom
def __init__(self, geom_input, srs=None):
"""Initialize Geometry on either WKT or an OGR pointer as input."""
str_instance = isinstance(geom_input, str)
# If HEX, unpack input to a binary buffer.
if str_instance and hex_regex.match(geom_input):
geom_input = memoryview(bytes.fromhex(geom_input))
str_instance = False
# Constructing the geometry,
if str_instance:
wkt_m = wkt_regex.match(geom_input)
json_m = json_regex.match(geom_input)
if wkt_m:
if wkt_m.group('srid'):
# If there's EWKT, set the SRS w/value of the SRID.
srs = int(wkt_m.group('srid'))
if wkt_m.group('type').upper() == 'LINEARRING':
# OGR_G_CreateFromWkt doesn't work with LINEARRING WKT.
# See https://trac.osgeo.org/gdal/ticket/1992.
g = capi.create_geom(OGRGeomType(wkt_m.group('type')).num)
capi.import_wkt(g, byref(c_char_p(wkt_m.group('wkt').encode())))
else:
g = capi.from_wkt(byref(c_char_p(wkt_m.group('wkt').encode())), None, byref(c_void_p()))
elif json_m:
g = self._from_json(geom_input.encode())
else:
# Seeing if the input is a valid short-hand string
# (e.g., 'Point', 'POLYGON').
OGRGeomType(geom_input)
g = capi.create_geom(OGRGeomType(geom_input).num)
elif isinstance(geom_input, memoryview):
# WKB was passed in
g = self._from_wkb(geom_input)
elif isinstance(geom_input, OGRGeomType):
# OGRGeomType was passed in, an empty geometry will be created.
g = capi.create_geom(geom_input.num)
elif isinstance(geom_input, self.ptr_type):
# OGR pointer (c_void_p) was the input.
g = geom_input
else:
raise GDALException('Invalid input type for OGR Geometry construction: %s' % type(geom_input))
# Now checking the Geometry pointer before finishing initialization
# by setting the pointer for the object.
if not g:
raise GDALException('Cannot create OGR Geometry from input: %s' % geom_input)
self.ptr = g
# Assigning the SpatialReference object to the geometry, if valid.
if srs:
self.srs = srs
# Setting the class depending upon the OGR Geometry Type
self.__class__ = GEO_CLASSES[self.geom_type.num]
# Pickle routines
def __getstate__(self):
srs = self.srs
if srs:
srs = srs.wkt
else:
srs = None
return bytes(self.wkb), srs
def __setstate__(self, state):
wkb, srs = state
ptr = capi.from_wkb(wkb, None, byref(c_void_p()), len(wkb))
if not ptr:
raise GDALException('Invalid OGRGeometry loaded from pickled state.')
self.ptr = ptr
self.srs = srs
@classmethod
def _from_wkb(cls, geom_input):
return capi.from_wkb(bytes(geom_input), None, byref(c_void_p()), len(geom_input))
@staticmethod
def _from_json(geom_input):
ptr = capi.from_json(geom_input)
if GDAL_VERSION < (2, 0):
try:
capi.get_geom_srs(ptr)
except SRSException:
srs = SpatialReference(4326)
capi.assign_srs(ptr, srs.ptr)
return ptr
@classmethod
def from_bbox(cls, bbox):
"Construct a Polygon from a bounding box (4-tuple)."
x0, y0, x1, y1 = bbox
return OGRGeometry('POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % (
x0, y0, x0, y1, x1, y1, x1, y0, x0, y0))
@staticmethod
def from_json(geom_input):
return OGRGeometry(OGRGeometry._from_json(force_bytes(geom_input)))
@classmethod
def from_gml(cls, gml_string):
return cls(capi.from_gml(force_bytes(gml_string)))
# ### Geometry set-like operations ###
# g = g1 | g2
def __or__(self, other):
"Return the union of the two geometries."
return self.union(other)
# g = g1 & g2
def __and__(self, other):
"Return the intersection of this Geometry and the other."
return self.intersection(other)
# g = g1 - g2
def __sub__(self, other):
"Return the difference this Geometry and the other."
return self.difference(other)
# g = g1 ^ g2
def __xor__(self, other):
"Return the symmetric difference of this Geometry and the other."
return self.sym_difference(other)
def __eq__(self, other):
"Is this Geometry equal to the other?"
return isinstance(other, OGRGeometry) and self.equals(other)
def __str__(self):
"WKT is used for the string representation."
return self.wkt
# #### Geometry Properties ####
@property
def dimension(self):
"Return 0 for points, 1 for lines, and 2 for surfaces."
return capi.get_dims(self.ptr)
def _get_coord_dim(self):
"Return the coordinate dimension of the Geometry."
return capi.get_coord_dim(self.ptr)
def _set_coord_dim(self, dim):
"Set the coordinate dimension of this Geometry."
if dim not in (2, 3):
raise ValueError('Geometry dimension must be either 2 or 3')
capi.set_coord_dim(self.ptr, dim)
coord_dim = property(_get_coord_dim, _set_coord_dim)
@property
def geom_count(self):
"Return the number of elements in this Geometry."
return capi.get_geom_count(self.ptr)
@property
def point_count(self):
"Return the number of Points in this Geometry."
return capi.get_point_count(self.ptr)
@property
def num_points(self):
"Alias for `point_count` (same name method in GEOS API.)"
return self.point_count
@property
def num_coords(self):
"Alias for `point_count`."
return self.point_count
@property
def geom_type(self):
"Return the Type for this Geometry."
return OGRGeomType(capi.get_geom_type(self.ptr))
@property
def geom_name(self):
"Return the Name of this Geometry."
return capi.get_geom_name(self.ptr)
@property
def area(self):
"Return the area for a LinearRing, Polygon, or MultiPolygon; 0 otherwise."
return capi.get_area(self.ptr)
@property
def envelope(self):
"Return the envelope for this Geometry."
# TODO: Fix Envelope() for Point geometries.
return Envelope(capi.get_envelope(self.ptr, byref(OGREnvelope())))
@property
def empty(self):
return capi.is_empty(self.ptr)
@property
def extent(self):
"Return the envelope as a 4-tuple, instead of as an Envelope object."
return self.envelope.tuple
# #### SpatialReference-related Properties ####
# The SRS property
def _get_srs(self):
"Return the Spatial Reference for this Geometry."
try:
srs_ptr = capi.get_geom_srs(self.ptr)
return SpatialReference(srs_api.clone_srs(srs_ptr))
except SRSException:
return None
def _set_srs(self, srs):
"Set the SpatialReference for this geometry."
# Do not have to clone the `SpatialReference` object pointer because
# when it is assigned to this `OGRGeometry` it's internal OGR
# reference count is incremented, and will likewise be released
# (decremented) when this geometry's destructor is called.
if isinstance(srs, SpatialReference):
srs_ptr = srs.ptr
elif isinstance(srs, (int, str)):
sr = SpatialReference(srs)
srs_ptr = sr.ptr
elif srs is None:
srs_ptr = None
else:
raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs))
capi.assign_srs(self.ptr, srs_ptr)
srs = property(_get_srs, _set_srs)
# The SRID property
def _get_srid(self):
srs = self.srs
if srs:
return srs.srid
return None
def _set_srid(self, srid):
if isinstance(srid, int) or srid is None:
self.srs = srid
else:
raise TypeError('SRID must be set with an integer.')
srid = property(_get_srid, _set_srid)
# #### Output Methods ####
def _geos_ptr(self):
from django.contrib.gis.geos import GEOSGeometry
return GEOSGeometry._from_wkb(self.wkb)
@property
def geos(self):
"Return a GEOSGeometry object from this OGRGeometry."
from django.contrib.gis.geos import GEOSGeometry
return GEOSGeometry(self._geos_ptr(), self.srid)
@property
def gml(self):
"Return the GML representation of the Geometry."
return capi.to_gml(self.ptr)
@property
def hex(self):
"Return the hexadecimal representation of the WKB (a string)."
return b2a_hex(self.wkb).upper()
@property
def json(self):
"""
Return the GeoJSON representation of this Geometry.
"""
return capi.to_json(self.ptr)
geojson = json
@property
def kml(self):
"Return the KML representation of the Geometry."
return capi.to_kml(self.ptr, None)
@property
def wkb_size(self):
"Return the size of the WKB buffer."
return capi.get_wkbsize(self.ptr)
@property
def wkb(self):
"Return the WKB representation of the Geometry."
if sys.byteorder == 'little':
byteorder = 1 # wkbNDR (from ogr_core.h)
else:
byteorder = 0 # wkbXDR
sz = self.wkb_size
# Creating the unsigned character buffer, and passing it in by reference.
buf = (c_ubyte * sz)()
capi.to_wkb(self.ptr, byteorder, byref(buf))
# Returning a buffer of the string at the pointer.
return memoryview(string_at(buf, sz))
@property
def wkt(self):
"Return the WKT representation of the Geometry."
return capi.to_wkt(self.ptr, byref(c_char_p()))
@property
def ewkt(self):
"Return the EWKT representation of the Geometry."
srs = self.srs
if srs and srs.srid:
return 'SRID=%s;%s' % (srs.srid, self.wkt)
else:
return self.wkt
# #### Geometry Methods ####
def clone(self):
"Clone this OGR Geometry."
return OGRGeometry(capi.clone_geom(self.ptr), self.srs)
def close_rings(self):
"""
If there are any rings within this geometry that have not been
closed, this routine will do so by adding the starting point at the
end.
"""
# Closing the open rings.
capi.geom_close_rings(self.ptr)
def transform(self, coord_trans, clone=False):
"""
Transform this geometry to a different spatial reference system.
May take a CoordTransform object, a SpatialReference object, string
WKT or PROJ.4, and/or an integer SRID. By default, return nothing
and transform the geometry in-place. However, if the `clone` keyword is
set, return a transformed clone of this geometry.
"""
if clone:
klone = self.clone()
klone.transform(coord_trans)
return klone
# Depending on the input type, use the appropriate OGR routine
# to perform the transformation.
if isinstance(coord_trans, CoordTransform):
capi.geom_transform(self.ptr, coord_trans.ptr)
elif isinstance(coord_trans, SpatialReference):
capi.geom_transform_to(self.ptr, coord_trans.ptr)
elif isinstance(coord_trans, (int, str)):
sr = SpatialReference(coord_trans)
capi.geom_transform_to(self.ptr, sr.ptr)
else:
raise TypeError('Transform only accepts CoordTransform, '
'SpatialReference, string, and integer objects.')
# #### Topology Methods ####
def _topology(self, func, other):
"""A generalized function for topology operations, takes a GDAL function and
the other geometry to perform the operation on."""
if not isinstance(other, OGRGeometry):
raise TypeError('Must use another OGRGeometry object for topology operations!')
# Returning the output of the given function with the other geometry's
# pointer.
return func(self.ptr, other.ptr)
def intersects(self, other):
"Return True if this geometry intersects with the other."
return self._topology(capi.ogr_intersects, other)
def equals(self, other):
"Return True if this geometry is equivalent to the other."
return self._topology(capi.ogr_equals, other)
def disjoint(self, other):
"Return True if this geometry and the other are spatially disjoint."
return self._topology(capi.ogr_disjoint, other)
def touches(self, other):
"Return True if this geometry touches the other."
return self._topology(capi.ogr_touches, other)
def crosses(self, other):
"Return True if this geometry crosses the other."
return self._topology(capi.ogr_crosses, other)
def within(self, other):
"Return True if this geometry is within the other."
return self._topology(capi.ogr_within, other)
def contains(self, other):
"Return True if this geometry contains the other."
return self._topology(capi.ogr_contains, other)
def overlaps(self, other):
"Return True if this geometry overlaps the other."
return self._topology(capi.ogr_overlaps, other)
# #### Geometry-generation Methods ####
def _geomgen(self, gen_func, other=None):
"A helper routine for the OGR routines that generate geometries."
if isinstance(other, OGRGeometry):
return OGRGeometry(gen_func(self.ptr, other.ptr), self.srs)
else:
return OGRGeometry(gen_func(self.ptr), self.srs)
@property
def boundary(self):
"Return the boundary of this geometry."
return self._geomgen(capi.get_boundary)
@property
def convex_hull(self):
"""
Return the smallest convex Polygon that contains all the points in
this Geometry.
"""
return self._geomgen(capi.geom_convex_hull)
def difference(self, other):
"""
Return a new geometry consisting of the region which is the difference
of this geometry and the other.
"""
return self._geomgen(capi.geom_diff, other)
def intersection(self, other):
"""
Return a new geometry consisting of the region of intersection of this
geometry and the other.
"""
return self._geomgen(capi.geom_intersection, other)
def sym_difference(self, other):
"""
Return a new geometry which is the symmetric difference of this
geometry and the other.
"""
return self._geomgen(capi.geom_sym_diff, other)
def union(self, other):
"""
Return a new geometry consisting of the region which is the union of
this geometry and the other.
"""
return self._geomgen(capi.geom_union, other)
# The subclasses for OGR Geometry.
class Point(OGRGeometry):
def _geos_ptr(self):
from django.contrib.gis import geos
return geos.Point._create_empty() if self.empty else super()._geos_ptr()
@classmethod
def _create_empty(cls):
return capi.create_geom(OGRGeomType('point').num)
@property
def x(self):
"Return the X coordinate for this Point."
return capi.getx(self.ptr, 0)
@property
def y(self):
"Return the Y coordinate for this Point."
return capi.gety(self.ptr, 0)
@property
def z(self):
"Return the Z coordinate for this Point."
if self.coord_dim == 3:
return capi.getz(self.ptr, 0)
@property
def tuple(self):
"Return the tuple of this point."
if self.coord_dim == 2:
return (self.x, self.y)
elif self.coord_dim == 3:
return (self.x, self.y, self.z)
coords = tuple
class LineString(OGRGeometry):
def __getitem__(self, index):
"Return the Point at the given index."
if 0 <= index < self.point_count:
x, y, z = c_double(), c_double(), c_double()
capi.get_point(self.ptr, index, byref(x), byref(y), byref(z))
dim = self.coord_dim
if dim == 1:
return (x.value,)
elif dim == 2:
return (x.value, y.value)
elif dim == 3:
return (x.value, y.value, z.value)
else:
raise IndexError('Index out of range when accessing points of a line string: %s.' % index)
def __len__(self):
"Return the number of points in the LineString."
return self.point_count
@property
def tuple(self):
"Return the tuple representation of this LineString."
return tuple(self[i] for i in range(len(self)))
coords = tuple
def _listarr(self, func):
"""
Internal routine that returns a sequence (list) corresponding with
the given function.
"""
return [func(self.ptr, i) for i in range(len(self))]
@property
def x(self):
"Return the X coordinates in a list."
return self._listarr(capi.getx)
@property
def y(self):
"Return the Y coordinates in a list."
return self._listarr(capi.gety)
@property
def z(self):
"Return the Z coordinates in a list."
if self.coord_dim == 3:
return self._listarr(capi.getz)
# LinearRings are used in Polygons.
class LinearRing(LineString):
pass
class Polygon(OGRGeometry):
def __len__(self):
"Return the number of interior rings in this Polygon."
return self.geom_count
def __getitem__(self, index):
"Get the ring at the specified index."
if 0 <= index < self.geom_count:
return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs)
else:
raise IndexError('Index out of range when accessing rings of a polygon: %s.' % index)
# Polygon Properties
@property
def shell(self):
"Return the shell of this Polygon."
return self[0] # First ring is the shell
exterior_ring = shell
@property
def tuple(self):
"Return a tuple of LinearRing coordinate tuples."
return tuple(self[i].tuple for i in range(self.geom_count))
coords = tuple
@property
def point_count(self):
"Return the number of Points in this Polygon."
# Summing up the number of points in each ring of the Polygon.
return sum(self[i].point_count for i in range(self.geom_count))
@property
def centroid(self):
"Return the centroid (a Point) of this Polygon."
# The centroid is a Point, create a geometry for this.
p = OGRGeometry(OGRGeomType('Point'))
capi.get_centroid(self.ptr, p.ptr)
return p
# Geometry Collection base class.
class GeometryCollection(OGRGeometry):
"The Geometry Collection class."
def __getitem__(self, index):
"Get the Geometry at the specified index."
if 0 <= index < self.geom_count:
return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs)
else:
raise IndexError('Index out of range when accessing geometry in a collection: %s.' % index)
def __len__(self):
"Return the number of geometries in this Geometry Collection."
return self.geom_count
def add(self, geom):
"Add the geometry to this Geometry Collection."
if isinstance(geom, OGRGeometry):
if isinstance(geom, self.__class__):
for g in geom:
capi.add_geom(self.ptr, g.ptr)
else:
capi.add_geom(self.ptr, geom.ptr)
elif isinstance(geom, str):
tmp = OGRGeometry(geom)
capi.add_geom(self.ptr, tmp.ptr)
else:
raise GDALException('Must add an OGRGeometry.')
@property
def point_count(self):
"Return the number of Points in this Geometry Collection."
# Summing up the number of points in each geometry in this collection
return sum(self[i].point_count for i in range(self.geom_count))
@property
def tuple(self):
"Return a tuple representation of this Geometry Collection."
return tuple(self[i].tuple for i in range(self.geom_count))
coords = tuple
# Multiple Geometry types.
class MultiPoint(GeometryCollection):
pass
class MultiLineString(GeometryCollection):
pass
class MultiPolygon(GeometryCollection):
pass
# Class mapping dictionary (using the OGRwkbGeometryType as the key)
GEO_CLASSES = {
1: Point,
2: LineString,
3: Polygon,
4: MultiPoint,
5: MultiLineString,
6: MultiPolygon,
7: GeometryCollection,
101: LinearRing,
1 + OGRGeomType.wkb25bit: Point,
2 + OGRGeomType.wkb25bit: LineString,
3 + OGRGeomType.wkb25bit: Polygon,
4 + OGRGeomType.wkb25bit: MultiPoint,
5 + OGRGeomType.wkb25bit: MultiLineString,
6 + OGRGeomType.wkb25bit: MultiPolygon,
7 + OGRGeomType.wkb25bit: GeometryCollection,
}
@@ -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,108 @@
"""
This module houses the ctypes function prototypes for GDAL DataSource (raster)
related data structures.
"""
from ctypes import POINTER, c_bool, c_char_p, c_double, c_int, c_void_p
from functools import partial
from django.contrib.gis.gdal.libgdal import GDAL_VERSION, std_call
from django.contrib.gis.gdal.prototypes.generation import (
chararray_output, const_string_output, double_output, int_output,
void_output, voidptr_output,
)
# For more detail about c function names and definitions see
# https://gdal.org/gdal_8h.html
# https://gdal.org/gdalwarper_8h.html
# https://www.gdal.org/gdal__utils_8h.html
# Prepare partial functions that use cpl error codes
void_output = partial(void_output, cpl=True)
const_string_output = partial(const_string_output, cpl=True)
double_output = partial(double_output, cpl=True)
# Raster Driver Routines
register_all = void_output(std_call('GDALAllRegister'), [], errcheck=False)
get_driver = voidptr_output(std_call('GDALGetDriver'), [c_int])
get_driver_by_name = voidptr_output(std_call('GDALGetDriverByName'), [c_char_p], errcheck=False)
get_driver_count = int_output(std_call('GDALGetDriverCount'), [])
get_driver_description = const_string_output(std_call('GDALGetDescription'), [c_void_p])
# Raster Data Source Routines
create_ds = voidptr_output(std_call('GDALCreate'), [c_void_p, c_char_p, c_int, c_int, c_int, c_int, c_void_p])
open_ds = voidptr_output(std_call('GDALOpen'), [c_char_p, c_int])
close_ds = void_output(std_call('GDALClose'), [c_void_p], errcheck=False)
flush_ds = int_output(std_call('GDALFlushCache'), [c_void_p])
copy_ds = voidptr_output(
std_call('GDALCreateCopy'),
[c_void_p, c_char_p, c_void_p, c_int, POINTER(c_char_p), c_void_p, c_void_p]
)
add_band_ds = void_output(std_call('GDALAddBand'), [c_void_p, c_int])
get_ds_description = const_string_output(std_call('GDALGetDescription'), [c_void_p])
get_ds_driver = voidptr_output(std_call('GDALGetDatasetDriver'), [c_void_p])
get_ds_xsize = int_output(std_call('GDALGetRasterXSize'), [c_void_p])
get_ds_ysize = int_output(std_call('GDALGetRasterYSize'), [c_void_p])
get_ds_raster_count = int_output(std_call('GDALGetRasterCount'), [c_void_p])
get_ds_raster_band = voidptr_output(std_call('GDALGetRasterBand'), [c_void_p, c_int])
get_ds_projection_ref = const_string_output(std_call('GDALGetProjectionRef'), [c_void_p])
set_ds_projection_ref = void_output(std_call('GDALSetProjection'), [c_void_p, c_char_p])
get_ds_geotransform = void_output(std_call('GDALGetGeoTransform'), [c_void_p, POINTER(c_double * 6)], errcheck=False)
set_ds_geotransform = void_output(std_call('GDALSetGeoTransform'), [c_void_p, POINTER(c_double * 6)])
get_ds_metadata = chararray_output(std_call('GDALGetMetadata'), [c_void_p, c_char_p], errcheck=False)
set_ds_metadata = void_output(std_call('GDALSetMetadata'), [c_void_p, POINTER(c_char_p), c_char_p])
get_ds_metadata_domain_list = chararray_output(std_call('GDALGetMetadataDomainList'), [c_void_p], errcheck=False)
get_ds_metadata_item = const_string_output(std_call('GDALGetMetadataItem'), [c_void_p, c_char_p, c_char_p])
set_ds_metadata_item = const_string_output(std_call('GDALSetMetadataItem'), [c_void_p, c_char_p, c_char_p, c_char_p])
free_dsl = void_output(std_call('CSLDestroy'), [POINTER(c_char_p)], errcheck=False)
if GDAL_VERSION >= (2, 1):
get_ds_info = const_string_output(std_call('GDALInfo'), [c_void_p, c_void_p])
else:
get_ds_info = None
# Raster Band Routines
band_io = void_output(
std_call('GDALRasterIO'),
[c_void_p, c_int, c_int, c_int, c_int, c_int, c_void_p, c_int, c_int, c_int, c_int, c_int]
)
get_band_xsize = int_output(std_call('GDALGetRasterBandXSize'), [c_void_p])
get_band_ysize = int_output(std_call('GDALGetRasterBandYSize'), [c_void_p])
get_band_index = int_output(std_call('GDALGetBandNumber'), [c_void_p])
get_band_description = const_string_output(std_call('GDALGetDescription'), [c_void_p])
get_band_ds = voidptr_output(std_call('GDALGetBandDataset'), [c_void_p])
get_band_datatype = int_output(std_call('GDALGetRasterDataType'), [c_void_p])
get_band_color_interp = int_output(std_call('GDALGetRasterColorInterpretation'), [c_void_p])
get_band_nodata_value = double_output(std_call('GDALGetRasterNoDataValue'), [c_void_p, POINTER(c_int)])
set_band_nodata_value = void_output(std_call('GDALSetRasterNoDataValue'), [c_void_p, c_double])
if GDAL_VERSION >= (2, 1):
delete_band_nodata_value = void_output(std_call('GDALDeleteRasterNoDataValue'), [c_void_p])
else:
delete_band_nodata_value = None
get_band_statistics = void_output(
std_call('GDALGetRasterStatistics'),
[
c_void_p, c_int, c_int, POINTER(c_double), POINTER(c_double),
POINTER(c_double), POINTER(c_double), c_void_p, c_void_p,
],
)
compute_band_statistics = void_output(
std_call('GDALComputeRasterStatistics'),
[c_void_p, c_int, POINTER(c_double), POINTER(c_double), POINTER(c_double), POINTER(c_double), c_void_p, c_void_p],
)
# Reprojection routine
reproject_image = void_output(
std_call('GDALReprojectImage'),
[c_void_p, c_char_p, c_void_p, c_char_p, c_int, c_double, c_double, c_void_p, c_void_p, c_void_p]
)
auto_create_warped_vrt = voidptr_output(
std_call('GDALAutoCreateWarpedVRT'),
[c_void_p, c_char_p, c_char_p, c_int, c_double, c_void_p]
)
# Create VSI gdal raster files from in-memory buffers.
# https://gdal.org/cpl__vsi_8h.html
create_vsi_file_from_mem_buffer = voidptr_output(std_call('VSIFileFromMemBuffer'), [c_char_p, c_void_p, c_int, c_int])
get_mem_buffer_from_vsi_file = voidptr_output(std_call('VSIGetMemFileBuffer'), [c_char_p, POINTER(c_int), c_bool])
unlink_vsi_file = int_output(std_call('VSIUnlink'), [c_char_p])
@@ -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,467 @@
import json
import os
import sys
import uuid
from ctypes import (
addressof, byref, c_buffer, c_char_p, c_double, c_int, c_void_p, string_at,
)
from django.contrib.gis.gdal.driver import Driver
from django.contrib.gis.gdal.error import GDALException
from django.contrib.gis.gdal.prototypes import raster as capi
from django.contrib.gis.gdal.raster.band import BandList
from django.contrib.gis.gdal.raster.base import GDALRasterBase
from django.contrib.gis.gdal.raster.const import (
GDAL_RESAMPLE_ALGORITHMS, VSI_DELETE_BUFFER_ON_READ,
VSI_FILESYSTEM_BASE_PATH, VSI_TAKE_BUFFER_OWNERSHIP,
)
from django.contrib.gis.gdal.srs import SpatialReference, SRSException
from django.contrib.gis.geometry import json_regex
from django.utils.encoding import force_bytes, force_text
from django.utils.functional import cached_property
class TransformPoint(list):
indices = {
'origin': (0, 3),
'scale': (1, 5),
'skew': (2, 4),
}
def __init__(self, raster, prop):
x = raster.geotransform[self.indices[prop][0]]
y = raster.geotransform[self.indices[prop][1]]
super().__init__([x, y])
self._raster = raster
self._prop = prop
@property
def x(self):
return self[0]
@x.setter
def x(self, value):
gtf = self._raster.geotransform
gtf[self.indices[self._prop][0]] = value
self._raster.geotransform = gtf
@property
def y(self):
return self[1]
@y.setter
def y(self, value):
gtf = self._raster.geotransform
gtf[self.indices[self._prop][1]] = value
self._raster.geotransform = gtf
class GDALRaster(GDALRasterBase):
"""
Wrap a raster GDAL Data Source object.
"""
destructor = capi.close_ds
def __init__(self, ds_input, write=False):
self._write = 1 if write else 0
Driver.ensure_registered()
# Preprocess json inputs. This converts json strings to dictionaries,
# which are parsed below the same way as direct dictionary inputs.
if isinstance(ds_input, str) and json_regex.match(ds_input):
ds_input = json.loads(ds_input)
# If input is a valid file path, try setting file as source.
if isinstance(ds_input, str):
try:
# GDALOpen will auto-detect the data source type.
self._ptr = capi.open_ds(force_bytes(ds_input), self._write)
except GDALException as err:
raise GDALException('Could not open the datasource at "{}" ({}).'.format(ds_input, err))
elif isinstance(ds_input, bytes):
# Create a new raster in write mode.
self._write = 1
# Get size of buffer.
size = sys.getsizeof(ds_input)
# Pass data to ctypes, keeping a reference to the ctypes object so
# that the vsimem file remains available until the GDALRaster is
# deleted.
self._ds_input = c_buffer(ds_input)
# Create random name to reference in vsimem filesystem.
vsi_path = os.path.join(VSI_FILESYSTEM_BASE_PATH, str(uuid.uuid4()))
# Create vsimem file from buffer.
capi.create_vsi_file_from_mem_buffer(
force_bytes(vsi_path),
byref(self._ds_input),
size,
VSI_TAKE_BUFFER_OWNERSHIP,
)
# Open the new vsimem file as a GDALRaster.
try:
self._ptr = capi.open_ds(force_bytes(vsi_path), self._write)
except GDALException:
# Remove the broken file from the VSI filesystem.
capi.unlink_vsi_file(force_bytes(vsi_path))
raise GDALException('Failed creating VSI raster from the input buffer.')
elif isinstance(ds_input, dict):
# A new raster needs to be created in write mode
self._write = 1
# Create driver (in memory by default)
driver = Driver(ds_input.get('driver', 'MEM'))
# For out of memory drivers, check filename argument
if driver.name != 'MEM' and 'name' not in ds_input:
raise GDALException('Specify name for creation of raster with driver "{}".'.format(driver.name))
# Check if width and height where specified
if 'width' not in ds_input or 'height' not in ds_input:
raise GDALException('Specify width and height attributes for JSON or dict input.')
# Check if srid was specified
if 'srid' not in ds_input:
raise GDALException('Specify srid for JSON or dict input.')
# Create null terminated gdal options array.
papsz_options = []
for key, val in ds_input.get('papsz_options', {}).items():
option = '{}={}'.format(key, val)
papsz_options.append(option.upper().encode())
papsz_options.append(None)
# Convert papszlist to ctypes array.
papsz_options = (c_char_p * len(papsz_options))(*papsz_options)
# Create GDAL Raster
self._ptr = capi.create_ds(
driver._ptr,
force_bytes(ds_input.get('name', '')),
ds_input['width'],
ds_input['height'],
ds_input.get('nr_of_bands', len(ds_input.get('bands', []))),
ds_input.get('datatype', 6),
byref(papsz_options),
)
# Set band data if provided
for i, band_input in enumerate(ds_input.get('bands', [])):
band = self.bands[i]
if 'nodata_value' in band_input:
band.nodata_value = band_input['nodata_value']
# Instantiate band filled with nodata values if only
# partial input data has been provided.
if band.nodata_value is not None and (
'data' not in band_input or
'size' in band_input or
'shape' in band_input):
band.data(data=(band.nodata_value,), shape=(1, 1))
# Set band data values from input.
band.data(
data=band_input.get('data'),
size=band_input.get('size'),
shape=band_input.get('shape'),
offset=band_input.get('offset'),
)
# Set SRID
self.srs = ds_input.get('srid')
# Set additional properties if provided
if 'origin' in ds_input:
self.origin.x, self.origin.y = ds_input['origin']
if 'scale' in ds_input:
self.scale.x, self.scale.y = ds_input['scale']
if 'skew' in ds_input:
self.skew.x, self.skew.y = ds_input['skew']
elif isinstance(ds_input, c_void_p):
# Instantiate the object using an existing pointer to a gdal raster.
self._ptr = ds_input
else:
raise GDALException('Invalid data source input type: "{}".'.format(type(ds_input)))
def __del__(self):
if self.is_vsi_based:
# Remove the temporary file from the VSI in-memory filesystem.
capi.unlink_vsi_file(force_bytes(self.name))
super().__del__()
def __str__(self):
return self.name
def __repr__(self):
"""
Short-hand representation because WKB may be very large.
"""
return '<Raster object at %s>' % hex(addressof(self._ptr))
def _flush(self):
"""
Flush all data from memory into the source file if it exists.
The data that needs flushing are geotransforms, coordinate systems,
nodata_values and pixel values. This function will be called
automatically wherever it is needed.
"""
# Raise an Exception if the value is being changed in read mode.
if not self._write:
raise GDALException('Raster needs to be opened in write mode to change values.')
capi.flush_ds(self._ptr)
@property
def vsi_buffer(self):
if not self.is_vsi_based:
return None
# Prepare an integer that will contain the buffer length.
out_length = c_int()
# Get the data using the vsi file name.
dat = capi.get_mem_buffer_from_vsi_file(
force_bytes(self.name),
byref(out_length),
VSI_DELETE_BUFFER_ON_READ,
)
# Read the full buffer pointer.
return string_at(dat, out_length.value)
@cached_property
def is_vsi_based(self):
return self.name.startswith(VSI_FILESYSTEM_BASE_PATH)
@property
def name(self):
"""
Return the name of this raster. Corresponds to filename
for file-based rasters.
"""
return force_text(capi.get_ds_description(self._ptr))
@cached_property
def driver(self):
"""
Return the GDAL Driver used for this raster.
"""
ds_driver = capi.get_ds_driver(self._ptr)
return Driver(ds_driver)
@property
def width(self):
"""
Width (X axis) in pixels.
"""
return capi.get_ds_xsize(self._ptr)
@property
def height(self):
"""
Height (Y axis) in pixels.
"""
return capi.get_ds_ysize(self._ptr)
@property
def srs(self):
"""
Return the SpatialReference used in this GDALRaster.
"""
try:
wkt = capi.get_ds_projection_ref(self._ptr)
if not wkt:
return None
return SpatialReference(wkt, srs_type='wkt')
except SRSException:
return None
@srs.setter
def srs(self, value):
"""
Set the spatial reference used in this GDALRaster. The input can be
a SpatialReference or any parameter accepted by the SpatialReference
constructor.
"""
if isinstance(value, SpatialReference):
srs = value
elif isinstance(value, (int, str)):
srs = SpatialReference(value)
else:
raise ValueError('Could not create a SpatialReference from input.')
capi.set_ds_projection_ref(self._ptr, srs.wkt.encode())
self._flush()
@property
def srid(self):
"""
Shortcut to access the srid of this GDALRaster.
"""
return self.srs.srid
@srid.setter
def srid(self, value):
"""
Shortcut to set this GDALRaster's srs from an srid.
"""
self.srs = value
@property
def geotransform(self):
"""
Return the geotransform of the data source.
Return the default geotransform if it does not exist or has not been
set previously. The default is [0.0, 1.0, 0.0, 0.0, 0.0, -1.0].
"""
# Create empty ctypes double array for data
gtf = (c_double * 6)()
capi.get_ds_geotransform(self._ptr, byref(gtf))
return list(gtf)
@geotransform.setter
def geotransform(self, values):
"Set the geotransform for the data source."
if len(values) != 6 or not all(isinstance(x, (int, float)) for x in values):
raise ValueError('Geotransform must consist of 6 numeric values.')
# Create ctypes double array with input and write data
values = (c_double * 6)(*values)
capi.set_ds_geotransform(self._ptr, byref(values))
self._flush()
@property
def origin(self):
"""
Coordinates of the raster origin.
"""
return TransformPoint(self, 'origin')
@property
def scale(self):
"""
Pixel scale in units of the raster projection.
"""
return TransformPoint(self, 'scale')
@property
def skew(self):
"""
Skew of pixels (rotation parameters).
"""
return TransformPoint(self, 'skew')
@property
def extent(self):
"""
Return the extent as a 4-tuple (xmin, ymin, xmax, ymax).
"""
# Calculate boundary values based on scale and size
xval = self.origin.x + self.scale.x * self.width
yval = self.origin.y + self.scale.y * self.height
# Calculate min and max values
xmin = min(xval, self.origin.x)
xmax = max(xval, self.origin.x)
ymin = min(yval, self.origin.y)
ymax = max(yval, self.origin.y)
return xmin, ymin, xmax, ymax
@property
def bands(self):
return BandList(self)
def warp(self, ds_input, resampling='NearestNeighbour', max_error=0.0):
"""
Return a warped GDALRaster with the given input characteristics.
The input is expected to be a dictionary containing the parameters
of the target raster. Allowed values are width, height, SRID, origin,
scale, skew, datatype, driver, and name (filename).
By default, the warp functions keeps all parameters equal to the values
of the original source raster. For the name of the target raster, the
name of the source raster will be used and appended with
_copy. + source_driver_name.
In addition, the resampling algorithm can be specified with the "resampling"
input parameter. The default is NearestNeighbor. For a list of all options
consult the GDAL_RESAMPLE_ALGORITHMS constant.
"""
# Get the parameters defining the geotransform, srid, and size of the raster
ds_input.setdefault('width', self.width)
ds_input.setdefault('height', self.height)
ds_input.setdefault('srid', self.srs.srid)
ds_input.setdefault('origin', self.origin)
ds_input.setdefault('scale', self.scale)
ds_input.setdefault('skew', self.skew)
# Get the driver, name, and datatype of the target raster
ds_input.setdefault('driver', self.driver.name)
if 'name' not in ds_input:
ds_input['name'] = self.name + '_copy.' + self.driver.name
if 'datatype' not in ds_input:
ds_input['datatype'] = self.bands[0].datatype()
# Instantiate raster bands filled with nodata values.
ds_input['bands'] = [{'nodata_value': bnd.nodata_value} for bnd in self.bands]
# Create target raster
target = GDALRaster(ds_input, write=True)
# Select resampling algorithm
algorithm = GDAL_RESAMPLE_ALGORITHMS[resampling]
# Reproject image
capi.reproject_image(
self._ptr, self.srs.wkt.encode(),
target._ptr, target.srs.wkt.encode(),
algorithm, 0.0, max_error,
c_void_p(), c_void_p(), c_void_p()
)
# Make sure all data is written to file
target._flush()
return target
def transform(self, srid, driver=None, name=None, resampling='NearestNeighbour',
max_error=0.0):
"""
Return a copy of this raster reprojected into the given SRID.
"""
# Convert the resampling algorithm name into an algorithm id
algorithm = GDAL_RESAMPLE_ALGORITHMS[resampling]
# Instantiate target spatial reference system
target_srs = SpatialReference(srid)
# Create warped virtual dataset in the target reference system
target = capi.auto_create_warped_vrt(
self._ptr, self.srs.wkt.encode(), target_srs.wkt.encode(),
algorithm, max_error, c_void_p()
)
target = GDALRaster(target)
# Construct the target warp dictionary from the virtual raster
data = {
'srid': srid,
'width': target.width,
'height': target.height,
'origin': [target.origin.x, target.origin.y],
'scale': [target.scale.x, target.scale.y],
'skew': [target.skew.x, target.skew.y],
}
# Set the driver and filepath if provided
if driver:
data['driver'] = driver
if name:
data['name'] = name
# Warp the raster into new srid
return self.warp(data, resampling=resampling, max_error=max_error)
@property
def info(self):
"""
Return information about this raster in a string format equivalent
to the output of the gdalinfo command line utility.
"""
if not capi.get_ds_info:
raise ValueError('GDAL ≥ 2.1 is required for using the info property.')
return capi.get_ds_info(self.ptr, None).decode()
@@ -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,21 @@
def City(response):
return {
'city': response.city.name,
'continent_code': response.continent.code,
'continent_name': response.continent.name,
'country_code': response.country.iso_code,
'country_name': response.country.name,
'dma_code': response.location.metro_code,
'latitude': response.location.latitude,
'longitude': response.location.longitude,
'postal_code': response.postal.code,
'region': response.subdivisions[0].iso_code if response.subdivisions else None,
'time_zone': response.location.time_zone,
}
def Country(response):
return {
'country_code': response.country.iso_code,
'country_name': response.country.name,
}
@@ -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,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,3 @@
class GEOSException(Exception):
"The base GEOS exception, indicates a GEOS-related error."
pass
@@ -0,0 +1,738 @@
"""
This module contains the 'base' GEOSGeometry object -- all GEOS Geometries
inherit from this object.
"""
import re
from ctypes import addressof, byref, c_double
from django.contrib.gis import gdal
from django.contrib.gis.geometry import hex_regex, json_regex, wkt_regex
from django.contrib.gis.geos import prototypes as capi
from django.contrib.gis.geos.base import GEOSBase
from django.contrib.gis.geos.coordseq import GEOSCoordSeq
from django.contrib.gis.geos.error import GEOSException
from django.contrib.gis.geos.libgeos import GEOM_PTR
from django.contrib.gis.geos.mutable_list import ListMixin
from django.contrib.gis.geos.prepared import PreparedGeometry
from django.contrib.gis.geos.prototypes.io import (
ewkb_w, wkb_r, wkb_w, wkt_r, wkt_w,
)
from django.utils.deconstruct import deconstructible
from django.utils.encoding import force_bytes, force_text
class GEOSGeometryBase(GEOSBase):
_GEOS_CLASSES = None
ptr_type = GEOM_PTR
destructor = capi.destroy_geom
has_cs = False # Only Point, LineString, LinearRing have coordinate sequences
def __init__(self, ptr, cls):
self._ptr = ptr
# Setting the class type (e.g., Point, Polygon, etc.)
if type(self) in (GEOSGeometryBase, GEOSGeometry):
if cls is None:
if GEOSGeometryBase._GEOS_CLASSES is None:
# Inner imports avoid import conflicts with GEOSGeometry.
from .linestring import LineString, LinearRing
from .point import Point
from .polygon import Polygon
from .collections import (
GeometryCollection, MultiPoint, MultiLineString, MultiPolygon,
)
GEOSGeometryBase._GEOS_CLASSES = {
0: Point,
1: LineString,
2: LinearRing,
3: Polygon,
4: MultiPoint,
5: MultiLineString,
6: MultiPolygon,
7: GeometryCollection,
}
cls = GEOSGeometryBase._GEOS_CLASSES[self.geom_typeid]
self.__class__ = cls
self._post_init()
def _post_init(self):
"Perform post-initialization setup."
# Setting the coordinate sequence for the geometry (will be None on
# geometries that do not have coordinate sequences)
self._cs = GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz) if self.has_cs else None
def __copy__(self):
"""
Return a clone because the copy of a GEOSGeometry may contain an
invalid pointer location if the original is garbage collected.
"""
return self.clone()
def __deepcopy__(self, memodict):
"""
The `deepcopy` routine is used by the `Node` class of django.utils.tree;
thus, the protocol routine needs to be implemented to return correct
copies (clones) of these GEOS objects, which use C pointers.
"""
return self.clone()
def __str__(self):
"EWKT is used for the string representation."
return self.ewkt
def __repr__(self):
"Short-hand representation because WKT may be very large."
return '<%s object at %s>' % (self.geom_type, hex(addressof(self.ptr)))
# Pickling support
def _to_pickle_wkb(self):
return bytes(self.wkb)
def _from_pickle_wkb(self, wkb):
return wkb_r().read(memoryview(wkb))
def __getstate__(self):
# The pickled state is simply a tuple of the WKB (in string form)
# and the SRID.
return self._to_pickle_wkb(), self.srid
def __setstate__(self, state):
# Instantiating from the tuple state that was pickled.
wkb, srid = state
ptr = self._from_pickle_wkb(wkb)
if not ptr:
raise GEOSException('Invalid Geometry loaded from pickled state.')
self.ptr = ptr
self._post_init()
self.srid = srid
@classmethod
def _from_wkb(cls, wkb):
return wkb_r().read(wkb)
@staticmethod
def from_ewkt(ewkt):
ewkt = force_bytes(ewkt)
srid = None
parts = ewkt.split(b';', 1)
if len(parts) == 2:
srid_part, wkt = parts
match = re.match(br'SRID=(?P<srid>\-?\d+)', srid_part)
if not match:
raise ValueError('EWKT has invalid SRID part.')
srid = int(match.group('srid'))
else:
wkt = ewkt
if not wkt:
raise ValueError('Expected WKT but got an empty string.')
return GEOSGeometry(GEOSGeometry._from_wkt(wkt), srid=srid)
@staticmethod
def _from_wkt(wkt):
return wkt_r().read(wkt)
@classmethod
def from_gml(cls, gml_string):
return gdal.OGRGeometry.from_gml(gml_string).geos
# Comparison operators
def __eq__(self, other):
"""
Equivalence testing, a Geometry may be compared with another Geometry
or an EWKT representation.
"""
if isinstance(other, str):
try:
other = GEOSGeometry.from_ewkt(other)
except (ValueError, GEOSException):
return False
return isinstance(other, GEOSGeometry) and self.srid == other.srid and self.equals_exact(other)
def __hash__(self):
return hash((self.srid, self.wkt))
# ### Geometry set-like operations ###
# Thanks to Sean Gillies for inspiration:
# http://lists.gispython.org/pipermail/community/2007-July/001034.html
# g = g1 | g2
def __or__(self, other):
"Return the union of this Geometry and the other."
return self.union(other)
# g = g1 & g2
def __and__(self, other):
"Return the intersection of this Geometry and the other."
return self.intersection(other)
# g = g1 - g2
def __sub__(self, other):
"Return the difference this Geometry and the other."
return self.difference(other)
# g = g1 ^ g2
def __xor__(self, other):
"Return the symmetric difference of this Geometry and the other."
return self.sym_difference(other)
# #### Coordinate Sequence Routines ####
@property
def coord_seq(self):
"Return a clone of the coordinate sequence for this Geometry."
if self.has_cs:
return self._cs.clone()
# #### Geometry Info ####
@property
def geom_type(self):
"Return a string representing the Geometry type, e.g. 'Polygon'"
return capi.geos_type(self.ptr).decode()
@property
def geom_typeid(self):
"Return an integer representing the Geometry type."
return capi.geos_typeid(self.ptr)
@property
def num_geom(self):
"Return the number of geometries in the Geometry."
return capi.get_num_geoms(self.ptr)
@property
def num_coords(self):
"Return the number of coordinates in the Geometry."
return capi.get_num_coords(self.ptr)
@property
def num_points(self):
"Return the number points, or coordinates, in the Geometry."
return self.num_coords
@property
def dims(self):
"Return the dimension of this Geometry (0=point, 1=line, 2=surface)."
return capi.get_dims(self.ptr)
def normalize(self):
"Convert this Geometry to normal form (or canonical form)."
capi.geos_normalize(self.ptr)
# #### Unary predicates ####
@property
def empty(self):
"""
Return a boolean indicating whether the set of points in this Geometry
are empty.
"""
return capi.geos_isempty(self.ptr)
@property
def hasz(self):
"Return whether the geometry has a 3D dimension."
return capi.geos_hasz(self.ptr)
@property
def ring(self):
"Return whether or not the geometry is a ring."
return capi.geos_isring(self.ptr)
@property
def simple(self):
"Return false if the Geometry isn't simple."
return capi.geos_issimple(self.ptr)
@property
def valid(self):
"Test the validity of this Geometry."
return capi.geos_isvalid(self.ptr)
@property
def valid_reason(self):
"""
Return a string containing the reason for any invalidity.
"""
return capi.geos_isvalidreason(self.ptr).decode()
# #### Binary predicates. ####
def contains(self, other):
"Return true if other.within(this) returns true."
return capi.geos_contains(self.ptr, other.ptr)
def covers(self, other):
"""
Return True if the DE-9IM Intersection Matrix for the two geometries is
T*****FF*, *T****FF*, ***T**FF*, or ****T*FF*. If either geometry is
empty, return False.
"""
return capi.geos_covers(self.ptr, other.ptr)
def crosses(self, other):
"""
Return true if the DE-9IM intersection matrix for the two Geometries
is T*T****** (for a point and a curve,a point and an area or a line and
an area) 0******** (for two curves).
"""
return capi.geos_crosses(self.ptr, other.ptr)
def disjoint(self, other):
"""
Return true if the DE-9IM intersection matrix for the two Geometries
is FF*FF****.
"""
return capi.geos_disjoint(self.ptr, other.ptr)
def equals(self, other):
"""
Return true if the DE-9IM intersection matrix for the two Geometries
is T*F**FFF*.
"""
return capi.geos_equals(self.ptr, other.ptr)
def equals_exact(self, other, tolerance=0):
"""
Return true if the two Geometries are exactly equal, up to a
specified tolerance.
"""
return capi.geos_equalsexact(self.ptr, other.ptr, float(tolerance))
def intersects(self, other):
"Return true if disjoint return false."
return capi.geos_intersects(self.ptr, other.ptr)
def overlaps(self, other):
"""
Return true if the DE-9IM intersection matrix for the two Geometries
is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves).
"""
return capi.geos_overlaps(self.ptr, other.ptr)
def relate_pattern(self, other, pattern):
"""
Return true if the elements in the DE-9IM intersection matrix for the
two Geometries match the elements in pattern.
"""
if not isinstance(pattern, str) or len(pattern) > 9:
raise GEOSException('invalid intersection matrix pattern')
return capi.geos_relatepattern(self.ptr, other.ptr, force_bytes(pattern))
def touches(self, other):
"""
Return true if the DE-9IM intersection matrix for the two Geometries
is FT*******, F**T***** or F***T****.
"""
return capi.geos_touches(self.ptr, other.ptr)
def within(self, other):
"""
Return true if the DE-9IM intersection matrix for the two Geometries
is T*F**F***.
"""
return capi.geos_within(self.ptr, other.ptr)
# #### SRID Routines ####
@property
def srid(self):
"Get the SRID for the geometry. Return None if no SRID is set."
s = capi.geos_get_srid(self.ptr)
if s == 0:
return None
else:
return s
@srid.setter
def srid(self, srid):
"Set the SRID for the geometry."
capi.geos_set_srid(self.ptr, 0 if srid is None else srid)
# #### Output Routines ####
@property
def ewkt(self):
"""
Return the EWKT (SRID + WKT) of the Geometry.
"""
srid = self.srid
return 'SRID=%s;%s' % (srid, self.wkt) if srid else self.wkt
@property
def wkt(self):
"Return the WKT (Well-Known Text) representation of this Geometry."
return wkt_w(dim=3 if self.hasz else 2, trim=True).write(self).decode()
@property
def hex(self):
"""
Return the WKB of this Geometry in hexadecimal form. Please note
that the SRID is not included in this representation because it is not
a part of the OGC specification (use the `hexewkb` property instead).
"""
# A possible faster, all-python, implementation:
# str(self.wkb).encode('hex')
return wkb_w(dim=3 if self.hasz else 2).write_hex(self)
@property
def hexewkb(self):
"""
Return the EWKB of this Geometry in hexadecimal form. This is an
extension of the WKB specification that includes SRID value that are
a part of this geometry.
"""
return ewkb_w(dim=3 if self.hasz else 2).write_hex(self)
@property
def json(self):
"""
Return GeoJSON representation of this Geometry.
"""
return self.ogr.json
geojson = json
@property
def wkb(self):
"""
Return the WKB (Well-Known Binary) representation of this Geometry
as a Python buffer. SRID and Z values are not included, use the
`ewkb` property instead.
"""
return wkb_w(3 if self.hasz else 2).write(self)
@property
def ewkb(self):
"""
Return the EWKB representation of this Geometry as a Python buffer.
This is an extension of the WKB specification that includes any SRID
value that are a part of this geometry.
"""
return ewkb_w(3 if self.hasz else 2).write(self)
@property
def kml(self):
"Return the KML representation of this Geometry."
gtype = self.geom_type
return '<%s>%s</%s>' % (gtype, self.coord_seq.kml, gtype)
@property
def prepared(self):
"""
Return a PreparedGeometry corresponding to this geometry -- it is
optimized for the contains, intersects, and covers operations.
"""
return PreparedGeometry(self)
# #### GDAL-specific output routines ####
def _ogr_ptr(self):
return gdal.OGRGeometry._from_wkb(self.wkb)
@property
def ogr(self):
"Return the OGR Geometry for this Geometry."
return gdal.OGRGeometry(self._ogr_ptr(), self.srs)
@property
def srs(self):
"Return the OSR SpatialReference for SRID of this Geometry."
if self.srid:
try:
return gdal.SpatialReference(self.srid)
except gdal.SRSException:
pass
return None
@property
def crs(self):
"Alias for `srs` property."
return self.srs
def transform(self, ct, clone=False):
"""
Requires GDAL. Transform the geometry according to the given
transformation object, which may be an integer SRID, and WKT or
PROJ.4 string. By default, transform the geometry in-place and return
nothing. However if the `clone` keyword is set, don't modify the
geometry and return a transformed clone instead.
"""
srid = self.srid
if ct == srid:
# short-circuit where source & dest SRIDs match
if clone:
return self.clone()
else:
return
if isinstance(ct, gdal.CoordTransform):
# We don't care about SRID because CoordTransform presupposes
# source SRS.
srid = None
elif srid is None or srid < 0:
raise GEOSException("Calling transform() with no SRID set is not supported")
# Creating an OGR Geometry, which is then transformed.
g = gdal.OGRGeometry(self._ogr_ptr(), srid)
g.transform(ct)
# Getting a new GEOS pointer
ptr = g._geos_ptr()
if clone:
# User wants a cloned transformed geometry returned.
return GEOSGeometry(ptr, srid=g.srid)
if ptr:
# Reassigning pointer, and performing post-initialization setup
# again due to the reassignment.
capi.destroy_geom(self.ptr)
self.ptr = ptr
self._post_init()
self.srid = g.srid
else:
raise GEOSException('Transformed WKB was invalid.')
# #### Topology Routines ####
def _topology(self, gptr):
"Return Geometry from the given pointer."
return GEOSGeometry(gptr, srid=self.srid)
@property
def boundary(self):
"Return the boundary as a newly allocated Geometry object."
return self._topology(capi.geos_boundary(self.ptr))
def buffer(self, width, quadsegs=8):
"""
Return a geometry that represents all points whose distance from this
Geometry is less than or equal to distance. Calculations are in the
Spatial Reference System of this Geometry. The optional third parameter sets
the number of segment used to approximate a quarter circle (defaults to 8).
(Text from PostGIS documentation at ch. 6.1.3)
"""
return self._topology(capi.geos_buffer(self.ptr, width, quadsegs))
def buffer_with_style(self, width, quadsegs=8, end_cap_style=1, join_style=1, mitre_limit=5.0):
"""
Same as buffer() but allows customizing the style of the buffer.
End cap style can be round (1), flat (2), or square (3).
Join style can be round (1), mitre (2), or bevel (3).
Mitre ratio limit only affects mitered join style.
"""
return self._topology(
capi.geos_bufferwithstyle(self.ptr, width, quadsegs, end_cap_style, join_style, mitre_limit),
)
@property
def centroid(self):
"""
The centroid is equal to the centroid of the set of component Geometries
of highest dimension (since the lower-dimension geometries contribute zero
"weight" to the centroid).
"""
return self._topology(capi.geos_centroid(self.ptr))
@property
def convex_hull(self):
"""
Return the smallest convex Polygon that contains all the points
in the Geometry.
"""
return self._topology(capi.geos_convexhull(self.ptr))
def difference(self, other):
"""
Return a Geometry representing the points making up this Geometry
that do not make up other.
"""
return self._topology(capi.geos_difference(self.ptr, other.ptr))
@property
def envelope(self):
"Return the envelope for this geometry (a polygon)."
return self._topology(capi.geos_envelope(self.ptr))
def intersection(self, other):
"Return a Geometry representing the points shared by this Geometry and other."
return self._topology(capi.geos_intersection(self.ptr, other.ptr))
@property
def point_on_surface(self):
"Compute an interior point of this Geometry."
return self._topology(capi.geos_pointonsurface(self.ptr))
def relate(self, other):
"Return the DE-9IM intersection matrix for this Geometry and the other."
return capi.geos_relate(self.ptr, other.ptr).decode()
def simplify(self, tolerance=0.0, preserve_topology=False):
"""
Return the Geometry, simplified using the Douglas-Peucker algorithm
to the specified tolerance (higher tolerance => less points). If no
tolerance provided, defaults to 0.
By default, don't preserve topology - e.g. polygons can be split,
collapse to lines or disappear holes can be created or disappear, and
lines can cross. By specifying preserve_topology=True, the result will
have the same dimension and number of components as the input. This is
significantly slower.
"""
if preserve_topology:
return self._topology(capi.geos_preservesimplify(self.ptr, tolerance))
else:
return self._topology(capi.geos_simplify(self.ptr, tolerance))
def sym_difference(self, other):
"""
Return a set combining the points in this Geometry not in other,
and the points in other not in this Geometry.
"""
return self._topology(capi.geos_symdifference(self.ptr, other.ptr))
@property
def unary_union(self):
"Return the union of all the elements of this geometry."
return self._topology(capi.geos_unary_union(self.ptr))
def union(self, other):
"Return a Geometry representing all the points in this Geometry and other."
return self._topology(capi.geos_union(self.ptr, other.ptr))
# #### Other Routines ####
@property
def area(self):
"Return the area of the Geometry."
return capi.geos_area(self.ptr, byref(c_double()))
def distance(self, other):
"""
Return the distance between the closest points on this Geometry
and the other. Units will be in those of the coordinate system of
the Geometry.
"""
if not isinstance(other, GEOSGeometry):
raise TypeError('distance() works only on other GEOS Geometries.')
return capi.geos_distance(self.ptr, other.ptr, byref(c_double()))
@property
def extent(self):
"""
Return the extent of this geometry as a 4-tuple, consisting of
(xmin, ymin, xmax, ymax).
"""
from .point import Point
env = self.envelope
if isinstance(env, Point):
xmin, ymin = env.tuple
xmax, ymax = xmin, ymin
else:
xmin, ymin = env[0][0]
xmax, ymax = env[0][2]
return (xmin, ymin, xmax, ymax)
@property
def length(self):
"""
Return the length of this Geometry (e.g., 0 for point, or the
circumference of a Polygon).
"""
return capi.geos_length(self.ptr, byref(c_double()))
def clone(self):
"Clone this Geometry."
return GEOSGeometry(capi.geom_clone(self.ptr))
class LinearGeometryMixin:
"""
Used for LineString and MultiLineString.
"""
def interpolate(self, distance):
return self._topology(capi.geos_interpolate(self.ptr, distance))
def interpolate_normalized(self, distance):
return self._topology(capi.geos_interpolate_normalized(self.ptr, distance))
def project(self, point):
from .point import Point
if not isinstance(point, Point):
raise TypeError('locate_point argument must be a Point')
return capi.geos_project(self.ptr, point.ptr)
def project_normalized(self, point):
from .point import Point
if not isinstance(point, Point):
raise TypeError('locate_point argument must be a Point')
return capi.geos_project_normalized(self.ptr, point.ptr)
@property
def merged(self):
"""
Return the line merge of this Geometry.
"""
return self._topology(capi.geos_linemerge(self.ptr))
@property
def closed(self):
"""
Return whether or not this Geometry is closed.
"""
return capi.geos_isclosed(self.ptr)
@deconstructible
class GEOSGeometry(GEOSGeometryBase, ListMixin):
"A class that, generally, encapsulates a GEOS geometry."
def __init__(self, geo_input, srid=None):
"""
The base constructor for GEOS geometry objects. It may take the
following inputs:
* strings:
- WKT
- HEXEWKB (a PostGIS-specific canonical form)
- GeoJSON (requires GDAL)
* buffer:
- WKB
The `srid` keyword specifies the Source Reference Identifier (SRID)
number for this Geometry. If not provided, it defaults to None.
"""
input_srid = None
if isinstance(geo_input, bytes):
geo_input = force_text(geo_input)
if isinstance(geo_input, str):
wkt_m = wkt_regex.match(geo_input)
if wkt_m:
# Handle WKT input.
if wkt_m.group('srid'):
input_srid = int(wkt_m.group('srid'))
g = self._from_wkt(force_bytes(wkt_m.group('wkt')))
elif hex_regex.match(geo_input):
# Handle HEXEWKB input.
g = wkb_r().read(force_bytes(geo_input))
elif json_regex.match(geo_input):
# Handle GeoJSON input.
ogr = gdal.OGRGeometry.from_json(geo_input)
g = ogr._geos_ptr()
input_srid = ogr.srid
else:
raise ValueError('String input unrecognized as WKT EWKT, and HEXEWKB.')
elif isinstance(geo_input, GEOM_PTR):
# When the input is a pointer to a geometry (GEOM_PTR).
g = geo_input
elif isinstance(geo_input, memoryview):
# When the input is a buffer (WKB).
g = wkb_r().read(geo_input)
elif isinstance(geo_input, GEOSGeometry):
g = capi.geom_clone(geo_input.ptr)
else:
raise TypeError('Improper geometry input type: %s' % type(geo_input))
if not g:
raise GEOSException('Could not initialize GEOS Geometry with given input.')
input_srid = input_srid or capi.geos_get_srid(g) or None
if input_srid and srid and input_srid != srid:
raise ValueError('Input geometry already has SRID: %d.' % input_srid)
super().__init__(g, None)
# Set the SRID, if given.
srid = input_srid or srid
if srid and isinstance(srid, int):
self.srid = srid
@@ -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,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,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,53 @@
"""
This module houses the GEOS ctypes prototype functions for the
topological operations on geometries.
"""
from ctypes import c_double, c_int
from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOSFuncFactory
from django.contrib.gis.geos.prototypes.errcheck import (
check_geom, check_minus_one, check_string,
)
from django.contrib.gis.geos.prototypes.geom import geos_char_p
class Topology(GEOSFuncFactory):
"For GEOS unary topology functions."
argtypes = [GEOM_PTR]
restype = GEOM_PTR
errcheck = staticmethod(check_geom)
# Topology Routines
geos_boundary = Topology('GEOSBoundary')
geos_buffer = Topology('GEOSBuffer', argtypes=[GEOM_PTR, c_double, c_int])
geos_bufferwithstyle = Topology('GEOSBufferWithStyle', argtypes=[GEOM_PTR, c_double, c_int, c_int, c_int, c_double])
geos_centroid = Topology('GEOSGetCentroid')
geos_convexhull = Topology('GEOSConvexHull')
geos_difference = Topology('GEOSDifference', argtypes=[GEOM_PTR, GEOM_PTR])
geos_envelope = Topology('GEOSEnvelope')
geos_intersection = Topology('GEOSIntersection', argtypes=[GEOM_PTR, GEOM_PTR])
geos_linemerge = Topology('GEOSLineMerge')
geos_pointonsurface = Topology('GEOSPointOnSurface')
geos_preservesimplify = Topology('GEOSTopologyPreserveSimplify', argtypes=[GEOM_PTR, c_double])
geos_simplify = Topology('GEOSSimplify', argtypes=[GEOM_PTR, c_double])
geos_symdifference = Topology('GEOSSymDifference', argtypes=[GEOM_PTR, GEOM_PTR])
geos_union = Topology('GEOSUnion', argtypes=[GEOM_PTR, GEOM_PTR])
geos_unary_union = GEOSFuncFactory('GEOSUnaryUnion', argtypes=[GEOM_PTR], restype=GEOM_PTR)
# GEOSRelate returns a string, not a geometry.
geos_relate = GEOSFuncFactory(
'GEOSRelate', argtypes=[GEOM_PTR, GEOM_PTR], restype=geos_char_p, errcheck=check_string
)
# Linear referencing routines
geos_project = GEOSFuncFactory(
'GEOSProject', argtypes=[GEOM_PTR, GEOM_PTR], restype=c_double, errcheck=check_minus_one
)
geos_interpolate = Topology('GEOSInterpolate', argtypes=[GEOM_PTR, c_double])
geos_project_normalized = GEOSFuncFactory(
'GEOSProjectNormalized', argtypes=[GEOM_PTR, GEOM_PTR], restype=c_double, errcheck=check_minus_one
)
geos_interpolate_normalized = Topology('GEOSInterpolateNormalized', argtypes=[GEOM_PTR, c_double])
@@ -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 غير مسجّل"
@@ -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 не е регистриран."
@@ -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,93 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Antoni Aloy <aaloy@apsl.net>, 2012,2015-2016
# Ernesto Avilés Vázquez <whippiii@gmail.com>, 2015
# Igor Támara <igor@tamarapatino.org>, 2015
# Jannis Leidel <jannis@leidel.info>, 2011
# Josue Naaman Nistal Guerra <josuenistal@hotmail.com>, 2014
# Marc Garcia <garcia.marc@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: Spanish (http://www.transifex.com/django/django/language/"
"es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "GIS"
msgstr "GIS"
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 base Geometry -- casa con el tipo OpenGis Specification Geometry"
msgid "Point"
msgstr "Punto"
msgid "Line string"
msgstr "Cadena de línea"
msgid "Polygon"
msgstr "Polígono"
msgid "Multi-point"
msgstr "Punto múltiple"
msgid "Multi-line string"
msgstr "Cadena de línea múltiple"
msgid "Multi polygon"
msgstr "Polígono múltiple"
msgid "Geometry collection"
msgstr "Colección de \"Geometry\""
msgid "Extent Aggregate Field"
msgstr "Extensión de campo agregado"
msgid "Raster Field"
msgstr "Campo Raster"
msgid "No geometry value provided."
msgstr "No se indico ningún valor de geometría."
msgid "Invalid geometry value."
msgstr "Valor de geometría inválido."
msgid "Invalid geometry type."
msgstr "Tipo de geometría inválido."
msgid ""
"An error occurred when transforming the geometry to the SRID of the geometry "
"form field."
msgstr ""
"Ocurrió un error al transformar la geometria al SRID de la geometria del "
"campo de formulario."
msgid "Delete all Features"
msgstr "Borrar todos los elementos"
msgid "WKT debugging window:"
msgstr "Ventana de depuración WKT"
msgid "Debugging window (serialized value)"
msgstr "Ventana de depuración (valores serializados)"
msgid "No feeds are registered."
msgstr "No se han registrado canales de contenido."
#, python-format
msgid "Slug %r isn't registered."
msgstr "El slug %r no está registrado."
@@ -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."
@@ -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 ثبت نشده"
@@ -0,0 +1,90 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Claude Paroz <claude@2xlibre.net>, 2014-2015,2017
# Claude Paroz <claude@2xlibre.net>, 2012
# 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: Claude Paroz <claude@2xlibre.net>\n"
"Language-Team: French (http://www.transifex.com/django/django/language/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
msgid "GIS"
msgstr "SIG"
msgid "The base GIS field."
msgstr "Le champ SIG de base."
msgid ""
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
msgstr ""
"Le champ géométrique de base, correspond au type Geometry de la "
"spécification OpenGIS."
msgid "Point"
msgstr "Point"
msgid "Line string"
msgstr "Chaîne de segment"
msgid "Polygon"
msgstr "Polygone"
msgid "Multi-point"
msgstr "Multipoint"
msgid "Multi-line string"
msgstr "Chaîne multisegment"
msgid "Multi polygon"
msgstr "Multipolygone"
msgid "Geometry collection"
msgstr "Collection géométrique"
msgid "Extent Aggregate Field"
msgstr "Champ d'agrégation d'étendue"
msgid "Raster Field"
msgstr "Champ matriciel"
msgid "No geometry value provided."
msgstr "Aucune valeur géométrique fournie."
msgid "Invalid geometry value."
msgstr "Valeur géométrique non valide."
msgid "Invalid geometry type."
msgstr "Type de géométrie non valide."
msgid ""
"An error occurred when transforming the geometry to the SRID of the geometry "
"form field."
msgstr ""
"Une erreur est survenue lors de la transformation de l'objet géométrique "
"dans le SRID du champ de formulaire géométrique."
msgid "Delete all Features"
msgstr "Supprimer toutes les localisations"
msgid "WKT debugging window:"
msgstr "Fenêtre de débogage WKT :"
msgid "Debugging window (serialized value)"
msgstr "Fenêtre de débogage (valeur sérialisée)"
msgid "No feeds are registered."
msgstr "Aucun flux enregistré."
#, python-format
msgid "Slug %r isn't registered."
msgstr "Le slug %r n'est pas enregistré."
@@ -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 ""
@@ -0,0 +1,88 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Jannis Leidel <jannis@leidel.info>, 2011
# Michael Thornhill <michael@maithu.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-23 18:54+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Irish (http://www.transifex.com/django/django/language/ga/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ga\n"
"Plural-Forms: nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : "
"4);\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 "Pointe"
msgid "Line string"
msgstr "Líne teaghrán"
msgid "Polygon"
msgstr "Polagán"
msgid "Multi-point"
msgstr "Il-phointe"
msgid "Multi-line string"
msgstr "Il-líne teaghrán"
msgid "Multi polygon"
msgstr "Il polagán"
msgid "Geometry collection"
msgstr "Céimseata bhailiú"
msgid "Extent Aggregate Field"
msgstr ""
msgid "Raster Field"
msgstr ""
msgid "No geometry value provided."
msgstr "Ní soláthair méid geoiméadracht"
msgid "Invalid geometry value."
msgstr "Méid geoiméadracht neamhbhailí"
msgid "Invalid geometry type."
msgstr "Tíopa geoiméadracht neamhbhailí"
msgid ""
"An error occurred when transforming the geometry to the SRID of the geometry "
"form field."
msgstr ""
"Tharla earráid ag claochlú an geoiméadracht go dtí SRID an réimse fhoirm "
"geoiméadracht."
msgid "Delete all Features"
msgstr ""
msgid "WKT debugging window:"
msgstr ""
msgid "Debugging window (serialized value)"
msgstr ""
msgid "No feeds are registered."
msgstr "Níl fothaí cláraithe."
#, python-format
msgid "Slug %r isn't registered."
msgstr "Níl slug %r cláraithe."
@@ -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."
@@ -0,0 +1,90 @@
# 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: Upper Sorbian (http://www.transifex.com/django/django/"
"language/hsb/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: hsb\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 polo GIS."
msgid ""
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
msgstr ""
"Bazowe geometrijowe polo -- zarysowane do geometrijoweho typa specifikacije "
"OpenGIS. "
msgid "Point"
msgstr "Dypk"
msgid "Line string"
msgstr "Čara"
msgid "Polygon"
msgstr "Wjeleróžk"
msgid "Multi-point"
msgstr "Wjacore dypki"
msgid "Multi-line string"
msgstr "Wjacore čary"
msgid "Multi polygon"
msgstr "Wjacore wjelróžki"
msgid "Geometry collection"
msgstr "Geometrijowa zběrka"
msgid "Extent Aggregate Field"
msgstr "Polo „extent aggregate“"
msgid "Raster Field"
msgstr "Rasterowe polo"
msgid "No geometry value provided."
msgstr "Žana geometrijowa hódnota podata."
msgid "Invalid geometry value."
msgstr "Njepłaćiwa geometrijowa hó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 ""
"Zmylk je wustupił, hdyž so geometrija do SRID pola geometrijoweho formulara "
"přetwori."
msgid "Delete all Features"
msgstr "Wšě funkcije zhašeć"
msgid "WKT debugging window:"
msgstr "Wokno pytanja zmylkow WKT:"
msgid "Debugging window (serialized value)"
msgstr "Wokno pytanja zmylkow (serijalizowana hódnota)"
msgid "No feeds are registered."
msgstr "Žane kanale zregistrowane."
#, python-format
msgid "Slug %r isn't registered."
msgstr "Adresowe mjeno %r njeje zregistrowane."
@@ -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."
@@ -0,0 +1,89 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Martijn Dekker <mcdutchie@hotmail.com>, 2012,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: Interlingua (http://www.transifex.com/django/django/language/"
"ia/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ia\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "GIS"
msgstr ""
msgid "The base GIS field."
msgstr "Le campo GIS de base."
msgid ""
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
msgstr ""
"Le campo Geometry de base. Corresponde al typo geometric del specification "
"OpenGIS."
msgid "Point"
msgstr "Puncto"
msgid "Line string"
msgstr "Texto de linea"
msgid "Polygon"
msgstr "Polygono"
msgid "Multi-point"
msgstr "Plure punctos"
msgid "Multi-line string"
msgstr "Texto con plure lineas"
msgid "Multi polygon"
msgstr "Plure polygonos"
msgid "Geometry collection"
msgstr "Collection geometric"
msgid "Extent Aggregate Field"
msgstr ""
msgid "Raster Field"
msgstr ""
msgid "No geometry value provided."
msgstr "Nulle valor geometric fornite."
msgid "Invalid geometry value."
msgstr "Valor geometric invalide."
msgid "Invalid geometry type."
msgstr "Typo geometric invalide."
msgid ""
"An error occurred when transforming the geometry to the SRID of the geometry "
"form field."
msgstr ""
"Un error occurreva durante le transformation del geometria al SRID del campo "
"del formulario geometric."
msgid "Delete all Features"
msgstr "Deler tote le elementos"
msgid "WKT debugging window:"
msgstr "Fenestra pro debugging WKT:"
msgid "Debugging window (serialized value)"
msgstr "Fenestra pro debugging (valor serialisate)"
msgid "No feeds are registered."
msgstr "Nulle syndication es registrate."
#, python-format
msgid "Slug %r isn't registered."
msgstr "Le denotation %r non es registrate."
@@ -0,0 +1,93 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# yakky <i.spalletti@nephila.it>, 2015
# Jannis Leidel <jannis@leidel.info>, 2011
# Marco Bonetti, 2014
# Nicola Larosa <transifex@teknico.net>, 2012
# palmux <palmux@gmail.com>, 2015
# Mattia Procopio <promat85@gmail.com>, 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: Italian (http://www.transifex.com/django/django/language/"
"it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: it\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "GIS"
msgstr "GIS"
msgid "The base GIS field."
msgstr "Il campo GIS base."
msgid ""
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
msgstr ""
"Il campo base GIS -- corrisponde al tipo Geometry delle specifiche OpenGIS."
msgid "Point"
msgstr "Punto"
msgid "Line string"
msgstr "Stringa linea"
msgid "Polygon"
msgstr "Poligono"
msgid "Multi-point"
msgstr "Multipunto"
msgid "Multi-line string"
msgstr "Stringa multilinea"
msgid "Multi polygon"
msgstr "Multi poligono"
msgid "Geometry collection"
msgstr "Raccolta Geometry"
msgid "Extent Aggregate Field"
msgstr "Campo di aggregazione esteso"
msgid "Raster Field"
msgstr "Campo raster"
msgid "No geometry value provided."
msgstr "Nessun valore geometrico fornito."
msgid "Invalid geometry value."
msgstr "Valore geometrico non valido."
msgid "Invalid geometry type."
msgstr "Tipo geometrico non valido."
msgid ""
"An error occurred when transforming the geometry to the SRID of the geometry "
"form field."
msgstr ""
"Si è verificato un errore durante la trasformazione della geometria nello "
"SRID del campo geometria della form."
msgid "Delete all Features"
msgstr "Cancella tutti gli oggetti"
msgid "WKT debugging window:"
msgstr "Finestra di debug WKT:"
msgid "Debugging window (serialized value)"
msgstr "Finestra di debug (valore serializzato)"
msgid "No feeds are registered."
msgstr "Non ci sono feed registrati."
#, python-format
msgid "Slug %r isn't registered."
msgstr "Lo slug %r non è registrato."
@@ -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: Khmer (http://www.transifex.com/projects/p/django/language/"
"km/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: km\n"
"Plural-Forms: nplurals=1; plural=0;\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 ""
@@ -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: Luxembourgish (http://www.transifex.com/projects/p/django/"
"language/lb/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: lb\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 ""
@@ -0,0 +1,88 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Aby Thomas <abyzthomas@gmail.com>, 2014
# Jannis Leidel <jannis@leidel.info>, 2011
# Rajeesh Nair <rajeeshrnair@gmail.com>, 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: Malayalam (http://www.transifex.com/django/django/language/"
"ml/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ml\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,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: Marathi (http://www.transifex.com/projects/p/django/language/"
"mr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: mr\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 ""
@@ -0,0 +1,85 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Yhal Htet Aung <jumoun@gmail.com>, 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-20 03:01+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Burmese (http://www.transifex.com/django/django/language/"
"my/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: my\n"
"Plural-Forms: nplurals=1; plural=0;\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 ""
@@ -0,0 +1,84 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Sagar Chalise <chalisesagar@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: Nepali (http://www.transifex.com/django/django/language/ne/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ne\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 ""
@@ -0,0 +1,94 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Blue <alexandervanratingen@gmail.com>, 2011
# Harro van der Klauw <hvdklauw@gmail.com>, 2012
# Ilja Maas <iljamaas@dreamsolution.nl>, 2015
# Jannis Leidel <jannis@leidel.info>, 2011
# Jeffrey Gelens <jeffrey@noppo.pro>, 2011
# Sander Steffann <inactive+steffann@transifex.com>, 2015
# Tonnes <tonnes.mb@gmail.com>, 2019
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: 2019-02-24 16:22+0000\n"
"Last-Translator: Tonnes <tonnes.mb@gmail.com>\n"
"Language-Team: Dutch (http://www.transifex.com/django/django/language/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: nl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "GIS"
msgstr "GIS"
msgid "The base GIS field."
msgstr "Het basis-GIS-veld."
msgid ""
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
msgstr ""
"Het basis-Geometrie-veld -- correspondeert met het Geometrie-type van de "
"OpenGIS-specificatie."
msgid "Point"
msgstr "Punt"
msgid "Line string"
msgstr "Tekenreeks"
msgid "Polygon"
msgstr "Polygoon"
msgid "Multi-point"
msgstr "Multipunt"
msgid "Multi-line string"
msgstr "Multi-tekenreeks"
msgid "Multi polygon"
msgstr "Multi-polygoon"
msgid "Geometry collection"
msgstr "Geometrie-verzameling"
msgid "Extent Aggregate Field"
msgstr "Gebieds-aggregatieveld"
msgid "Raster Field"
msgstr "Rasterveld"
msgid "No geometry value provided."
msgstr "Geen geometriewaarde opgegeven."
msgid "Invalid geometry value."
msgstr "Ongeldige geometriewaarde."
msgid "Invalid geometry type."
msgstr "Ongeldig geometrietype."
msgid ""
"An error occurred when transforming the geometry to the SRID of the geometry "
"form field."
msgstr ""
"Er is een fout opgetreden bij het omvormen van de geometrie naar de SRID van "
"het geometrieveld."
msgid "Delete all Features"
msgstr "Alle kenmerken verwijderen"
msgid "WKT debugging window:"
msgstr "WKT-debugvenster:"
msgid "Debugging window (serialized value)"
msgstr "Debugvenster (geserialiseerde waarde)"
msgid "No feeds are registered."
msgstr "Er zijn geen feeds geregistreerd."
#, python-format
msgid "Slug %r isn't registered."
msgstr "Slug %r is niet geregistreerd."
@@ -0,0 +1,95 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Claudio Fernandes <squeral@gmail.com>, 2015
# Jannis Leidel <jannis@leidel.info>, 2011
# jorgecarleitao <jorgecarleitao@gmail.com>, 2015
# Manuela Silva <inactive+h_manuela_rodsilva@transifex.com>, 2015
# Nuno Mariz <nmariz@gmail.com>, 2011-2012,2015
# Paulo Köch <paulo.koch@gmail.com>, 2011
# Raúl Pedro Fernandes Santos, 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 18:54+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Portuguese (http://www.transifex.com/django/django/language/"
"pt/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pt\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "GIS"
msgstr "SIG"
msgid "The base GIS field."
msgstr "O campo GIS base."
msgid ""
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
msgstr ""
"O campo de Geometria base -- mapeia para o tipo de Geometria de "
"Especificação do OpenGIS."
msgid "Point"
msgstr "Ponto"
msgid "Line string"
msgstr "Linha"
msgid "Polygon"
msgstr "Polígono"
msgid "Multi-point"
msgstr "Multi-ponto"
msgid "Multi-line string"
msgstr "Multi-linha"
msgid "Multi polygon"
msgstr "Multi-polígono"
msgid "Geometry collection"
msgstr "Coleção geométrica"
msgid "Extent Aggregate Field"
msgstr "Extender Campo Agregado"
msgid "Raster Field"
msgstr "Campo Raster"
msgid "No geometry value provided."
msgstr "Não foi submetido nenhum valor do tipo geometria."
msgid "Invalid geometry value."
msgstr "Valor inválido de geometria."
msgid "Invalid geometry type."
msgstr "Tipo inválido de geometria."
msgid ""
"An error occurred when transforming the geometry to the SRID of the geometry "
"form field."
msgstr ""
"Ocorreu um erro na transformação da geometria para o SRID da geometria do "
"campo do formulário."
msgid "Delete all Features"
msgstr "Eliminar todas as Caraterísticas"
msgid "WKT debugging window:"
msgstr "Janela de depuração de WKT:"
msgid "Debugging window (serialized value)"
msgstr "Janela de depuração (valor serializado)"
msgid "No feeds are registered."
msgstr "Nenhum feed está registado."
#, python-format
msgid "Slug %r isn't registered."
msgstr "O slug %r não está registado."
@@ -0,0 +1,94 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Allisson Azevedo <allisson@gmail.com>, 2014
# Carlos Leite <caduado@gmail.com>, 2015-2016
# Eduardo Cereto Carvalho, 2011
# semente, 2012
# Jannis Leidel <jannis@leidel.info>, 2011
# Lucas Infante <maccinza@gmail.com>, 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: Portuguese (Brazil) (http://www.transifex.com/django/django/"
"language/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pt_BR\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
msgid "GIS"
msgstr "GIS"
msgid "The base GIS field."
msgstr "O campo GIS base."
msgid ""
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
msgstr ""
"O campo base de geometria -- mapeia para o tipo de geometria da "
"especificação OpenGis"
msgid "Point"
msgstr "Ponto"
msgid "Line string"
msgstr "Linha string"
msgid "Polygon"
msgstr "Polígono"
msgid "Multi-point"
msgstr "Multiponto"
msgid "Multi-line string"
msgstr "Multilinha string"
msgid "Multi polygon"
msgstr "Multipolígono"
msgid "Geometry collection"
msgstr "Coleção geométrica"
msgid "Extent Aggregate Field"
msgstr "Campo agregado extendido"
msgid "Raster Field"
msgstr "Campo Raster."
msgid "No geometry value provided."
msgstr "Nenhum valor geométrico fornecido."
msgid "Invalid geometry value."
msgstr "Valor geométrico inválido."
msgid "Invalid geometry type."
msgstr "Tipo geométrico inválido."
msgid ""
"An error occurred when transforming the geometry to the SRID of the geometry "
"form field."
msgstr ""
"Ocorreu um erro ao transformar a geometria para o SRID do campo de "
"formulário de geometria."
msgid "Delete all Features"
msgstr "Deletar todas os elementos"
msgid "WKT debugging window:"
msgstr "Janela de debug WKT"
msgid "Debugging window (serialized value)"
msgstr "Janela de debug (valor seralizado)"
msgid "No feeds are registered."
msgstr "Nenhum feed foi registrado."
#, python-format
msgid "Slug %r isn't registered."
msgstr "O slug %r não foi registrado."
@@ -0,0 +1,92 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Jannis Leidel <jannis@leidel.info>, 2011
# Primož Verdnik <primoz.verdnik@gmail.com>, 2017
# zejn <zejn@kiberpipa.org>, 2016
# zejn <zejn@kiberpipa.org>, 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-23 18:54+0000\n"
"Last-Translator: Primož Verdnik <primoz.verdnik@gmail.com>\n"
"Language-Team: Slovenian (http://www.transifex.com/django/django/language/"
"sl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: sl\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 "Osnovno GIS polje"
msgid ""
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
msgstr ""
"Osnovno polje GIS -- se preslika v vrsto Geometry po določilih OpenGIS."
msgid "Point"
msgstr "Točka"
msgid "Line string"
msgstr "Črtni niz"
msgid "Polygon"
msgstr "Mnogokotnik"
msgid "Multi-point"
msgstr "Več-točkovi predmet"
msgid "Multi-line string"
msgstr "Več črtni niz"
msgid "Multi polygon"
msgstr "Večkratni mnogokotnikov"
msgid "Geometry collection"
msgstr "Zbirka likov"
msgid "Extent Aggregate Field"
msgstr "Polje z agregiranim območjem"
msgid "Raster Field"
msgstr "Rastersko polje"
msgid "No geometry value provided."
msgstr "Ni navedene geometrijske vrednosti."
msgid "Invalid geometry value."
msgstr "Neveljavna geometrijska vrednost."
msgid "Invalid geometry type."
msgstr "Neveljavna vrsta geometrije."
msgid ""
"An error occurred when transforming the geometry to the SRID of the geometry "
"form field."
msgstr ""
"Med pretvarjanjem geometrijskega zapisa v SRID geometrijskega polja je "
"prišlo do napake."
msgid "Delete all Features"
msgstr "Pobriši vse atribute"
msgid "WKT debugging window:"
msgstr "Okno za razhroščevanje WKT"
msgid "Debugging window (serialized value)"
msgstr "Okno za razhroščevanje (serializirana vrednost)"
msgid "No feeds are registered."
msgstr "Ni vpisanih virov."
#, python-format
msgid "Slug %r isn't registered."
msgstr "Okrajšava %r ni vpisana."
@@ -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: Tamil (http://www.transifex.com/projects/p/django/language/"
"ta/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ta\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 ""
@@ -0,0 +1,84 @@
# 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: Telugu (http://www.transifex.com/django/django/language/te/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: te\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 ""
@@ -0,0 +1,86 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Jannis Leidel <jannis@leidel.info>, 2011
# Kowit Charoenratchatabhan <kowit.s.c@gmail.com>, 2012
# Vichai Vongvorakul <vongvichai@gmail.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: Thai (http://www.transifex.com/django/django/language/th/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: th\n"
"Plural-Forms: nplurals=1; plural=0;\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 "Slug %r ไม่ได้ลงทะเบียน"
@@ -0,0 +1,91 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# BouRock, 2015
# BouRock, 2014-2015
# Jannis Leidel <jannis@leidel.info>, 2011
# Murat Çorlu <muratcorlu@gmail.com>, 2012
# Murat Sahin <martinamca@gmail.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-23 18:54+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Turkish (http://www.transifex.com/django/django/language/"
"tr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: tr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
msgid "GIS"
msgstr "GIS"
msgid "The base GIS field."
msgstr "Temel GIS alanı."
msgid ""
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
msgstr "Temel Geometri alanı - OpenGIS Özellikleri Geometri türüyle eşlenir."
msgid "Point"
msgstr "Nokta"
msgid "Line string"
msgstr "Satır dizgesi"
msgid "Polygon"
msgstr "Çokgen"
msgid "Multi-point"
msgstr "Çok noktalı"
msgid "Multi-line string"
msgstr "Çok satırlı dizge"
msgid "Multi polygon"
msgstr "Çoklu çokgen"
msgid "Geometry collection"
msgstr "Geometri koleksiyonu"
msgid "Extent Aggregate Field"
msgstr "Toplama Alanını Genişlet"
msgid "Raster Field"
msgstr "Tarama Alanı"
msgid "No geometry value provided."
msgstr "Verilen hiç geometri değeri yok."
msgid "Invalid geometry value."
msgstr "Geçersiz geometri değeri."
msgid "Invalid geometry type."
msgstr "Geçersiz geometri türü."
msgid ""
"An error occurred when transforming the geometry to the SRID of the geometry "
"form field."
msgstr ""
"Geometri verisi geometri form alanının SRID değerine dönüştürülürken bir "
"hata meydana geldi."
msgid "Delete all Features"
msgstr "Tüm Özellikleri Sil"
msgid "WKT debugging window:"
msgstr "WKT hata ayıklama penceresi:"
msgid "Debugging window (serialized value)"
msgstr "Hata ayıklama penceresi (serileştirilmiş değer)"
msgid "No feeds are registered."
msgstr "Hiçbir besleme kayıtlı değil."
#, python-format
msgid "Slug %r isn't registered."
msgstr "%r kısaltması kayıtlı değil."
@@ -0,0 +1,86 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Mansoorulhaq Mansoor <mnsrknp@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: Urdu (http://www.transifex.com/django/django/language/ur/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ur\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 ""
@@ -0,0 +1,92 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Daniel Duan <DaNmarner@gmail.com>, 2011
# Jannis Leidel <jannis@leidel.info>, 2011
# Lei Yang <yanglei.go@gmail.com>, 2011
# Lele Long <schemacs@gmail.com>, 2011,2015
# mozillazg <opensource.mozillazg@gmail.com>, 2016
# Ronald White <tkliuxing@me.com>, 2014
# Kevin Sze <leiarix@gmail.com>, 2012
# 航 柯 <korhang@163.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-10-15 08:06+0000\n"
"Last-Translator: 航 柯 <korhang@163.com>\n"
"Language-Team: Chinese (China) (http://www.transifex.com/django/django/"
"language/zh_CN/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "GIS"
msgstr "GIS"
msgid "The base GIS field."
msgstr "基础 GIS 字段。"
msgid ""
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
msgstr "基础 Geometry 字段 -- 映射到 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 "Slug %r 没有注册."
@@ -0,0 +1,89 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Chen Chun-Chia <ccc.larc@gmail.com>, 2015
# ilay <ilay@ilay.tw>, 2012
# Jannis Leidel <jannis@leidel.info>, 2011
# quantum9876 <quantum9876@gmail.com>, 2011
# Tzu-ping Chung <uranusjr@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: Chinese (Taiwan) (http://www.transifex.com/django/django/"
"language/zh_TW/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: zh_TW\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "GIS"
msgstr "GIS"
msgid "The base GIS field."
msgstr "基礎 GIS 欄位。"
msgid ""
"The base Geometry field -- maps to the OpenGIS Specification Geometry type."
msgstr "基本的 Geometry 欄位——對應於 OpenGIS 規格的 Geometry 類型。"
msgid "Point"
msgstr "Point 類別"
msgid "Line string"
msgstr "Line string 類別"
msgid "Polygon"
msgstr "Polygon 類別"
msgid "Multi-point"
msgstr "Multi-point 類別"
msgid "Multi-line string"
msgstr "Multi-line string 類別"
msgid "Multi polygon"
msgstr "Multi polygon 類別"
msgid "Geometry collection"
msgstr "Geometry collection (幾何型別之叢集) 類別"
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 "當把目前地理資訊-GIS,轉成空間參考識別碼-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 尚未註冊。"
@@ -0,0 +1,4 @@
# Geo-enabled Sitemap classes.
from django.contrib.gis.sitemaps.kml import KMLSitemap, KMZSitemap
__all__ = ['KMLSitemap', 'KMZSitemap']

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