web/lib/django/contrib/gis/measure.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 # Copyright (c) 2007, Robert Coup <robert.coup@onetrackmind.co.nz>
       
     2 # All rights reserved.
       
     3 #
       
     4 # Redistribution and use in source and binary forms, with or without modification,
       
     5 # are permitted provided that the following conditions are met:
       
     6 #
       
     7 #   1. Redistributions of source code must retain the above copyright notice,
       
     8 #      this list of conditions and the following disclaimer.
       
     9 #
       
    10 #   2. Redistributions in binary form must reproduce the above copyright
       
    11 #      notice, this list of conditions and the following disclaimer in the
       
    12 #      documentation and/or other materials provided with the distribution.
       
    13 #
       
    14 #   3. Neither the name of Distance nor the names of its contributors may be used
       
    15 #      to endorse or promote products derived from this software without
       
    16 #      specific prior written permission.
       
    17 #
       
    18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
       
    19 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
    20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
       
    21 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
       
    22 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
       
    23 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
       
    24 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
       
    25 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
       
    26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
       
    27 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    28 #
       
    29 """
       
    30 Distance and Area objects to allow for sensible and convienient calculation 
       
    31 and conversions.
       
    32 
       
    33 Authors: Robert Coup, Justin Bronn
       
    34 
       
    35 Inspired by GeoPy (http://exogen.case.edu/projects/geopy/)
       
    36 and Geoff Biggs' PhD work on dimensioned units for robotics.
       
    37 """
       
    38 __all__ = ['A', 'Area', 'D', 'Distance']
       
    39 from decimal import Decimal
       
    40 
       
    41 class MeasureBase(object):
       
    42     def default_units(self, kwargs):
       
    43         """
       
    44         Return the unit value and the default units specified
       
    45         from the given keyword arguments dictionary.
       
    46         """
       
    47         val = 0.0
       
    48         for unit, value in kwargs.iteritems():
       
    49             if not isinstance(value, float): value = float(value)
       
    50             if unit in self.UNITS:
       
    51                 val += self.UNITS[unit] * value
       
    52                 default_unit = unit
       
    53             elif unit in self.ALIAS:
       
    54                 u = self.ALIAS[unit]
       
    55                 val += self.UNITS[u] * value
       
    56                 default_unit = u
       
    57             else:
       
    58                 lower = unit.lower()
       
    59                 if lower in self.UNITS:
       
    60                     val += self.UNITS[lower] * value
       
    61                     default_unit = lower
       
    62                 elif lower in self.LALIAS:
       
    63                     u = self.LALIAS[lower]
       
    64                     val += self.UNITS[u] * value
       
    65                     default_unit = u
       
    66                 else:
       
    67                     raise AttributeError('Unknown unit type: %s' % unit)
       
    68         return val, default_unit
       
    69 
       
    70     @classmethod
       
    71     def unit_attname(cls, unit_str):
       
    72         """
       
    73         Retrieves the unit attribute name for the given unit string.  
       
    74         For example, if the given unit string is 'metre', 'm' would be returned.
       
    75         An exception is raised if an attribute cannot be found.
       
    76         """
       
    77         lower = unit_str.lower()
       
    78         if unit_str in cls.UNITS:
       
    79             return unit_str
       
    80         elif lower in cls.UNITS:
       
    81             return lower
       
    82         elif lower in cls.LALIAS:
       
    83             return cls.LALIAS[lower]
       
    84         else:
       
    85             raise Exception('Could not find a unit keyword associated with "%s"' % unit_str)
       
    86 
       
    87 class Distance(MeasureBase):
       
    88     UNITS = {
       
    89         'chain' : 20.1168,
       
    90         'chain_benoit' : 20.116782,
       
    91         'chain_sears' : 20.1167645,
       
    92         'british_chain_benoit' : 20.1167824944,
       
    93         'british_chain_sears' : 20.1167651216,
       
    94         'british_chain_sears_truncated' : 20.116756,
       
    95         'cm' : 0.01,
       
    96         'british_ft' : 0.304799471539,
       
    97         'british_yd' : 0.914398414616,
       
    98         'clarke_ft' : 0.3047972654,
       
    99         'clarke_link' : 0.201166195164,
       
   100         'fathom' :  1.8288,
       
   101         'ft': 0.3048,
       
   102         'german_m' : 1.0000135965,
       
   103         'gold_coast_ft' : 0.304799710181508,
       
   104         'indian_yd' : 0.914398530744,
       
   105         'inch' : 0.0254,
       
   106         'km': 1000.0,
       
   107         'link' : 0.201168,
       
   108         'link_benoit' : 0.20116782,
       
   109         'link_sears' : 0.20116765,
       
   110         'm': 1.0,
       
   111         'mi': 1609.344,
       
   112         'mm' : 0.001,
       
   113         'nm': 1852.0,
       
   114         'nm_uk' : 1853.184,
       
   115         'rod' : 5.0292,
       
   116         'sears_yd' : 0.91439841,
       
   117         'survey_ft' : 0.304800609601,
       
   118         'um' : 0.000001,
       
   119         'yd': 0.9144,
       
   120         }
       
   121 
       
   122     # Unit aliases for `UNIT` terms encountered in Spatial Reference WKT.
       
   123     ALIAS = {
       
   124         'centimeter' : 'cm',
       
   125         'foot' : 'ft',
       
   126         'inches' : 'inch',
       
   127         'kilometer' : 'km',
       
   128         'kilometre' : 'km',
       
   129         'meter' : 'm',
       
   130         'metre' : 'm',
       
   131         'micrometer' : 'um',
       
   132         'micrometre' : 'um',
       
   133         'millimeter' : 'mm',
       
   134         'millimetre' : 'mm',
       
   135         'mile' : 'mi',
       
   136         'yard' : 'yd',
       
   137         'British chain (Benoit 1895 B)' : 'british_chain_benoit',
       
   138         'British chain (Sears 1922)' : 'british_chain_sears',
       
   139         'British chain (Sears 1922 truncated)' : 'british_chain_sears_truncated',
       
   140         'British foot (Sears 1922)' : 'british_ft',
       
   141         'British foot' : 'british_ft',
       
   142         'British yard (Sears 1922)' : 'british_yd',
       
   143         'British yard' : 'british_yd',
       
   144         "Clarke's Foot" : 'clarke_ft',
       
   145         "Clarke's link" : 'clarke_link',
       
   146         'Chain (Benoit)' : 'chain_benoit',
       
   147         'Chain (Sears)' : 'chain_sears',
       
   148         'Foot (International)' : 'ft',
       
   149         'German legal metre' : 'german_m',
       
   150         'Gold Coast foot' : 'gold_coast_ft',
       
   151         'Indian yard' : 'indian_yd',
       
   152         'Link (Benoit)': 'link_benoit',
       
   153         'Link (Sears)': 'link_sears',
       
   154         'Nautical Mile' : 'nm',
       
   155         'Nautical Mile (UK)' : 'nm_uk',
       
   156         'US survey foot' : 'survey_ft',
       
   157         'U.S. Foot' : 'survey_ft',
       
   158         'Yard (Indian)' : 'indian_yd',
       
   159         'Yard (Sears)' : 'sears_yd'
       
   160         }
       
   161     LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])
       
   162 
       
   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     
       
   213     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 __div__(self, other):
       
   229         if isinstance(other, (int, float, long, Decimal)):
       
   230             return Distance(default_unit=self._default_unit, m=(self.m / float(other)))
       
   231         else:
       
   232             raise TypeError('Distance must be divided with number')
       
   233 
       
   234     def __idiv__(self, other):
       
   235         if isinstance(other, (int, float, long, Decimal)):
       
   236             self.m /= float(other)
       
   237             return self
       
   238         else:
       
   239             raise TypeError('Distance must be divided with number')
       
   240 
       
   241     def __nonzero__(self):
       
   242         return bool(self.m)
       
   243 
       
   244 class Area(MeasureBase):
       
   245     # Getting the square units values and the alias dictionary.
       
   246     UNITS = dict([('sq_%s' % k, v ** 2) for k, v in Distance.UNITS.items()])
       
   247     ALIAS = dict([(k, 'sq_%s' % v) for k, v in Distance.ALIAS.items()])
       
   248     LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])
       
   249 
       
   250     def __init__(self, default_unit=None, **kwargs):
       
   251         self.sq_m, self._default_unit = self.default_units(kwargs)
       
   252         if default_unit and isinstance(default_unit, str):
       
   253             self._default_unit = default_unit
       
   254     
       
   255     def __getattr__(self, name):
       
   256         if name in self.UNITS:
       
   257             return self.sq_m / self.UNITS[name]
       
   258         else:
       
   259             raise AttributeError('Unknown unit type: ' + name)
       
   260     
       
   261     def __repr__(self):
       
   262         return 'Area(%s=%s)' % (self._default_unit, getattr(self, self._default_unit))
       
   263 
       
   264     def __str__(self):
       
   265         return '%s %s' % (getattr(self, self._default_unit), self._default_unit)
       
   266 
       
   267     def __cmp__(self, other):
       
   268         if isinstance(other, Area):
       
   269             return cmp(self.sq_m, other.sq_m)
       
   270         else:
       
   271             return NotImplemented
       
   272         
       
   273     def __add__(self, other):
       
   274         if isinstance(other, Area):
       
   275             return Area(default_unit=self._default_unit, sq_m=(self.sq_m + other.sq_m))
       
   276         else:
       
   277             raise TypeError('Area must be added with Area')
       
   278     
       
   279     def __iadd__(self, other):
       
   280         if isinstance(other, Area):
       
   281             self.sq_m += other.sq_m
       
   282             return self
       
   283         else:
       
   284             raise TypeError('Area must be added with Area')
       
   285     
       
   286     def __sub__(self, other):
       
   287         if isinstance(other, Area):
       
   288             return Area(default_unit=self._default_unit, sq_m=(self.sq_m - other.sq_m))
       
   289         else:
       
   290             raise TypeError('Area must be subtracted from Area')
       
   291     
       
   292     def __isub__(self, other):
       
   293         if isinstance(other, Area):
       
   294             self.sq_m -= other.sq_m
       
   295             return self
       
   296         else:
       
   297             raise TypeError('Area must be subtracted from Area')
       
   298     
       
   299     def __mul__(self, other):
       
   300         if isinstance(other, (int, float, long, Decimal)):
       
   301             return Area(default_unit=self._default_unit, sq_m=(self.sq_m * float(other)))
       
   302         else:
       
   303             raise TypeError('Area must be multiplied with number')
       
   304     
       
   305     def __imul__(self, other):
       
   306         if isinstance(other, (int, float, long, Decimal)):
       
   307             self.sq_m *= float(other)
       
   308             return self
       
   309         else:
       
   310             raise TypeError('Area must be multiplied with number')
       
   311     
       
   312     def __div__(self, other):
       
   313         if isinstance(other, (int, float, long, Decimal)):
       
   314             return Area(default_unit=self._default_unit, sq_m=(self.sq_m / float(other)))
       
   315         else:
       
   316             raise TypeError('Area must be divided with number')
       
   317 
       
   318     def __idiv__(self, other):
       
   319         if isinstance(other, (int, float, long, Decimal)):
       
   320             self.sq_m /= float(other)
       
   321             return self
       
   322         else:
       
   323             raise TypeError('Area must be divided with number')
       
   324 
       
   325     def __nonzero__(self):
       
   326         return bool(self.sq_m)
       
   327         
       
   328 # Shortcuts
       
   329 D = Distance
       
   330 A = Area