import json
import requests
import datetime
from django import forms
from django.dispatch import receiver
from django.db.models.signals import post_save
from django.urls import reverse
from django.apps import apps
from django.contrib.gis.geos import GEOSGeometry, fromstr, MultiPolygon, Point, LineString, MultiLineString
from django.contrib.gis.db import models
from django.contrib.sitemaps import Sitemap
from django.utils.safestring import mark_safe
from django.utils import timezone
from django.conf import settings
from utils.util import render_images, extract_main_image, markdown_to_html
"http://staticmap.openstreetmap.de/staticmap.php?center=object.location.geometry.centroid.y,object.location.geometry.centroid.x&zoom=14&size=1140x300&maptype=osmarenderer&markers=40.702147,-74.015794,lightblue1"
class Region(models.Model):
"""Model to define arbitrary regions based on where I've been"""
name = models.CharField(max_length=50)
slug = models.SlugField()
pub_date = models.DateTimeField('Date published', null=True)
geometry = models.MultiPolygonField(srid=4326, null=True)
lon = models.FloatField('Longitude', help_text="Longitude of centerpoint", null=True)
lat = models.FloatField('Latitude', help_text="Latitude of centerpoint", null=True)
zoom_level = models.CharField(max_length=2, null=True)
def get_absolute_url(self):
return "/locations/region/%s/" % (self.slug)
def __str__(self):
return self.name
class Country(models.Model):
"""
A geographic model based on the v3 of the simplified world borders multipolygon shapefile from http://thematicmapping.org/downloads/world_borders.php.
"""
name = models.CharField(max_length=50)
area = models.IntegerField(help_text="Area of Country in SQ meters")
pop2005 = models.IntegerField('Population 2005')
fips = models.CharField('FIPS Code', max_length=2, help_text=mark_safe('Federal Information Processing Standard Code'))
iso2 = models.CharField('2 Digit ISO', max_length=2, help_text=mark_safe('International Organization for Standardization'))
iso3 = models.CharField('3 Digit ISO', max_length=3, help_text=mark_safe('International Organization for Standardization'))
un = models.IntegerField('United Nations Code')
REGION_CODES = (
(0, 'MISC'),
(2, 'Africa'),
(9, 'Oceania'),
(19, 'Americas'),
(142, 'Asia'),
(150, 'Europe'),
)
SUBREGION_CODES = (
(0, 'MISC'),
(5, 'South America'),
(11, 'Western Africa'),
(13, 'Central America'),
(14, 'Eastern Africa'),
(15, 'Northern Africa'),
(17, 'Middle Africa'),
(18, 'Southern Africa'),
(21, 'North America'),
(29, 'Caribbean'),
(30, 'Eastern Asia'),
(34, 'Southern Asia'),
(35, 'Southeast Asia'),
(39, 'Southern Europe'),
(53, 'Australia and New Zealand'),
(54, 'Melanesia'),
(57, 'Micronesia'),
(61, 'Polynesia'),
(143, 'Central Asia'),
(145, 'Western Asia'),
(151, 'Eastern Europe'),
(154, 'Northern Europe'),
(155, 'Western Europe'),
)
region = models.IntegerField('Region Code', choices=REGION_CODES)
subregion = models.IntegerField('Sub-Region Code', choices=SUBREGION_CODES)
lon = models.FloatField('Longitude', help_text="Longitude of centerpoint")
lat = models.FloatField('Latitude', help_text="Latitude of centerpoint")
zoom_level = models.CharField(max_length=2, null=True)
slug = models.SlugField(null=True)
visited = models.BooleanField(default=False)
lux_region = models.ForeignKey(Region, on_delete=models.CASCADE, null=True)
pub_date = models.DateTimeField('Date published', null=True)
geometry = models.MultiPolygonField('Country Border', srid=4326)
class Meta:
ordering = ['name']
verbose_name_plural = 'Countries'
def __str__(self):
return self.name
def get_absolute_url(self):
return "/locations/%s/" % (self.slug)
class State(models.Model):
"""Model to hold state boundaries"""
name = models.CharField(max_length=250, blank=True, null=True,)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
slug = models.SlugField()
code = models.CharField(max_length=2, null=True, blank=True)
pub_date = models.DateTimeField('Date published', null=True)
geometry = models.MultiPolygonField(srid=4326, null=True)
class Meta:
ordering = ['name']
def __str__(self):
return "%s" % (self.name)
def get_absolute_url(self):
return "/locations/%s/%s/" % (self.country.slug, self.slug)
class Location(models.Model):
"""
Model to hold location shapes as arbitrarily defined by me
"""
state = models.ForeignKey(State, on_delete=models.CASCADE)
name = models.CharField(max_length=50, )
slug = models.SlugField()
pub_date = models.DateTimeField('Date published')
geometry = models.MultiPolygonField(srid=4326)
parent = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True)
state_name = models.CharField(max_length=50, blank=True)
state_slug = models.CharField(max_length=50, blank=True)
country_name = models.CharField(max_length=50, blank=True)
country_slug = models.CharField(max_length=50, blank=True)
region_name = models.CharField(max_length=50, blank=True)
region_slug = models.CharField(max_length=50, blank=True)
description = models.TextField(blank=True)
description_html = models.TextField(blank=True)
class Meta:
ordering = ('-pub_date',)
get_latest_by = 'pub_date'
def __str__(self):
return self.name
def comma_name(self):
if self.country_name == "United States":
return self.state_name
else:
return self.country_name
@property
def get_previous_admin_url(self):
return reverse('admin:%s_%s_change' %(self._meta.app_label, self._meta.model_name), args=[self.get_previous_by_pub_date().id] )
@property
def get_next_admin_url(self):
model = apps.get_model(app_label=self._meta.app_label, model_name=self._meta.model_name)
try:
return reverse('admin:%s_%s_change' %(self._meta.app_label, self._meta.model_name), args=[self.get_next_by_pub_date().pk] )
except model.DoesNotExist:
return ''
def get_absolute_url(self):
return "/locations/%s/%s/%s/" % (self.country_slug, self.state_slug, self.slug)
def save(self, *args, **kwargs):
self.state_name = self.state.name
self.state_slug = self.state.slug
self.country_name = self.state.country.name
self.country_slug = self.state.country.slug
self.region_name = self.state.country.lux_region.name
self.region_slug = self.state.country.lux_region.slug
self.description_html = markdown_to_html(self.description)
super(Location, self).save()
class Route(models.Model):
"""Model to hold routes for longer trips"""
name = models.CharField(max_length=200)
slug = models.SlugField()
zoom = models.CharField(max_length=2, null=True)
template_var_name = models.CharField(max_length=10, null=True)
pub_date = models.DateTimeField('Date published', null=True)
geometry = models.MultiPointField(srid=4326)
def get_absolute_url(self):
return "/locations/%s/%s/%s/" % (self.slug)
def __str__(self):
return self.name
class CheckIn(models.Model):
point = models.PointField(blank=True)
location = models.ForeignKey(Location, on_delete=models.CASCADE, blank=True, null=True)
date = models.DateField(default=timezone.now)
class Meta:
ordering = ('-date',)
get_latest_by = 'date'
def __str__(self):
return str(self.date)
@property
def lon(self):
'''Get the site's longitude.'''
return self.point.x
@property
def lat(self):
'''Get the site's latitude.'''
return self.point.y
def save(self):
try:
self.location = Location.objects.filter(geometry__contains=self.point).get()
except Location.DoesNotExist:
raise forms.ValidationError("There is no location associated with that point, add it: %sadmin/locations/location/add/" % (settings.BASE_URL))
super(CheckIn, self).save()
class LuxCheckIn(models.Model):
point = models.PointField(blank=True)
location = models.ForeignKey(Location, on_delete=models.CASCADE, blank=True, null=True)
date = models.DateField(default=timezone.now)
location_name = models.CharField(max_length=200, blank=True, null=True)
state_name = models.CharField(max_length=200, blank=True, null=True)
country_name = models.CharField(max_length=200, blank=True, null=True)
region_name = models.CharField(max_length=200, blank=True, null=True)
class Meta:
ordering = ('-date',)
get_latest_by = 'date'
def __str__(self):
return str(self.date)
@property
def lon(self):
'''Get the site's longitude.'''
return self.point.x
@property
def lat(self):
'''Get the site's latitude.'''
return self.point.y
def save(self):
if not self.location:
try:
self.location = Location.objects.filter(geometry__contains=self.point).get()
except Location.DoesNotExist:
raise forms.ValidationError("There is no location associated with that point, add it: %sadmin/locations/location/add/" % (settings.BASE_URL))
self.location_name = self.location.name
self.state_name = self.location.state.name
self.country_name = self.location.state.country.name
self.region_name = self.location.state.country.lux_region.name
super(LuxCheckIn, self).save()
class Campsite(models.Model):
name = models.CharField(max_length=200)
point = models.PointField(blank=True)
location = models.ForeignKey(Location, on_delete=models.CASCADE, blank=True, null=True)
date_arrived = models.DateField(default=timezone.now)
date_left = models.DateField(default=timezone.now, null=True, blank=True)
CAMPSITE_TYPE = (
(0, 'National Park'),
(1, 'National Forest'),
(2, 'National Other'),
(3, 'State Park'),
(4, 'State Forest'),
(5, 'County Park'),
(6, 'City Park'),
(7, 'Boondocking'),
(8, 'Other'),
)
campsite_type = models.IntegerField(choices=CAMPSITE_TYPE, default=0)
campsite_number = models.IntegerField(blank=True, null=True)
campsite_price = models.IntegerField(blank=True, null=True)
campsite_we_wish_we_had = models.IntegerField(blank=True, null=True)
body_markdown = models.TextField(blank=True, null=True)
body_html = models.TextField(blank=True, null=True)
image = models.ForeignKey("media.LuxImage", on_delete=models.CASCADE, null=True, blank=True)
class Meta:
ordering = ('-date_arrived',)
get_latest_by = 'date_arrived'
def __str__(self):
return "%s - %s" % (self.name, self.location)
@property
def lon(self):
'''Get the site's longitude.'''
return self.point.x
@property
def lat(self):
'''Get the site's latitude.'''
return self.point.y
@property
def nights_stayed(self):
'''Get the number of nights we spent there '''
delta = self.date_left - self.date_arrived
return delta.days
@property
def total_price(self):
'''Get the price and total price for display in admin '''
if not self.campsite_price:
price = 0
total = 0
else:
price = self.campsite_price
total = self.nights_stayed * price
return "${0} (${1})".format(price, total)
def save(self, *args, **kwargs):
created = self.pk is None
if not created:
md = render_images(self.body_markdown)
self.body_html = markdown_to_html(md)
try:
self.location = Location.objects.filter(geometry__contains=self.point).get()
except Location.DoesNotExist:
raise forms.ValidationError("There is no location associated with that point, add it: %sadmin/locations/location/add/" % (settings.BASE_URL))
main_image = extract_main_image(self.body_markdown)
if main_image:
self.image = main_image
super(Campsite, self).save(*args, **kwargs)
class WritingbyCountrySitemap(Sitemap):
changefreq = "weekly"
priority = 0.6
protocol = "https"
def location(self, item):
return '/jrnl/%s' % item.slug
def items(self):
return Country.objects.filter(visited=True)
def get_bounds(lat, lon):
'''
given a set of lat,lon coords return the nearest administrative level boundary (usually turn out to be city, but sometimes it's a county, occasionally a state)
'''
r = requests.get('http://nominatim.openstreetmap.org/reverse',
params={
'lat': lat,
'lon': lon,
'accept-language': 'en',
'format': 'json'
})
r.raise_for_status()
data = json.loads(r.text)
adr = data.get('address', {})
city = adr.get('hamlet') or adr.get('village') or adr.get('town') or adr.get('city')
state = adr.get('state')
country = adr.get('country')
r = requests.get('http://nominatim.openstreetmap.org/search',
params={
'city': city,
'state': state,
'country': country,
'polygon_text': 1,
'format': 'json',
'featuretype': 'neighborhood'
})
r.raise_for_status()
data = json.loads(r.text)
poly = GEOSGeometry(data[0]['geotext'])
# Sometimes you get a multipolygon, sometimes just a polygon
if poly.geom_type == 'Polygon':
poly = MultiPolygon(fromstr(data[0]['geotext']))
return poly
def get_gpx_folder(instance, filename):
return "images/gpx/%s/%s" % (datetime.datetime.today().strftime("%Y"), filename)
class GPXFile(models.Model):
title = models.CharField(max_length=300)
raw_data = models.TextField(blank=True)
gpx_file = models.FileField(upload_to=get_gpx_folder, blank=True)
class Meta:
ordering = ('title',)
def __str__(self):
return str(self.title)
def save(self, *args, **kwargs):
created = self.pk is None
print(self.gpx_file.path)
super(GPXFile, self).save(*args, **kwargs)
class GPXPoint(models.Model):
name = models.CharField("Name", max_length=50, blank=True)
description = models.CharField("Description", max_length=250, blank=True)
gpx_file = models.ForeignKey(GPXFile, on_delete=models.CASCADE)
point = models.PointField()
def __str__(self):
return str(self.name)
class GPXTrack(models.Model):
track = models.MultiLineStringField()
gpx_file = models.ForeignKey(GPXFile, on_delete=models.CASCADE)
@receiver(post_save, sender=GPXFile)
def post_save_events(sender, update_fields, created, instance, **kwargs):
#if created:
with open(instance.gpx_file.path, 'r') as f:
instance.raw_data = f.read()
parse_gpx(instance, instance.raw_data)
post_save.disconnect(post_save_events, sender=GPXFile)
instance.save()
post_save.connect(post_save_events, sender=GPXFile)
import gpxpy
import gpxpy.gpx
def parse_gpx(gpx_obj, gpx_data):
gpx = gpxpy.parse(gpx_data)
if gpx.waypoints:
for waypoint in gpx.waypoints:
new_waypoint = GPXPoint()
if waypoint.name:
new_waypoint.name = waypoint.name
else:
new_waypoint.name = 'unknown'
new_waypoint.point = Point(waypoint.longitude, waypoint.latitude)
new_waypoint.gpx_file = gpx_obj
new_waypoint.save()
if gpx.tracks:
for track in gpx.tracks:
print("track name:" +str(track.name))
for segment in track.segments:
track_list_of_points = []
for point in segment.points:
point_in_segment = Point(point.longitude, point.latitude)
track_list_of_points.append(point_in_segment.coords)
new_track_segment = LineString(track_list_of_points)
new_track = GPXTrack.objects.create(
gpx_file = gpx_obj,
track = MultiLineString(new_track_segment)
)
new_track.save()
class Track(models.Model):
title = models.CharField(max_length=300)
subtitle = models.CharField(max_length=200, blank=True)
slug = models.SlugField()
body_markdown = models.TextField()
body_html = models.TextField(blank=True)
date_walked = models.DateField(default=timezone.now)
point = models.PointField(blank=True)
location = models.ForeignKey(Location, on_delete=models.CASCADE, blank=True, null=True)
gpx_file = models.ForeignKey(GPXFile, null=True, on_delete=models.SET_NULL)
RATINGS = (
('1', "1 Star"),
('2', "2 Stars"),
('3', "3 Stars"),
('4', "4 Stars"),
('5', "5 Stars"),
)
rating = models.CharField(max_length=1, choices=RATINGS, null=True, blank=True)
PACE = (
('1', "Walk"),
('2', "Run"),
)
pace = models.CharField(max_length=1, choices=PACE, default=2)
distance = models.DecimalField(max_digits=3, decimal_places=2, null=True)
duration = models.DecimalField(max_digits=3, decimal_places=0, null=True, help_text="in minutes")
def __str_(self):
return self.title
class Meta:
ordering = ('-date_walked',)
def get_rating(self):
return int(self.rating)
@property
def lon(self):
'''Get the site's longitude.'''
return self.point.x
@property
def lat(self):
'''Get the site's latitude.'''
return self.point.y
@property
def ratings_range(cls):
return range(1, 6)
def save(self, *args, **kwargs):
created = self.pk is None
if not created:
md = render_images(self.body_markdown)
self.body_html = markdown_to_html(md)
try:
self.location = Location.objects.filter(geometry__contains=self.point).get()
except Location.DoesNotExist:
raise forms.ValidationError("There is no location associated with that point, add it: %sadmin/locations/location/add/" % (settings.BASE_URL))
super(Track, self).save(*args, **kwargs)