Fixed #17754 -- Refactored gis.measure

This refactoring does allow much easier MeasureBase subclassing.
Many thanks to Ricardo di Virgilio for the initial patch.
This commit is contained in:
Claude Paroz 2012-06-14 15:24:47 +02:00
parent fe873e2765
commit 4d46106f8c
3 changed files with 142 additions and 178 deletions

View File

@ -161,6 +161,7 @@ answer newbie questions, and generally made Django that much better:
Rajesh Dhawan <rajesh.dhawan@gmail.com> Rajesh Dhawan <rajesh.dhawan@gmail.com>
Sander Dijkhuis <sander.dijkhuis@gmail.com> Sander Dijkhuis <sander.dijkhuis@gmail.com>
Jordan Dimov <s3x3y1@gmail.com> Jordan Dimov <s3x3y1@gmail.com>
Riccardo Di Virgilio
Nebojša Dorđević Nebojša Dorđević
dne@mayonnaise.net dne@mayonnaise.net
dready <wil@mojipage.com> dready <wil@mojipage.com>

View File

@ -30,7 +30,7 @@
Distance and Area objects to allow for sensible and convienient calculation Distance and Area objects to allow for sensible and convienient calculation
and conversions. and conversions.
Authors: Robert Coup, Justin Bronn Authors: Robert Coup, Justin Bronn, Riccardo Di Virgilio
Inspired by GeoPy (http://exogen.case.edu/projects/geopy/) Inspired by GeoPy (http://exogen.case.edu/projects/geopy/)
and Geoff Biggs' PhD work on dimensioned units for robotics. and Geoff Biggs' PhD work on dimensioned units for robotics.
@ -40,13 +40,134 @@ from decimal import Decimal
from django.utils.functional import total_ordering from django.utils.functional import total_ordering
NUMERIC_TYPES = (int, float, long, Decimal)
AREA_PREFIX = "sq_"
def pretty_name(obj):
return obj.__name__ if obj.__class__ == type else obj.__class__.__name__
@total_ordering
class MeasureBase(object): class MeasureBase(object):
STANDARD_UNIT = None
ALIAS = {}
UNITS = {}
LALIAS = {}
def __init__(self, default_unit=None, **kwargs):
value, self._default_unit = self.default_units(kwargs)
setattr(self, self.STANDARD_UNIT, value)
if default_unit and isinstance(default_unit, basestring):
self._default_unit = default_unit
def _get_standard(self):
return getattr(self, self.STANDARD_UNIT)
def _set_standard(self, value):
setattr(self, self.STANDARD_UNIT, value)
standard = property(_get_standard, _set_standard)
def __getattr__(self, name):
if name in self.UNITS:
return self.standard / self.UNITS[name]
else:
raise AttributeError('Unknown unit type: %s' % name)
def __repr__(self):
return '%s(%s=%s)' % (pretty_name(self), self._default_unit,
getattr(self, self._default_unit))
def __str__(self):
return '%s %s' % (getattr(self, self._default_unit), self._default_unit)
# **** Comparison methods ****
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.standard == other.standard
else:
return NotImplemented
def __lt__(self, other):
if isinstance(other, self.__class__):
return self.standard < other.standard
else:
return NotImplemented
# **** Operators methods ****
def __add__(self, other):
if isinstance(other, self.__class__):
return self.__class__(default_unit=self._default_unit,
**{self.STANDARD_UNIT: (self.standard + other.standard)})
else:
raise TypeError('%(class)s must be added with %(class)s' % {"class":pretty_name(self)})
def __iadd__(self, other):
if isinstance(other, self.__class__):
self.standard += other.standard
return self
else:
raise TypeError('%(class)s must be added with %(class)s' % {"class":pretty_name(self)})
def __sub__(self, other):
if isinstance(other, self.__class__):
return self.__class__(default_unit=self._default_unit,
**{self.STANDARD_UNIT: (self.standard - other.standard)})
else:
raise TypeError('%(class)s must be subtracted from %(class)s' % {"class":pretty_name(self)})
def __isub__(self, other):
if isinstance(other, self.__class__):
self.standard -= other.standard
return self
else:
raise TypeError('%(class)s must be subtracted from %(class)s' % {"class":pretty_name(self)})
def __mul__(self, other):
if isinstance(other, NUMERIC_TYPES):
return self.__class__(default_unit=self._default_unit,
**{self.STANDARD_UNIT: (self.standard * other)})
else:
raise TypeError('%(class)s must be multiplied with number' % {"class":pretty_name(self)})
def __imul__(self, other):
if isinstance(other, NUMERIC_TYPES):
self.standard *= float(other)
return self
else:
raise TypeError('%(class)s must be multiplied with number' % {"class":pretty_name(self)})
def __rmul__(self, other):
return self * other
def __div__(self, other):
if isinstance(other, self.__class__):
return self.standard / other.standard
if isinstance(other, NUMERIC_TYPES):
return self.__class__(default_unit=self._default_unit,
**{self.STANDARD_UNIT: (self.standard / other)})
else:
raise TypeError('%(class)s must be divided with number or %(class)s' % {"class":pretty_name(self)})
def __idiv__(self, other):
if isinstance(other, NUMERIC_TYPES):
self.standard /= float(other)
return self
else:
raise TypeError('%(class)s must be divided with number' % {"class":pretty_name(self)})
def __nonzero__(self):
return bool(self.standard)
def default_units(self, kwargs): def default_units(self, kwargs):
""" """
Return the unit value and the default units specified Return the unit value and the default units specified
from the given keyword arguments dictionary. from the given keyword arguments dictionary.
""" """
val = 0.0 val = 0.0
default_unit = self.STANDARD_UNIT
for unit, value in kwargs.iteritems(): for unit, value in kwargs.iteritems():
if not isinstance(value, float): value = float(value) if not isinstance(value, float): value = float(value)
if unit in self.UNITS: if unit in self.UNITS:
@ -86,8 +207,8 @@ class MeasureBase(object):
else: else:
raise Exception('Could not find a unit keyword associated with "%s"' % unit_str) raise Exception('Could not find a unit keyword associated with "%s"' % unit_str)
@total_ordering
class Distance(MeasureBase): class Distance(MeasureBase):
STANDARD_UNIT = "m"
UNITS = { UNITS = {
'chain' : 20.1168, 'chain' : 20.1168,
'chain_benoit' : 20.116782, 'chain_benoit' : 20.116782,
@ -163,189 +284,33 @@ class Distance(MeasureBase):
} }
LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()]) LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])
def __init__(self, default_unit=None, **kwargs):
# The base unit is in meters.
self.m, self._default_unit = self.default_units(kwargs)
if default_unit and isinstance(default_unit, str):
self._default_unit = default_unit
def __getattr__(self, name):
if name in self.UNITS:
return self.m / self.UNITS[name]
else:
raise AttributeError('Unknown unit type: %s' % name)
def __repr__(self):
return 'Distance(%s=%s)' % (self._default_unit, getattr(self, self._default_unit))
def __str__(self):
return '%s %s' % (getattr(self, self._default_unit), self._default_unit)
def __eq__(self, other):
if isinstance(other, Distance):
return self.m == other.m
else:
return NotImplemented
def __lt__(self, other):
if isinstance(other, Distance):
return self.m < other.m
else:
return NotImplemented
def __add__(self, other):
if isinstance(other, Distance):
return Distance(default_unit=self._default_unit, m=(self.m + other.m))
else:
raise TypeError('Distance must be added with Distance')
def __iadd__(self, other):
if isinstance(other, Distance):
self.m += other.m
return self
else:
raise TypeError('Distance must be added with Distance')
def __sub__(self, other):
if isinstance(other, Distance):
return Distance(default_unit=self._default_unit, m=(self.m - other.m))
else:
raise TypeError('Distance must be subtracted from Distance')
def __isub__(self, other):
if isinstance(other, Distance):
self.m -= other.m
return self
else:
raise TypeError('Distance must be subtracted from Distance')
def __mul__(self, other): def __mul__(self, other):
if isinstance(other, (int, float, long, Decimal)): if isinstance(other, self.__class__):
return Distance(default_unit=self._default_unit, m=(self.m * float(other))) return Area(default_unit=AREA_PREFIX + self._default_unit,
elif isinstance(other, Distance): **{AREA_PREFIX + self.STANDARD_UNIT: (self.standard * other.standard)})
return Area(default_unit='sq_' + self._default_unit, sq_m=(self.m * other.m)) elif isinstance(other, NUMERIC_TYPES):
return self.__class__(default_unit=self._default_unit,
**{self.STANDARD_UNIT: (self.standard * other)})
else: else:
raise TypeError('Distance must be multiplied with number or Distance') raise TypeError('%(distance)s must be multiplied with number or %(distance)s' % {
"distance" : pretty_name(self.__class__),
})
def __imul__(self, other):
if isinstance(other, (int, float, long, Decimal)):
self.m *= float(other)
return self
else:
raise TypeError('Distance must be multiplied with number')
def __rmul__(self, other):
return self * other
def __div__(self, other):
if isinstance(other, (int, float, long, Decimal)):
return Distance(default_unit=self._default_unit, m=(self.m / float(other)))
else:
raise TypeError('Distance must be divided with number')
def __idiv__(self, other):
if isinstance(other, (int, float, long, Decimal)):
self.m /= float(other)
return self
else:
raise TypeError('Distance must be divided with number')
def __nonzero__(self):
return bool(self.m)
@total_ordering
class Area(MeasureBase): class Area(MeasureBase):
STANDARD_UNIT = AREA_PREFIX + Distance.STANDARD_UNIT
# Getting the square units values and the alias dictionary. # Getting the square units values and the alias dictionary.
UNITS = dict([('sq_%s' % k, v ** 2) for k, v in Distance.UNITS.items()]) UNITS = dict([('%s%s' % (AREA_PREFIX, k), v ** 2) for k, v in Distance.UNITS.items()])
ALIAS = dict([(k, 'sq_%s' % v) for k, v in Distance.ALIAS.items()]) ALIAS = dict([(k, '%s%s' % (AREA_PREFIX, v)) for k, v in Distance.ALIAS.items()])
LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()]) LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])
def __init__(self, default_unit=None, **kwargs):
self.sq_m, self._default_unit = self.default_units(kwargs)
if default_unit and isinstance(default_unit, str):
self._default_unit = default_unit
def __getattr__(self, name):
if name in self.UNITS:
return self.sq_m / self.UNITS[name]
else:
raise AttributeError('Unknown unit type: ' + name)
def __repr__(self):
return 'Area(%s=%s)' % (self._default_unit, getattr(self, self._default_unit))
def __str__(self):
return '%s %s' % (getattr(self, self._default_unit), self._default_unit)
def __eq__(self, other):
if isinstance(other, Area):
return self.sq_m == other.sq_m
else:
return NotImplemented
def __lt__(self, other):
if isinstance(other, Area):
return self.sq_m < other.sq_m
else:
return NotImplemented
def __add__(self, other):
if isinstance(other, Area):
return Area(default_unit=self._default_unit, sq_m=(self.sq_m + other.sq_m))
else:
raise TypeError('Area must be added with Area')
def __iadd__(self, other):
if isinstance(other, Area):
self.sq_m += other.sq_m
return self
else:
raise TypeError('Area must be added with Area')
def __sub__(self, other):
if isinstance(other, Area):
return Area(default_unit=self._default_unit, sq_m=(self.sq_m - other.sq_m))
else:
raise TypeError('Area must be subtracted from Area')
def __isub__(self, other):
if isinstance(other, Area):
self.sq_m -= other.sq_m
return self
else:
raise TypeError('Area must be subtracted from Area')
def __mul__(self, other):
if isinstance(other, (int, float, long, Decimal)):
return Area(default_unit=self._default_unit, sq_m=(self.sq_m * float(other)))
else:
raise TypeError('Area must be multiplied with number')
def __imul__(self, other):
if isinstance(other, (int, float, long, Decimal)):
self.sq_m *= float(other)
return self
else:
raise TypeError('Area must be multiplied with number')
def __rmul__(self, other):
return self * other
def __div__(self, other): def __div__(self, other):
if isinstance(other, (int, float, long, Decimal)): if isinstance(other, NUMERIC_TYPES):
return Area(default_unit=self._default_unit, sq_m=(self.sq_m / float(other))) return self.__class__(default_unit=self._default_unit,
**{self.STANDARD_UNIT: (self.standard / other)})
else: else:
raise TypeError('Area must be divided with number') raise TypeError('%(class)s must be divided by a number' % {"class":pretty_name(self)})
def __idiv__(self, other):
if isinstance(other, (int, float, long, Decimal)):
self.sq_m /= float(other)
return self
else:
raise TypeError('Area must be divided with number')
def __nonzero__(self):
return bool(self.sq_m)
# Shortcuts # Shortcuts
D = Distance D = Distance

View File

@ -93,6 +93,8 @@ class DistanceTest(unittest.TestCase):
self.assertEqual(d4.m, 50) self.assertEqual(d4.m, 50)
d4 /= 5 d4 /= 5
self.assertEqual(d4.m, 10) self.assertEqual(d4.m, 10)
d5 = d1 / D(m=2)
self.assertEqual(d5, 50)
a5 = d1 * D(m=10) a5 = d1 * D(m=10)
self.assertTrue(isinstance(a5, Area)) self.assertTrue(isinstance(a5, Area))
@ -102,10 +104,6 @@ class DistanceTest(unittest.TestCase):
d1 *= D(m=1) d1 *= D(m=1)
self.fail('Distance *= Distance should raise TypeError') self.fail('Distance *= Distance should raise TypeError')
with self.assertRaises(TypeError):
d5 = d1 / D(m=1)
self.fail('Distance / Distance should raise TypeError')
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
d1 /= D(m=1) d1 /= D(m=1)
self.fail('Distance /= Distance should raise TypeError') self.fail('Distance /= Distance should raise TypeError')