web/lib/django/contrib/gis/measure.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     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 __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)
       
   246 
       
   247 class Area(MeasureBase):
       
   248     # 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()])
       
   251     LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])
       
   252 
       
   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 
       
   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')
       
   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 
       
   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
       
   335 D = Distance
       
   336 A = Area