#!/usr/bin/env python
#coding:utf-8

#Utilities and such for SWMMIO processing
import math
from PIL import Image, ImageDraw, ImageFont, ImageOps
from time import strftime
import os
import numpy as np
# import matplotlib.path as mplPath
# from matplotlib.transforms import BboxBase
import pickle
import json
#import arcpy
from swmmio.graphics import draw_utils as du

#contants
sPhilaBox = 	((2683629, 220000), (2700700, 231000))
sPhilaSq =		((2683629, 220000), (2700700, 237071))
sPhilaSm1 = 	((2689018, 224343), (2691881, 226266))
sPhilaSm2 = 	((2685990, 219185), (2692678, 223831))
sPhilaSm3 = 	((2688842, 220590), (2689957, 221240))
sPhilaSm4 = 	((2689615, 219776), (2691277, 220738))
sPhilaSm5 = 	((2690303, 220581), (2690636, 220772))
sm6 = 			((2692788, 225853), (2693684, 226477))
chris = 		((2688798, 221573), (2702834, 230620))
nolibbox= 		((2685646, 238860),	(2713597, 258218))
mckean = 		((2691080, 226162),	(2692236, 226938))
d68d70 = 		((2691647, 221073),	(2702592, 227171))
d70 = 			((2694096, 222741),	(2697575, 225059))
ritner_moyamen =((2693433, 223967),	(2694587, 224737))
morris_10th = 	((2693740, 227260),	(2694412, 227693))
morris_12th = 	((2693257, 227422),	(2693543, 227614))
study_area = 	((2682005, 219180), (2701713, 235555))
dickenson_7th = ((2695378, 227948), (2695723, 228179))
packer_18th = 	((2688448, 219932), (2691332, 221857))
moore_broad = 	((2689315, 225537), (2695020, 228592))
oregon_front =  ((2695959, 221033), (2701749, 224921))
mckean_2nd = 	((2696719, 224600), (2699010, 226150))

#COLOR DEFS
red = 		(250, 5, 5)
blue = 		(5, 5, 250)
shed_blue = (0,169,230)
white =		(250,250,240)
black = 	(0,3,18)
lightgrey = (235, 235, 225)
grey = 		(100,95,97)
park_green = (115, 178, 115)
green = 	(115, 220, 115)
water_grey = (130, 130, 130)
purple = (250, 0, 250)

#FONTS
fontFile = r"C:\Data\Code\Fonts\Raleway-Regular.ttf"

def getFeatureExtent(feature, where="SHEDNAME = 'D68-C1'", geodb=r'C:\Data\ArcGIS\GDBs\LocalData.gdb'):

	import arcpy
	features = os.path.join(geodb, feature)
	for row in arcpy.da.SearchCursor(features, ["SHAPE@"], where_clause=where):

		#extent = row[0].extent
		#xy1,  xy2 =(extent.XMin, extent.YMin), (extent.XMax, extent.YMax)
		#return xy1, xy2
		#return row
		for part in row[0]:
			xy1,  xy2 =(part[0].X, part[0].Y), (part[1].X, part[1].Y)

			print xy1, xy2
			#print part

#FUNCTIONS
def traceFromNode(model, startNode, mode='up', stopnode=None):

	#nodes = model.organizeNodeData(bbox)['nodeDictionaries']
	#links = model.organizeConduitData(bbox)['conduitDictionaries']
	inp = model.inp
	conduitsDict = inp.createDictionary("[CONDUITS]")
	storagesDict = inp.createDictionary("[STORAGE]")
	junctionsDict = inp.createDictionary("[JUNCTIONS]")
	outfallsDict = inp.createDictionary("[OUTFALLS]")
	allNodesDict = merge_dicts(storagesDict, junctionsDict, outfallsDict)

	tracedNodes = [startNode] #include the starting node
	tracedConduits = []

	#recursive function to trace upstream
	def trace (nodeID):
		#print "tracing from {}".format(nodeID)
		for conduit, data in conduitsDict.iteritems():

			conduitUpNodeID = conduitDnNodeID = None
			if len(data) >= 1:
				#not sure why i need to do this check, but it prevents an indexing error on some
				conduitUpNodeID = data[0]
				conduitDnNodeID = data[1]

			if mode=='down' and conduitUpNodeID == nodeID and conduit not in tracedConduits:
				#conduit not in traced conduits to prevent duplicates for some reason
				#grab its dnstream node ID
				tracedConduits.append(conduit)
				tracedNodes.append(conduitDnNodeID)
				if stopnode and conduitDnNodeID == stopnode:
					break
				trace(conduitDnNodeID)

			if mode == 'up' and conduitDnNodeID == nodeID and conduit not in tracedConduits:
				#conduit not in traced conduits to prevent duplicates for some reason
				#this conduit is upstream of current node
				#grab its upstream node ID
				tracedConduits.append(conduit)
				tracedNodes.append(conduitUpNodeID)
				if stopnode and conduitUpNodeID == stopnode:
					break
				trace(conduitUpNodeID)

	#kickoff the trace
	print "Starting trace {0} from {1}".format(mode, startNode)
	trace(startNode)
	print "Traced {0} nodes from {1}".format(len(tracedNodes), startNode)
	return {'nodes':tracedNodes, 'conduits':tracedConduits}

