Skip to content

Commit

Permalink
gis: added additional units (including WKT aliases) and the `unit_att…
Browse files Browse the repository at this point in the history
…name` class method to `Distance`.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6865 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
jbronn committed Dec 3, 2007
1 parent 1c45c6f commit d2fd4f0
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 32 deletions.
159 changes: 131 additions & 28 deletions django/contrib/gis/measure.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,106 @@

class Distance(object):
UNITS = {
'm': 1.0,
'chain' : 20.1168,
'chain_benoit' : 20.116782,
'chain_sears' : 20.1167645,
'british_chain_benoit' : 20.1167824944,
'british_chain_sears' : 20.1167651216,
'british_chain_sears_truncated' : 20.116756,
'cm' : 0.01,
'british_ft' : 0.304799471539,
'british_yd' : 0.914398414616,
'degree' : 0.0174532925199,
'clarke_ft' : 0.3047972654,
'clarke_link' : 0.201166195164,
'fathom' : 1.8288,
'ft': 0.3048,
'german_m' : 1.0000135965,
'grad' : 0.0157079632679,
'gold_coast_ft' : 0.304799710181508,
'indian_yd' : 0.914398530744,
'in' : 0.0254,
'km': 1000.0,
'link' : 0.201168,
'link_benoit' : 0.20116782,
'link_sears' : 0.20116765,
'm': 1.0,
'mi': 1609.344,
'ft': 0.3048,
'yd': 0.9144,
'mm' : 0.001,
'nm': 1852.0,
}
'nm_uk' : 1853.184,
'rod' : 5.0292,
'sears_yd' : 0.91439841,
'survey_ft' : 0.304800609601,
'um' : 0.000001,
'yd': 0.9144,
}

# Unit aliases for `UNIT` terms encountered in Spatial Reference WKT.
ALIAS = {
'centimeter' : 'cm',
'foot' : 'ft',
'inches' : 'in',
'kilometer' : 'km',
'kilometre' : 'km',
'meter' : 'm',
'metre' : 'm',
'micrometer' : 'um',
'micrometre' : 'um',
'millimeter' : 'mm',
'millimetre' : 'mm',
'mile' : 'mi',
'yard' : 'yd',
'British chain (Benoit 1895 B)' : 'british_chain_benoit',
'British chain (Sears 1922)' : 'british_chain_sears',
'British chain (Sears 1922 truncated)' : 'british_chain_sears_truncated',
'British foot (Sears 1922)' : 'british_ft',
'British yard (Sears 1922)' : 'british_yd',
"Clarke's Foot" : 'clarke_ft',
"Clarke's foot" : 'clarke_ft',
"Clarke's link" : 'clarke_link',
'Chain (Benoit)' : 'chain_benoit',
'Chain (Sears)' : 'chain_sears',
'Decimal Degree' : 'degree',
'Foot (International)' : 'ft',
'German legal metre' : 'german_m',
'Gold Coast foot' : 'gold_coast_ft',
'Indian yard' : 'indian_yd',
'Link (Benoit)': 'link_benoit',
'Link (Sears)': 'link_sears',
'Nautical Mile' : 'nm',
'Nautical Mile (UK)' : 'nm_uk',
'US survey foot' : 'survey_ft',
'U.S. Foot' : 'survey_ft',
'Yard (Indian)' : 'indian_yd',
'Yard (Sears)' : 'sears_yd'
}
REV_ALIAS = dict((value, key) for key, value in ALIAS.items())

def __init__(self, default_unit=None, **kwargs):
# The base unit is in meters.
self.m = 0.0
self._default_unit = 'm'

for unit,value in kwargs.items():
if unit in self.UNITS:
self.m += self.UNITS[unit] * value
self._default_unit = unit
elif unit in self.ALIAS:
u = self.ALIAS[unit]
self.m += self.UNITS[u] * value
self._default_unit = u
else:
raise AttributeError("Unknown unit type: " + unit)
lower = unit.lower()
if lower in self.UNITS:
self.m += self.UNITS[lower] * value
self._default_unit = lower
elif lower in self.ALIAS:
u = self.ALIAS[lower]
self.m += self.UNITS[u] * value
self._default_unit = u
else:
raise AttributeError('Unknown unit type: %s' % unit)

if default_unit and isinstance(default_unit, str):
self._default_unit = default_unit
Expand All @@ -65,13 +147,13 @@ def __getattr__(self, name):
if name in self.UNITS:
return self.m / self.UNITS[name]
else:
raise AttributeError("Unknown unit type: " + name)
raise AttributeError('Unknown unit type: %s' % name)

def __repr__(self):
return "Distance(%s=%s)" % (self._default_unit, getattr(self, self._default_unit))
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)
return '%s %s' % (getattr(self, self._default_unit), self._default_unit)

