PageRenderTime 45ms CodeModel.GetById 13ms app.highlight 29ms RepoModel.GetById 1ms app.codeStats 0ms

/django/contrib/gis/measure.py

https://code.google.com/p/mango-py/
Python | 336 lines | 300 code | 0 blank | 36 comment | 3 complexity | fc0142a37292eb9c41893aabb9a7b12f MD5 | raw file
  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"""
 30Distance and Area objects to allow for sensible and convienient calculation
 31and conversions.
 32
 33Authors: Robert Coup, Justin Bronn
 34
 35Inspired by GeoPy (http://exogen.case.edu/projects/geopy/)
 36and Geoff Biggs' PhD work on dimensioned units for robotics.
 37"""
 38__all__ = ['A', 'Area', 'D', 'Distance']
 39from decimal import Decimal
 40
 41class 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
 87class 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
247class 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
335D = Distance
336A = Area