def length_of_conduits(conduitsubset):

	#return the total length of conduits given a dictionary of Link objects
	l = 0
	for id, conduit in conduitsubset.iteritems():
		l +=conduit.length

	return l

def length_of_new_conduit(model1, model2):
    newconduitIDs = scomp.get_all_unmatched_inp_elements(model1, model2)['[CONDUITS]']['added']
    all_conduits = model2.organizeConduitData()['conduit_objects']
    l = 0
    for condID in newconduitIDs:
        l += all_conduits[condID].length

    return l



def pointIsInBox (bbox, point):

		#pass a lower left (origin) and upper right tuple representing a box,
		#and a tuple point to be tested

		LB = bbox[0]
		RU = bbox[1]

		x = point[0]
		y = point[1]

		if x < LB[0] or x >  RU[0]:
			return False
		elif y < LB[1] or y > RU[1]:
			return False
		else:
			return True

def pipeLengthPlanView(upstreamXY, downstreamXY):

	#return the distance (units based on input) between two points
	x1 = float(upstreamXY[0])
	x2 = float(downstreamXY[0])
	y1 = float(upstreamXY[1])
	y2 = float(downstreamXY[1])

	return math.hypot(x2 - x1, y2 - y1)
def pipeProfileLengthTransform(planLength, upstreamEl, downstreamEl, xTrans, yTrans):

	#tranform a pipe legth in x and y direction via its components

	ycomponent = upstreamEl - downstreamEl
	newXComponent = planLength * xTrans
	newYComponent = ycomponent * yTrans

	return math.hypot(newXComponent, newYComponent)
def merge_dicts(*dict_args):
    '''
    Given any number of dicts, shallow copy and merge into a new dict,
    precedence goes to key value pairs in latter dicts.
    '''
    result = {}
    for dictionary in dict_args:
		if dictionary:
			result.update(dictionary)
    return result


def elementChange(elementData, parameter='maxflow'):

	#with a joined dictionary item, returns change from existing to proposed

	proposedVal = elementData['proposed'].get(parameter, 0.0)
	existingVal = elementData['existing'].get(parameter, 0.0)

	return proposedVal - existingVal

def subsetConduitsInBoundingBox(conduitsDict, boundingBox):

	newDict = {}
	for conduit, conduitData in conduitsDict.iteritems():
		if 'existing' and 'proposed' in conduitData:
			#then we have a "compare" dictionary, must drill down further, use proposed
			coordPair = conduitData['proposed']['coordinates']
		else:
			coordPair = conduitData['coordinates']

		if boundingBox and (not pointIsInBox(boundingBox, coordPair[0]) or not pointIsInBox(boundingBox, coordPair[1])):
			#ship conduits who are not within a given boudning box
			continue

		newDict.update({conduit:conduitData})

	return 	newDict

def subsetElements(model, type='node', key='floodDuration', min=0.083, max=99999, bbox=None, pair_only=False):
	#return a subset of a dictionary of swmm elements based on a value

	if type=='node':
		elems = model.organizeNodeData(bbox)['node_objects']

	elif type=='conduit':
		elems = model.organizeConduitData(bbox)['conduit_objects']
	else: return []

	if pair_only:
		#only return the element and the value being filtered on
		subset = {k:v.flood_duration for (k,v) in elems.items() if v.flood_duration >= min }
	else:
		subset = {k:v for (k,v) in elems.items() if v.flood_duration >= min }

	return subset

