Dec

9

Hickory Dickory Dock...

This is the nerdiest, least useful thing I've added to this site so far: an analog clock renderer!

Check it out: you get a regular clock with an hour hand and a minute hand, but you also get a sun indicator, to help you tell AM from PM. When it's daylight, the sun travels along the top half of the clock's edge in a nice happy yellow. When it's night time the sun travels around the lower half of the circle and gets drawn a dark blue. The extra keen part is that if it's near sunrise or sunset, the sun's colour is drawn from a gradient between the noon-day yellow and a bright red. I'll have to make a point of posting at dusk tomorrow.

I have no idea if this technique for representing time is at all intuitive, but I really enjoyed making it.

To make this happen I wrote eight image rendering functions and five datetime functions. They're all pretty short. Here's the function that draws the sun onto the image:

def draw_sun_image(draw, timestamp, arc_radius, sun_radius):
sun_angle = -2 math.pi (timestamp.hour / 24.0) - (math.pi / 2.0)
 
sun_x = arc_radius + math.cos(sun_angle) (arc_radius - sun_radius)
sun_y = arc_radius - math.sin(sun_angle)
(arc_radius - sun_radius)
bounding_box = [(sun_x - sun_radius, sun_y - sun_radius), (sun_x + sun_radius, sun_y + sun_radius)]
sun_colour = get_sun_colour(timestamp)
draw.ellipse(bounding_box, fill='rgb(%s,%s,%s)' % sun_colour)

And here is get_sun_colour, which draws on what I call the "clockwork" functionality and a colour blending utility function to figure out what colour the sun should be:

def get_sun_colour(timestamp):
sunnyness = get_amount_of_sun(timestamp)
if sunnyness == 0.0:
return NIGHT_SUN_COLOUR
else:
return blend_colours(DAY_SUN_COLOUR, TWILIGHT_SUN_COLOUR, sunnyness)

I've also got a matrix rotation function and a matrix translation function in here. I'm sure there's an awesome library for these guys, but it was short code, and I'm caching the clock images to disk once I make them, so I'm not worried about the heavy lifting. It was actually a lot of fun to write all this stuff that had nothing to do with flow control or text manipulation. Just some basic algorithms for getting data from state A to state B.

For the clockwork code, I used an awesome library called Sun.py. This module can calculate the rise and set times of the sun for any point on the globe for any day within about a hundred years of today. I am indebted to Sean Russell, Henrik Härkönen, and Miguel Tremblay for their time spent porting and enhancing the code, and to Paul Schlyter for writing the original C code in 1989 and for releasing it to the public domain in 1992. This code has been under periodic active development for almost twenty years. It's a nice little piece of work!

I'd kind of like to stick my name on that list of contributors and get Sun.py to give back datetime.time objects instead of floats representing hours. I'll have to work myself up to it, I think.

I wish I had more cool code to post for this one, but honestly each function is just pretty boring. And none of it is really specific to Django. Oh yeah. Here's the template tag:

@register.filter
def clock_url(timestamp, width):
"""
Takes a datetime object and a width and returns a URL to an image
that contains an analog clock representation of the datetime's time,
plus an icon of the sun indicating time of day or night.
 
Usage:
 
{{ blogpost.pub_date|clock_url:60 }}
"""
rel_image_dir = 'managed/clock/%s/%s/' % (
timestamp.strftime('%Y-%B').lower(),
timestamp.strftime('%d'),
)
image_filename = '%s-w%s.png' % (
timestamp.strftime('%H-%M'),
width
)
image_dir_on_disk = '%s%s' % (settings.MEDIA_ROOT, rel_image_dir)
image_path_on_disk = '%s%s' % (image_dir_on_disk, image_filename)
if not os.path.exists(image_path_on_disk):
image = create_clock(timestamp, radius=int(width)/2, thickness=7)
if not os.path.exists(image_dir_on_disk):
os.makedirs(image_dir_on_disk)
image.save(image_path_on_disk)
return '%s%s%s' % (settings.MEDIA_URL, rel_image_dir, image_filename)

I'm going to end up with quite the proliferation of little clock images. Well. One for each blog post. That's not too bad.

I'm a little ashamed of the trends you can see here for when I get these posts up. I'll have to post more during the day just so I get to see more of that cheery, yellow sun.

Awesome! Thank you.

— Nut Geb (December 09, 2008 at 11:51 a.m.)