def __cmp__(self, other):
if isinstance(other, Distance):
Expand All @@ -83,60 +165,81 @@ 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")
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")
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")
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")
raise TypeError('Distance must be subtracted from Distance')

def __mul__(self, other):
if isinstance(other, (int, float, long, Decimal)):
return Distance(default_unit=self._default_unit, m=(self.m * float(other)))
elif isinstance(other, Distance):
return Area(default_unit='sq_' + self._default_unit, sq_m=(self.m * other.m))
else:
raise TypeError("Distance must be multiplied with number or Distance")
raise TypeError('Distance must be multiplied with number or Distance')

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")
raise TypeError('Distance must be multiplied with number')

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")
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")
raise TypeError('Distance must be divided with number')

def __nonzero__(self):
return bool(self.m)

@classmethod
def unit_attname(cls, unit_str):
"""
Retrieves the unit attribute name for the given unit string.
For example, if the given unit string is 'metre', 'm' would be returned.
An exception is raised if an attribute cannot be found.
"""
lower = unit_str.lower()

if unit_str in cls.UNITS:
return unit_str
elif lower in cls.UNITS:
return lower
elif unit_str in cls.ALIAS:
return cls.ALIAS[unit_str]
elif lower in cls.ALIAS:
return cls.ALIAS[lower]
else:
raise Exception('Could not find a unit keyword associated with "%s"' % unit_str)

class Area(object):
# TODO: Add units from above.
UNITS = {
'sq_m': 1.0,
'sq_km': 1000000.0,
Expand All @@ -155,7 +258,7 @@ def __init__(self, default_unit=None, **kwargs):
self.sq_m += self.UNITS[unit] * value
self._default_unit = unit
else:
raise AttributeError("Unknown unit type: " + unit)
raise AttributeError('Unknown unit type: ' + unit)

if default_unit:
self._default_unit = default_unit
Expand All @@ -164,13 +267,13 @@ def __getattr__(self, name):
if name in self.UNITS:
return self.sq_m / self.UNITS[name]
else:
raise AttributeError("Unknown unit type: " + name)
raise AttributeError('Unknown unit type: ' + name)

def __repr__(self):
return "Area(%s=%s)" % (self._default_unit, getattr(self, self._default_unit))
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)
return '%s %s' % (getattr(self, self._default_unit), self._default_unit)

def __cmp__(self, other):
if isinstance(other, Area):
Expand All @@ -182,53 +285,53 @@ 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")
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")
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")
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")
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")
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")
raise TypeError('Area must be multiplied with number')

def __div__(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 divided with number")
raise TypeError('Area must be divided with number')

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")
raise TypeError('Area must be divided with number')

def __nonzero__(self):
return bool(self.sq_m)
Expand Down
26 changes: 22 additions & 4 deletions django/contrib/gis/tests/test_measure.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,22 @@ def testInit(self):
d = Distance(m=100)
self.assertEqual(d.m, 100)

d = D(m=100)
self.assertEqual(d.m, 100)

d1, d2, d3 = D(m=100), D(meter=100), D(metre=100)
for d in (d1, d2, d3):
self.assertEqual(d.m, 100)

d = D(nm=100)
self.assertEqual(d.m, 185200)

y1, y2, y3 = D(yd=100), D(yard=100), D(Yard=100)
for d in (y1, y2, y3):
self.assertEqual(d.yd, 100)

mm1, mm2 = D(millimeter=1000), D(MiLLiMeTeR=1000)
for d in (mm1, mm2):
self.assertEqual(d.m, 1.0)
self.assertEqual(d.mm, 1000.0)


def testInitInvalid(self):
"Testing initialisation from invalid units"
Expand Down Expand Up @@ -152,6 +163,13 @@ def testUnitsStr(self):
self.assertEqual(repr(d1), 'Distance(m=100.0)')
self.assertEqual(repr(d2), 'Distance(km=3.5)')

def testUnitAttName(self):
"Testing the `unit_attname` class method"
unit_tuple = [('Yard', 'yd'), ('Nautical Mile', 'nm'), ('German legal metre', 'german_m'),
('Indian yard', 'indian_yd'), ('Chain (Sears)', 'chain_sears'), ('Chain', 'chain')]
for nm, att in unit_tuple:
self.assertEqual(att, D.unit_attname(nm))

class AreaTest(unittest.TestCase):
"Testing the Area object"

Expand Down Expand Up @@ -312,4 +330,4 @@ def run(verbosity=2):
unittest.TextTestRunner(verbosity=verbosity).run(suite())

if __name__=="__main__":
run()
run()

0 comments on commit d2fd4f0

Please sign in to comment.