Ticket #17754: measure.diff

File measure.diff, 16.5 KB (added by Riccardo Di Virgilio, 13 years ago)

Measure diff unified format

  • measure.py

    old new  
    3535Inspired by GeoPy (http://exogen.case.edu/projects/geopy/)
    3636and Geoff Biggs' PhD work on dimensioned units for robotics.
    3737"""
    38 __all__ = ['A', 'Area', 'D', 'Distance']
     38__all__ = ['A', 'Area', 'D', 'Distance', 'V', 'Volume', 'W', 'Weight']
    3939from decimal import Decimal
    4040
     41def is_number(obj):
     42    return isinstance(obj, (int, float, long, Decimal))
     43   
     44def pretty_name(obj):
     45    if obj.__class__ == type:
     46        return obj.__name__
     47    return obj.__class__.__name__
     48
    4149class MeasureBase(object):
     50   
     51    STANDARD_UNIT  = None
     52    ALIAS  = {}
     53    UNITS  = {}
     54    LALIAS = {}
     55   
     56    def __init__(self, default_unit=None, **kwargs):
     57        # The base unit is in meters.
     58        value, self._default_unit = self.default_units(kwargs)
     59        setattr(self, self.STANDARD_UNIT, value)
     60        if default_unit and isinstance(default_unit, str):
     61            self._default_unit = default_unit
     62           
     63    def _get_standard(self):
     64        return getattr(self, self.STANDARD_UNIT)
     65       
     66    def _set_standard(self, value):
     67        setattr(self, self.STANDARD_UNIT, value)
     68       
     69    standard = property(_get_standard, _set_standard)
     70           
     71    def __getattr__(self, name):
     72        if name in self.UNITS:
     73            return self.standard / self.UNITS[name]
     74        else:
     75            raise AttributeError('Unknown unit type: %s' % name)
     76
     77    def __repr__(self):
     78        return '%s(%s=%s)' % (pretty_name(self), self._default_unit, getattr(self, self._default_unit))
     79
     80    def __str__(self):
     81        return '%s %s' % (getattr(self, self._default_unit), self._default_unit)
     82
     83    def __cmp__(self, other):
     84        if isinstance(other, self.__class__):
     85            return cmp(self.m, other.m)
     86        else:
     87            return NotImplemented
     88
     89    def __add__(self, other):
     90        if isinstance(other, self.__class__):
     91            return self.__class__(default_unit=self._default_unit,
     92                **{self.STANDARD_UNIT: (self.standard + other.standard)})
     93        else:
     94            raise TypeError('%(class)s must be added with %(class)s' % {"class":pretty_name(self)})
     95
     96    def __iadd__(self, other):
     97        if isinstance(other, self.__class__):
     98            self.standard += other.standard
     99            return self
     100        else:
     101            raise TypeError('%(class)s must be added with %(class)s' % {"class":pretty_name(self)})
     102
     103    def __sub__(self, other):
     104        if isinstance(other, self.__class__):
     105            return self.__class__(default_unit=self._default_unit,
     106                **{self.STANDARD_UNIT: (self.standard - other.standard)})
     107        else:
     108            raise TypeError('%(class)s must be subtracted from %(class)s' % {"class":pretty_name(self)})
     109
     110    def __isub__(self, other):
     111        if isinstance(other, self.__class__):
     112            self.standard -= other.standard
     113            return self
     114        else:
     115            raise TypeError('%(class)s must be subtracted from %(class)s' % {"class":pretty_name(self)})
     116
     117    def __mul__(self, other):
     118        if is_number(other):
     119            return self.__class__(default_unit=self._default_unit,
     120                **{self.STANDARD_UNIT: (self.standard * other)})
     121        else:
     122            raise TypeError('%(class)s must be multiplied with number' % {"class":pretty_name(self)})
     123
     124    def __imul__(self, other):
     125        if is_number(other):
     126            self.standard *= float(other)
     127            return self
     128        else:
     129            raise TypeError('%(class)s must be multiplied with number' % {"class":pretty_name(self)})
     130
     131    def __rmul__(self, other):
     132        return self * other
     133
     134    def __div__(self, other):
     135        if isinstance(other, self.__class__):
     136            return self.standard / other.standard     
     137        if is_number(other):
     138            return self.__class__(default_unit=self._default_unit,
     139                **{self.STANDARD_UNIT: (self.standard / other)})
     140        else:
     141            raise TypeError('%(class)s must be divided with number or %(class)s' % {"class":pretty_name(self)})
     142
     143    def __idiv__(self, other):
     144        if is_number(other):
     145            self.standard /= float(other)
     146            return self
     147        else:
     148            raise TypeError('%(class)s must be divided with number' % {"class":pretty_name(self)})
     149
     150    def __nonzero__(self):
     151        return bool(self.default)           
     152   
    42153    def default_units(self, kwargs):
    43154        """
    44155        Return the unit value and the default units specified
     
    85196            raise Exception('Could not find a unit keyword associated with "%s"' % unit_str)
    86197
    87198class Distance(MeasureBase):
     199    STANDARD_UNIT = "m"
    88200    UNITS = {
    89201        'chain' : 20.1168,
    90202        'chain_benoit' : 20.116782,
     
    160272        }
    161273    LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])
    162274
    163     def __init__(self, default_unit=None, **kwargs):
    164         # The base unit is in meters.
    165         self.m, self._default_unit = self.default_units(kwargs)
    166         if default_unit and isinstance(default_unit, str):
    167             self._default_unit = default_unit
    168 
    169     def __getattr__(self, name):
    170         if name in self.UNITS:
    171             return self.m / self.UNITS[name]
    172         else:
    173             raise AttributeError('Unknown unit type: %s' % name)
    174 
    175     def __repr__(self):
    176         return 'Distance(%s=%s)' % (self._default_unit, getattr(self, self._default_unit))
    177 
    178     def __str__(self):
    179         return '%s %s' % (getattr(self, self._default_unit), self._default_unit)
    180 
    181     def __cmp__(self, other):
    182         if isinstance(other, Distance):
    183             return cmp(self.m, other.m)
    184         else:
    185             return NotImplemented
    186 
    187     def __add__(self, other):
    188         if isinstance(other, Distance):
    189             return Distance(default_unit=self._default_unit, m=(self.m + other.m))
    190         else:
    191             raise TypeError('Distance must be added with Distance')
    192 
    193     def __iadd__(self, other):
    194         if isinstance(other, Distance):
    195             self.m += other.m
    196             return self
    197         else:
    198             raise TypeError('Distance must be added with Distance')
    199 
    200     def __sub__(self, other):
    201         if isinstance(other, Distance):
    202             return Distance(default_unit=self._default_unit, m=(self.m - other.m))
    203         else:
    204             raise TypeError('Distance must be subtracted from Distance')
    205 
    206     def __isub__(self, other):
    207         if isinstance(other, Distance):
    208             self.m -= other.m
    209             return self
    210         else:
    211             raise TypeError('Distance must be subtracted from Distance')
    212 
    213275    def __mul__(self, other):
    214         if isinstance(other, (int, float, long, Decimal)):
    215             return Distance(default_unit=self._default_unit, m=(self.m * float(other)))
    216         elif isinstance(other, Distance):
    217             return Area(default_unit='sq_' + self._default_unit, sq_m=(self.m * other.m))
    218         else:
    219             raise TypeError('Distance must be multiplied with number or Distance')
    220 
    221     def __imul__(self, other):
    222         if isinstance(other, (int, float, long, Decimal)):
    223             self.m *= float(other)
    224             return self
    225         else:
    226             raise TypeError('Distance must be multiplied with number')
    227 
    228     def __rmul__(self, other):
    229         return self * other
    230 
    231     def __div__(self, other):
    232         if isinstance(other, (int, float, long, Decimal)):
    233             return Distance(default_unit=self._default_unit, m=(self.m / float(other)))
    234         else:
    235             raise TypeError('Distance must be divided with number')
    236 
    237     def __idiv__(self, other):
    238         if isinstance(other, (int, float, long, Decimal)):
    239             self.m /= float(other)
    240             return self
    241         else:
    242             raise TypeError('Distance must be divided with number')
    243 
    244     def __nonzero__(self):
    245         return bool(self.m)
     276        if isinstance(other, Area):
     277            return Volume(default_unit=VOLUME_PREFIX + self._default_unit,
     278                **{VOLUME_PREFIX + self.STANDARD_UNIT: (self.standard * other.standard)})       
     279        elif isinstance(other, self.__class__):
     280            return Area(default_unit=AREA_PREFIX + self._default_unit,
     281                **{AREA_PREFIX + self.STANDARD_UNIT: (self.standard * other.standard)})
     282        elif is_number(other):
     283            return self.__class__(default_unit=self._default_unit,
     284                **{self.STANDARD_UNIT: (self.standard * other)})
     285        else:
     286            raise TypeError('%(distance)s must be multiplied with number, %(distance)s or %(area)s' % {
     287                "distance" : pretty_name(self.__class__),
     288                "area"     : pretty_name(Area),
     289                })
     290           
     291AREA_PREFIX = "sq_"
    246292
    247293class Area(MeasureBase):
     294    STANDARD_UNIT = AREA_PREFIX + Distance.STANDARD_UNIT
    248295    # Getting the square units values and the alias dictionary.
    249     UNITS = dict([('sq_%s' % k, v ** 2) for k, v in Distance.UNITS.items()])
    250     ALIAS = dict([(k, 'sq_%s' % v) for k, v in Distance.ALIAS.items()])
     296    UNITS = dict([(AREA_PREFIX + k, v ** 2) for k, v in Distance.UNITS.items()])
     297    ALIAS = dict([(k, AREA_PREFIX + v) for k, v in Distance.ALIAS.items()])
    251298    LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])
    252299
    253     def __init__(self, default_unit=None, **kwargs):
    254         self.sq_m, self._default_unit = self.default_units(kwargs)
    255         if default_unit and isinstance(default_unit, str):
    256             self._default_unit = default_unit
    257 
    258     def __getattr__(self, name):
    259         if name in self.UNITS:
    260             return self.sq_m / self.UNITS[name]
    261         else:
    262             raise AttributeError('Unknown unit type: ' + name)
    263 
    264     def __repr__(self):
    265         return 'Area(%s=%s)' % (self._default_unit, getattr(self, self._default_unit))
    266 
    267     def __str__(self):
    268         return '%s %s' % (getattr(self, self._default_unit), self._default_unit)
    269 
    270     def __cmp__(self, other):
    271         if isinstance(other, Area):
    272             return cmp(self.sq_m, other.sq_m)
    273         else:
    274             return NotImplemented
    275 
    276     def __add__(self, other):
    277         if isinstance(other, Area):
    278             return Area(default_unit=self._default_unit, sq_m=(self.sq_m + other.sq_m))
    279         else:
    280             raise TypeError('Area must be added with Area')
    281 
    282     def __iadd__(self, other):
    283         if isinstance(other, Area):
    284             self.sq_m += other.sq_m
    285             return self
    286         else:
    287             raise TypeError('Area must be added with Area')
    288 
    289     def __sub__(self, other):
    290         if isinstance(other, Area):
    291             return Area(default_unit=self._default_unit, sq_m=(self.sq_m - other.sq_m))
    292         else:
    293             raise TypeError('Area must be subtracted from Area')
    294 
    295     def __isub__(self, other):
    296         if isinstance(other, Area):
    297             self.sq_m -= other.sq_m
    298             return self
    299         else:
    300             raise TypeError('Area must be subtracted from Area')
    301 
     300    def __div__(self, other):
     301        if isinstance(other, self.__class__):
     302            return self.standard / other.standard         
     303        if isinstance(other, Distance):
     304            return Distance(default_unit=self._default_unit[len(AREA_PREFIX):],
     305                **{Distance.STANDARD_UNIT: (self.standard / other.standard)})
     306        elif is_number(other):
     307            return self.__class__(default_unit=self._default_unit,
     308                **{self.STANDARD_UNIT: (self.standard / other)})
     309        else:
     310            raise TypeError('%(area)s must be divided with number, %(distance)s or %(area)s' % {
     311                "distance" : pretty_name(Distance),
     312                "area"     : pretty_name(self.__class__),
     313                })
     314           
    302315    def __mul__(self, other):
    303         if isinstance(other, (int, float, long, Decimal)):
    304             return Area(default_unit=self._default_unit, sq_m=(self.sq_m * float(other)))
    305         else:
    306             raise TypeError('Area must be multiplied with number')
    307 
    308     def __imul__(self, other):
    309         if isinstance(other, (int, float, long, Decimal)):
    310             self.sq_m *= float(other)
    311             return self
    312         else:
    313             raise TypeError('Area must be multiplied with number')
    314 
    315     def __rmul__(self, other):
    316         return self * other
    317 
     316        if isinstance(other, Distance):
     317            return Volume(default_unit=VOLUME_PREFIX+self._default_unit[len(AREA_PREFIX):],
     318                **{Volume.STANDARD_UNIT: (self.standard * other.standard)})       
     319        elif is_number(other):
     320            return self.__class__(default_unit=self._default_unit,
     321                **{self.STANDARD_UNIT: (self.standard * other)})
     322        else:
     323            raise TypeError('%(area)s must be multiplied with number or %(distance)s' % {
     324                "distance" : pretty_name(Distance),
     325                "area"     : pretty_name(self.__class__),
     326                })
     327           
     328VOLUME_PREFIX = "vol_"
     329
     330class Volume(MeasureBase):
     331    STANDARD_UNIT = VOLUME_PREFIX + Distance.STANDARD_UNIT
     332    # Getting the cube units values and the alias dictionary.
     333    UNITS = dict([(VOLUME_PREFIX + k, v ** 3) for k, v in Distance.UNITS.items()])
     334    ALIAS = dict([(k, VOLUME_PREFIX + v) for k, v in Distance.ALIAS.items()])
     335    LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])   
     336   
    318337    def __div__(self, other):
    319         if isinstance(other, (int, float, long, Decimal)):
    320             return Area(default_unit=self._default_unit, sq_m=(self.sq_m / float(other)))
    321         else:
    322             raise TypeError('Area must be divided with number')
    323 
    324     def __idiv__(self, other):
    325         if isinstance(other, (int, float, long, Decimal)):
    326             self.sq_m /= float(other)
    327             return self
    328         else:
    329             raise TypeError('Area must be divided with number')
     338        if isinstance(other, self.__class__):
     339            return self.standard / other.standard         
     340        if isinstance(other, Area):
     341            return Distance(default_unit=self._default_unit[len(VOLUME_PREFIX):],
     342                **{Distance.STANDARD_UNIT: (self.standard / other.standard)})       
     343        if isinstance(other, Distance):
     344            return Area(default_unit=AREA_PREFIX+self._default_unit[len(VOLUME_PREFIX):],
     345                **{Area.STANDARD_UNIT: (self.standard / other.standard)})
     346        elif is_number(other):
     347            return self.__class__(default_unit=self._default_unit,
     348                **{self.STANDARD_UNIT: (self.standard / other)})
     349        else:
     350            raise TypeError('%(volume)s must be divided with number, %(distance)s, %(area)s or %(volume)s' % {
     351                "distance" : pretty_name(Distance),
     352                "area"     : pretty_name(Area),
     353                "volume"   : pretty_name(self.__class__),
     354                })     
     355   
     356class Weight(MeasureBase):
     357   
     358    DEFUALT_UNIT = "gr"
     359   
     360    UNITS = {
     361        'mg':  1.0 / 1000,
     362        'gr':  1.0,
     363        'kg':  1.0 * 1000,
     364        'q' :  1.0 * 1000 * 100,
     365        'ton': 1.0 * 1000 * 1000,
     366        }
    330367
    331     def __nonzero__(self):
    332         return bool(self.sq_m)
     368    # Unit aliases for `UNIT` terms encountered in Spatial Reference WKT.
     369    ALIAS = {
     370        'Milligram':  'mg',
     371        'Gram':       'gr',
     372        'Kilogram':   'kg',
     373        'Quintal' :   'q',
     374        'Ton' :       'ton',
     375        }
     376    LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])
    333377
    334378# Shortcuts
    335379D = Distance
    336380A = Area
     381V = Volume
     382W = Weight
     383
     384
     385if __name__ == '__main__':
     386
     387    from itertools import product
     388   
     389    CONSTRUCTORS = {
     390        float: lambda cls, val: cls(val),
     391        A:     lambda cls, val: cls(**{cls.STANDARD_UNIT:val}),
     392        D:     lambda cls, val: cls(**{cls.STANDARD_UNIT:val}),
     393        V:     lambda cls, val: cls(**{cls.STANDARD_UNIT:val}),
     394    }
     395   
     396    classes = [D, A, V, float]
     397   
     398    combs = product(classes, repeat = 2)
     399   
     400    a_val = 10
     401    b_val = 5       
     402   
     403    for a_class, b_class in combs:
     404       
     405        print "-----------%s-vs-%s-----------" % (a_class.__name__.title(), b_class.__name__.title())
     406       
     407        a = CONSTRUCTORS[a_class](a_class, a_val)
     408        b = CONSTRUCTORS[b_class](b_class, b_val)
     409       
     410        for verbose, operator in (("+", "__add__"), ("-", "__sub__"), ("*", "__mul__"), ("/", "__div__")):
     411           
     412            desc = "%s %s %s" % (a, verbose, b)
     413           
     414            print desc.ljust(25), ">    ",
     415           
     416            try:
     417                print getattr(a, operator)(b)
     418            except Exception, e:
     419                print e   
     420
Back to Top