field.py 6.76 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
from ctypes import byref, c_int
from datetime import date, datetime, time

from django.contrib.gis.gdal.base import GDALBase
from django.contrib.gis.gdal.error import GDALException
from django.contrib.gis.gdal.prototypes import ds as capi
from django.utils.encoding import force_str


# For more information, see the OGR C API source code:
#  https://gdal.org/api/vector_c_api.html
#
# The OGR_Fld_* routines are relevant here.
class Field(GDALBase):
    """
    Wrap an OGR Field. Needs to be instantiated from a Feature object.
    """

    def __init__(self, feat, index):
        """
        Initialize on the feature object and the integer index of
        the field within the feature.
        """
        # Setting the feature pointer and index.
        self._feat = feat
        self._index = index

        # Getting the pointer for this field.
        fld_ptr = capi.get_feat_field_defn(feat.ptr, index)
        if not fld_ptr:
            raise GDALException("Cannot create OGR Field, invalid pointer given.")
        self.ptr = fld_ptr

        # Setting the class depending upon the OGR Field Type (OFT)
        self.__class__ = OGRFieldTypes[self.type]

    def __str__(self):
        "Return the string representation of the Field."
        return str(self.value).strip()

    # #### Field Methods ####
    def as_double(self):
        "Retrieve the Field's value as a double (float)."
        return (
            capi.get_field_as_double(self._feat.ptr, self._index)
            if self.is_set
            else None
        )

    def as_int(self, is_64=False):
        "Retrieve the Field's value as an integer."
        if is_64:
            return (
                capi.get_field_as_integer64(self._feat.ptr, self._index)
                if self.is_set
                else None
            )
        else:
            return (
                capi.get_field_as_integer(self._feat.ptr, self._index)
                if self.is_set
                else None
            )

    def as_string(self):
        "Retrieve the Field's value as a string."
        if not self.is_set:
            return None
        string = capi.get_field_as_string(self._feat.ptr, self._index)
        return force_str(string, encoding=self._feat.encoding, strings_only=True)

    def as_datetime(self):
        "Retrieve the Field's value as a tuple of date & time components."
        if not self.is_set:
            return None
        yy, mm, dd, hh, mn, ss, tz = [c_int() for i in range(7)]
        status = capi.get_field_as_datetime(
            self._feat.ptr,
            self._index,
            byref(yy),
            byref(mm),
            byref(dd),
            byref(hh),
            byref(mn),
            byref(ss),
            byref(tz),
        )
        if status:
            return (yy, mm, dd, hh, mn, ss, tz)
        else:
            raise GDALException(
                "Unable to retrieve date & time information from the field."
            )

    # #### Field Properties ####
    @property
    def is_set(self):
        "Return True if the value of this field isn't null, False otherwise."
        return capi.is_field_set(self._feat.ptr, self._index)

    @property
    def name(self):
        "Return the name of this Field."
        name = capi.get_field_name(self.ptr)
        return force_str(name, encoding=self._feat.encoding, strings_only=True)

    @property
    def precision(self):
        "Return the precision of this Field."
        return capi.get_field_precision(self.ptr)

    @property
    def type(self):
        "Return the OGR type of this Field."
        return capi.get_field_type(self.ptr)

    @property
    def type_name(self):
        "Return the OGR field type name for this Field."
        return capi.get_field_type_name(self.type)

    @property
    def value(self):
        "Return the value of this Field."
        # Default is to get the field as a string.
        return self.as_string()

    @property
    def width(self):
        "Return the width of this Field."
        return capi.get_field_width(self.ptr)


# ### The Field sub-classes for each OGR Field type. ###
class OFTInteger(Field):
    _bit64 = False

    @property
    def value(self):
        "Return an integer contained in this field."
        return self.as_int(self._bit64)

    @property
    def type(self):
        """
        GDAL uses OFTReals to represent OFTIntegers in created
        shapefiles -- forcing the type here since the underlying field
        type may actually be OFTReal.
        """
        return 0


class OFTReal(Field):
    @property
    def value(self):
        "Return a float contained in this field."
        return self.as_double()


# String & Binary fields, just subclasses
class OFTString(Field):
    pass


class OFTWideString(Field):
    pass


class OFTBinary(Field):
    pass


# OFTDate, OFTTime, OFTDateTime fields.
class OFTDate(Field):
    @property
    def value(self):
        "Return a Python `date` object for the OFTDate field."
        try:
            yy, mm, dd, hh, mn, ss, tz = self.as_datetime()
            return date(yy.value, mm.value, dd.value)
        except (TypeError, ValueError, GDALException):
            return None


class OFTDateTime(Field):
    @property
    def value(self):
        "Return a Python `datetime` object for this OFTDateTime field."
        # TODO: Adapt timezone information.
        #  See https://lists.osgeo.org/pipermail/gdal-dev/2006-February/007990.html
        #  The `tz` variable has values of: 0=unknown, 1=localtime (ambiguous),
        #  100=GMT, 104=GMT+1, 80=GMT-5, etc.
        try:
            yy, mm, dd, hh, mn, ss, tz = self.as_datetime()
            return datetime(yy.value, mm.value, dd.value, hh.value, mn.value, ss.value)
        except (TypeError, ValueError, GDALException):
            return None


class OFTTime(Field):
    @property
    def value(self):
        "Return a Python `time` object for this OFTTime field."
        try:
            yy, mm, dd, hh, mn, ss, tz = self.as_datetime()
            return time(hh.value, mn.value, ss.value)
        except (ValueError, GDALException):
            return None


class OFTInteger64(OFTInteger):
    _bit64 = True


# List fields are also just subclasses
class OFTIntegerList(Field):
    pass


class OFTRealList(Field):
    pass


class OFTStringList(Field):
    pass


class OFTWideStringList(Field):
    pass


class OFTInteger64List(Field):
    pass


# Class mapping dictionary for OFT Types and reverse mapping.
OGRFieldTypes = {
    0: OFTInteger,
    1: OFTIntegerList,
    2: OFTReal,
    3: OFTRealList,
    4: OFTString,
    5: OFTStringList,
    6: OFTWideString,
    7: OFTWideStringList,
    8: OFTBinary,
    9: OFTDate,
    10: OFTTime,
    11: OFTDateTime,
    # New 64-bit integer types in GDAL 2
    12: OFTInteger64,
    13: OFTInteger64List,
}
ROGRFieldTypes = {cls: num for num, cls in OGRFieldTypes.items()}