def shape2Pixels(feature, cols = ["OBJECTID", "SHAPE@"],  where="SHEDNAME = 'D68-C1'", shiftRatio=None, targetImgW=1024, bbox=None, gdb=r'C:\Data\ArcGIS\GDBs\LocalData.gdb'):

	#take data from a geodatabase and organize in a dictionary with coordinates and draw_coordinates
	import arcpy
	features = os.path.join(gdb, feature)
	geometryDicts = {}
	for row in arcpy.da.SearchCursor(features, cols, where_clause=where):

		try:
			#detect what shape type this is
			geomType = row[1].type
			jsonkey = 'rings' #default for polygons, for accessing polygon vert coords
			if geomType == 'polyline':
				jsonkey = 'paths'


			geometrySections = json.loads(row[1].JSON)[jsonkey] # an array of parts
			#geomArr = json.loads(row[1].JSON)[jsonkey][0] #(assumes poly has one ring)

			for i, section in enumerate(geometrySections):

				#check if part of geometry is within the bbox, skip if not
				if bbox and len ( [x for x in section if pointIsInBox(bbox, x)] ) == 0:
					continue #skip this section if none of it is within the bounding box

				id = str(row[0])
				if len(geometrySections) > 1:
					id = str(row[0]) + "." + str(i)

				geometryDict = {'coordinates':section, 'geomType':geomType}
				#geometryDicts.update({id:{'coordinates':geomArr}})

				#add any other optional cols, starting at 3rd col item
				for j, col in enumerate(cols[2:]):
					col_data = str(row[j+2])
					geometryDict.update({col:col_data})

				geometryDicts.update({id:geometryDict})

		except:
			"prob with ", row[0]

	#find mins and maxs
	maxX = maxY = 0.0 #determine current width prior to ratio transform
	minX = minY = 99999999.0
	if not bbox:
		#review all points of the polygon to find min of all points
		for geom, geomData in geometryDicts.iteritems():
			for seg in geomData['coordinates']:
				minX = min(minX, seg[0])
				minY = min(minY, seg[1])
				maxX = max(maxX, seg[0])
				maxY = max(maxY, seg[1])
		bbox = [(minX, minY),(maxX, maxY)]

	#can probably get rid of these, if polygons are always secondary to the model drawing stuff (in which these params should be defined)
	height = float(bbox[1][1]) - float(bbox[0][1])
	width = float(bbox[1][0]) - float(bbox[0][0])
	shiftRatio = float(targetImgW / width) # to scale down from coordinate to pixels

	for geom, geomData in geometryDicts.iteritems():
		drawCoords = []
		for coord in geomData['coordinates']:

			drawCoord = coordToDrawCoord(coord, bbox, shiftRatio)
			drawCoords.append(drawCoord)
		geomData.update({'draw_coordinates':drawCoords})

	imgSize = (width*shiftRatio, height*shiftRatio)
	imgSize = [int(math.ceil(x)) for x in imgSize] #convert to ints

	polyImgDict = {'geometryDicts':geometryDicts, 'imgSize':imgSize , 'boundingBox': bbox, 'shiftRatio':shiftRatio }

	return polyImgDict

def convertCoordinatesToPixels(element_objs, targetImgW=1024, bbox=None, shiftRatio=None):

	#adds a dictionary to each conduit or node dict called
	#'draw_coordinates' which is a two part tuple, xy1, xy2

	if not bbox:
		#start by determining the max and min coordinates in the whole model
		maxX = maxY = 0.0 #determine current width prior to ratio transform
		minX = minY = 99999999
		for id, element in element_objs.iteritems():

			coordPair = element.coordinates

			if type(coordPair[0]) is list:
				#loop for elements with multiple coordinates (lines)
				#each coordinate is a list
				for coord in coordPair:
					minX = min(minX, coord[0])
					minY = min(minY, coord[1])
					maxX = max(maxX, coord[0])
					maxY = max(maxY, coord[1])

			else:
				minX = min(minX, coordPair[0])
				minY = min(minY, coordPair[1])
				maxX = max(maxX, coordPair[0])
				maxY = max(maxY, coordPair[1])

		bbox = [(minX, minY),(maxX, maxY)]

	height = float(bbox[1][1]) - float(bbox[0][1])
	width = float(bbox[1][0]) - float(bbox[0][0])
	if not shiftRatio:
		shiftRatio = float(targetImgW / width) # to scale down from coordinate to pixels

	for id, element in element_objs.iteritems():

		coordPair = element.coordinates

		drawcoords = []
		if type(coordPair[0]) is list:
			#loop for elements with multiple coordinates (lines)
			#each coordinate is a list
			for coord in coordPair:
				xy = coordToDrawCoord(coord, bbox, shiftRatio)
				drawcoords.append(xy)
		else:
			#zeroth index is not list, therefore coordPair is a single coord
			drawcoords = coordToDrawCoord(coordPair, bbox, shiftRatio)

		element.draw_coordinates = drawcoords

	imgSize = (width*shiftRatio, height*shiftRatio)
	imgSize = [int(math.ceil(x)) for x in imgSize] #convert to ints
	modelSizeDict = {'imgSize':imgSize, 'boundingBox': bbox, 'shiftRatio':shiftRatio }
	return modelSizeDict

