text: from veroviz._common import *
  from veroviz._validation import valCreateCesium
  
  from veroviz._deconstructAssignments import deconstructAssignments
  from veroviz._internal import delTailSlash
  from veroviz._internal import delHeadSlash
  from veroviz._internal import addHeadSlash
  from veroviz._internal import replaceBackslashToSlash
  
  from veroviz.utilities import getMapBoundary
  from veroviz.utilities import exportDataframe
  
  def createCesium(assignments=None, nodes=None, startDate=None, startTime='08:00:00', postBuffer=30, cesiumDir=None, problemDir=None, nodeColor=None, nodeStyle=None, pathColor=None, pathWeight=None, pathStyle=None, pathOpacity=None):
  	"""
  	This function generates several files required to view a solution in Cesium. The function requires assignments and/or nodes dataframes as input. 
  
  	Parameters
  	----------
  	assignments: :ref:`Assignments`, Conditional, `assignments` and `nodes` can not be None at the same time
  		An :ref:`Assignments` dataframe describing vehicle movement over time.  The assignments will be displayed as routes/paths in Cesium.  If a 3D model is defined in the `modelFile` column of the assignments dataframe, this object will also be displayed.
  	nodes: :ref:`Nodes`, Conditional, `assignments` and `nodes` can not be None at the same time
  		A :ref:`Nodes` dataframe describing the locations of nodes.  These nodes will be displayed on the map in Cesium.  
  	startDate: string, Optional, format is "YYYY-MM-DD", default as today
  		Defines the start date to be displayed in Cesium.
  	startTime: string, Optional, format is "HH:MM:SS", default as '08:00:00'
  		Defines the time at which the Cesium video begins, on the start date.
  	postBuffer: int, Optional, default as 30
  		Specifies the additional time (in seconds) that the Cesium video will continue to run after the last assignment is completed. 
  	cesiumDir: string, Required, default as None
  		This should be the full absolute path to the directory where Cesium is installed. For example, for Windows it might be "D:/Cesium"; for Linux it might be "/home/user/Cesium".
  	problemDir: string, Required, default as None
  		The path name of the generated problem directory. This path is relative to the root of Cesium. For example, if `cesiumDir = '/home/user/Cesium'` and `problemDir = 'veroviz/problems/TSP'` then the files will be generated in the directory `'/home/user/Cesium/veroviz/problems/TSP'`.
  	nodeColor: string, Optional, default as None
  		Overrides the `cesiumColor` column of the input `nodes` dataframe.  This will define the color of all nodes displayed in Cesium.  See :ref:`Cesium Style` for the collection of available colors. 
  	nodeStyle: string, Optional, default as None
  		Overrides the `cesiumIconType` column of the input `nodes` dataframe.  Currently, the only option is 'pin'.
  	pathColor: string, Optional, default as None
  		Overrides the `cesiumColor` column of the input `assignments` dataframe.  This will define the color of all arcs displayed in Cesium.  See :ref:`Cesium Style` for the collection of available colors.
  	pathWeight: int, Optional, default as None
  		Overrides the `cesiumWeight` column of the input `assignments` dataframe. This will define the weight (in pixels) of all arcs displayed in Cesium. See :ref:`Cesium Style` for more information.
  	pathStyle: string, Optional, default as None
  		Overrides the `cesiumStyle` column of the input `assignments` dataframe. This will define the style of all arcs displayed in Cesium. See :ref:`Cesium Style` for available options.
  	pathOpacity: float in [0, 1], Optional, default as None
  		Overrides the `cesiumOpacity` column of the input `assignments` dataframe.  This will define the opacity of all arcs displayed in Cesium.  See :ref:`Cesium Style` for more information.
  
  	Return
  	------
  	N/A
  
  	Note
  	----
  	This function generates the following files within the directory specified by input argument `problemDir`:
  
  	- [problemDir].vrv (where [problemDir] is replaced by the value of `problemDir`);
  	- config.js
  	- displayNodes.js
  	- displayPath.js
  	- routes.czml
  
  	Instructions for starting Cesium are provided at https://veroviz.org/documentation.html
  
  	Example
  	--------
  	Import veroviz and check the latest version.
  		>>> import veroviz as vrv
  		>>> import os
  
  		>>> vrv.checkVersion()
  		
  	Create two nodes.
  		>>> myNodes = vrv.createNodesFromLocs(
  		...     locs = [[42.1538, -78.4253],
  		...             [42.6343, -78.1146]])
  		>>> myNodes
  
  	Move the truck from one node to the other.
  		>>> myAssignments = vrv.getShapepoints2D(
  		...     odID           = 0,
  		...     objectID       = 'truck',
  		...     modelFile      = 'veroviz/models/ub_truck.gltf',
  		...     modelScale     = 80, 
  		...     modelMinPxSize = 20, 
  		...     startLoc       = list(myNodes.loc[0][['lat', 'lon']].values),
  		...     endLoc         = list(myNodes.loc[1][['lat', 'lon']].values),
  		...     routeType      = 'euclidean2D',
  		...     dataProvider   = None,
  		...     speedMPS       = vrv.convertSpeed(55, 'miles', 'hr', 'm', 's'))	
  		
  	Create Cesium output.
  		>>> vrv.createCesium(
  		...     assignments = myAssignments, 
  		...     nodes       = myNodes, 
  		...     startTime   = '08:00:00', 
  		...     cesiumDir   = os.environ['CESIUMDIR'],
  		...     problemDir  = 'createCesium_example')
  	"""
  
  	# validation
  	[valFlag, errorMsg, warningMsg] = valCreateCesium(assignments, nodes, startDate, startTime, postBuffer, cesiumDir, problemDir, nodeColor, pathColor, pathWeight, pathStyle, pathOpacity)
  	if (not valFlag):
  		print (errorMsg)
  		return
  	elif (VRV_SETTING_SHOWWARNINGMESSAGE and warningMsg != ""):
  		print (warningMsg)
  
  	# Set default start date as today
  	if (startDate is None):
  		startDate = datetime.date.today()
  
  	# Some modification about slashes:
  	# cesiumDir - no tail slash
  	# problemDir - no head slash and no tail slash
  	# Change all backslash to slash
  	cesiumDir = delTailSlash(cesiumDir)
  	problemDir = delTailSlash(problemDir)
  	problemDir = delHeadSlash(problemDir)
  	cesiumDir = replaceBackslashToSlash(cesiumDir)
  	problemDir = replaceBackslashToSlash(problemDir)
  
  	# In case the problemDir does not exist
  	fullDir = '%s/%s' % (cesiumDir, problemDir)
  	if not os.path.exists(fullDir):
  		os.makedirs(fullDir, exist_ok=True)
  
  	# Mission duration, from time zero to endtime+postBuffer
  	availStart = _getCesiumTime(startDate, startTime, 0)
  	availEnd   = _getCesiumTime(startDate, startTime, (max(assignments['endTimeSec']) + postBuffer))
  
  	# Decode Assignments dataframe to a Path dataframe (with details of a path) and a list of Assignments dataframes, and generate .js and .czml files
  	[path, lstSubAssignments] = _getPathsDetails(assignments)
  	lstNonStationarySubAssignments = deconstructAssignments(assignments=assignments, includeVerticalFlag=True)
  
  	# Update 'intervalStart' and 'intervalEnd' to cesiumTime
  	for i in path.index:
  		path.at[i, 'intervalStart'] = _getCesiumTime(startDate, startTime, path.at[i, 'startTimeSec'])
  		path.at[i, 'intervalEnd'] = _getCesiumTime(startDate, startTime, path.at[i, 'endTimeSec']) if (path.at[i, 'endTimeSec'] >= 0) else availEnd
  	path.drop(columns = ['startTimeSec', 'endTimeSec'])
  
  	# Write problem selector
  	_writeSelector(fullDir, problemDir)
  
  	# Write Configs
  	mapBoundary = getMapBoundary(nodes=nodes, arcs=assignments, locs=None)
  	_writeConfigs(mapBoundary, availStart, path, fullDir, problemDir)
  
  	# Write Nodes
  	if (nodes is not None):
  		_writeNodes(nodes, nodeColor, fullDir)
  
  	# Write Assignments
  	if (len(path) > 0):
  		_writeAssignmentsJS(lstNonStationarySubAssignments, pathColor, pathWeight, pathStyle, pathOpacity, fullDir)
  		_writeAssignmentsCZML(path, lstSubAssignments, availStart, availEnd, fullDir)
  
  	return
  
  def _writeSelector(fullDir, problemDir):
  
  	"""
  	This script generates a .vrv file, in index.html, load [problemDir].vrv to load the other files
  
  	Parameters
  	----------
  	fullDir: string
  		The directory of cesium, including the name of the instance.
  	problemDir: string
  		The name of the instance
  	"""
  
  	# Replace "/" with ";"
  	replacedDir = addHeadSlash(problemDir)
  	replacedDir = replacedDir.replace("/", ";")
  
  	# .vrv file path
  	vrvFilePath = '%s/%s.vrv' % (fullDir, replacedDir)
  	f = open(vrvFilePath, 'w')
  
  	vrvStr = "~\n"
  
  	f.write(vrvStr)
  	f.close()
  	if (VRV_SETTING_SHOWOUTPUTMESSAGE):
  		print ("Message: File selector was written to %s ..." % (vrvFilePath))
  
  	return
  
  def _writeConfigs(mapBoundary, availStart, path, fullDir, problemDir):
  
  	"""
  	This script generates config.js file
  
  	Parameters
  	----------
  	mapBoundary: list, the format is [minLon, minLat, maxLon, maxLat]
  		The boundary of the map
  	availStart: JulianDate
  		The start time of entire routing visualization
  	path: dataframe
  		A list of detail information from `_getPathsDetails()`
  	modelIDs: list
  		`model` column from path dataframe
  	actions: list
  		`action` column from path dataframe
  	fullDir: string
  		The directory of cesium, including the name of the instance.
  	problemDir: string
  		The name of the instance
  	"""
  	# Decode
  	allIDs = list(dict.fromkeys(path['czmlID'].tolist()))
  	orientationIDs = list(dict.fromkeys(path.loc[path['action'] == "move", 'czmlID'].tolist()))
  	[[minLat, maxLon], [maxLat, minLon]] = mapBoundary
  
  	# .js file path
  	jsFilePath = '%s/config.js' % (fullDir)
  	f = open(jsFilePath, 'w')
  
  	# Head description
  	jsStr  =         "// This .js file is auto-generated by `createCesium()` from VeRoViz\n"
  	jsStr +=         "// The configs for cesium application go to here\n\n"
  
  	# Configuration and load czml file, runRoutes() is defined in index.html
  	jsStr +=         "function setConfigs() {\n"
  
  	# Set map boundary
  	jsStr +=         "    viewer.camera.flyTo({\n"
  	jsStr +=         "        destination: Cesium.Rectangle.fromDegrees(%f, %f, %f, %f) \n" % (minLon, minLat, maxLon, maxLat)
  	jsStr +=         "    });\n"
  
  	# Set clock
  	jsStr +=         "    viewer.clock.currentTime = Cesium.JulianDate.addSeconds('%s', 0, new Cesium.JulianDate());\n" % (availStart)
  
  	# allIDs and orientationIDs
  	jsStr +=         "    allIDs = [\n"
  	if (len(allIDs) > 0):
  		for i in range(0, len(allIDs)):
  			jsStr += "        '%s', \n" % (allIDs[i])
  		jsStr = jsStr[:-3]
  		jsStr +=     "    \n"
  	jsStr +=         "    ];\n"
  
  	jsStr +=         "    orientationIDs = [\n"
  	if (len(orientationIDs) > 0):
  		for i in range(0, len(orientationIDs)):
  			jsStr += "        '%s', \n" % (orientationIDs[i])
  		jsStr = jsStr[:-3]
  		jsStr +=     "    \n"
  	jsStr +=         "    ];\n"
  
  	# load .czml, `runRoutes()` are writen in index.html 
  	jsStr +=         "    czmlRouteFile = '%s/routes.czml';\n" % ("/" + problemDir)
  	jsStr +=         "    runRoutes(czmlRouteFile, allIDs, orientationIDs);\n"
  
  	# Get all unrepeated objectID, modelID, child model
  	# The hierarchy is: objectID (one) - modelID (many), modelID (one) - child model (many)
  	# E.g., For drones
  	# objectID - UAV, 
  	# model - UAV-drone.gltf; UAV-drone_package.gltf, 
  	# child model - 'o-UAV-drone_package.gltf-move'; 'o-UAV-drone_package.gltf-vertical'; 'o-UAV-drone_package.gltf-stationary'
  	objectIDs = list(dict.fromkeys(path['objectID'].tolist()))
  	for i in range(len(objectIDs)):
  		models = list(dict.fromkeys(path.loc[path['objectID'] == objectIDs[i], 'modelFile'].tolist()))
  		for j in range(len(models)):
  			childModels = list(dict.fromkeys(path.loc[(path['objectID'] == objectIDs[i]) & (path['modelFile'] == models[j]), 'action'].tolist()))
  			strChildModels = ""
  			for k in range(len(childModels)):
  				strChildModels += "'o-%s-%s-%s', " % (objectIDs[i], models[j], childModels[k])
  			if (strChildModels != ""):
  				strChildModels = strChildModels[:-2]
  			jsStr += "objectInfo['%s-%s'] = {\n" % (objectIDs[i], models[j])
  			jsStr += "    label : '%s (%s)', \n" % (objectIDs[i], models[j])
  			jsStr += "    childModels : [%s],\n" % (strChildModels)
  			jsStr += "    scale : %s, \n" % (path.loc[(path['objectID'] == objectIDs[i]) & (path['modelFile'] == models[j]), 'modelScale'].tolist()[0])
  			jsStr += "    minPxSize : %s \n" %  (path.loc[(path['objectID'] == objectIDs[i]) & (path['modelFile'] == models[j]), 'modelMinPxSize'].tolist()[0])
  			jsStr += "}; \n"
  
  	# Register objects
  	jsStr +=         "    registerObjects(objectInfo); \n"
  
  	# End of the displayPaths function
  	jsStr +=         "}"
  
  	# Write contents and close file stream
  	f.write(jsStr)
  	f.close()
  
  	if (VRV_SETTING_SHOWOUTPUTMESSAGE):
  		print("Message: Configs were written to %s ..." % (jsFilePath))
  
  	return
  
  def _writeNodes(nodes, cesiumIconColor, fullDir):
  
  	"""
  	This script generates displayNodes.js file
  
  	Parameters
  	----------
  	nodes: :ref:`Nodes`
  		Nodes to be appeared in cesium
  	cesiumIconColor: string
  		The color of nodes when displayed in Cesium. If provided, it will overrides the color in nodes dataframe. One of a collection of pre-specified colors. See :ref:`Cesium Style`
  	fullDir: string
  		The directory of cesium, including the name of the instance.	
  	"""
  
  	# .js file path
  	jsFilePath = '%s/displayNodes.js' % (fullDir)
  	f = open(jsFilePath, 'w')
  
  	# Head description
  	jsStr  =     "// This .js file is auto-generated by `createCesium()` from VeRoViz\n"
  	jsStr +=     "// Display nodes for cesium application\n\n"
  
  	# In case some there is skipped index
  	indNodes = nodes.copy().reset_index(drop=True)
  
  	# Display the nodes
  	jsStr +=     "function displayNodes() {\n"
  	jsStr +=     "    var pin = new Array;\n"
  	for i in range(0, len(indNodes)):
  		jsStr += "    pin[%s] = viewer.entities.add({\n" % (i)
  		jsStr += "        name : '%s',\n" % (indNodes.iloc[i]['cesiumIconText'])
  		jsStr += "        position : Cesium.Cartesian3.fromDegrees(%s, %s),\n" % (indNodes.iloc[i]['lon'], indNodes.iloc[i]['lat'])
  		jsStr += "        billboard : {\n"
  		jsStr += "            image : pinBuilder.fromText('%s', %s, 40).toDataURL(),\n" % (indNodes.iloc[i]['id'], cesiumIconColor if (cesiumIconColor != None) else indNodes.iloc[i]['cesiumColor'])
  		jsStr += "            verticalOrigin : Cesium.VerticalOrigin.BOTTOM\n"
  		jsStr += "        }\n"
  		jsStr += "    });\n\n"
  	jsStr +=     "}"
  
  	# Write contents and close file stream
  	f.write(jsStr)
  	f.close()
  
  	if (VRV_SETTING_SHOWOUTPUTMESSAGE):
  		print("Message: Nodes were written to %s ..." % (jsFilePath))
  
  	return
  
  def _writeAssignmentsCZML(path, lstSubAssignments, availStart, availEnd, fullDir):
  
  	"""
  	This script generates routes.czml file
  
  	Parameters
  	----------
  	path: path dataframe
  		A list of "path", defines the details of each sub-assignments. Each "path" has an origin coordinate and a destinate coordinate, represent a group of assignments (a sub-assignment) with same odID
  	lstSubAssignments: list of :ref:`Assignments`
  		A list of Assignments dataframe, each dataframe will have the same odID
  	availStart: JulianDate
  		Start time of entire .czml file
  	availEnd: JulianDate
  		End time of entire .czml file
  	fullDir: string
  		The directory of cesium, including the name of the instance.
  	"""
  
  	# .czml file path
  	czmlFilePath = '%s/routes.czml' % (fullDir)
  	f = open(czmlFilePath, 'w')
  
  	# Group the path, so that the path with the same czmlID can ends up with the same block
  	czmlIDList = path['czmlID'].tolist()
  	czmlIDList = list(dict.fromkeys(czmlIDList))
  	lstCzml = []
  
  	for i in range(len(czmlIDList)):
  		lstCzml.append(path.loc[path['czmlID'] == czmlIDList[i]])
  
  	# Head of CZML file (can't comment on .czml file so there is no head description)
  	czmlStr  =                 '[ \n'
  	czmlStr +=                 '{ \n'
  	czmlStr +=                 '    "id": "document", \n'
  	czmlStr +=                 '    "version": "1.0" \n'
  	czmlStr +=                 '}, \n'
  
  	# For each combination of 'objectID', 'modelFile'. 'action', we have a section (in fact we treat a combination of those three fields as unique 'odID')
  	for i in range(len(lstCzml)):
  		czmlStr +=             '{ \n'
  		czmlStr +=             '    "id": "%s", \n' % (czmlIDList[i])
  		czmlStr +=             '    "name": "%s", \n' % (czmlIDList[i])
  		czmlStr +=             '    "availability": "%s/%s", \n' % (availStart, availEnd)
  		czmlStr +=             '    "model": { \n'
  		czmlStr +=             '        "show": true, \n'
  		czmlStr +=             '        "gltf": "%s", \n' % (lstCzml[i].iloc[0]['modelFile'])
  		czmlStr +=             '        "scale": %d, \n' % (lstCzml[i].iloc[0]['modelScale']/100.0)
  		czmlStr +=             '        "minimumPixelSize": %d \n' % (lstCzml[i].iloc[0]['modelMinPxSize'])
  		czmlStr +=             '    }, \n'
  		czmlStr +=             '    "label": { \n'
  		czmlStr +=             '        "fillColor":[{"rgba":[255,255,0,255]}], \n'
  		czmlStr +=             '        "font":"bold 10pt Segoe UI Semibold", \n'
  		czmlStr +=             '        "horizontalOrigin":"LEFT", \n'
  		czmlStr +=             '        "outlineColor":{"rgba":[0,0,0,255]}, \n'
  		czmlStr +=             '        "pixelOffset":{"cartesian2":[10.0,0.0]}, \n'
  		czmlStr +=             '        "scale":1.0, \n'
  		czmlStr +=             '        "show":[{"boolean":false}], \n'
  		czmlStr +=             '        "style":"FILL", \n'
  		czmlStr +=             '        "text":"Object %s", \n' % (lstCzml[i].iloc[0]['objectID'])
  		czmlStr +=             '        "verticalOrigin":"BOTTOM"\n'
  		czmlStr +=             '    }, \n'
  		czmlStr +=             '    "path":{ \n'
  		czmlStr +=             '        "material":{"solidColor":{"color":{"rgba":[255,255,0,255]}}}, \n'
  		czmlStr +=             '        "width":[{"number":2.0}], \n'
  		czmlStr +=             '        "show":[{"boolean":false}] \n'
  		czmlStr +=             '    }, \n'
  		czmlStr +=             '    "position": [ \n'
  		for j in range(len(lstCzml[i])):
  			if (lstCzml[i].iloc[j]['action'] == "stationary"):
  				czmlStr +=     '        { \n'
  				czmlStr +=     '            "interval": "%s/%s", \n' % (lstCzml[i].iloc[j]['intervalStart'], lstCzml[i].iloc[j]['intervalEnd'])
  				lat = lstSubAssignments[lstCzml[i].iloc[j]['indexInlstShapepoints']].iloc[0]['startLat']
  				lon = lstSubAssignments[lstCzml[i].iloc[j]['indexInlstShapepoints']].iloc[0]['startLon']
  				alt = lstSubAssignments[lstCzml[i].iloc[j]['indexInlstShapepoints']].iloc[0]['startAltMeters']
  				czmlStr +=     '            "cartographicDegrees": [%s, %s, %s] \n' % (lon, lat, alt)
  				czmlStr +=     '        }, \n'
  			else:
  				czmlStr +=     '        { \n'
  				czmlStr +=     '            "interval": "%s/%s",\n' % (lstCzml[i].iloc[j]['intervalStart'], lstCzml[i].iloc[j]['intervalEnd'])
  				czmlStr +=     '            "interpolationAlgorithm":"LAGRANGE", \n'
  				czmlStr +=     '            "interpolationDegree": 1, \n'
  				czmlStr +=     '            "epoch": "%s", \n' % (availStart)
  				czmlStr +=     '            "cartographicDegrees": [ \n'
  				# There will be a list of LLA
  				time = lstSubAssignments[lstCzml[i].iloc[j]['indexInlstShapepoints']]['startTimeSec'].tolist()
  				lats = lstSubAssignments[lstCzml[i].iloc[j]['indexInlstShapepoints']]['startLat'].tolist()
  				lons = lstSubAssignments[lstCzml[i].iloc[j]['indexInlstShapepoints']]['startLon'].tolist()
  				alts = lstSubAssignments[lstCzml[i].iloc[j]['indexInlstShapepoints']]['startAltMeters'].tolist()
  				for k in range(0, len(lats)):
  					czmlStr += '                %.2f, %f, %f, %f, \n' % (time[k], lons[k], lats[k], alts[k])
  				lastTime = lstSubAssignments[lstCzml[i].iloc[j]['indexInlstShapepoints']].iloc[-1]['endTimeSec']
  				lastLat = lstSubAssignments[lstCzml[i].iloc[j]['indexInlstShapepoints']].iloc[-1]['endLat']
  				lastLon = lstSubAssignments[lstCzml[i].iloc[j]['indexInlstShapepoints']].iloc[-1]['endLon']
  				lastAlt = lstSubAssignments[lstCzml[i].iloc[j]['indexInlstShapepoints']].iloc[-1]['endAltMeters']
  				czmlStr +=     '                %.2f, %f, %f, %f \n' % (lastTime, lastLon, lastLat, lastAlt)
  
  				czmlStr +=     '\n'
  				czmlStr +=     '            ] \n'
  				czmlStr +=     '        }, \n'
  		czmlStr = czmlStr[:-3]
  		czmlStr +=             '\n'
  		czmlStr +=             '    ]\n'
  		czmlStr +=             '}, \n'
  	czmlStr = czmlStr[:-3]
  	czmlStr +=                 '\n'
  
  	# Closing bracket for entire .czml file
  	czmlStr +=                 ']'
  
  	# Write contents and close file stream
  	f.write(czmlStr)
  	f.close()
  
  	if (VRV_SETTING_SHOWOUTPUTMESSAGE):
  		print("Message: Assignments (.czml) were written to %s ..." % (czmlFilePath))
  
  	return
  
  def _writeAssignmentsJS(lstSubAssignments, cesiumColor, cesiumWeight, cesiumStyle, cesiumOpacity, fullDir):
  
  	"""
  	This script generates the displayPaths.js file
  
  	Parameters
  	----------
  	lstSubAssignments: list of :ref:`Assignments`
  		A list of Assignments dataframe, each dataframe will have the same odID
  	cesiumColor: string
  		The color of arcs when displayed in Cesium. If provided, it will overrides the color in assignments dataframe. One of a collection of pre-specified colors. See :ref:`Cesium Style`
  	cesiumWeight: int
  		The weight of arcs when displayed in Cesium. If provided, it will overrides the weight in assignments dataframe. See :ref:`Cesium Style`
  	cesiumStyle: string
  		The style of arcs when displayed in Cesium. If provided, it will overrides the style in assignments dataframe. See :ref: `Cesium style`
  	cesiumOpacity: string
  		The opacity of arcs when displayed in Cesium. If provided, it will overrides the opacity in assignments dataframe. See :ref: `Cesium style`
  	fullDir: string
  		The directory of cesium, including the name of the instance.
  	"""
  
  	# .js file path
  	jsFilePath = '%s/displayPaths.js' % (fullDir)
  	f = open(jsFilePath, 'w')
  
  	# Head description
  	jsStr  =             "// This .js file is auto-generated by `createCesium` from VeRoViz\n"
  	jsStr +=             "// Display paths for cesium application\n\n"
  
  	# Begin of the displayPaths function
  	jsStr +=             "function displayPaths() {\n"
  
  	# Collect all moving objects ID for path names
  	movingObjects = []
  	for i in range(len(lstSubAssignments)):
  		newObjectID = lstSubAssignments[i].iloc[0]['objectID']
  		if (newObjectID not in movingObjects):
  			movingObjects.append(newObjectID)
  	strMovingObjects = ""
  	for i in range(len(movingObjects)):
  		strMovingObjects += "'%s', " % (movingObjects[i])
  	strMovingObjects = strMovingObjects[:-2]
  	jsStr +=             "    var pathNames = [%s]; \n" % (strMovingObjects)
  
  	# Register paths
  	jsStr +=             "    registerPaths(pathNames); \n"
  
  	# For each `odID`, draw a polyline
  	for i in range(len(lstSubAssignments)):
  		# Get the lat/lon/alt for each waypoint, (polygon in 3D)
  		lstSubAssignments[i] = lstSubAssignments[i].reset_index(drop=True)
  		assignmentLats = []
  		assignmentLons = []
  		assignmentAltMeters = []
  		assignmentLats.append(lstSubAssignments[i]['startLat'][0])
  		assignmentLons.append(lstSubAssignments[i]['startLon'][0])
  		assignmentAltMeters.append(lstSubAssignments[i]['startAltMeters'][0])
  		assignmentLats.extend(lstSubAssignments[i]['endLat'].tolist())
  		assignmentLons.extend(lstSubAssignments[i]['endLon'].tolist())
  		assignmentAltMeters.extend(lstSubAssignments[i]['endAltMeters'].tolist())
  
  		assignmentDimension = 3
  		if (max(assignmentAltMeters) == 0 and min(assignmentAltMeters) == 0):
  			assignmentDimension = 2
  		else:
  			assignmentDimension = 3
  
  		# Check if cesium styles have been overrided, if not, assume that for entire path the style is the same
  		if (cesiumColor != None):
  			color = cesiumColor
  		else:
  			color = lstSubAssignments[i].iloc[0]['cesiumColor']
  		if (cesiumWeight != None):
  			weight = cesiumWeight
  		else:
  			weight = lstSubAssignments[i].iloc[0]['cesiumWeight']
  		if (cesiumOpacity != None):
  			opacity = cesiumOpacity
  		else:
  			opacity = lstSubAssignments[i].iloc[0]['cesiumOpacity']
  		if (cesiumStyle != None):
  			style = cesiumStyle
  		else:
  			style = lstSubAssignments[i].iloc[0]['cesiumStyle']
  
  		try:
  			style = style.lower()
  		except:
  			pass
  			
  		if (style == 'dashed'):
  			dashLength = 40
  		elif (style == 'dotted'):
  			dashLength = 10
  		elif (style == 'solid'):
  			dashLength = 0
  
  		if (assignmentDimension == 3):
  			# For each path, generate one polyline entity
  			jsStr +=     "    paths[%d] = viewer.entities.add({\n" % (i)
  			jsStr +=     "        parent: vehiclePolylines['%s'],\n" % (lstSubAssignments[i].iloc[0]['objectID'])
  			jsStr +=     "        name: 'Objects %s',\n" % (lstSubAssignments[i].iloc[0]['objectID'])
  			jsStr +=     "        polyline: {\n"
  			jsStr +=     "            positions: Cesium.Cartesian3.fromDegreesArrayHeights([\n"
  			for j in range(0, len(assignmentLats)):
  				jsStr += "                %f, %f, %f, \n" % (assignmentLons[j], assignmentLats[j], assignmentAltMeters[j])
  			jsStr = jsStr[:-3]
  			jsStr +=     "            \n"
  			jsStr +=     "            ]),\n"
  			jsStr +=     "            width: %d, \n" % (weight)
  			jsStr +=     "            material: new Cesium.PolylineDashMaterialProperty({\n"
  			jsStr +=     "                dashLength: %f,\n" % (dashLength)
  			jsStr +=     "                color: %s.withAlpha(%f)\n" % (color, opacity)
  			jsStr +=     "            })\n"
  			jsStr +=     "        }\n"
  			jsStr +=     "    });\n"
  		elif (assignmentDimension == 2):
  			# For each path, generate one polyline entity
  			jsStr +=     "    paths[%d] = viewer.entities.add({\n" % (i)
  			jsStr +=     "        parent: vehiclePolylines['%s'],\n" % (lstSubAssignments[i].iloc[0]['objectID'])
  			jsStr +=     "        name: 'Objects %s',\n" % (lstSubAssignments[i].iloc[0]['objectID'])
  			jsStr +=     "        polyline: {\n"
  			jsStr +=     "            positions: Cesium.Cartesian3.fromDegreesArray([\n"
  			for j in range(0, len(assignmentLats)):
  				jsStr += "                %f, %f, \n" % (assignmentLons[j], assignmentLats[j])
  			jsStr = jsStr[:-3]
  			jsStr +=     "            \n"
  			jsStr +=     "            ]),\n"
  			jsStr +=     "            clampToGround: true, \n"
  			jsStr +=     "            width: %d, \n" % (weight)
  			jsStr +=     "            material: new Cesium.PolylineDashMaterialProperty({\n"
  			jsStr +=     "                dashLength: %f,\n" % (dashLength)
  			jsStr +=     "                color: %s.withAlpha(%f)\n" % (color, opacity)
  			jsStr +=     "            })\n"
  			jsStr +=     "        }\n"
  			jsStr +=     "    });\n"
  
  	# End of the displayPaths function
  	jsStr +=             "}"
  
  	# Write contents and close file stream
  	f.write(jsStr)
  	f.close()
  
  	if (VRV_SETTING_SHOWOUTPUTMESSAGE):
  		print("Message: Assignments (.js) were written to %s ..." % (jsFilePath))
  
  	return
  
  def _getCesiumTime(startDate, startTime, timeSec):
  	"""
  	This script gives a JulianDate format time for cesium
  
  	Parameters
  	----------
  	startDate: string, format is "YYYY-MM-DD", default as today
  		The start date of the video generated
  	startTime: string, format is "HH:MM:SS", default as '08:00:00'
  		The start time of the start date
  	timeSec: float
  		Time past after start time
  
  	Return
  	------
  	JulianDate
  		Time in JulianDate format
  	"""
  
  	# Set time zero
  	timeZero = dateutil.parser.parse("%s %s" % (startDate, startTime))
  
  	# Return timeSec in cesium format (semi-Julian format)
  	cesiumTime = datetime.datetime.strftime(timeZero + datetime.timedelta(seconds = timeSec), '%Y-%m-%dT%H:%M:%SZ')
  
  	return cesiumTime
  
  def _getAction(subAssignments):
  
  	"""
  	Give a group of assignments with same odID, find if this group is stationary/vertical/move, each will be treated differently in cesium and folium.
  
  	Parameters
  	----------
  	subAssignments: :ref:`Assignments`
  		An assignment dataframe with the same odID
  
  	Return
  	------
  	string
  		An enumerate string, out put can be 'stationary'/'vertical'/'move'
  	"""
  
  	# if it is static or vertical, there should (must) be one row for correspondence `odID`
  	if (len(subAssignments) == 1):
  		if (subAssignments.iloc[0]['startLat'] == subAssignments.iloc[0]['endLat']
  			and subAssignments.iloc[0]['startLon'] == subAssignments.iloc[0]['endLon']
  			and subAssignments.iloc[0]['startAltMeters'] == subAssignments.iloc[0]['endAltMeters']):
  			action = "stationary"
  		elif (subAssignments.iloc[0]['startLat'] == subAssignments.iloc[0]['endLat']
  			and subAssignments.iloc[0]['startLon'] == subAssignments.iloc[0]['endLon']
  			and subAssignments.iloc[0]['startAltMeters'] != subAssignments.iloc[0]['endAltMeters']):
  			action = "vertical"
  		else:
  			action = "move"
  	else:
  		action = "move"
  
  	return action
  
  def _getPathsDetails(assignments):
  
  	"""
  	Given an Assignments dataframe, this script deconstruct it into a list of assignments(or called subAssignments) each subAssignment will have: the same odID; same type of movement(stationary/vertical/move)
  
  	Parameters
  	----------
  	assignments: :ref:`Assignments`, Required
  		The Assignments dataframe to be deconstructed into lists
  
  	Returns
  	-------
  	path: Path dataframe
  		A list of description of each subAssignment
  	lstSubAssignments: list of :ref:`Assignments`
  		A list of Assignments dataframe each with the same odID
  
  	"""
  
  	# Icon list
  	modelWithDuplicates = assignments['modelFile'].tolist()
  	uniqueIconList = list(dict.fromkeys(modelWithDuplicates))
  
  	# Get path list from assignments dataframe
  	lstSubAssignments = deconstructAssignments(assignments=assignments, includeStationaryFlag=True, includeVerticalFlag=True)
  
  	# Now we prepare for .czml, the following is a Path Dataframe, which has the same length as lstSubAssignments
  	path = pd.DataFrame(columns=['odID', 'czmlID', 'objectID', 'modelFile', 'action', 'modelScale', 'modelMinPxSize', 'startTimeSec', 'endTimeSec', 'intervalStart', 'intervalEnd', 'indexInlstShapepoints'])
  	for i in range(len(lstSubAssignments)):
  		path = path.append({
  			'odID': lstSubAssignments[i].iloc[0]['odID'],
  			'czmlID': 'o-%s-%s-%s' % (lstSubAssignments[i].iloc[0]['objectID'], lstSubAssignments[i].iloc[0]['modelFile'], _getAction(lstSubAssignments[i])),
  			'objectID': lstSubAssignments[i].iloc[0]['objectID'],
  			'modelFile': lstSubAssignments[i].iloc[0]['modelFile'],
  			'action': _getAction(lstSubAssignments[i]),
  			'modelScale': lstSubAssignments[i].iloc[0]['modelScale'],
  			'modelMinPxSize': lstSubAssignments[i].iloc[0]['modelMinPxSize'],
  			'startTimeSec': lstSubAssignments[i].iloc[0]['startTimeSec'],
  			'endTimeSec': lstSubAssignments[i].iloc[-1]['endTimeSec'],
  			'intervalStart': "",
  			'intervalEnd': "",
  			'indexInlstShapepoints': i
  			}, ignore_index=True)
  	path.sort_values('odID', ascending=True)
  
  	return [path, lstSubAssignments]
  