#!/usr/bin/env python
"""
Coded by Kyle Hovey a.k.a. spel3o
This is a script that uses the Python Imaging Library to model the eliptical orbit of a planet.
This is not a perfect model of an orbit, and models an ellipse by use of regular intervals where
gravity takes a "blow" on the intertial vector of the planet. The vector of the blow is a function
of gravity vs. distance and thus is an application of inverse square. In other words:
g = H/(x/K)**2
Where H and K are the vertical and horizontal scaling constants and x is the distance from the
orbital body.
Errors in orbit calculation are found when:
a.) A vector lands somewhere in the middle of a pixel (can be augmented by scaling)
b.) The gravity vector's direction is determined (off by less than a degree due to rounding)
"""
import Image, ImageDraw, getpass, os, math
user = getpass.getuser()
path = '/Users/'+user+'/Desktop' #Get current path
def img_new(x, y, name='0'): #Create function for making new blank images
img = Image.new('RGBA', (x, y), 'white')
img.save(path+'/'+name+'.png', 'PNG')
del img
def conv_coords(tup, x, y): #Convert cartesian coordinates to PIL coordinates
return (tup[0], y-tup[1])
def grav_pull(dist, scale=1, K=6.35, H=6.336): #Determine the gravitational vector length from the distance and two constants
return scale*(K/((dist/scale)/H)**2)
def find_center(dim): #Find the center of a rectangle with given dimensions (x,y)
return (dim[1]/2,dim[0]/2)
def draw_vector(working_vector, center, color, path, image_no): #Define function for drawing a complete vector between two points
current_image = Image.open(path+'/'+str(image_no)+'.png') #Vectors are stored with both of their coordinates in a list [(x0,y0),(x1,y1)]
draw = ImageDraw.Draw(current_image) #Open active image
draw.line(conv_coords(working_vector[0], center[1], center[0]) + conv_coords(working_vector[1], center[1], center[0]), fill=color, width=4)
current_image.save(path+'/'+str(image_no)+'.png', 'PNG') #Save image
def draw_sun(center, path, image_no=0): #Draw a dot in the center of the image for the sun
draw_vector([(center[0]-1,center[1]+1),(center[0]+1,center[1]-1)], center, '#000', path, image_no)
def draw_arb_vector(start, end, length, center, color, image_no, path, ret=1): #Define a function to draw an arbitrarily long vector towards another point
if end[1]-start[1] < 0: #Find if vector will be moving up or down
y_neg = -1
else:
y_neg = 1
if end[0]-start[0] < 0: #Find if vector will be moving left or right
x_neg = -1
else:
x_neg = 1
a = x_neg*math.sqrt(length**2/(1+((start[1]-end[1])/(start[0]-end[0]))**2)) #Find the change in the y value by solving for slope (rise/run) and pythagorean theorem a^2+b^2=c^2
b = y_neg*math.sqrt(length**2-a**2) #Find the change in the x value by using pythagorean theorem
new_endpoint = (start[0]+a, start[1]+b)
arb_vector = [start, new_endpoint]
draw_vector(arb_vector, center, color, path, image_no) #Draw the new vector
if ret == 1: #Return the new vector
return arb_vector
def iterate(working_vector, center, image_no, path, scale): #Define the function for making a new iteration of the orbit
vector_color = '#7513D6' #
inertia_color = '#CF8A15' #Make things pretty
gravity_color = '#069677' #
segment_color = '#000'
draw_sun(center, path, image_no) #Draw the sun
draw_vector([working_vector[0], center], center, segment_color, path, image_no) #Draw initial vector
draw_vector(working_vector, center, vector_color, path, image_no) #Draw line from vector start to center
delta_y = working_vector[1][1]-working_vector[0][1] #
delta_x = working_vector[1][0]-working_vector[0][0] #Determine the rise and run of the vector
end_coords = working_vector[1] #Save the end of the original vector for the drawing of the next
working_vector = [working_vector[1], (working_vector[1][0]+delta_x, working_vector[1][1]+delta_y)] #Apply the change in rise and run for the first vector to double it
draw_vector(working_vector, center, inertia_color, path, image_no) #Draw the extended vector copy of the first vector
distance = math.sqrt((working_vector[0][0]-center[0])**2+(working_vector[0][1]-center[1])**2) #Use Pythagorean theorem to find distance to center
g = grav_pull(distance, scale) #Find gravitational pull
working_vector = draw_arb_vector(working_vector[1], (center[0]+delta_x,center[1]+delta_y), g, center, gravity_color, image_no, path) #Draw the gravity vector
working_vector = [working_vector[1], end_coords] #Create new vector starting at the end of the initial vector and ending
draw_vector(working_vector, center, vector_color, path, image_no) #at the end of the gravity vector, then draw it.
return [working_vector[1],working_vector[0]] #Return resultant vector for next iteration
def do_main(x=1000, y=2000): #Start the whole shebang (no pun intended)
image_no = 0 #Start at image 0
initial_distance = input('Please enter the starting distance of the orbital body from the sun:') #
initial_vector = input('Please enter the initial speed vector in cm proportional to velocity:') #Get preliminary data
scale = input('Please enter the scale of the model:') #
img_new(y,x) #Create a new image with the default or provided dimensions
size = (x,y)
center = find_center(size) #Find center of new image
working_vector =[(center[0]+initial_distance*scale, center[1]), (center[0]+initial_distance*scale, center[1]+initial_vector*scale)] #Establish the initial vector
while True:
questionaire = raw_input('Hit enter to execute an iteration or d for done:') #Begin the iterations
if questionaire == 'd': #Break out of iterations if d for done is received
##compile_gif(path+'/*.png)
print('Program execution completed.')
return False
else:
working_vector = iterate(working_vector, center, image_no, path, scale) #Create new iteration
os.system('cp '+path+'/'+str(image_no)+'.png '+path+'/'+str(image_no+1)+'.png') #Copy the image and increase the filename number by one
image_no += 1 #Increment the image count
do_main() #Start the prompt