Skip to content

Instantly share code, notes, and snippets.

@Pk13055
Created May 11, 2018 14:35
Show Gist options
  • Save Pk13055/08da5732f7f2f1f90d270c003b038ba7 to your computer and use it in GitHub Desktop.
Save Pk13055/08da5732f7f2f1f90d270c003b038ba7 to your computer and use it in GitHub Desktop.
Django generic location model compatible with Google MapsAPI
class Location(models.Model):
id = models.AutoField(primary_key=True)
place_id = models.CharField(max_length=128, unique=True, editable=False) # used to reference the place using maps API
name = models.CharField(max_length=100) # can be used for object referral
full_name = models.CharField(max_length=200) # can be used to display
extra = models.CharField(max_length=50, null=True, blank=True) # can be used to store door no, and other info
lat = models.DecimalField(max_digits=8, decimal_places=5)
long = models.DecimalField(max_digits=8, decimal_places=5)
rad_lat = models.DecimalField(max_digits=8, decimal_places=5, editable=False) # internal field for calculation
rad_long = models.DecimalField(max_digits=8, decimal_places=5, editable=False) # internal field for calculation
landmark = models.CharField(max_length=100) # used for lazy grouping
# direct storage of address components
# this field adapts directly from the JSON returned by the MAPS API
# type -> Array | len -> 7 | assert(eles == JSON) |
# keys -> long_name <String>, short_name <String>, types <Array>
address_component = JSONBField(JSONField(validators=[
]), default=(7 * [{'long_name' : "", 'short_name' : "", 'types' : [] }]), editable=False)
class Meta:
managed = True
db_table = 'locations'
def __init__(self, *args, **kwargs):
'''
@description override init to directly init the values from the google place API
accepts a JSON as follows:
place = {
'place_id' : <String>,
'name' : <String>,
'formatted_address' : <String>,
'vicinity' : <String>, // corresponds to landmark
'location' : {
'lat' : <Decimal 8,5>,
'lng' : <Decimal 8,5>,
},
'address_component' : [
{
'short_name' : <String>,
'long_name' : <String>,
'types' : <Array>,
}, ... <7 records>
],
}
@param [place] -> JSON
@params (opt.) keys
'''
place = kwargs.pop('place', None)
super(Location, self).__init__(*args, **kwargs)
if place is not None:
self.place_id = place['place_id']
self.name = place['name']
self.full_name = place['formatted_address']
self.lat = Decimal(str(round(float(place['location']['lat']), 5)))
self.long = Decimal(str(round(float(place['location']['lng']), 5)))
self.landmark = place['vicinity']
self.address_component = place['address_components']
def __str__(self):
''' pretty repr for an instance '''
return "%s | %f, %f" % (self.full_name, self.lat, self.long)
def clean(self, *args, **kwargs):
''' override to check validity of address_components '''
# address_component checking
error_msg = "Invalid address_component | %s"
# length check
if len(self.address_component) < 1:
raise ValidationError({ 'address_component' : error_msg % "Length is invalid" })
# JSON sanity check
if not all([isinstance(_, dict) for _ in self.address_component]):
raise ValidationError({ 'address_component' : error_msg % "Components are not all JSON" })
# key validation
if not all([all([_ in ['short_name', 'long_name', 'types'] for _ in x])
for x in self.address_component]):
raise ValidationError({ 'address_component' : error_msg % "Invalid keys" })
# key type validation
if not all(all([isinstance(_['short_name'], str), isinstance(_['long_name'], str),
isinstance(_['types'], list), ]) for _ in self.address_component):
raise ValidationError({ 'address_component' : error_msg % "Invalid key type(s)" })
return super().clean(*args, **kwargs)
def save(self):
''' override save to save internal fields at model level '''
self.full_clean()
self.rad_lat = math.radians(self.lat)
self.rad_long = math.radians(self.long)
super().save()
def getCoords(self):
''' returns the lat, long of the current location '''
return { 'lat' : self.lat, 'long' : self.long }
def getDistance(self, lat, lng):
'''
@description returns the distance between a given place and this current location
if
φ1 = lat1.toRadians()
φ2 = lat2.toRadians()
Δλ = (lon2-lon1).toRadians()
R = 6371e3;
then
return acos( Math.sin(φ1) * Math.sin(φ2) + Math.cos(φ1) * Math.cos(φ2) * Math.cos(Δλ) ) * R
@param lat -> decimal field
@param lng -> decimal field
@return dist -> float, metres
'''
distance = 0
rad_lat_2 = math.radians(lat)
del_lng = math.radians(lng - self.long)
R = 6371e3
distance = math.acos( math.sin(self.rad_lat) * math.sin(rad_lat_2) + \
math.cos(self.rad_lat) * math.cos(rad_lat_2) * math.cos(del_lng) ) * R
return distance
/**
* @description Converts a given Google MapAPI place to
* the required JSON for storage
* @param {JSON} place
* @param {function} callback
*/
function placeToJSON(places, callback) {
let place = (places.__proto__ == [].__proto__)? places[0] : places;
let fields = ['address_components', 'vicinity', 'name', 'formatted_address', 'place_id'];
let return_place = {};
// commonplace field addition
fields.forEach((v, i) => {
return_place[v] = place[v];
});
// add the lat long details
return_place['location'] = place.geometry.location.toJSON();
callback && callback(return_place);
return return_place;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment