Ticket #17754: measure.2.diff

File measure.2.diff, 17.4 KB (added by Riccardo Di Virgilio, 12 years ago)
  • 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', 'V', 'Volume', 'W', 'Weight']
     38__all__ = ['A', 'Area', 'D', 'Distance']
    3939from decimal import Decimal
    4040
    41 def is_number(obj):
    42     return isinstance(obj, (int, float, long, Decimal))
    43    
    44 def pretty_name(obj):
    45     if obj.__class__ == type:
    46         return obj.__name__
    47     return obj.__class__.__name__
    48 
    4941class 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 __len__(self):
    72         return len(str(self))
    73    
    74     def __getattr__(self, name):
    75         if name in self.UNITS:
    76             return self.standard / self.UNITS[name]
    77         else:
    78             raise AttributeError('Unknown unit type: %s' % name)
    79 
    80     def __repr__(self):
    81         return '%s(%s=%s)' % (pretty_name(self), self._default_unit, getattr(self, self._default_unit))
    82 
    83     def __str__(self):
    84         val = getattr(self, self._default_unit)
    85         if val == int(val):
    86             val = int(val)
    87         return '%s %s' % (val, self._default_unit)
    88 
    89     def __cmp__(self, other):
    90         if isinstance(other, self.__class__):
    91             return cmp(self.standard, other.standard)
    92         else:
    93             return NotImplemented
    94 
    95     def __add__(self, other):
    96         if isinstance(other, self.__class__):
    97             return self.__class__(default_unit=self._default_unit,
    98                 **{self.STANDARD_UNIT: (self.standard + other.standard)})
    99         else:
    100             raise TypeError('%(class)s must be added with %(class)s' % {"class":pretty_name(self)})
    101 
    102     def __iadd__(self, other):
    103         if isinstance(other, self.__class__):
    104             self.standard += other.standard
    105             return self
    106         else:
    107             raise TypeError('%(class)s must be added with %(class)s' % {"class":pretty_name(self)})
    108 
    109     def __sub__(self, other):
    110         if isinstance(other, self.__class__):
    111             return self.__class__(default_unit=self._default_unit,
    112                 **{self.STANDARD_UNIT: (self.standard - other.standard)})
    113         else:
    114             raise TypeError('%(class)s must be subtracted from %(class)s' % {"class":pretty_name(self)})
    115 
    116     def __isub__(self, other):
    117         if isinstance(other, self.__class__):
    118             self.standard -= other.standard
    119             return self
    120         else:
    121             raise TypeError('%(class)s must be subtracted from %(class)s' % {"class":pretty_name(self)})
    122 
    123     def __mul__(self, other):
    124         if is_number(other):
    125             return self.__class__(default_unit=self._default_unit,
    126                 **{self.STANDARD_UNIT: (self.standard * other)})
    127         else:
    128             raise TypeError('%(class)s must be multiplied with number' % {"class":pretty_name(self)})
    129 
    130     def __imul__(self, other):
    131         if is_number(other):
    132             self.standard *= float(other)
    133             return self
    134         else:
    135             raise TypeError('%(class)s must be multiplied with number' % {"class":pretty_name(self)})
    136 
    137     def __rmul__(self, other):
    138         return self * other
    139 
    140     def __div__(self, other):
    141         if isinstance(other, self.__class__):
    142             return self.standard / other.standard     
    143         if is_number(other):
    144             return self.__class__(default_unit=self._default_unit,
    145                 **{self.STANDARD_UNIT: (self.standard / other)})
    146         else:
    147             raise TypeError('%(class)s must be divided with number or %(class)s' % {"class":pretty_name(self)})
    148 
    149     def __idiv__(self, other):
    150         if is_number(other):
    151             self.standard /= float(other)
    152             return self
    153         else:
    154             raise TypeError('%(class)s must be divided with number' % {"class":pretty_name(self)})
    155 
    156     def __nonzero__(self):
    157         return bool(self.standard)           
    158    
    15942    def default_units(self, kwargs):
    16043        """
    16144        Return the unit value and the default units specified
    16245        from the given keyword arguments dictionary.
    16346        """
    16447        val = 0.0
    165         default_unit = self.STANDARD_UNIT
    16648        for unit, value in kwargs.iteritems():
    16749            if not isinstance(value, float): value = float(value)
    16850            if unit in self.UNITS:
     
    20385            raise Exception('Could not find a unit keyword associated with "%s"' % unit_str)
    20486
    20587class Distance(MeasureBase):
    206     STANDARD_UNIT = "m"
    20788    UNITS = {
    20889        'chain' : 20.1168,
    20990        'chain_benoit' : 20.116782,
     
    236117        'survey_ft' : 0.304800609601,
    237118        'um' : 0.000001,
    238119        'yd': 0.9144,
    239         'pt': 0.0254 / 72,
    240120        }
    241121
    242122    # Unit aliases for `UNIT` terms encountered in Spatial Reference WKT.
     
    276156        'US survey foot' : 'survey_ft',
    277157        'U.S. Foot' : 'survey_ft',
    278158        'Yard (Indian)' : 'indian_yd',
    279         'Yard (Sears)' : 'sears_yd',
    280         'Point': 'pt',
    281         'Pixel': 'pt',
     159        'Yard (Sears)' : 'sears_yd'
    282160        }
    283161    LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])
    284162
     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
    285213    def __mul__(self, other):
    286         if isinstance(other, Area):
    287             return Volume(default_unit=VOLUME_PREFIX + self._default_unit,
    288                 **{VOLUME_PREFIX + self.STANDARD_UNIT: (self.standard * other.standard)})       
    289         elif isinstance(other, self.__class__):
    290             return Area(default_unit=AREA_PREFIX + self._default_unit,
    291                 **{AREA_PREFIX + self.STANDARD_UNIT: (self.standard * other.standard)})
    292         elif is_number(other):
    293             return self.__class__(default_unit=self._default_unit,
    294                 **{self.STANDARD_UNIT: (self.standard * other)})
    295         else:
    296             raise TypeError('%(distance)s must be multiplied with number, %(distance)s or %(area)s' % {
    297                 "distance" : pretty_name(self.__class__),
    298                 "area"     : pretty_name(Area),
    299                 })
    300            
    301 AREA_PREFIX = "sq_"
     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)
    302246
    303247class Area(MeasureBase):
    304     STANDARD_UNIT = AREA_PREFIX + Distance.STANDARD_UNIT
    305248    # Getting the square units values and the alias dictionary.
    306     UNITS = dict([(AREA_PREFIX + k, v ** 2) for k, v in Distance.UNITS.items()])
    307     ALIAS = dict([(k, AREA_PREFIX + v) for k, v in Distance.ALIAS.items()])
     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()])
    308251    LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])
    309252
    310     def __div__(self, other):
    311         if isinstance(other, self.__class__):
    312             return self.standard / other.standard         
    313         if isinstance(other, Distance):
    314             return Distance(default_unit=self._default_unit[len(AREA_PREFIX):],
    315                 **{Distance.STANDARD_UNIT: (self.standard / other.standard)})
    316         elif is_number(other):
    317             return self.__class__(default_unit=self._default_unit,
    318                 **{self.STANDARD_UNIT: (self.standard / other)})
    319         else:
    320             raise TypeError('%(area)s must be divided with number, %(distance)s or %(area)s' % {
    321                 "distance" : pretty_name(Distance),
    322                 "area"     : pretty_name(self.__class__),
    323                 })
    324            
    325     def __mul__(self, other):
    326         if isinstance(other, Distance):
    327             return Volume(default_unit=VOLUME_PREFIX+self._default_unit[len(AREA_PREFIX):],
    328                 **{Volume.STANDARD_UNIT: (self.standard * other.standard)})       
    329         elif is_number(other):
    330             return self.__class__(default_unit=self._default_unit,
    331                 **{self.STANDARD_UNIT: (self.standard * other)})
    332         else:
    333             raise TypeError('%(area)s must be multiplied with number or %(distance)s' % {
    334                 "distance" : pretty_name(Distance),
    335                 "area"     : pretty_name(self.__class__),
    336                 })
    337            
    338 VOLUME_PREFIX = "vol_"
    339 
    340 class Volume(MeasureBase):
    341     STANDARD_UNIT = VOLUME_PREFIX + Distance.STANDARD_UNIT
    342     # Getting the cube units values and the alias dictionary.
    343     UNITS = dict([(VOLUME_PREFIX + k, v ** 3) for k, v in Distance.UNITS.items()])
    344     ALIAS = dict([(k, VOLUME_PREFIX + v) for k, v in Distance.ALIAS.items()])
    345     LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])   
    346    
    347     def __div__(self, other):
    348         if isinstance(other, self.__class__):
    349             return self.standard / other.standard         
     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):
    350271        if isinstance(other, Area):
    351             return Distance(default_unit=self._default_unit[len(VOLUME_PREFIX):],
    352                 **{Distance.STANDARD_UNIT: (self.standard / other.standard)})       
    353         if isinstance(other, Distance):
    354             return Area(default_unit=AREA_PREFIX+self._default_unit[len(VOLUME_PREFIX):],
    355                 **{Area.STANDARD_UNIT: (self.standard / other.standard)})
    356         elif is_number(other):
    357             return self.__class__(default_unit=self._default_unit,
    358                 **{self.STANDARD_UNIT: (self.standard / other)})
    359         else:
    360             raise TypeError('%(volume)s must be divided with number, %(distance)s, %(area)s or %(volume)s' % {
    361                 "distance" : pretty_name(Distance),
    362                 "area"     : pretty_name(Area),
    363                 "volume"   : pretty_name(self.__class__),
    364                 })     
    365    
    366 class Weight(MeasureBase):
    367    
    368     STANDARD_UNIT = "gr"
    369    
    370     UNITS = {
    371         'mg':  1.0 / 1000,
    372         'gr':  1.0,
    373         'kg':  1.0 * 1000,
    374         'q' :  1.0 * 1000 * 100,
    375         'ton': 1.0 * 1000 * 1000,
    376         }
     272            return cmp(self.sq_m, other.sq_m)
     273        else:
     274            return NotImplemented
    377275
    378     # Unit aliases for `UNIT` terms encountered in Spatial Reference WKT.
    379     ALIAS = {
    380         'Milligram':  'mg',
    381         'Gram':       'gr',
    382         'Kilogram':   'kg',
    383         'Quintal' :   'q',
    384         'Ton' :       'ton',
    385         }
    386     LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])
     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')
    387281
    388 # Shortcuts
    389 D = Distance
    390 A = Area
    391 V = Volume
    392 W = Weight
     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')
    393301
     302    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')
    394307
    395 if __name__ == '__main__':
     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')
    396314
    397     from itertools import product
    398    
    399     CONSTRUCTORS = {
    400         float: lambda cls, val: cls(val),
    401         A:     lambda cls, val: cls(**{cls.STANDARD_UNIT:val}),
    402         D:     lambda cls, val: cls(**{cls.STANDARD_UNIT:val}),
    403         V:     lambda cls, val: cls(**{cls.STANDARD_UNIT:val}),
    404     }
    405    
    406     classes = [D, A, V, float]
    407    
    408     combs = product(classes, repeat = 2)
    409    
    410     a_val = 10
    411     b_val = 5       
    412    
    413     for a_class, b_class in combs:
    414        
    415         print "-----------%s-vs-%s-----------" % (a_class.__name__.title(), b_class.__name__.title())
    416        
    417         a = CONSTRUCTORS[a_class](a_class, a_val)
    418         b = CONSTRUCTORS[b_class](b_class, b_val)
    419        
    420         for verbose, operator in (("+", "__add__"), ("-", "__sub__"), ("*", "__mul__"), ("/", "__div__")):
    421            
    422             desc = "%s %s %s" % (a, verbose, b)
    423            
    424             print desc.ljust(25), ">    ",
    425            
    426             try:
    427                 print getattr(a, operator)(b).__repr__()
    428             except Exception, e:
    429                 print e   
     315    def __rmul__(self, other):
     316        return self * other
    430317
     318    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')
     330
     331    def __nonzero__(self):
     332        return bool(self.sq_m)
     333
     334# Shortcuts
     335D = Distance
     336A = Area
Back to Top