def coordToDrawCoord(coordinates, bbox=None, shiftRatio=None, shiftRatioY=None, width=None, height=None):

	#convert single coordinate pair into the drawing space (pixels rather than cartesian coords)
	#given a cartesian bbox and shiftRatio

	#transform coords by normalizing by the mins, apply a ratio to
	#produce the desired width pixel space
	minX = minY = 0
	if bbox:
		minX = float(bbox[0][0])
		minY = float(bbox[0][1])
		maxX = float(bbox[1][0])
		maxY = float(bbox[1][1])

		height = maxY - minY
		width = maxX - minX

	if not shiftRatioY:
		shiftRatioY = shiftRatio
	x =  (coordinates[0] - minX) * shiftRatio
	y =  (height - coordinates[1] + minY) * shiftRatioY #subtract from height because PIL origin is in top right

	return (x,y)

def circleBBox(coordinates, radius):
	#returns the bounding box of a circle given as centriod coordinate and radius
	x = coordinates[0] #this indexing is because other elements haev more than on coordinate (ulgy pls fix)
	y = coordinates[1]
	r = radius

	return (x-r, y-r, x+r, y+r)


def drawNode(node, draw, options, rpt=None, dTime=None, xplier=1):

	color = (210, 210, 230) #default color
	radius = 0 #aka don't show this node by default
	outlineColor = None
	xy = node.draw_coordinates
	threshold = options['threshold']
	type = options['type']
	if dTime:

		data = rpt.returnDataAtDTime(node.id, dTime, sectionTitle="Node Results")
		q = abs(float(data[2])) #absolute val because backflow
		floodingQ = float(data[3])
		HGL = float(data[5])

		if type=='flood':

			#node params
			if floodingQ > 1:
				radius = q/2
				color = red #greenRedGradient(HGLup, 0, 15) #color

	elif node.is_delta:
		#we're dealing with "compare" object
		#floodDurationChange = elementChange(node, parameter='floodDuration')

		if node.flood_duration > 0 :
			#Flood duration increases
			radius = node.flood_duration*20
			color = red

		#if nodeData['existing'].get('floodDuration', 0) == 0 and nodeData['proposed'].get('floodDuration', 0) > 0:
		if node.delta_type == 'new_flooding':
			#new flooding found
			radius = node.flood_duration*20
			color = purple #purple
			outlineColor = (90, 90, 90)

	else:
		floodDuration = node.flood_duration
		maxDepth = node.maxDepth
		maxHGL = node.maxHGL

		if type == 'flood':
			#if floodDuration > 0.08333:
			if floodDuration >= threshold:
				radius = floodDuration*3
				color = red

		if type == 'flood_color':
			radius = 3
			if floodDuration >= threshold:
				yellow = (221, 220, 0)
				color = du.col2RedGradient(floodDuration, 0, 1, startCol=yellow) #options['fill'](floodDuration, 0, 1+threshold)
			else:

				radius = 1

	radius *= xplier
	draw.ellipse(circleBBox(xy, radius), fill =color, outline=outlineColor)

