public
Description: A generic, easy-to-use location-awareness application built for use in Django projects.
Homepage: http://django-geo.googlecode.com/
Clone URL: git://github.com/obeattie/django-geo.git
django-geo / models.py
100644 125 lines (106 sloc) 4.707 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import datetime
from geopy import distance as geopy_distance
 
from django.db import models
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
 
from geo import geocoding, managers, fields as custom_fields
from geo.dateutil.relativedelta import relativedelta
 
class Location(models.Model):
  """A Location on the earth."""
  query = models.CharField(_('Location'), max_length=250, blank=False, null=False, unique=True)
  friendly_name = models.CharField(max_length=250, blank=True, null=True, help_text=_('Use this to assign a friendly display-name to this location like \'Home\'.'))
  geocoded = models.BooleanField(default=True)
  result = custom_fields.PickledObjectField(blank=True, null=True, editable=False)
  latitude = models.FloatField(blank=True, null=False)
  longitude = models.FloatField(blank=True, null=False)
  refreshed = models.DateTimeField(editable=False, blank=True, null=False, default=datetime.datetime.now())
  extra = custom_fields.DictionaryField(_('A dictionary of additional information'), blank=True, null=True, editable=False)
  created = models.DateTimeField(editable=False, blank=True, null=True, default=datetime.datetime.now())
  is_public = models.BooleanField(default=True)
  # Manager
  objects = managers.LocationManager()
  
  class Admin:
    list_display = ('__str__', 'latitude', 'longitude', 'created', 'refreshed')
    list_filter = ('created', 'refreshed')
  
  def save(self, *args, **kwargs):
    self.refresh()
    return super(Location, self).save(*args, **kwargs)
  
  # General
  def __unicode__(self):
    return unicode(self.name)
  
  def __getitem__(self, index):
    """Gets either a latitude or longitude by indexing the coords_tuple."""
    return self.coords_tuple[index]
  
  def get_geocoder(self):
    """Returns an instantiated geocoder for this object. Make sure you have settings.DEFAULT_GEOCODER set correctly."""
    return geocoding.SHORT_NAME_MAPPINGS[settings.DEFAULT_GEOCODER]
  
  @property
  def coords(self):
    if hasattr(self.result, 'coords'):
      return self.result.coords
    else:
      return geocoding.Coordinates(float(self.latitude or 0), float(self.longitude or 0))
  
  @property
  def coords_tuple(self):
    if not hasattr(self.result, 'coords'):
      return (self.latitude, self.longitude)
    else:
      return tuple(self.result.coords)
  
  @property
  def coords_dict(self):
    if not hasattr(self.result, 'coords'):
      return {u'latitude': self.latitude, u'longitude': self.longitude}
    else:
      return {u'longitude': self.result.coords.latitude, u'longitude': self.result.coords.longitude}
  
  @property
  def name(self):
    return self.friendly_name or self.query
  
  # Caching
  @property
  def expires(self):
    """Returns the datetime when this object will be deemed to have expired."""
    return self.refreshed + relativedelta(**settings.MAX_LOCATION_CACHE_AGE)
  
  @property
  def expired(self):
    """Returns boolean as to whether this object has 'expired'. Always returns False if not geocoded."""
    if not self.geocoded:
      # This location hasn't been geocoded
      return False
    elif datetime.datetime.now() >= self.expires:
      # The location has expired
      return True
    elif not (self.result and hasattr(self.result, 'coords')):
      # The location hasn't yet been geocoded, but it should have been
      return True
    else:
      # The location hasn't expired
      return False
  
  def force_refresh(self):
    """Forces a refresh of the geo-mapping by re-geocoding (if the location is geocoded)."""
    if self.geocoded:
      self.result = self.get_geocoder()(self).geocode()
      self.latitude, self.longitude = tuple(self.result.coords)[:2]
      self.refreshed = datetime.datetime.now()
    return self
  
  def refresh(self):
    """Refreshes the geo-mapping it has already expired."""
    if self.expired:
      self.force_refresh()
    return self
  
  # Conveniences
  def distance_between(self, other_location, units='miles'):
    """Calculates the distance between this Location object and another Location object.
     units should be a string containing the unit of measurement (default: miles) you would like
     the result returned in (kilometers, miles, feet or nautical)."""
    if self.coords_tuple == other_location.coords_tuple:
      return 0
    dist_obj = geopy_distance.distance(self.coords_tuple, other_location.coords_tuple)
    return getattr(dist_obj, str(units))
  
  def within_bounds(self, north_west, south_east):
    """Given 2x two-tuples containing lat/long pairs (the northwest and southeast corners
     bounding a segment of the earth), returns Boolean as to whether this Location falls
     inside the area."""
    if (north_west[0] > self.latitude > south_east[0]) and (north_west[1] < self.longitude < south_east[1]):
      return True
    else:
      return False