Ticket #4797: gis_measure_0_r5634.diff

File gis_measure_0_r5634.diff, 15.9 KB (added by robert.coup@…, 17 years ago)

Initial patch against r5634

  • django/contrib/gis/measure.py

     
     1"""
     2Distance and Area objects to allow for sensible and convienient calculation
     3and conversions.
     4
     5Inspired by GeoPy (http://exogen.case.edu/projects/geopy/)
     6and Geoff Biggs' PhD work on dimensioned units for robotics.
     7
     8Copyright (c) 2007, Robert Coup <robert.coup@onetrackmind.co.nz>
     9"""
     10
     11from math import *
     12from decimal import Decimal
     13
     14class Distance(object):
     15    UNITS = {
     16        'm': 1.0,
     17        'km': 1000.0,
     18        'mi': 1609.344,
     19        'ft': 0.3048,
     20        'yd': 0.9144,
     21        'nm': 1852.0,
     22    }
     23
     24    def __init__(self, default_unit=None, **kwargs):
     25        self.m = 0.0
     26        self._default_unit = 'm'
     27       
     28        for unit,value in kwargs.items():
     29            if unit in self.UNITS:
     30                self.m += self.UNITS[unit] * value
     31                self._default_unit = unit
     32            else:
     33                raise AttributeError("Unknown unit type: " + unit)
     34
     35        if default_unit:
     36            self._default_unit = default_unit
     37   
     38    def __getattr__(self, name):
     39        if name in self.UNITS:
     40            return self.m / self.UNITS[name]
     41        else:
     42            raise AttributeError("Unknown unit type: " + name)
     43   
     44    def __repr__(self):
     45        return "Distance(%s=%s)" % (self._default_unit, getattr(self, self._default_unit))
     46
     47    def __str__(self):
     48        return "%s %s" % (getattr(self, self._default_unit), self._default_unit)
     49       
     50    def __cmp__(self, other):
     51        if isinstance(other, Distance):
     52            return cmp(self.m, other.m)
     53        else:
     54            return NotImplemented
     55       
     56    def __add__(self, other):
     57        if isinstance(other, Distance):
     58            return Distance(default_unit=self._default_unit, m=(self.m + other.m))
     59        else:
     60            raise TypeError("Distance must be added with Distance")
     61   
     62    def __iadd__(self, other):
     63        if isinstance(other, Distance):
     64            self.m += other.m
     65            return self
     66        else:
     67            raise TypeError("Distance must be added with Distance")
     68   
     69    def __sub__(self, other):
     70        if isinstance(other, Distance):
     71            return Distance(default_unit=self._default_unit, m=(self.m - other.m))
     72        else:
     73            raise TypeError("Distance must be subtracted from Distance")
     74   
     75    def __isub__(self, other):
     76        if isinstance(other, Distance):
     77            self.m -= other.m
     78            return self
     79        else:
     80            raise TypeError("Distance must be subtracted from Distance")
     81   
     82    def __mul__(self, other):
     83        if isinstance(other, (int, float, long, Decimal)):
     84            return Distance(default_unit=self._default_unit, m=(self.m * float(other)))
     85        elif isinstance(other, Distance):
     86            return Area(default_unit='sq_' + self._default_unit, sq_m=(self.m * other.m))
     87        else:
     88            raise TypeError("Distance must be multiplied with number or Distance")
     89   
     90    def __imul__(self, other):
     91        if isinstance(other, (int, float, long, Decimal)):
     92            self.m *= float(other)
     93            return self
     94        else:
     95            raise TypeError("Distance must be multiplied with number")
     96   
     97    def __div__(self, other):
     98        if isinstance(other, (int, float, long, Decimal)):
     99            return Distance(default_unit=self._default_unit, m=(self.m / float(other)))
     100        else:
     101            raise TypeError("Distance must be divided with number")
     102
     103    def __idiv__(self, other):
     104        if isinstance(other, (int, float, long, Decimal)):
     105            self.m /= float(other)
     106            return self
     107        else:
     108            raise TypeError("Distance must be divided with number")
     109
     110    def __nonzero__(self):
     111        return bool(self.m)
     112
     113class Area(object):
     114    UNITS = {
     115        'sq_m': 1.0,
     116        'sq_km': 1000000.0,
     117        'sq_mi': 2589988.110336,
     118        'sq_ft': 0.09290304,
     119        'sq_yd': 0.83612736,
     120        'sq_nm': 3429904.0,
     121    }
     122
     123    def __init__(self, default_unit=None, **kwargs):
     124        self.sq_m = 0.0
     125        self._default_unit = 'sq_m'
     126       
     127        for unit,value in kwargs.items():
     128            if unit in self.UNITS:
     129                self.sq_m += self.UNITS[unit] * value
     130                self._default_unit = unit
     131            else:
     132                raise AttributeError("Unknown unit type: " + unit)
     133
     134        if default_unit:
     135            self._default_unit = default_unit
     136   
     137    def __getattr__(self, name):
     138        if name in self.UNITS:
     139            return self.sq_m / self.UNITS[name]
     140        else:
     141            raise AttributeError("Unknown unit type: " + name)
     142   
     143    def __repr__(self):
     144        return "Area(%s=%s)" % (self._default_unit, getattr(self, self._default_unit))
     145
     146    def __str__(self):
     147        return "%s %s" % (getattr(self, self._default_unit), self._default_unit)
     148
     149    def __cmp__(self, other):
     150        if isinstance(other, Area):
     151            return cmp(self.sq_m, other.sq_m)
     152        else:
     153            return NotImplemented
     154       
     155    def __add__(self, other):
     156        if isinstance(other, Area):
     157            return Area(default_unit=self._default_unit, sq_m=(self.sq_m + other.sq_m))
     158        else:
     159            raise TypeError("Area must be added with Area")
     160   
     161    def __iadd__(self, other):
     162        if isinstance(other, Area):
     163            self.sq_m += other.sq_m
     164            return self
     165        else:
     166            raise TypeError("Area must be added with Area")
     167   
     168    def __sub__(self, other):
     169        if isinstance(other, Area):
     170            return Area(default_unit=self._default_unit, sq_m=(self.sq_m - other.sq_m))
     171        else:
     172            raise TypeError("Area must be subtracted from Area")
     173   
     174    def __isub__(self, other):
     175        if isinstance(other, Area):
     176            self.sq_m -= other.sq_m
     177            return self
     178        else:
     179            raise TypeError("Area must be subtracted from Area")
     180   
     181    def __mul__(self, other):
     182        if isinstance(other, (int, float, long, Decimal)):
     183            return Area(default_unit=self._default_unit, sq_m=(self.sq_m * float(other)))
     184        else:
     185            raise TypeError("Area must be multiplied with number")
     186   
     187    def __imul__(self, other):
     188        if isinstance(other, (int, float, long, Decimal)):
     189            self.sq_m *= float(other)
     190            return self
     191        else:
     192            raise TypeError("Area must be multiplied with number")
     193   
     194    def __div__(self, other):
     195        if isinstance(other, (int, float, long, Decimal)):
     196            return Area(default_unit=self._default_unit, sq_m=(self.sq_m / float(other)))
     197        else:
     198            raise TypeError("Area must be divided with number")
     199
     200    def __idiv__(self, other):
     201        if isinstance(other, (int, float, long, Decimal)):
     202            self.sq_m /= float(other)
     203            return self
     204        else:
     205            raise TypeError("Area must be divided with number")
     206
     207    def __nonzero__(self):
     208        return bool(self.sq_m)
     209
     210       
     211# Shortcuts
     212D = Distance
     213A = Area
  • django/contrib/gis/tests/test_measure.py

     
     1"""
     2Distance and Area objects to allow for sensible and convienient calculation
     3and conversions. Here are some tests.
     4"""
     5
     6import unittest
     7from django.contrib.gis.measure import Distance, Area, D, A
     8
     9class DistanceTest(unittest.TestCase):
     10    "Testing the Distance object"
     11
     12    def testInit(self):
     13        "Testing initialisation from valid units"
     14        d = Distance(m=100)
     15        self.assertEqual(d.m, 100)
     16
     17        d = D(m=100)
     18        self.assertEqual(d.m, 100)
     19       
     20        d = D(nm=100)
     21        self.assertEqual(d.m, 185200)
     22   
     23    def testInitInvalid(self):
     24        "Testing initialisation from invalid units"
     25        self.assertRaises(AttributeError, D, banana=100)
     26
     27    def testAccess(self):
     28        "Testing access in different units"
     29        d = D(m=100)
     30        self.assertEqual(d.km, 0.1)
     31        self.assertAlmostEqual(d.ft, 328.084, 3)
     32   
     33    def testAccessInvalid(self):
     34        "Testing access in invalid units"
     35        d = D(m=100)
     36        self.failIf(hasattr(d, 'banana'))
     37
     38    def testAddition(self):
     39        "Test addition & subtraction"
     40        d1 = D(m=100)
     41        d2 = D(m=200)
     42
     43        d3 = d1 + d2
     44        self.assertEqual(d3.m, 300)
     45        d3 += d1
     46        self.assertEqual(d3.m, 400)
     47       
     48        d4 = d1 - d2
     49        self.assertEqual(d4.m, -100)
     50        d4 -= d1
     51        self.assertEqual(d4.m, -200)
     52       
     53        try:
     54            d5 = d1 + 1
     55        except TypeError, e:
     56            pass
     57        else:
     58            self.fail('Distance + number should raise TypeError')
     59
     60        try:
     61            d5 = d1 - 1
     62        except TypeError, e:
     63            pass
     64        else:
     65            self.fail('Distance - number should raise TypeError')
     66
     67        try:
     68            d1 += 1
     69        except TypeError, e:
     70            pass
     71        else:
     72            self.fail('Distance += number should raise TypeError')
     73
     74        try:
     75            d1 -= 1
     76        except TypeError, e:
     77            pass
     78        else:
     79            self.fail('Distance -= number should raise TypeError')
     80           
     81    def testMultiplication(self):
     82        "Test multiplication & division"
     83        d1 = D(m=100)
     84
     85        d3 = d1 * 2
     86        self.assertEqual(d3.m, 200)
     87        d3 *= 5
     88        self.assertEqual(d3.m, 1000)
     89       
     90        d4 = d1 / 2
     91        self.assertEqual(d4.m, 50)
     92        d4 /= 5
     93        self.assertEqual(d4.m, 10)
     94       
     95        a5 = d1 * D(m=10)
     96        self.assert_(isinstance(a5, Area))
     97        self.assertEqual(a5.sq_m, 100*10)
     98
     99        try:
     100            d1 *= D(m=1)
     101        except TypeError, e:
     102            pass
     103        else:
     104            self.fail('Distance *= Distance should raise TypeError')
     105           
     106        try:
     107            d5 = d1 / D(m=1)
     108        except TypeError, e:
     109            pass
     110        else:
     111            self.fail('Distance / Distance should raise TypeError')
     112
     113        try:
     114            d1 /= D(m=1)
     115        except TypeError, e:
     116            pass
     117        else:
     118            self.fail('Distance /= Distance should raise TypeError')
     119
     120    def testUnitConversions(self):
     121        "Testing default units during maths"
     122        d1 = D(m=100)
     123        d2 = D(km=1)
     124
     125        d3 = d1 + d2
     126        self.assertEqual(d3._default_unit, 'm')
     127        d4 = d2 + d1
     128        self.assertEqual(d4._default_unit, 'km')
     129        d5 = d1 * 2
     130        self.assertEqual(d5._default_unit, 'm')
     131        d6 = d1 / 2
     132        self.assertEqual(d6._default_unit, 'm')
     133   
     134    def testComparisons(self):
     135        "Testing comparisons"
     136        d1 = D(m=100)
     137        d2 = D(km=1)
     138        d3 = D(km=0)
     139       
     140        self.assert_(d2 > d1)
     141        self.assert_(d1 == d1)
     142        self.assert_(d1 < d2)
     143        self.failIf(d3)
     144       
     145    def testUnitsStr(self):
     146        "Testing conversion to strings"
     147        d1 = D(m=100)
     148        d2 = D(km=3.5)
     149       
     150        self.assertEqual(str(d1), '100.0 m')
     151        self.assertEqual(str(d2), '3.5 km')
     152        self.assertEqual(repr(d1), 'Distance(m=100.0)')
     153        self.assertEqual(repr(d2), 'Distance(km=3.5)')
     154
     155class AreaTest(unittest.TestCase):
     156    "Testing the Area object"
     157
     158    def testInit(self):
     159        "Testing initialisation from valid units"
     160        a = Area(sq_m=100)
     161        self.assertEqual(a.sq_m, 100)
     162
     163        a = A(sq_m=100)
     164        self.assertEqual(a.sq_m, 100)
     165
     166        a = A(sq_mi=100)
     167        self.assertEqual(a.sq_m, 258998811.0336)
     168   
     169    def testInitInvaliA(self):
     170        "Testing initialisation from invalid units"
     171        self.assertRaises(AttributeError, A, banana=100)
     172
     173    def testAccess(self):
     174        "Testing access in different units"
     175        a = A(sq_m=100)
     176        self.assertEqual(a.sq_km, 0.0001)
     177        self.assertAlmostEqual(a.sq_ft, 1076.391, 3)
     178   
     179    def testAccessInvaliA(self):
     180        "Testing access in invalid units"
     181        a = A(sq_m=100)
     182        self.failIf(hasattr(a, 'banana'))
     183
     184    def testAddition(self):
     185        "Test addition & subtraction"
     186        a1 = A(sq_m=100)
     187        a2 = A(sq_m=200)
     188
     189        a3 = a1 + a2
     190        self.assertEqual(a3.sq_m, 300)
     191        a3 += a1
     192        self.assertEqual(a3.sq_m, 400)
     193       
     194        a4 = a1 - a2
     195        self.assertEqual(a4.sq_m, -100)
     196        a4 -= a1
     197        self.assertEqual(a4.sq_m, -200)
     198       
     199        try:
     200            a5 = a1 + 1
     201        except TypeError, e:
     202            pass
     203        else:
     204            self.fail('Area + number should raise TypeError')
     205
     206        try:
     207            a5 = a1 - 1
     208        except TypeError, e:
     209            pass
     210        else:
     211            self.fail('Area - number should raise TypeError')
     212
     213        try:
     214            a1 += 1
     215        except TypeError, e:
     216            pass
     217        else:
     218            self.fail('Area += number should raise TypeError')
     219
     220        try:
     221            a1 -= 1
     222        except TypeError, e:
     223            pass
     224        else:
     225            self.fail('Area -= number should raise TypeError')
     226           
     227    def testMultiplication(self):
     228        "Test multiplication & division"
     229        a1 = A(sq_m=100)
     230
     231        a3 = a1 * 2
     232        self.assertEqual(a3.sq_m, 200)
     233        a3 *= 5
     234        self.assertEqual(a3.sq_m, 1000)
     235       
     236        a4 = a1 / 2
     237        self.assertEqual(a4.sq_m, 50)
     238        a4 /= 5
     239        self.assertEqual(a4.sq_m, 10)
     240       
     241        try:
     242            a5 = a1 * A(sq_m=1)
     243        except TypeError, e:
     244            pass
     245        else:
     246            self.fail('Area * Area should raise TypeError')
     247
     248        try:
     249            a1 *= A(sq_m=1)
     250        except TypeError, e:
     251            pass
     252        else:
     253            self.fail('Area *= Area should raise TypeError')
     254           
     255        try:
     256            a5 = a1 / A(sq_m=1)
     257        except TypeError, e:
     258            pass
     259        else:
     260            self.fail('Area / Area should raise TypeError')
     261
     262        try:
     263            a1 /= A(sq_m=1)
     264        except TypeError, e:
     265            pass
     266        else:
     267            self.fail('Area /= Area should raise TypeError')
     268
     269    def testUnitConversions(self):
     270        "Testing default units during maths"
     271        a1 = A(sq_m=100)
     272        a2 = A(sq_km=1)
     273
     274        a3 = a1 + a2
     275        self.assertEqual(a3._default_unit, 'sq_m')
     276        a4 = a2 + a1
     277        self.assertEqual(a4._default_unit, 'sq_km')
     278        a5 = a1 * 2
     279        self.assertEqual(a5._default_unit, 'sq_m')
     280        a6 = a1 / 2
     281        self.assertEqual(a6._default_unit, 'sq_m')
     282   
     283    def testComparisons(self):
     284        "Testing comparisons"
     285        a1 = A(sq_m=100)
     286        a2 = A(sq_km=1)
     287        a3 = A(sq_km=0)
     288       
     289        self.assert_(a2 > a1)
     290        self.assert_(a1 == a1)
     291        self.assert_(a1 < a2)
     292        self.failIf(a3)
     293       
     294    def testUnitsStr(self):
     295        "Testing conversion to strings"
     296        a1 = A(sq_m=100)
     297        a2 = A(sq_km=3.5)
     298       
     299        self.assertEqual(str(a1), '100.0 sq_m')
     300        self.assertEqual(str(a2), '3.5 sq_km')
     301        self.assertEqual(repr(a1), 'Area(sq_m=100.0)')
     302        self.assertEqual(repr(a2), 'Area(sq_km=3.5)')
     303
     304       
     305def suite():
     306    s = unittest.TestSuite()
     307    s.addTest(unittest.makeSuite(DistanceTest))
     308    s.addTest(unittest.makeSuite(AreaTest))
     309    return s
     310
     311def run(verbosity=2):
     312    unittest.TextTestRunner(verbosity=verbosity).run(suite())
     313
     314if __name__=="__main__":
     315    run()
     316 No newline at end of file
Back to Top