def drawConduit(conduit, draw, options, rpt=None, dTime = None, xplier = 1, highlighted=None):

	#Method for drawing (in plan view) a single conduit, given its RPT results

	#default fill and size
	fill = (120, 120, 130)
	drawSize = 1
	should_draw = True #boolean that can prevent a draw based on params
	coordPair = conduit.draw_coordinates
	type = options['type']
	#general method for drawing one conduit
	if rpt:
		#if an RPT is supplied, collect the summary data
		maxQPercent = conduit.maxQpercent
		q =  conduit.maxflow

		if maxQPercent !=0:
			capacity = q / maxQPercent
		else:
			capacity = 1
		stress = q / capacity

		if dTime:
			#if a datetime is provided, grab the specif flow data at this time
			data = rpt.returnDataAtDTime(conduit.id, dTime) #this is slow
			q = abs(float(data[2])) #absolute val because backflow
			stress = q / capacity    #how taxed is the pipe

		remaining_capacity = capacity-q
		#=================================
		#draw the conduit type as specifed
		#=================================
		if type == "flow":

			fill = options['fill']
			drawSize = options['draw_size'](q, options['exp'])

		if type == "flow_stress":

			fill = options['fill'](q*100, 0, capacity*175)
			drawSize = options['draw_size'](q, options['exp']) #int(round(math.pow(q, 0.67)))

		elif type == "stress":

			if maxQPercent >= 1:
				fill = options['fill'](q*100, 0, capacity*300) #greenRedGradient(q*100, 0, capacity*300)
				drawSize = options['draw_size'](stress*options['xplier'], options['exp']) #int(round(math.pow(stress*4, 1)))

		elif type == "capacity_remaining":

			if remaining_capacity > 0:
				fill = (0, 100, 255)
				drawSize = int( round( math.pow(remaining_capacity, 0.8)))



			#drawSize = int( round(max(remaining_capacity, 1)*xplier) )

	elif conduit.is_delta:
		#we're dealing with "compare" dictionary
		qChange = 	conduit.maxflow #elementChange(conduitData, parameter='maxflow')
		maxQperc = 	conduit.maxQpercent #elementChange(conduitData, parameter='maxQpercent')

		#default draw behavoir = draw grey lines proportional to geom1
		drawSize = min(7, conduit.geom1*0.7)
		fill = du.mediumgrey
		# #FIRST DRAW NEW OR CHANGED CONDUITS IN A CLEAR WAY
		# if conduit.lifecycle == 'new':
		# 	fill = blue
		# if conduit.lifecycle == 'changed':
		# 	fill = green #blue
			#drawSize = min(10, conduit.geom1)

		#IF THE CONDUITS IS 'EXISTING', DISPLAY SYMBOLOGY ACCORDINGLY (how things changed, etc)
		if conduit.lifecycle == 'existing':

			if type == 'proposed_simple':
				#drawSize = 0 #don't draw, only print the proposed infrastructure
				#fill = red
				#should_draw = False
				pass
			if type == 'compare_flow':
				drawSize = options['draw_size'](abs(qChange*options['xplier']), options['exp'])
				if qChange > 0:
					fill = du.greyRedGradient(qChange+15, 0, 20)
					#drawSize = int(round(math.pow(qChange, 0.67)))

				if qChange <= 0:
					fill = du.greyGreenGradient(abs(qChange)+15, 0, 20)
					#drawSize = int(round(math.pow(abs(qChange), 0.67)))

			if type == 'compare_hgl':

				avgHGL = (conduit.maxHGLUpstream + conduit.maxHGLDownstream) / 2.0
				drawSize = options['draw_size'](abs(avgHGL*options['xplier']), options['exp'])
				if avgHGL > 0:
					fill = du.red #du.greyRedGradient(avgHGL+15, 0, 20)
					#drawSize = int(round(math.pow(avgHGL, 1)))

				if avgHGL <= 0:
					fill = du.green_bright #du.greyGreenGradient(abs(avgHGL)+15, 0, 20)
					#drawSize = int(round(math.pow(avgHGL, 1)))


	#if highlighted list is provided, overide any symbology for the highlighted conduits
	if highlighted and conduit.id in highlighted:
		fill = blue
		drawSize = 3
	#FIRST DRAW NEW OR CHANGED CONDUITS IN A CLEAR WAY
	if conduit.lifecycle == 'new':
		fill = blue
	if conduit.lifecycle == 'changed':
		fill = green #blue
	drawSize = int(drawSize*xplier)


	if should_draw:
		#draw that thing
		draw.line(coordPair, fill = fill, width = drawSize)
		if pipeLengthPlanView(coordPair[0], coordPair[1]) > drawSize*0.75:
			#if length is long enough, add circles on the ends to smooth em out
			#this check avoids circles being drawn for tiny pipe segs
			draw.ellipse(du.circleBBox(coordPair[0], drawSize*0.5), fill =fill)
			draw.ellipse(du.circleBBox(coordPair[1], drawSize*0.5), fill =fill)

def angleBetweenPoint(xy1, xy2):
	dx, dy = (xy2[0] - xy1[0]), (xy2[1] - xy1[1])

	angle = (math.atan(float(dx)/float(dy)) * 180/math.pi )
	if angle < 0:
		angle = 270 - angle
	else:
		angle = 90 - angle
	#angle in radians
	return angle

def midPoint(xy1, xy2):

	dx, dy = (xy2[0] + xy1[0]), (xy2[1] + xy1[1])
	midpt = ( int(dx/2), int(dy/2.0) )

	#angle in radians
	return midpt

def annotateLine (img, dataDict, fontScale=1, annoKey='name', labeled = None):

	txt = dataDict[annoKey]

	if not txt in labeled:
		#do not repeat labels
		font = ImageFont.truetype(fontFile, int(25 * fontScale))
		imgTxt = Image.new('L', font.getsize(txt))
		drawTxt = ImageDraw.Draw(imgTxt)
		drawTxt.text((0,0), txt, font=font, fill=(10,10,12))

		coords = dataDict['coordinates']
		drawCoord = dataDict['draw_coordinates']
		angle = angleBetweenPoint(coords[0], coords[1])
		texRot = imgTxt.rotate(angle, expand=1)
		#canvas.paste( ImageOps.colorize(texRot, (0,0,0), (255,255,84)), (242,60),  texRot)

		midpoint = midPoint(drawCoord[0], drawCoord[1])
		#img.paste(texRot , midpoint,  texRot)
		img.paste(ImageOps.colorize(texRot, (0,0,0), (10,10,12)), midpoint,  texRot)
		labeled.append(txt) #keep tracj of whats been labeled



#def drawAnnotation (canvas, inp, rpt=None, imgWidth=1024, title=None, currentTstr = None, description = None,
#					objects = None, symbologyType=None, fill=(50,50,50), xplier=None):
def annotateMap (canvas, model, model2=None, currentTstr = None, options=None, results={}):

	#unpack the options
	nodeSymb = 		options['nodeSymb']
	conduitSymb = 	options['conduitSymb']
	basemap = 		options['basemap']
	parcelSymb = 	options['parcelSymb']
	traceUpNodes =	options['traceUpNodes']
	traceDnNodes =	options['traceDnNodes']

	modelSize = (canvas.im.getbbox()[2], canvas.im.getbbox()[3])

	#define main fonts
	fScale = 1 * modelSize[0] / 2048
	titleFont = ImageFont.truetype(fontFile, int(40 * fScale))
	font = ImageFont.truetype(fontFile, int(20 * fScale))

	#Buid the title and files list (handle 1 or two input models)
	#this is hideous, or elegant?
	files = title = results_string = symbology_string = annotationTxt = ""
	files = '\n'.join([m.rpt.filePath for m in filter(None, [model, model2])])
	title = ' to '.join([m.inp.name for m in filter(None, [model, model2])])
	symbology_string = ', '.join([s['title'] for s in filter(None, [nodeSymb, conduitSymb, parcelSymb])])
	title += "\n" + symbology_string

	#params_string = ''.join([" > " + str(round(s['threshold']*600)/10) + "min " for s in filter(None, [parcelSymb])])
	#build the title


	#collect results
	for result, value in results.iteritems():
		results_string += '\n' + result + ": " + str(value)

	#compile the annotation text
	if results:
		annotationTxt = results_string + "\n"
	annotationTxt += files


	annoHeight = canvas.textsize(annotationTxt, font)[1]

	canvas.text((10, 15), title, fill=black, font=titleFont)
	canvas.text((10, modelSize[1] - annoHeight - 10), annotationTxt, fill=black, font=font)


	if currentTstr:
		#timestamp in lower right corner
		annoHeight = canvas.textsize(currentTstr, font)[1]
		annoWidth = canvas.textsize(currentTstr, font)[0]
		canvas.text((modelSize[0] - annoWidth - 10, modelSize[1] - annoHeight - 10), currentTstr, fill=black, font=font)

	#add postprocessing timestamp
	timestamp = strftime("%b-%d-%Y %H:%M:%S")
	annoHeight = canvas.textsize(timestamp, font)[1]
	annoWidth = canvas.textsize(timestamp, font)[0]
	canvas.text((modelSize[0] - annoWidth - 10, annoHeight - 5), timestamp, fill=du.grey, font=font)




#end
