Index: /issm/trunk-jpl/src/m/array/arrayoperations.js
===================================================================
--- /issm/trunk-jpl/src/m/array/arrayoperations.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/array/arrayoperations.js	(revision 26300)
@@ -54,5 +54,5 @@
 	for (var a = 0; a < arguments.length; a++) {
         arg = arguments[a];
-        if (arg instanceof Array) {
+        if (arg instanceof Array || arg instanceof Float64Array) {
             size = arg.length;
             array = arg;
@@ -64,7 +64,7 @@
 	for (var a = 0; a < arguments.length; a++) {
         arg = arguments[a];
-        if (arg instanceof Array && arg.length != size) {
+        if ((arg instanceof Array || arg instanceof Float64Array) && arg.length != size) {
             throw Error("ArrayAdd error message: arrays provided as arguments are not of the same length!");
-        } else if (!(arg instanceof Array) && typeof arg != 'number') {
+        } else if (!(arg instanceof Array || arg instanceof Float64Array) && typeof arg != 'number') {
             throw Error("ArrayAdd error message: arguments provided are not of the type Array or Number!");
         }
@@ -75,9 +75,9 @@
 		if (a != initial) {
 			arg = arguments[a];
-			if (arg instanceof Array) {
+			if (arg instanceof Array || arg instanceof Float64Array) {
 				for(var i = 0; i < result.length; i++){
 					result[i] += arg[i];
 				}
-			} else if (typeof arg != 'number') {
+			} else if (typeof arg === 'number') {
 				for(var i = 0; i < result.length; i++){
 					result[i] += arg;
@@ -94,5 +94,5 @@
 	for (var a = 0; a < arguments.length; a++) {
         arg = arguments[a];
-        if (arg instanceof Array) {
+        if (arg instanceof Array || arg instanceof Float64Array) {
             size = arg.length;
             array = arg;
@@ -104,7 +104,7 @@
 	for (var a = 0; a < arguments.length; a++) {
         arg = arguments[a];
-        if (arg instanceof Array && arg.length != size) {
+        if ((arg instanceof Array || arg instanceof Float64Array) && arg.length != size) {
             throw Error("ArrayAdd error message: arrays provided as arguments are not of the same length!");
-        } else if (!(arg instanceof Array) && typeof arg != 'number') {
+        } else if (!(arg instanceof Array || arg instanceof Float64Array) && typeof arg != 'number') {
             throw Error("ArrayAdd error message: arguments provided are not of the type Array or Number!");
         }
@@ -115,11 +115,57 @@
 		if (a !== initial) {
 			arg = arguments[a];
-			if (arg instanceof Array) {
+			if (arg instanceof Array || arg instanceof Float64Array) {
 				for(var i = 0; i < result.length; i++){
 					result[i] -= arg[i];
 				}
-			} else if (typeof arg != 'number') {
+			} else if (typeof arg === 'number') {
 				for(var i = 0; i < result.length; i++){
 					result[i] -= arg;
+				}
+			}
+        }
+	}
+	return result;
+} //}}}
+function ArraySubtract2D(){ //{{{
+    //Takes in any number of scalars or arrays, and calculates the subtraction. Scalars are treated as similar length arrays of the scalar.
+    //Determine reference array and size
+    var size, array, arg, initial;
+	for (var a = 0; a < arguments.length; a++) {
+        arg = arguments[a];
+        if (arg instanceof Array || arg instanceof Float64Array) {
+            size = arg.length;
+            array = arg;
+			initial = a;
+            break;
+        }
+    }
+	//check internal consistency of arrays provided!: 
+	for (var a = 0; a < arguments.length; a++) {
+        arg = arguments[a];
+        if ((arg instanceof Array || arg instanceof Float64Array) && arg.length != size) {
+            throw Error("ArrayAdd error message: arrays provided as arguments are not of the same length!");
+        } else if (!(arg instanceof Array || arg instanceof Float64Array) && typeof arg != 'number') {
+            throw Error("ArrayAdd error message: arguments provided are not of the type Array or Number!");
+        }
+	}
+	//calculate the result, using the first argument to initialize:
+	var result = [];
+	for (var a = 0; a < arguments.length; a++) {
+		if (a !== initial) {
+			arg = arguments[a];
+			if (arg instanceof Array || arg instanceof Float64Array) {
+				for(var i = 0; i < array.length; i++){
+					result[i] = [];
+					for(var j = 0; j < array[i].length; j++){
+					    result[i][j] = array[i][j] - arg[i][j];
+					}
+				}
+			} else if (typeof arg === 'number') {
+				for(var i = 0; i < array.length; i++){
+					result[i] = [];
+					for(var j = 0; j < array[i].length; j++){
+					    result[i][j] = array[i][j] - arg;
+					}
 				}
 			}
@@ -134,5 +180,5 @@
 	for (var a = 0; a < arguments.length; a++) {
         arg = arguments[a];
-        if (arg instanceof Array) {
+        if (arg instanceof Array || arg instanceof Float64Array) {
             size = arg.length;
             array = arg;
@@ -144,7 +190,7 @@
 	for (var a = 0; a < arguments.length; a++) {
         arg = arguments[a];
-        if (arg instanceof Array && arg.length != size) {
+        if ((arg instanceof Array || arg instanceof Float64Array) && arg.length != size) {
             throw Error("ArrayAdd error message: arrays provided as arguments are not of the same length!");
-        } else if (!(arg instanceof Array) && typeof arg != 'number') {
+        } else if (!(arg instanceof Array || arg instanceof Float64Array) && typeof arg != 'number') {
             throw Error("ArrayAdd error message: arguments provided are not of the type Array or Number!");
         }
@@ -155,9 +201,9 @@
 		if (a !== initial) {
 			arg = arguments[a];
-			if (arg instanceof Array) {
+			if (arg instanceof Array || arg instanceof Float64Array) {
 				for(var i = 0; i < result.length; i++){
 					result[i] *= arg[i];
 				}
-			} else if (typeof arg != 'number') {
+			} else if (typeof arg === 'number') {
 				for(var i = 0; i < result.length; i++){
 					result[i] *= arg;
@@ -174,5 +220,5 @@
 	for (var a = 0; a < arguments.length; a++) {
         arg = arguments[a];
-        if (arg instanceof Array) {
+        if (arg instanceof Array || arg instanceof Float64Array) {
             size = arg.length;
             array = arg;
@@ -184,7 +230,7 @@
 	for (var a = 0; a < arguments.length; a++) {
         arg = arguments[a];
-        if (arg instanceof Array && arg.length != size) {
+        if ((arg instanceof Array || arg instanceof Float64Array) && arg.length != size) {
             throw Error("ArrayAdd error message: arrays provided as arguments are not of the same length!");
-        } else if (!(arg instanceof Array) && typeof arg != 'number') {
+        } else if (!(arg instanceof Array || arg instanceof Float64Array) && typeof arg != 'number') {
             throw Error("ArrayAdd error message: arguments provided are not of the type Array or Number!");
         }
@@ -195,9 +241,9 @@
 		if (a !== initial) {
 			arg = arguments[a];
-			if (arg instanceof Array) {
+			if (arg instanceof Array || arg instanceof Float64Array) {
 				for(var i = 0; i < result.length; i++){
 					result[i] /= arg[i];
 				}
-			} else if (typeof arg != 'number') {
+			} else if (typeof arg === 'number') {
 				for(var i = 0; i < result.length; i++){
 					result[i] /= arg;
@@ -282,27 +328,4 @@
 
 } //}}}
-function ArrayOr(){ //{{{
-    if (arguments.length<2)throw Error("ArrayOr error message: sum has to be for at least two arrays!");
-
-	//check internal consistency of arrays provided!: 
-	var firstarray=arguments[0];
-	var firstsize=firstarray.length;
-	
-	for(var a=1;a<arguments.length;a++){
-		var array=arguments[a];
-		if(array.length!=firstsize)throw Error("ArrayOr error message: arrays provided as arguments are not of the same length!");
-	}
-
-	//do the or:
-	var or=NewArrayFill(firstsize,0);
-	for(var a=0;a<arguments.length;a++){
-		var array=arguments[a];
-		for(var i=0;i<array.length;i++){
-			or[i] = or[i] | array[i];
-		}
-	}
-	return or;
-
-} //}}}
 function ArrayConcat(a, b) { //{{{
 	// Make sure that both typed arrays are of the same type
@@ -322,22 +345,22 @@
 } //}}}
 function ArrayCol(matrix, cols) { //{{{
-    var column = [];
-    if (cols instanceof Array) {
+    var columns = [];
+    if (cols instanceof Array || cols instanceof Float64Array) {
         for (var i = 0; i < matrix.length; i++){
-            var row = [];
-            for (var j = cols[0]; j <= cols[1]; j++){
-                row.push(matrix[i][j]);
+            var col = [];
+            for (var j = 0; j < cols.length; j++){
+                col.push(matrix[i][cols[j]]);
             }
-            column.push(row); 
+            columns.push(col); 
         }
 
     } else if (typeof cols == 'number') {
         for (var i = 0; i < matrix.length; i++){
-            column.push(matrix[i][cols]);
+            columns.push(matrix[i][cols]);
         }
     } else {
         throw new Error("ArrayCol error: cols must be a single integer or an array with 2 integers!");
     }
-   return column;
+   return columns;
 } //}}}
 function ListToMatrix(list, elementsPerSubArray) { //{{{
@@ -445,16 +468,147 @@
     return 0;
 } //}}}
-function ArrayUnique(arr) { //{{{
-
-	return arr.reverse().filter(function (e, i, arr) {
-		    return arr.indexOf(e, i+1) === -1;
-	}).reverse();
-} //}}}
-function ArraySort(array) { //{{{
-
-	return array.sort(function(a, b) {
-		return a - b;
-	});
-
+function ArrayUnique(arr,rows) { //{{{
+	if (arguments.length == 2){
+		if (rows == 'rows') {
+			//See Matlab unique function and https://stackoverflow.com/a/20339709/1905613
+			let equals = (a, b) => JSON.stringify(a) === JSON.stringify(b);
+			let uniques = [];
+			let indexA = [];
+			let indexC = [];
+			let itemsFound = {};;
+			for(let i = 0, l = arr.length; i < l; i++) {
+				let stringified = JSON.stringify(arr[i]);
+				if (typeof(itemsFound[stringified]) != 'undefined') {
+					indexC.push(itemsFound[stringified]);
+					continue;
+				}
+				uniques.push(arr[i]);
+				indexA.push(i);
+				itemsFound[stringified] = uniques.length-1;
+				indexC.push(itemsFound[stringified]);
+			}
+			//assert arr == uniques[indexC,:];
+			for (let i = 0; i < indexC.length; i++) {
+				if (!equals(arr[i], uniques[indexC[i]])) {
+					throw new Error('bad implementation');	
+				}
+			}
+			//assert uniques == arr[indexA, :];
+			for (let i = 0; i < indexA.length; i++) {
+				if (!equals(uniques[i], arr[indexA[i]])) {
+					throw new Error('bad implementation');	
+				}
+			}
+			let [uniquesSorted, indexInToOut, indexOutToIn] = ArraySortWithIndices(uniques);
+			//indexMapping is the index of the edge in the old array
+			indexCSorted = []; //indexC.length == arr.length
+			//assert uniquesSorted[i,:] = uniques[indexInToOut[i],:]
+			for (let i = 0; i < indexInToOut.length; i++) {
+				if (!equals(uniquesSorted[i], uniques[indexInToOut[i]])) {
+					console.log(i, uniquesSorted[indexInToOut[i]], uniques[i]);
+					throw new Error('bad implementation');	
+				}
+			}
+			//assert uniques[i,:] = uniquesSorted[indexOutToIn[i],:]
+			for (let i = 0; i < indexOutToIn.length; i++) {
+				if (!equals(uniques[i], uniquesSorted[indexOutToIn[i]])) {
+					console.log(i, uniques[indexOutToIn[i]], uniquesSorted[i]);
+					throw new Error('bad implementation');	
+				}
+			}
+			//GOAL: assert arr[i,:] == uniquesSorted[indexCSorted[i], :]
+			//GIVEN: assert arr[i,:] == uniques[indexC[i],:];
+			//GIVEN: assert uniquesSorted[i,:] = uniques[indexInToOut[i],:]
+			//GIVEN: assert uniques[i,:] = uniquesSorted[indexOutToIn[i],:]
+			//assert uniques[indexC[i],:] == uniquesSorted[indexOutToIn[indexC[i]],:]
+			//assert uniquesSorted[indexCSorted[i],:]; == uniquesSorted[indexOutToIn[indexC[i]],:];
+			for (let i = 0; i < arr.length; i++) {
+				indexCSorted[i] = indexOutToIn[indexC[i]];
+			}
+			for (let i = 0; i < indexC.length; i++) {
+				if (!equals(arr[i], uniquesSorted[indexCSorted[i]])) {
+					console.log(i, arr[i], uniquesSorted[indexCSorted[i]]);
+					throw new Error('bad implementation');	
+				}
+			}
+
+			indexASorted = []; //indexA.length == uniques.length
+			//GOAL: uniquesSorted[i, :] == arr[indexASorted[i], :]
+			//GIVEN: assert arr[i,:] == uniques[indexC[i],:];
+			//GIVEN: assert arr[indexA[i],:] == uniques[i,:];
+			//GIVEN: assert uniques[indexInToOut[i],:] == uniquesSorted[i,:]
+			//GIVEN: assert uniques[i,:] = uniquesSorted[indexOutToIn[i],:]
+			//assert uniquesSorted[i,:] == uniques[indexInToOut[i],:]
+			//assert uniques[indexInToOut[i],:] == arr[indexA[indexInToOut[i]],:];
+			//assert indexA[indexInToOut] == indexASorted
+			//indexASorted == indexA[indexMapping[i]]
+			for (let i = 0; i < indexA.length; i++) {
+				indexASorted[i] = indexA[indexInToOut[i]];
+			}
+			//assert uniques == arr[indexA, :];
+			for (let i = 0; i < indexASorted.length; i++) {
+				if (!equals(uniquesSorted[i], arr[indexASorted[i]])) {
+					throw new Error('bad implementation');	
+				}
+			}
+			console.log('Good uniques');
+			return [uniquesSorted, indexASorted, indexCSorted];
+		} else {
+			throw new Error('ArrayUnique non "rows" not supported');	
+		}
+	} else {
+		return arr.reverse().filter(function (e, i, arr) {
+				return arr.indexOf(e, i+1) === -1;
+		}).reverse();
+	}
+} //}}}
+function ArraySortWithIndices(toSort, sortingFunction) { //{{{
+	//returns the sorted and index such that toSort[index[i]] == sorted[i]
+	let toSortCopy = [];
+	for (var i = 0; i < toSort.length; i++) {
+	    toSortCopy[i] = [toSort[i], i];
+	}
+	if (typeof(sortingFunction) == 'undefined') {
+		let numeric2DFunction = function(a, b) {
+			if (a[0][0] == b[0][0]) {
+                return a[0][1] - b[0][1];
+			} else {
+			    return a[0][0] - b[0][0];
+			}
+		};
+		sortingFunction = numeric2DFunction;
+	}
+	toSortCopy.sort(sortingFunction);
+	let indicesInToOut = [];
+	let indicesOutToIn = [];
+	let sorted = [];
+	for (var j = 0; j < toSortCopy.length; j++) {
+	    indicesInToOut[j] = toSortCopy[j][1];
+	    indicesOutToIn[toSortCopy[j][1]] = j;
+	    sorted[j] = toSortCopy[j][0];
+	}
+	return [sorted, indicesInToOut, indicesOutToIn];
+} //}}}
+function ArraySort(array,dim) { //{{{
+	let numericFunction = function(a, b) {
+	    return a - b;
+	};
+	let numeric2DFunction = function(a, b) {
+	    return a[0] - b[0];
+	};
+	if (arguments.length == 2){
+		if (dim == 1) {
+			array.sort(numeric2DFunction);
+		} else if (dim == 2) {
+			for (let i = 0; i < array.length; i++) {
+				array[i].sort(numericFunction);
+			}
+		} else {
+			throw new Error('ArraySort dim > 2 not yet supported')
+		}
+		return array;
+	} else {
+		return array.sort(numericFunction);
+	}
 } //}}}
 function ArrayRange(lower, upper) { //{{{
@@ -515,7 +669,100 @@
 } //}}}
 function ArrayAnd(array1,array2) { //{{{
-
-	var array=array1;
-	for (var i=0;i<array1.length;i++)array[i]=array1[i] & array2[i];
+	var array = new Array(array1.length);
+	for (var i=0;i<array1.length;i++) {
+		array[i]=array1[i] & array2[i];
+	}
+	return array;
+} //}}}
+function ArrayOr(array1,array2) { //{{{
+	var array = new Array(array1.length);
+	for (var i=0;i<array1.length;i++) {
+		array[i]=array1[i] | array2[i];
+	}
+	return array;
+} //}}}
+function ArrayEqual(array1,array2) { //{{{
+	var array = new Array(array1.length);
+
+	if (typeof(array1[0]) == 'number') {
+		if (typeof(array2) == 'number') {
+			for(var i=0;i<array1.length;i++){
+				array[i] = array1[i] == array2;
+			}
+		} else {
+			for(var i=0;i<array1.length;i++){
+				array[i] = array1[i] == array2[i];
+			}
+		}
+	} else { //provide support for 2d arrays
+		if (typeof(array2) == 'number') {
+			for(var i=0;i<array1.length;i++){
+				array[i] = new Array(array1[i].length);
+				for(var j=0;j<array1[i].length;j++){
+					array[i][j] = array1[i][j] == array2;
+				}
+			}
+		} else {
+			for(var i=0;i<array1.length;i++){
+				array[i] = new Array(array1[i].length);
+				for(var j=0;j<array1[i].length;j++){
+					array[i][j] = array1[i][j] == array2[i][j];
+				}
+			}
+		}
+	}
+	return array;
+} //}}}
+function ArrayLessThan(array1,array2) { //{{{
+	var array = new Array(array1.length);
+
+	if (typeof(array2) == 'number') {
+		for(var i=0;i<array1.length;i++){
+			array[i] = array1[i] < array2;
+		}
+	} else {
+		for(var i=0;i<array1.length;i++){
+			array[i] = array1[i] < array2[i];
+		}
+	}
+	return array;
+} //}}}
+function ArrayGreaterThan(array1,array2) { //{{{
+	var array = new Array(array1.length);
+	if (typeof(array2) == 'number') {
+		for(var i=0;i<array1.length;i++){
+			array[i] = array1[i] > array2;
+		}
+	} else {
+		for(var i=0;i<array1.length;i++){
+			array[i] = array1[i] > array2[i];
+		}
+	}
+	return array;
+} //}}}
+function ArrayLessEqualThan(array1,array2) { //{{{
+	var array = new Array(array1.length);
+	if (typeof(array2) == 'number') {
+		for(var i=0;i<array1.length;i++){
+			array[i] = array1[i] <= array2;
+		}
+	} else {
+		for(var i=0;i<array1.length;i++){
+			array[i] = array1[i] <= array2[i];
+		}
+	}
+	return array;
+} //}}}
+function ArrayGreaterEqualThan(array1,array2) { //{{{
+	var array = new Array(array1.length);
+	if (typeof(array2) == 'number') {
+		for(var i=0;i<array1.length;i++){
+			array[i] = array1[i] >= array2;
+		}
+	} else {
+		for(var i=0;i<array1.length;i++){
+			array[i] = array1[i] >= array2[i];
+		}
+	}
 	return array;
 } //}}}
@@ -551,5 +798,5 @@
 	return arr;
 } //}}}
-function NewArrayFillIncrement(size,start,increment) { //{{{
+function NewArrayFillIncrement(start,size,increment) { //{{{
 	var array=new Array(size); 
 
@@ -559,4 +806,10 @@
 
 	return array;
+} //}}}
+function ones(size) { //{{{
+	return NewArrayFill(size,1);
+} //}}}
+function zeros(size) { //{{{
+	return NewArrayFill(size,0);
 } //}}}
 function ArrayFind(array,value) { //{{{
@@ -611,4 +864,30 @@
 	}
 	return indices;
+} //}}}
+function ArrayIndex(array1,array2,value) { //{{{
+	//Change behavior between get (if no value is provided) to set (if value to set is provided)
+	if (arguments.length == 2){
+		let data = []
+		if (typeof(array2[0]) == 'number') {
+			for (let i=0;i<array2.length;i++){
+				data.push(array1[array2[i]]);
+			}
+		} else {
+			//2d index array
+			for (let i=0;i<array2.length;i++){
+				let data2 = [];
+				for (let j=0;j<array2[i].length;j++){
+				    data2.push(array1[array2[i][j]]);
+				}
+				data.push(data2);
+			}
+		}
+		return data;
+	} else {
+		for (var i=0;i<array2.length;i++){
+			array1[array2[i]]=value;
+		}
+		return array1;
+	}
 } //}}}
 function Create2DArray(rows,cols) { //{{{
@@ -644,5 +923,5 @@
 
 	// Handle Array
-	if (obj instanceof Array) {
+	if (obj instanceof Array || arg instanceof Float64Array) {
 		copy = [];
 		for (var i = 0, len = obj.length; i < len; i++) {
Index: /issm/trunk-jpl/src/m/classes/SMBforcing.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/SMBforcing.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/SMBforcing.js	(revision 26300)
@@ -12,5 +12,10 @@
 		console.log(sprintf('   surface forcings parameters:'));
 		fielddisplay(this,'mass_balance','surface mass balance [m/yr ice eq]');
+		fielddisplay(this,'steps_per_step', 'number of smb steps per time step');
 		fielddisplay(this,'requested_outputs','additional outputs requested');
+		fielddisplay(this,'averaging','averaging methods from short to long steps');
+		console.log(sprintf('%51s  0: Arithmetic (default)',' '));
+		console.log(sprintf('%51s  1: Geometric',' '));
+		console.log(sprintf('%51s  2: Harmonic',' '));
 	} // }}}
 	this.defaultoutputs = function(){ // {{{
@@ -33,5 +38,5 @@
     } // }}}
     this.checkconsistency = function(md,solution,analyses) { //{{{
-
+		if (solution=='TransientSolution' && md.transient.issmb == 0) return;
         if(ArrayAnyEqual(ArrayIsMember('MasstransportAnalysis',analyses),1)){
             checkfield(md,'fieldname','smb.mass_balance','timeseries',1,'NaN',1,'Inf',1);
@@ -40,6 +45,7 @@
             checkfield(md,'fieldname','smb.mass_balance','size',[md.mesh.numberofvertices,1],'NaN',1,'Inf',1);
         }
-        checkfield(md,'fieldname','smb.requested_outputs','stringrow',1);
-
+		checkfield(md,'fieldname','smb.steps_per_step','>=',1,'numel',[1]);
+		checkfield(md,'fieldname','smb.requested_outputs','stringrow',1);
+		checkfield(md,'fieldname','smb.averaging','numel',[1],'values',[0,1,2]);
     } // }}}
     this.marshall=function(md,prefix,fid) { //{{{
@@ -49,4 +55,6 @@
         WriteData(fid,prefix,'name','md.smb.model','data',1,'format','Integer');
         WriteData(fid,prefix,'object',this,'class','smb','fieldname','mass_balance','format','DoubleMat','mattype',1,'scale',1./yts,'timeserieslength',md.mesh.numberofvertices+1,'yts',md.constants.yts);
+		WriteData(fid,prefix,'object',this,'fieldname','steps_per_step','format','Integer');
+		WriteData(fid,prefix,'object',this,'fieldname','averaging','format','Integer');
 
         //process requested outputs
@@ -66,6 +74,8 @@
 	//properties 
     // {{{
-	this.mass_balance = NaN;
-	this.requested_outputs      = [];
+	this.mass_balance 	   = NaN;
+	this.requested_outputs = [];
+	this.steps_per_step    = 1;
+	this.averaging         = 0;
 	this.setdefaultparameters();
     // }}}
Index: /issm/trunk-jpl/src/m/classes/amr.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/amr.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/amr.js	(revision 26300)
@@ -7,10 +7,20 @@
 	//methods
 	this.setdefaultparameters = function(){// {{{
+
+		//hmin and hmax
 		this.hmin								= 100.;
 		this.hmax								= 100.e3;
+
+		//fields
 		this.fieldname							= "Vel";
 		this.err								= 3.;
+
+		//keep metric?
 		this.keepmetric							= 1;
+
+		//control of element lengths
 		this.gradation							= 1.5;
+
+		//other criterias
 		this.groundingline_resolution			= 500.;
 		this.groundingline_distance				= 0;
@@ -25,4 +35,7 @@
 		this.deviatoricerror_groupthreshold		= 0;	
 		this.deviatoricerror_maximum			= 0;	
+
+		//is restart? This calls femmodel->ReMesh() before first time step. 
+		this.restart							= 0;
 	}// }}}
 	this.disp= function(){// {{{
@@ -45,4 +58,6 @@
 		fielddisplay(this,'deviatoricerror_groupthreshold','maximum group threshold deviatoricstress error permitted');
 		fielddisplay(this,'deviatoricerror_maximum','maximum deviatoricstress error permitted');
+		fielddisplay(this,'deviatoricerror_maximum','maximum deviatoricstress error permitted');
+		fielddisplay(this,'restart','indicates if ReMesh() will call before first time step');
 	}// }}}
 	this.classname= function(){// {{{
@@ -67,4 +82,5 @@
 		checkfield(md,'fieldname','amr.deviatoricerror_groupthreshold','numel',[1],'>=',0,'<=',1,'NaN',1);
 		checkfield(md,'fieldname','amr.deviatoricerror_maximum','numel',[1],'>=',0,'NaN',1,'Inf',1);
+		checkfield(md,'fieldname','amr.restart','numel',[1],'>=',0,'<=',1,'NaN',1);
 	} // }}}
 	this.marshall=function(md,prefix,fid) { //{{{
@@ -88,4 +104,5 @@
 		WriteData(fid,prefix,'object',this,'fieldname','deviatoricerror_groupthreshold','format','Double');
 		WriteData(fid,prefix,'object',this,'fieldname','deviatoricerror_maximum','format','Double');
+		WriteData(fid,prefix,'object',this,'fieldname','restart','format','Integer');
 	}//}}}
 	this.fix=function() { //{{{
@@ -111,4 +128,5 @@
 	this.deviatoricerror_groupthreshold		= 0.;
 	this.deviatoricerror_maximum			= 0.;
+	this.restart							= 0.;
 
 	this.setdefaultparameters();
Index: /issm/trunk-jpl/src/m/classes/balancethickness.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/balancethickness.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/balancethickness.js	(revision 26300)
@@ -40,4 +40,7 @@
 			WriteData(fid,prefix,'object',this,'fieldname','thickening_rate','format','DoubleMat','mattype',1,'scale',1/yts);
 			WriteData(fid,prefix,'object',this,'fieldname','stabilization','format','Integer');
+
+			WriteData(fid,prefix,'object',this,'fieldname','slopex','format','DoubleMat','mattype',1);
+			WriteData(fid,prefix,'object',this,'fieldname','slopey','format','DoubleMat','mattype',1);
 			WriteData(fid,prefix,'object',this,'fieldname','omega','format','DoubleMat','mattype',1);
 
@@ -53,5 +56,8 @@
 	this.thickening_rate   = NaN;
 	this.stabilization     = 0;
+
 	this.omega             = NaN;
+	this.slopex            = NaN;
+	this.slopey            = NaN;
 	this.setdefaultparameters();
 	//}}}
Index: /issm/trunk-jpl/src/m/classes/calving.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/calving.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/calving.js	(revision 26300)
@@ -35,4 +35,5 @@
 		this.fix=function() { //{{{
 			this.calvingrate=NullFix(this.calvingrate,NaN);
+			this.meltingrate=NullFix(this.meltingrate,NaN);
 		}//}}}
 	//properties 
Index: /issm/trunk-jpl/src/m/classes/constants.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/constants.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/constants.js	(revision 26300)
@@ -19,4 +19,7 @@
 			//the reference temperature for enthalpy model (cf Aschwanden)
 			this.referencetemperature=223.15;
+
+			//gravitational constant: 
+			this.gravitational_constant = 6.67259e-11;
 		}// }}}
 		this.disp = function () { //{{{
@@ -27,4 +30,5 @@
 			fielddisplay(this,'yts','number of seconds in a year [s/yr]');
 			fielddisplay(this,'referencetemperature','reference temperature used in the enthalpy model [K]');
+			fielddisplay(this,'gravitational_constant','Newtonian constant of gravitation [m^3/kg/s^2]');
 
 		} //}}}
@@ -39,4 +43,5 @@
 			checkfield(md,'fieldname','constants.yts','>',0,'size',[1,1]);
 			checkfield(md,'fieldname','constants.referencetemperature','size',[1,1]);
+			checkfield(md,'fieldname','constants.gravitational_constant','size',[1,1]);
 
 		} // }}}
@@ -45,4 +50,5 @@
 			WriteData(fid,prefix,'object',this,'fieldname','yts','format','Double');
 			WriteData(fid,prefix,'object',this,'fieldname','referencetemperature','format','Double');
+			WriteData(fid,prefix,'object',this,'fieldname','gravitational_constant','format','Double');
 		}//}}}
 		this.fix=function() { //{{{
@@ -50,8 +56,9 @@
 	//properties 
 	// {{{
-		this.g                    = 0.;
-		this.omega                = 0.;
-		this.yts                  = 0.;
-		this.referencetemperature = 0.;
+		this.g						  = 0.;
+		this.omega					  = 0.;
+		this.yts					  = 0.;
+		this.referencetemperature	  = 0.;
+		this.gravitational_constant   = 0.;
 		this.setdefaultparameters();
 		//}}}
Index: /issm/trunk-jpl/src/m/classes/flowequation.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/flowequation.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/flowequation.js	(revision 26300)
@@ -22,7 +22,9 @@
 		fielddisplay(this,'isSSA','is the Shelfy-Stream Approximation (SSA) used ?');
 		fielddisplay(this,'isL1L2','is the L1L2 approximation used ?');
-		fielddisplay(this,'isMLHO','is the MLHO approximation used ?');
+		fielddisplay(this,'isMLHO','is the Mono-Layer Higher-Order approximation used?');
 		fielddisplay(this,'isHO','is the Higher-Order (HO) approximation used ?');
 		fielddisplay(this,'isFS','are the Full-FS (FS) equations used ?');
+		fielddisplay(this,'isNitscheBC','is weakly imposed condition used?');
+		fielddisplay(this,'FSNitscheGamma','Gamma value for the Nitsche term (default: 1e6)');
 		fielddisplay(this,'fe_SSA',"Finite Element for SSA  'P1', 'P1bubble' 'P1bubblecondensed' 'P2'");
 		fielddisplay(this,'fe_HO', "Finite Element for HO   'P1' 'P1bubble' 'P1bubblecondensed' 'P1xP2' 'P2xP1' 'P2'");
@@ -60,4 +62,6 @@
 			checkfield(md,'fieldname','flowequation.isHO','numel',[1],'values',[0, 1]);
 			checkfield(md,'fieldname','flowequation.isFS','numel',[1],'values',[0, 1]);
+			checkfield(md,'fieldname','flowequation.isNitscheBC','numel',[1],'values',[0, 1]);
+			checkfield(md,'fieldname','flowequation.FSNitscheGamma','numel',[1], '>=', 0);
 			checkfield(md,'fieldname','flowequation.fe_SSA','values',['P1','P1bubble','P1bubblecondensed','P2','P2bubble']);
 			checkfield(md,'fieldname','flowequation.fe_HO' ,'values',['P1','P1bubble','P1bubblecondensed','P1xP2','P2xP1','P2','P2bubble','P1xP3','P2xP4']);
@@ -80,19 +84,19 @@
 			}
 			else if (md.mesh.domaintype() =='2Dvertical'){
-				checkfield(md,'fieldname','flowequation.vertex_equation','size',[md.mesh.numberofvertices, 1],'values',[2,5,6]);
-				checkfield(md,'fieldname','flowequation.element_equation','size',[md.mesh.numberofelements, 1],'values',[2,5,6]);
+				checkfield(md,'fieldname','flowequation.vertex_equation','size',[md.mesh.numberofvertices, 1],'values',[2,4,5]);
+				checkfield(md,'fieldname','flowequation.element_equation','size',[md.mesh.numberofelements, 1],'values',[2,4,5]);
 			}
 			else if (md.mesh.domaintype() =='3D'){
-				checkfield(md,'fieldname','flowequation.vertex_equation','size',[md.mesh.numberofvertices, 1],'values',[0,1,2,3,4,5,6,7,8,9]);
-				checkfield(md,'fieldname','flowequation.element_equation','size',[md.mesh.numberofelements, 1],'values',[0,1,2,3,4,5,6,7,8,9]);
+				checkfield(md,'fieldname','flowequation.vertex_equation','size',[md.mesh.numberofvertices, 1],'values',[0,1,2,3,4,5,6,7,8]);
+				checkfield(md,'fieldname','flowequation.element_equation','size',[md.mesh.numberofelements, 1],'values',[0,1,2,3,4,5,6,7,8]);
 			}
 			else throw Error('Case not supported yet');
 			
 			if (!(this.isSIA | this.isSSA | this.isL1L2 | this.isMLHO | this.isHO | this.isFS)){
-				md = checkmessage(md,['no element types set for this model']);
+				checkmessage(md,['no element types set for this model']);
 			}
 			if(ArrayAnyEqual(ArrayIsMember('StressbalanceSIAAnalysis', analyses),1)){
 				if (ArrayAnyEqual(this.element_equation,1)){
-					if(this.vertex_equation & ArrayAnyBelowStrict(md.mask.ocean_levelset)){
+					if(this.vertex_equation & ArrayAnyBelowStrict(md.mask.groundedice_levelset)){
 						console.log(sprintf("\n !!! Warning: SIA's model is not consistent on ice shelves !!!\n"));
 					}
@@ -107,4 +111,6 @@
 			WriteData(fid,prefix,'object',this,'fieldname','isHO','format','Boolean');
 			WriteData(fid,prefix,'object',this,'fieldname','isFS','format','Boolean');
+			WriteData(fid,prefix,'object',this,'fieldname','isNitscheBC','format','Boolean');
+			WriteData(fid,prefix,'object',this,'fieldname','FSNitscheGamma','format','Double');
 			WriteData(fid,prefix,'object',this,'fieldname','fe_SSA','data',this.fe_SSA,'format','String');
 			WriteData(fid,prefix,'object',this,'fieldname','fe_HO','data',this.fe_HO,'format','String');
@@ -135,4 +141,6 @@
 	this.isHO                           = 0;
 	this.isFS                           = 0;
+	this.isNitscheBC                    = 0;
+	this.FSNitscheGamma                 = 0;
 	this.fe_SSA                         = '';
 	this.fe_HO                          = '';
Index: /issm/trunk-jpl/src/m/classes/fourierlove.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/fourierlove.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/fourierlove.js	(revision 26300)
@@ -11,14 +11,24 @@
 	this.setdefaultparameters = function() { // {{{
 		//we setup an elastic love number computation by default.
-		this.nfreq=1; 
-		this.frequencies=[0]; //Hz
-		this.sh_nmax=256; // .35 degree, 40 km at the equator.
-		this.sh_nmin=1; 
-		this.g0=10; // m/s^2; 
-		this.r0=6378*1e3; //m;
-		this.mu0=10**11; // Pa
-		this.allow_layer_deletion=1; 
-		this.love_kernels=0; 
-		this.forcing_type = 11; 
+			this.nfreq=1; 
+			this.frequencies=[0]; //Hz
+			this.sh_nmax=256; // .35 degree, 40 km at the equator.
+			this.sh_nmin=1;
+			// work on matlab script for computing g0 for given Earth's structure. 
+			this.g0=9.81; // m/s^2; 
+			this.r0=6371*1e3; //m;
+			this.mu0=10^11; // Pa
+			this.Gravitational_Constant=6.67259e-11; // m^3 kg^-1 s^-2
+			this.allow_layer_deletion=1;
+			this.underflow_tol=1e-16; //threshold of deep to surface love number ratio to trigger the deletion of layer 
+			this.integration_steps_per_layer=100;
+			this.istemporal=0;
+			this.n_temporal_iterations=8;
+			this.time=[0]; //s
+			this.love_kernels=0; 
+			this.forcing_type = 11; // surface loading
+			this.inner_core_boundary=1;
+			this.core_mantle_boundary=2;
+			this.complex_computation=0;
 	} // }}}
 	this.disp = function() { // {{{
@@ -28,26 +38,61 @@
 		fielddisplay(this,'sh_nmin','minimum spherical harmonic degree (default 1)');
 		fielddisplay(this,'g0','adimensioning constant for gravity (default 10) [m/s^2]');
-		fielddisplay(this,'r0','adimensioning constant for radius (default 6378*10^3) [m]');
+		fielddisplay(this,'r0','adimensioning constant for radius (default 6371*10^3) [m]');
 		fielddisplay(this,'mu0','adimensioning constant for stress (default 10^11) [Pa]');
+		fielddisplay(this,'Gravitational_Constant','Newtonian constant of gravitation (default 6.67259e-11 [m^3 kg^-1 s^-2])');
 		fielddisplay(this,'allow_layer_deletion','allow for migration of the integration boundary with increasing spherical harmonics degree (default 1)');
+		fielddisplay(this,'underflow_tol','threshold of deep to surface love number ratio to trigger the deletion of layers (default 2.2204460492503131E-016)');
+		fielddisplay(this,'integration_steps_per_layer','number of radial steps to propagate the yi system from the bottom to the top of each layer (default 100)');
+		fielddisplay(this,'istemporal','1 for time-dependent love numbers, 0 for frequency-dependent or elastic love numbers (default 0)', 'If 1: use fourierlove function build_frequencies_from_time to meet consistency');
+		fielddisplay(this,'n_temporal_iterations','max number of iterations in the inverse Laplace transform. Also the number of spectral samples per time step requested (default 8)');
+		fielddisplay(this,'time','time vector for deformation if istemporal (default 0) [s]');
 		fielddisplay(this,'love_kernels','compute love numbers at depth? (default 0)');
-		fielddisplay(this,'forcing_type',['integer indicating the nature and depth of the forcing for the Love number calculation (default 11) :','1:  Inner core boundary -- Volumic Potential','2:  Inner core boundary -- Pressure','3:  Inner core boundary -- Loading','4:  Inner core boundary -- Tangential traction','5:  Core mantle boundary -- Volumic Potential','6:  Core mantle boundary -- Pressure','7:  Core mantle boundary -- Loading','8:  Core mantle boundary -- Tangential traction','9:  Surface -- Volumic Potential','10: Surface -- Pressure','11: Surface -- Loading','12: Surface -- Tangential traction ']); 
+		fielddisplay(this,'forcing_type','integer indicating the nature and depth of the forcing for the Love number calculation (default 11) :','1:  Inner core boundary -- Volumic Potential','2:  Inner core boundary -- Pressure','3:  Inner core boundary -- Loading','4:  Inner core boundary -- Tangential traction','5:  Core mantle boundary -- Volumic Potential','6:  Core mantle boundary -- Pressure','7:  Core mantle boundary -- Loading','8:  Core mantle boundary -- Tangential traction','9:  Surface -- Volumic Potential','10: Surface -- Pressure','11: Surface -- Loading','12: Surface -- Tangential traction ');
+		fielddisplay(this,'inner_core_boundary','interface index in materials.radius locating forcing. Only used for forcing_type 1--4 (default 1)');
+		fielddisplay(this,'core_mantle_boundary','interface index in materials.radius locating forcing. Only used for forcing_type 5--8 (default 2)'); 
 
 	} // }}}
 	this.checkconsistency = function(md,solution,analyses) { // {{{
 
-		md = checkfield(md,'fieldname','love.nfreq','NaN',1,'Inf',1,'numel',1,'>',0);
-		md = checkfield(md,'fieldname','love.frequencies','NaN',1,'Inf',1,'numel',md.love.nfreq);
-		md = checkfield(md,'fieldname','love.sh_nmax','NaN',1,'Inf',1,'numel',1,'>',0);
-		md = checkfield(md,'fieldname','love.sh_nmin','NaN',1,'Inf',1,'numel',1,'>',0);
-		md = checkfield(md,'fieldname','love.g0','NaN',1,'Inf',1,'numel',1,'>',0);
-		md = checkfield(md,'fieldname','love.r0','NaN',1,'Inf',1,'numel',1,'>',0);
-		md = checkfield(md,'fieldname','love.mu0','NaN',1,'Inf',1,'numel',1,'>',0);
-		md = checkfield(md,'fieldname','love.allow_layer_deletion','values',[0, 1]);
-		md = checkfield(md,'fieldname','love.love_kernels','values',[0, 1]);
-		md = checkfield(md,'fieldname','love.forcing_type','NaN',1,'Inf',1,'numel',1,'>',0, '<=', 12);
-		if (md.love.sh_nmin<=1 && md.love.forcing_type==9) {
+		if (ArrayAnyEqual(ArrayIsMember('LoveAnalysis',analyses),1)) return; 
+
+		checkfield(md,'fieldname','love.nfreq','NaN',1,'Inf',1,'numel',1,'>',0);
+		checkfield(md,'fieldname','love.frequencies','NaN',1,'Inf',1,'numel',md.love.nfreq);
+		checkfield(md,'fieldname','love.sh_nmax','NaN',1,'Inf',1,'numel',1,'>',0);
+		checkfield(md,'fieldname','love.sh_nmin','NaN',1,'Inf',1,'numel',1,'>',0);
+		checkfield(md,'fieldname','love.g0','NaN',1,'Inf',1,'numel',1,'>',0);
+		checkfield(md,'fieldname','love.r0','NaN',1,'Inf',1,'numel',1,'>',0);
+		checkfield(md,'fieldname','love.mu0','NaN',1,'Inf',1,'numel',1,'>',0);
+		checkfield(md,'fieldname','love.Gravitational_Constant','NaN',1,'Inf',1,'numel',1,'>',0);
+		checkfield(md,'fieldname','love.allow_layer_deletion','values',[0, 1]);
+		checkfield(md,'fieldname','love.underflow_tol','NaN',1,'Inf',1,'numel',1,'>',0);
+		checkfield(md,'fieldname','love.integration_steps_per_layer','NaN',1,'Inf',1,'numel',1,'>',0);
+		checkfield(md,'fieldname','love.love_kernels','values',[0, 1]);
+		checkfield(md,'fieldname','love.forcing_type','NaN',1,'Inf',1,'numel',1,'>',0, '<=', 12);
+		checkfield(md,'fieldname','love.complex_computation','NaN',1,'Inf',1,'numel',1,'values',[0, 1]);
+
+		checkfield(md,'fieldname','love.istemporal','values',[0, 1]);
+
+		if (md.love.istemporal==1){
+			checkfield(md,'fieldname','love.n_temporal_iterations','NaN',1,'Inf',1,'numel',1,'>',0);
+			checkfield(md,'fieldname','love.time','NaN',1,'Inf',1,'numel',md.love.nfreq/2/md.love.n_temporal_iterations);
+		}
+		if (md.love.sh_nmin<=1 && md.love.forcing_type==9 || md.love.forcing_type==5 || md.love.forcing_type==1) {
 			throw 'Degree 1 not supported for Volumetric Potential forcing. Use sh_min>=2 for this kind of calculation.';
 		}
+
+		//need 'litho' material: 
+		console.log('md.fourierlove check consistency only paritally implemented for litho material');
+		/*
+		if ~isa(md.materials,'materials') | ~sum(strcmpi(md.materials.nature,'litho'))
+			error('Need a ''litho'' material to run a Fourier Love number analysis');
+		end
+
+		mat=find(strcmpi(md.materials.nature,'litho'));
+		if (md.love.forcing_type<=4) {
+			checkfield(md,'fieldname','love.inner_core_boundary','NaN',1,'Inf',1,'numel',1,'>',0, '<=', md.materials(mat).numlayers);
+		} else if (md.love.forcing_type<=8) {
+			checkfield(md,'fieldname','love.core_mantle_boundary','NaN',1,'Inf',1,'numel',1,'>',0, '<=', md.materials(mat).numlayers);
+		} */
 	} // }}}
 	this.marshall = function(md,prefix,fid) { // {{{
@@ -60,22 +105,39 @@
 		WriteData(fid,prefix,'object',this,'fieldname','r0','format','Double');
 		WriteData(fid,prefix,'object',this,'fieldname','mu0','format','Double');
+		WriteData(fid,prefix,'object',this,'fieldname','Gravitational_Constant','format','Double');
 		WriteData(fid,prefix,'object',this,'fieldname','allow_layer_deletion','format','Boolean');
+		WriteData(fid,prefix,'object',this,'fieldname','underflow_tol','format','Double');
+		WriteData(fid,prefix,'object',this,'fieldname','integration_steps_per_layer','format','Integer');
+		WriteData(fid,prefix,'object',this,'fieldname','istemporal','format','Boolean');
+		WriteData(fid,prefix,'object',this,'fieldname','n_temporal_iterations','format','Integer');
+		WriteData(fid,prefix,'object',this,'fieldname','complex_computation','format','Boolean');
+		//note: no need to marshall the time vector, we have frequencies
 		WriteData(fid,prefix,'object',this,'fieldname','love_kernels','format','Boolean');
 		WriteData(fid,prefix,'object',this,'fieldname','forcing_type','format','Integer');
+		WriteData(fid,prefix,'object',this,'fieldname','inner_core_boundary','format','Integer');
+		WriteData(fid,prefix,'object',this,'fieldname','core_mantle_boundary','format','Integer');
 
 	} // }}}
 	//properties 
 	// {{{
-		
-	this.nfreq                =  NaN;
-	this.frequencies          =  NaN;
-	this.sh_nmax              =  NaN;
-	this.sh_nmin              =  NaN;
-	this.g0                   =  NaN; 
-	this.r0                   =  NaN; 
-	this.mu0                  =  NaN;
-	this.allow_layer_deletion =  NaN;
-	this.love_kernels =  NaN;
-	this.forcing_type         =  NaN;
+	this.nfreq                		= NaN;
+	this.frequencies          		= NaN;
+	this.sh_nmax              		= NaN;
+	this.sh_nmin              		= NaN;
+	this.g0                   		= NaN; 
+	this.r0                   		= NaN; 
+	this.mu0                  		= NaN;
+	this.Gravitational_Constant 	= 0;
+	this.allow_layer_deletion 		= NaN;
+	this.underflow_tol              = 0;
+	this.integration_steps_per_layer= 0;
+	this.istemporal		   			= 0;
+	this.n_temporal_iterations	  	= 0;
+	this.time			            = 0;
+	this.love_kernels 				= NaN;
+	this.forcing_type         		= NaN;
+	this.inner_core_boundary	    = 0;
+	this.core_mantle_boundary	    = 0;
+	this.complex_computation        = 0;
 	
 	//set defaults
Index: /issm/trunk-jpl/src/m/classes/friction.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/friction.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/friction.js	(revision 26300)
@@ -14,5 +14,7 @@
 		fielddisplay(this,'p','p exponent');
 		fielddisplay(this,'q','q exponent');
+		fielddisplay(this,'effective_pressure','Effective Pressure for the forcing if not coupled [Pa]');
 		fielddisplay(this,'coupling','Coupling flag: 0 for default, 1 for forcing(provide md.friction.effective_pressure)  and 2 for coupled(not implemented yet)');
+		fielddisplay(this,'effective_pressure_limit','Neff do not allow to fall below a certain limit: effective_pressure_limit*rho_ice*g*thickness (default 0)');
 	} // }}}
 	this.extrude = function(md) {//{{{
@@ -42,12 +44,13 @@
 				return; 
 			}
-			md = checkfield(md,'fieldname','friction.coefficient','timeseries',1,'NaN',1,'Inf',1);
-			md = checkfield(md,'fieldname','friction.q','NaN',1,'Inf',1,'size',[md.mesh.numberofelements ,1]);
-			md = checkfield(md,'fieldname','friction.p','NaN',1,'Inf',1,'size',[md.mesh.numberofelements ,1]);
-			md = checkfield(md,'fieldname','friction.coupling','numel',[1],'values',[0, 1, 2]);
+			checkfield(md,'fieldname','friction.coefficient','timeseries',1,'NaN',1,'Inf',1);
+			checkfield(md,'fieldname','friction.q','NaN',1,'Inf',1,'size',[md.mesh.numberofelements ,1]);
+			checkfield(md,'fieldname','friction.p','NaN',1,'Inf',1,'size',[md.mesh.numberofelements ,1]);
+			checkfield(md,'fieldname','friction.coupling','numel',[1],'values',[0, 1, 2]);
+			checkfield(md,'fieldname','friction.effective_pressure_limit','numel',[1],'>=',0);
 			switch (this.coupling) {
 				case 0:
 				case 1:
-					md = checkfield(md,'fieldname','friction.effective_pressure','NaN',1,'Inf',1,'timeseries',1);
+					checkfield(md,'fieldname','friction.effective_pressure','NaN',1,'Inf',1,'timeseries',1);
 					break;
 				case 2:
@@ -63,9 +66,17 @@
 
 			WriteData(fid,prefix,'name','md.friction.law','data',1,'format','Integer');
-			WriteData(fid,prefix,'object',this,'fieldname','coefficient','format','DoubleMat','mattype',1,'timeserieslength',md.mesh.numberofvertices+1,'yts',md.constants.yts);
-			//WriteData(fid,prefix,'object',this,'fieldname','coefficient','format','DoubleMat','mattype',1);
+			let mattype,tsl;
+			if ((size(this.coefficient,1)==md.mesh.numberofvertices | size(this.coefficient,1)==md.mesh.numberofvertices+1)) {
+				mattype=1;
+				tsl = md.mesh.numberofvertices;
+			} else {
+				mattype=2;
+				tsl = md.mesh.numberofelements;
+			}
+			WriteData(fid,prefix,'object',this,'fieldname','coefficient','format','DoubleMat','mattype',mattype,'timeserieslength',tsl+1,'yts',md.constants.yts);
 			WriteData(fid,prefix,'object',this,'fieldname','p','format','DoubleMat','mattype',2);
 			WriteData(fid,prefix,'object',this,'fieldname','q','format','DoubleMat','mattype',2);
 			WriteData(fid,prefix,'class','friction','object',this,'fieldname','coupling','format','Integer');
+			WriteData(fid,prefix,'object',this,'class','friction','fieldname','effective_pressure_limit','format','Double');
 			switch (this.coupling) {
 				case 0:
@@ -79,6 +90,4 @@
 					console.error('not supported yet');		
 			}
-			
-
 		}//}}}
 		this.fix=function() { //{{{
@@ -86,9 +95,10 @@
 	//properties 
 	//{{{
-	this.coefficient = NaN;
-	this.p           = NaN;
-	this.q           = NaN;
-	this.coupling    = 0;
-	this.effective_pressure = NaN;
+	this.coefficient			  = NaN;
+	this.p						  = NaN;
+	this.q						  = NaN;
+	this.coupling				  = 0;
+	this.effective_pressure 	  = NaN;
+	this.effective_pressure_limit = 0;
 	this.setdefaultparameters();
 	//}}}
Index: /issm/trunk-jpl/src/m/classes/geometry.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/geometry.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/geometry.js	(revision 26300)
@@ -30,5 +30,5 @@
 
 			if ((solution=='TransientSolution' & md.trans.isgia) | (solution=='GiaSolution')){
-				checkfield(md,'fieldname','geometry.thickness','timeseries',1,'NaN',1,'Inf',1);
+				checkfield(md,'fieldname','geometry.thickness','timeseries',1,'NaN',1,'Inf',1,'>=',0);
 			}
 			else{
@@ -38,5 +38,5 @@
 				for(var i=0;i<md.mesh.numberofvertices;i++){
 					if (Math.abs(md.geometry.thickness[i]-md.geometry.surface[i]+md.geometry.base[i])>Math.pow(10,9)){
-						md = checkmessage(md,'equality thickness=surface-base violated');
+						checkmessage(md,'equality thickness=surface-base violated');
 						break;
 					}
@@ -48,6 +48,13 @@
 		} // }}}
 		this.marshall=function(md,prefix,fid) { //{{{
+			let length_thickness=size(this.thickness,1);
+			if (length_thickness==md.mesh.numberofvertices || length_thickness==md.mesh.numberofvertices+1) {
+				WriteData(fid,prefix,'object',this,'fieldname','thickness','format','DoubleMat','mattype',1,'timeserieslength',md.mesh.numberofvertices+1,'yts',md.constants.yts);
+			} else if (length_thickness==md.mesh.numberofelements || length_thickness==md.mesh.numberofelements+1) {
+				WriteData(fid,prefix,'object',this,'fieldname','thickness','format','DoubleMat','mattype',1,'timeserieslength',md.mesh.numberofelements+1,'yts',md.constants.yts);
+			} else {
+				error('geometry thickness time series should be a vertex or element time series');
+			}
 			WriteData(fid,prefix,'object',this,'fieldname','surface','format','DoubleMat','mattype',1);
-			WriteData(fid,prefix,'object',this,'fieldname','thickness','format','DoubleMat','mattype',1,'timeserieslength',md.mesh.numberofvertices+1,'yts',md.constants.yts);
 			WriteData(fid,prefix,'object',this,'fieldname','base','format','DoubleMat','mattype',1);
 			WriteData(fid,prefix,'object',this,'fieldname','bed','format','DoubleMat','mattype',1);
Index: /issm/trunk-jpl/src/m/classes/groundingline.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/groundingline.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/groundingline.js	(revision 26300)
@@ -8,7 +8,7 @@
 	this.setdefaultparameters = function(){// {{{
 		//Type of migration
-		this.migration='AggressiveMigration';
-		this.friction_interpolation='SubelementFriction1';
-		this.melt_interpolation='NoMeltOnPartiallyFloating';
+		this.migration				= 'SubelementMigration';
+		this.friction_interpolation	= 'SubelementFriction1';
+		this.melt_interpolation		= 'NoMeltOnPartiallyFloating';
 
 	}// }}}
@@ -29,15 +29,15 @@
 			checkfield(md,'fieldname','groundingline.melt_interpolation','values',['NoMeltOnPartiallyFloating', 'SubelementMelt1', 'SubelementMelt2', 'FullMeltOnPartiallyFloating']);
 
-			if(this.migration !='None' & md.trans.isgroundingline==1 & solution == 'TransientSolution'){
+			if (this.migration !='None'){
 				if (isNaN(md.geometry.bed)){
 					md.checkmessage('requesting grounding line migration, but bathymetry is absent!');
 				}
 				for (var i=0;i<md.mesh.numberofvertices;i++){
-					if(md.mask.ocean_levelset[i]>0){
+					if(md.mask.groundedice_levelset[i]>0){
 						md.checkmessage('base not equal to bed on grounded ice!');
 						break;
 					}
 					if(md.geometry.bed[i] - md.geometry.base[i] > Math.pow(10,-9)){
-						md = checkmessage(md,'bed superior to base on floating ice!');
+						checkmessage(md,'bed superior to base on floating ice!');
 						break;
 					}
Index: /issm/trunk-jpl/src/m/classes/initialization.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/initialization.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/initialization.js	(revision 26300)
@@ -17,9 +17,16 @@
 		fielddisplay(this,'pressure','pressure field [Pa]');
 		fielddisplay(this,'temperature','temperature [K]');
+		fielddisplay(this,'enthalpy','enthalpy [J]');
 		fielddisplay(this,'waterfraction','fraction of water in the ice');
 		fielddisplay(this,'sediment_head','sediment water head of subglacial system [m]');
 		fielddisplay(this,'epl_head','epl water head of subglacial system [m]');
 		fielddisplay(this,'epl_thickness','epl layer thickness [m]');
-		fielddisplay(this,'watercolumn','thickness of subglacial water [m]');
+		fielddisplay(this,'watercolumn','subglacial water sheet thickness (for Shreve and GlaDS) [m]');
+		fielddisplay(this,'hydraulic_potential','Hydraulic potential (for GlaDS) [Pa]');
+		fielddisplay(this,'channelarea','subglacial water channel area (for GlaDS) [m2]');
+		fielddisplay(this,'sample','Realization of a Gaussian random field');
+		fielddisplay(this,'bottompressure','Bottom pressures');
+		fielddisplay(this,'dsl','Dynamic sea level.');
+		fielddisplay(this,'str','Steric sea level.');
 
 	}// }}}
@@ -51,4 +58,11 @@
 				checkfield(md,'fieldname','initialization.vy','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices, 1]);
 			}
+			if(ArrayAnyEqual(ArrayIsMember('OceantransportAnalysis',analyses),1)){
+				if (strcmp(solution,'TransientSolution') && md.transient.isslc && md.transient.isoceantransport) {
+					checkfield(md,'fieldname','initialization.bottompressure','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices, 1]);
+					checkfield(md,'fieldname','initialization.dsl','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices, 1]);
+					checkfield(md,'fieldname','initialization.str','NaN',1,'Inf',1,'size',[1, 1]);
+				}
+			}
 			if(ArrayAnyEqual(ArrayIsMember('BalancethicknessSolution',analyses),1) & (solution=='BalancethicknessSolution')){
 				checkfield(md,'fieldname','initialization.vx','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices, 1]);
@@ -65,28 +79,45 @@
 				}
 			}
-			if(ArrayAnyEqual(ArrayIsMember('ThermalAnalysis',analyses),1)){
+			if ((ArrayAnyEqual(ArrayIsMember('ThermalAnalysis',analyses),1)) && !strcmp(solution,'TransientSolution') && md.transient.isthermal == 0){
 				checkfield(md,'fieldname','initialization.vx','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices, 1]);
 				checkfield(md,'fieldname','initialization.vy','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices, 1]);
-				if (md.mesh.dimension() == 3){
-					checkfield(md,'fieldname','initialization.vz','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices ,1]);
-				}
-				checkfield(md,'fieldname','initialization.pressure','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices ,1]);
-				checkfield(md,'fieldname','initialization.temperature','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices ,1]);
-			}
-			if( ArrayAnyEqual(ArrayIsMember('EnthalpyAnalysis',analyses),1) & md.thermal.isenthalpy){
-				checkfield(md,'fieldname','initialization.waterfraction','>=',0,'size',[md.mesh.numberofvertices, 1]);
-				checkfield(md,'fieldname','initialization.watercolumn'  ,'>=',0,'size',[md.mesh.numberofvertices, 1]);
-			}
-			if(ArrayAnyEqual(ArrayIsMember('HydrologyShreveAnalysis',analyses),1)){
+				if (md.mesh.dimension()==3) {
+					checkfield(md,'fieldname','initialization.vz','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices, 1]);
+				}
+				checkfield(md,'fieldname','initialization.pressure','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices, 1]);
+				checkfield(md,'fieldname','initialization.temperature','NaN',1,'Inf',1,'size','universal');
+			}
+			if (ArrayAnyEqual(ArrayIsMember('EnthalpyAnalysis',analyses),1) && md.thermal.isenthalpy){
+				checkfield(md,'fieldname','initialization.waterfraction','>=',0,'size','universal');
+				checkfield(md,'fieldname','initialization.watercolumn'  ,'>=',0,'size','universal');
+			}
+			if (ArrayAnyEqual(ArrayIsMember('HydrologyShreveAnalysis',analyses),1)){
 				if (md.hydrology.type() == 'hydrologyshreve'){
 					checkfield(md,'fieldname','initialization.watercolumn','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices ,1]);
 				}
 			}
-			if(ArrayAnyEqual(ArrayIsMember('HydrologyDCInefficientAnalysis',analyses),1)){
+			if (ArrayAnyEqual(ArrayIsMember('HydrologyTwsAnalysis',analyses),1)){
+				if (md.hydrology.type() == 'hydrologytws'){
+					checkfield(md,'fieldname','initialization.watercolumn','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices ,1]);
+				}
+			}
+			if (ArrayAnyEqual(ArrayIsMember('SealevelchangeAnalysis',analyses),1)){
+				if (strcmp(solution,'TransientSolution') && md.transient.isslc) {
+					checkfield(md,'fieldname','initialization.sealevel','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices, 1]);
+				}
+			}
+			if (ArrayAnyEqual(ArrayIsMember('HydrologyGlaDSAnalysis',analyses),1)){
+				if (md.hydrology.type() == 'hydrologyglads'){
+					checkfield(md,'fieldname','initialization.watercolumn','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices, 1]);
+					checkfield(md,'fieldname','initialization.hydraulic_potential','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices, 1]);
+					checkfield(md,'fieldname','initialization.channelarea','NaN',1,'Inf',1,'>=',0,'size',[md.mesh.numberofedges, 1]);
+				}
+			}
+			if (ArrayAnyEqual(ArrayIsMember('HydrologyDCInefficientAnalysis',analyses),1)){
 				if (md.hydrology.type() == 'hydrologydc'){
 					checkfield(md,'fieldname','initialization.sediment_head','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices, 1]);
 				}
 			}
-			if(ArrayAnyEqual(ArrayIsMember('HydrologyDCEfficientAnalysis',analyses),1)){
+			if (ArrayAnyEqual(ArrayIsMember('HydrologyDCEfficientAnalysis',analyses),1)){
 				if (md.hydrology.type() == 'hydrologydc'){
 					if (md.hydrology.isefficientlayer==1){
@@ -96,4 +127,9 @@
 				}
 			}
+			if (ArrayAnyEqual(ArrayIsMember('SamplingAnalysis',analyses),1) && !strcmp(solution,'TransientSolution')&& md.transient.issampling == 0){
+				if (!isNaN(md.initialization.sample)) {
+					checkfield(md,'fieldname','initialization.sample','NaN',1,'Inf',1,'size',[md.mesh.numberofvertices, 1]);
+				}
+			}
 		} //}}}
 		this.marshall=function(md,prefix,fid) { //{{{
@@ -105,4 +141,8 @@
 			WriteData(fid,prefix,'object',this,'fieldname','vz','format','DoubleMat','mattype',1,'scale',1./yts);
 			WriteData(fid,prefix,'object',this,'fieldname','pressure','format','DoubleMat','mattype',1);
+			WriteData(fid,prefix,'object',this,'fieldname','sealevel','format','DoubleMat','mattype',1,'timeserieslength',md.mesh.numberofvertices+1,'yts',md.constants.yts);
+			WriteData(fid,prefix,'object',this,'fieldname','bottompressure','format','DoubleMat','mattype',1);
+			WriteData(fid,prefix,'object',this,'fieldname','str','format','DoubleMat','mattype',1);
+			WriteData(fid,prefix,'object',this,'fieldname','dsl','format','DoubleMat','mattype',1);
 			WriteData(fid,prefix,'object',this,'fieldname','temperature','format','DoubleMat','mattype',1);
 			WriteData(fid,prefix,'object',this,'fieldname','waterfraction','format','DoubleMat','mattype',1);
@@ -111,13 +151,20 @@
 			WriteData(fid,prefix,'object',this,'fieldname','epl_thickness','format','DoubleMat','mattype',1);
 			WriteData(fid,prefix,'object',this,'fieldname','watercolumn','format','DoubleMat','mattype',1);
+			WriteData(fid,prefix,'object',this,'fieldname','channelarea','format','DoubleMat','mattype',1);
+			WriteData(fid,prefix,'object',this,'fieldname','hydraulic_potential','format','DoubleMat','mattype',1);
+			WriteData(fid,prefix,'object',this,'fieldname','sample','format','DoubleMat','mattype',1);
 
 			if (md.thermal.isenthalpy){
-				tpmp=NewArrayFill(md.mesh.numberofvertices,0);
-				for (var i=0;i<md.mesh.numberofvertices;i++) tpmp[i]= md.materials.meltingpoint - md.materials.beta*md.initialization.pressure[i];
-				enthalpy=NewArrayFill(md.mesh.numberofvertices,0); 
-				for (var i=0;i<md.mesh.numberofvertices;i++)enthalpy[i] = md.materials.heatcapacity*(md.initialization.temperature[i]-md.constants.referencetemperature);
-				
-				for (var i=0;i<md.mesh.numberofvertices;i++)if(md.initialization.temperature[i]>=tpmp[i]){
-					enthalpy[i] = md.materials.heatcapacity*(tpmp[i] - md.constants.referencetemperature) + md.materials.latentheat*md.initialization.waterfraction[i];
+				let enthalpy;
+				if (isNaN(self.enthalpy) && this.enthalpy.length <= 1) {
+					tpmp=NewArrayFill(md.mesh.numberofvertices,0);
+					for (var i=0;i<md.mesh.numberofvertices;i++) tpmp[i]= md.materials.meltingpoint - md.materials.beta*md.initialization.pressure[i];
+					enthalpy=NewArrayFill(md.mesh.numberofvertices,0); 
+					for (var i=0;i<md.mesh.numberofvertices;i++)enthalpy[i] = md.materials.heatcapacity*(md.initialization.temperature[i]-md.constants.referencetemperature);
+					for (var i=0;i<md.mesh.numberofvertices;i++)if(md.initialization.temperature[i]>=tpmp[i]){
+						enthalpy[i] = md.materials.heatcapacity*(tpmp[i] - md.constants.referencetemperature) + md.materials.latentheat*md.initialization.waterfraction[i];
+					}
+				} else {
+					enthalpy = this.enthalpy;
 				}
 				WriteData(fid,prefix,'data',enthalpy,'format','DoubleMat','mattype',1,'name','md.initialization.enthalpy');
@@ -136,15 +183,23 @@
 	//properties 
 	// {{{
-	this.vx            = NaN;
-	this.vy            = NaN;
-	this.vz            = NaN;
-	this.vel           = NaN;
-	this.pressure      = NaN;
-	this.temperature   = NaN;
-	this.waterfraction = NaN;
-	this.sediment_head = NaN;
-	this.epl_head      = NaN;
-	this.epl_thickness = NaN;
-	this.watercolumn   = NaN;
+	this.vx                  = NaN;
+	this.vy                  = NaN;
+	this.vz                  = NaN;
+	this.vel                 = NaN;
+	this.pressure            = NaN;
+	this.temperature         = NaN;
+	this.enthalpy            = NaN;
+	this.waterfraction       = NaN;
+	this.sediment_head       = NaN;
+	this.epl_head            = NaN;
+	this.epl_thickness       = NaN;
+	this.watercolumn         = NaN;
+	this.hydraulic_potential = NaN;
+	this.channelarea         = NaN;
+	this.sealevel            = NaN;
+	this.bottompressure      = NaN;
+	this.dsl                 = NaN;
+	this.str                 = NaN;
+	this.sample              = NaN;
 	this.setdefaultparameters();
 
Index: /issm/trunk-jpl/src/m/classes/inversion.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/inversion.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/inversion.js	(revision 26300)
@@ -140,4 +140,5 @@
 			WriteData(fid,prefix,'object',this,'fieldname','iscontrol','format','Boolean');
 			WriteData(fid,prefix,'object',this,'fieldname','incomplete_adjoint','format','Boolean');
+			WriteData(fid,prefix,'object',this,'fieldname','vel_obs','format','DoubleMat','mattype',1,'scale',1/yts);
 			if (!this.iscontrol) return;
 			WriteData(fid,prefix,'object',this,'fieldname','nsteps','format','Integer');
Index: /issm/trunk-jpl/src/m/classes/issmsettings.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/issmsettings.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/issmsettings.js	(revision 26300)
@@ -5,90 +5,95 @@
 
 function issmsettings (){
-	//methods
-	this.setdefaultparameters = function(){// {{{
-		//are we short in memory ? (0 faster but requires more memory)
-		this.lowmem=0;
+    //methods
+    this.setdefaultparameters = function(){// {{{
+        //are we short in memory ? (0 faster but requires more memory)
+        this.lowmem=0;
 
-		//i/o:
-		this.io_gather=1;
+        //i/o:
+        this.io_gather=1;
 
-		//results frequency by default every step
-		this.output_frequency=1;
+        //results frequency by default every step
+        this.output_frequency=1;
 
-		//checkpoints frequency, by default never: 
-		this.checkpoint_frequency=0;
+        //coupling frequency of the stress balance solver by default every step
+        this.sb_coupling_frequency=1;
 
-		//this option can be activated to load automatically the results
-		//onto the model after a parallel run by waiting for the lock file
-		//N minutes that is generated once the solution has converged
-		//0 to deactivate
-		this.waitonlock=Infinity;
+        //checkpoints frequency, by default never:
+        this.checkpoint_frequency=0;
 
-		//upload options: 
-		upload_port         = 0;
-		
-		//throw an error if solver residue exceeds this value
-		this.solver_residue_threshold=1e-6;
+        //this option can be activated to load automatically the results
+        //onto the model after a parallel run by waiting for the lock file
+        //N minutes that is generated once the solution has converged
+        //0 to deactivate
+        this.waitonlock=Infinity;
 
-	}// }}}
-	this.disp= function(){// {{{
-		console.log(sprintf('   issmsettings class echo:'));
-		
-		fielddisplay(this,'results_on_nodes','results are output for all the nodes of each element');
-		fielddisplay(this,'io_gather','I/O gathering strategy for result outputs (default 1)');
-		fielddisplay(this,'lowmem','is the memory limited ? (0 or 1)');
-		fielddisplay(this,'output_frequency','frequency at which results are saved in all solutions with multiple time_steps');
-		fielddisplay(this,'checkpoint_frequency','frequency at which the runs are being recorded, allowing for a restart');
-		fielddisplay(this,'waitonlock','maximum number of minutes to wait for batch results (NaN to deactivate)');
-		fielddisplay(this,'upload_server','server hostname where model should be uploaded');
-		fielddisplay(this,'upload_path','path on server where model should be uploaded');
-		fielddisplay(this,'upload_login','server login');
-		fielddisplay(this,'upload_port','port login (default is 0)');
-		fielddisplay(this,'upload_filename','unique id generated when uploading the file to server');
-		fielddisplay(this,'solver_residue_threshold','throw an error if solver residue exceeds this value');
+        //upload options:
+        this.upload_port         = 0;
+
+        //throw an error if solver residue exceeds this value
+        this.solver_residue_threshold=1e-6;
+
+    }// }}}
+    this.disp= function(){// {{{
+        console.log(sprintf('   issmsettings class echo:'));
+
+        fielddisplay(this,'results_on_nodes','results are output for all the nodes of each element');
+        fielddisplay(this,'io_gather','I/O gathering strategy for result outputs (default 1)');
+        fielddisplay(this,'lowmem','is the memory limited ? (0 or 1)');
+        fielddisplay(this,'output_frequency','frequency at which results are saved in all solutions with multiple time_steps');
+        fielddisplay(this,'checkpoint_frequency','frequency at which the runs are being recorded, allowing for a restart');
+        fielddisplay(this,'waitonlock','maximum number of minutes to wait for batch results (NaN to deactivate)');
+        fielddisplay(this,'upload_server','server hostname where model should be uploaded');
+        fielddisplay(this,'upload_path','path on server where model should be uploaded');
+        fielddisplay(this,'upload_login','server login');
+        fielddisplay(this,'upload_port','port login (default is 0)');
+        fielddisplay(this,'upload_filename','unique id generated when uploading the file to server');
+        fielddisplay(this,'solver_residue_threshold','throw an error if solver residue exceeds this value');
 
 
-	}// }}}
-	this.classname= function(){// {{{
-		return "issmsettings";
+    }// }}}
+    this.classname= function(){// {{{
+        return "issmsettings";
 
-	}// }}}
-		this.checkconsistency = function(md,solution,analyses) { // {{{
+    }// }}}
+	this.checkconsistency = function(md,solution,analyses) { // {{{
 
-			checkfield(md,'fieldname','settings.results_on_nodes','numel',[1],'values',[0, 1]);
-			checkfield(md,'fieldname','settings.io_gather','numel',[1],'values',[0, 1]);
-			checkfield(md,'fieldname','settings.lowmem','numel',[1],'values',[0, 1]);
-			checkfield(md,'fieldname','settings.output_frequency','numel',[1],'>=',1);
-			checkfield(md,'fieldname','settings.checkpoint_frequency','numel',[1],'>=',0);
-			checkfield(md,'fieldname','settings.waitonlock','numel',[1]);
-			checkfield(md,'fieldname','settings.solver_residue_threshold','numel',[1],'>',0);
-		} // }}}
-		this.marshall=function(md,prefix,fid) { //{{{
-			WriteData(fid,prefix,'object',this,'class','settings','fieldname','results_on_nodes','format','Boolean');
-			WriteData(fid,prefix,'object',this,'class','settings','fieldname','io_gather','format','Boolean');
-			WriteData(fid,prefix,'object',this,'class','settings','fieldname','lowmem','format','Boolean');
-			WriteData(fid,prefix,'object',this,'class','settings','fieldname','output_frequency','format','Integer');
-			WriteData(fid,prefix,'object',this,'class','settings','fieldname','checkpoint_frequency','format','Integer');
-			WriteData(fid,prefix,'object',this,'class','settings','fieldname','solver_residue_threshold','format','Double');
-			if (this.waitonlock>0) WriteData(fid,prefix,'name','md.settings.waitonlock','data',true,'format','Boolean');
-			else WriteData(fid,prefix,'name','md.settings.waitonlock','data',false,'format','Boolean');
-		}//}}}
-		this.fix=function() { //{{{
-		}//}}}
-	//properties 
-	// {{{
-	this.results_on_nodes    = 0;
-	this.io_gather           = 0;
-	this.lowmem              = 0;
-	this.output_frequency    = 0;
-	this.checkpoint_frequency   = 0;
-	this.waitonlock          = 0;
-	this.upload_server       = '';
-	this.upload_path         = '';
-	this.upload_login        = '';
-	this.upload_port         = 0;
-	this.upload_filename     = '';
-	this.solver_residue_threshold = 0;
-	this.setdefaultparameters();
-	//}}}
+		checkfield(md,'fieldname','settings.results_on_nodes','stringrow',1);
+		checkfield(md,'fieldname','settings.io_gather','numel',[1],'values',[0, 1]);
+		checkfield(md,'fieldname','settings.lowmem','numel',[1],'values',[0, 1]);
+		checkfield(md,'fieldname','settings.output_frequency','numel',[1],'>=',1);
+		checkfield(md,'fieldname','settings.sb_coupling_frequency','numel',[1],'>=',1);
+		checkfield(md,'fieldname','settings.checkpoint_frequency','numel',[1],'>=',0);
+		checkfield(md,'fieldname','settings.waitonlock','numel',[1]);
+		checkfield(md,'fieldname','settings.solver_residue_threshold','numel',[1],'>',0);
+	} // }}}
+	this.marshall=function(md,prefix,fid) { //{{{
+		WriteData(fid,prefix,'object',this,'class','settings','fieldname','results_on_nodes','format','StringArray');
+		WriteData(fid,prefix,'object',this,'class','settings','fieldname','io_gather','format','Boolean');
+		WriteData(fid,prefix,'object',this,'class','settings','fieldname','lowmem','format','Boolean');
+		WriteData(fid,prefix,'object',this,'class','settings','fieldname','output_frequency','format','Integer');
+		WriteData(fid,prefix,'object',this,'class','settings','fieldname','sb_coupling_frequency','format','Integer');
+		WriteData(fid,prefix,'object',this,'class','settings','fieldname','checkpoint_frequency','format','Integer');
+		WriteData(fid,prefix,'object',this,'class','settings','fieldname','waitonlock','data',this.waitonlock>0,'format','Boolean');
+		WriteData(fid,prefix,'object',this,'class','settings','fieldname','solver_residue_threshold','format','Double');
+	}//}}}
+	this.fix=function() { //{{{
+	}//}}}
+	//properties
+    // {{{
+    this.results_on_nodes        = '';
+    this.io_gather               = 0;
+    this.lowmem                  = 0;
+    this.output_frequency        = 0;
+    this.sb_coupling_frequency   = 0;
+    this.checkpoint_frequency    = 0;
+    this.waitonlock              = 0;
+    this.upload_server           = '';
+    this.upload_path             = '';
+    this.upload_login            = '';
+    this.upload_port             = 0;
+    this.upload_filename         = '';
+    this.solver_residue_threshold = 0;
+    this.setdefaultparameters();
+    //}}}
 }
Index: /issm/trunk-jpl/src/m/classes/levelset.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/levelset.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/levelset.js	(revision 26300)
@@ -11,4 +11,9 @@
 		this.stabilization		= 2;
 		this.reinit_frequency	= 5;
+		this.kill_icebergs      = 1;
+		this.migration_max      = 1e12; //No need for general cases, unless specified
+
+		//Linear elements by default
+		this.fe='P1';
 	
 	}// }}}
@@ -19,10 +24,13 @@
 		fielddisplay(this,'spclevelset','Levelset constraints (NaN means no constraint)');
 		fielddisplay(this,'reinit_frequency','Amount of time steps after which the levelset function in re-initialized (NaN: no re-initialization).');
+		fielddisplay(this,'kill_icebergs','remove floating icebergs to prevent rigid body motions (1: true, 0: false)');
+		fielddisplay(this,'migration_max','maximum allowed migration rate (m/a)');
+		fielddisplay(this,'fe','Finite Element type: "P1" (default), or "P2"');
 
 	}// }}}
-	this.extrude = function(md) {//{{{
-		this.spclevelset=project3d(md,'vector',this.spclevelset,'type','node');
-		return this;
-	}//}}}
+    this.extrude = function(md) {//{{{
+        this.spclevelset=project3d(md,'vector',this.spclevelset,'type','node');
+        return this;
+    }//}}}
 	this.classname= function(){// {{{
 		return "levelset";
@@ -34,20 +42,31 @@
 		checkfield(md,'fieldname','levelset.spclevelset','Inf',1,'timeseries',1);
 		checkfield(md,'fieldname','levelset.stabilization','values',[0,1,2]);
+		checkfield(md,'fieldname','levelset.kill_icebergs','numel',1,'values',[0, 1]);
+		checkfield(md,'fieldname','levelset.migration_max','numel',1,'NaN',1,'Inf',1,'>',0);
+		checkfield(md,'fieldname','levelset.fe','values',['P1','P2']);
 	} //}}}
 	this.marshall=function(md,prefix,fid) { //{{{
+
+		let yts=md.constants.yts;
+
 		WriteData(fid,prefix,'object',this,'fieldname','stabilization','format','Integer');
 		WriteData(fid,prefix,'object',this,'fieldname','spclevelset','format','DoubleMat','mattype',1,'timeserieslength',md.mesh.numberofvertices+1,'yts',md.constants.yts);
 		WriteData(fid,prefix,'object',this,'fieldname','reinit_frequency','format','Integer');
-
+		WriteData(fid,prefix,'object',this,'fieldname','kill_icebergs','format','Boolean');
+		WriteData(fid,prefix,'object',this,'fieldname','migration_max','format','Double','scale',1/yts);
+		WriteData(fid,prefix,'object',this,'fieldname','fe','format','String');
 	}//}}}
-	this.fix=function() { //{{{
-		this.spclevelset=NullFix(this.spclevelset,NaN);
-	}//}}}
+		this.fix=function() { //{{{
+			this.spclevelset=NullFix(this.spclevelset,NaN);
+		}//}}}
 	//properties 
 	// {{{
 
 	this.stabilization		= 0;
-	this.spclevelset			= NaN;
+	this.spclevelset		= NaN;
 	this.reinit_frequency	= NaN;
+	this.kill_icebergs     	= 0;
+	this.migration_max      = 0.;
+	this.fe              	= 'P1';
 
 	this.setdefaultparameters();
Index: /issm/trunk-jpl/src/m/classes/mask.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/mask.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/mask.js	(revision 26300)
@@ -7,6 +7,6 @@
 	//properties 
 	// {{{
-		this.ocean_levelset                           = NaN;
-		this.ice_levelset                           = NaN;
+		this.ocean_levelset	= NaN;
+		this.ice_levelset	= NaN;
 		//}}}
 	//methods 
@@ -14,8 +14,8 @@
 		} // }}}
 		this.disp = function () { //{{{
-			console.log(sprintf("   mask:")); 
+			console.log(sprintf("   masks:")); 
 
-			fielddisplay(this,"ocean_levelset","presence of ocean if < 0, coastline/grounding line if = 0, no ocean if > 0");
-			fielddisplay(this,"ice_levelset","presence of ice if < 0, icefront position if = 0, no ice if > 0");
+			fielddisplay(this,'ocean_levelset','presence of ocean if < 0, coastline/grounding line if = 0, no ocean if > 0');
+			fielddisplay(this,'ice_levelset','presence of ice if < 0, icefront position if = 0, no ice if > 0');
 		} //}}}
 		this.extrude = function(md) {//{{{
@@ -28,4 +28,5 @@
 		} //}}}
 		this.checkconsistency = function(md,solution,analyses){ //{{{
+			if (solution=='LoveSolution') return;
 
 			checkfield(md,'fieldname','mask.ocean_levelset','size',[md.mesh.numberofvertices, 1]);
@@ -46,4 +47,3 @@
 		this.fix=function() { //{{{
 		}//}}}
-
 }
Index: /issm/trunk-jpl/src/m/classes/mesh3dprisms.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/mesh3dprisms.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/mesh3dprisms.js	(revision 26300)
@@ -23,5 +23,5 @@
 
             if(ArrayAnyEqual(ArrayIsMember(NewArrayFillIncrement(md.mesh.numberofvertices,1,1),ArraySort(ArrayUnique(MatrixToList(md.mesh.elements)))),0)){
-				//md = checkmessage(md,'orphan nodes have been found. Check the mesh outline'); @TODO
+				//checkmessage(md,'orphan nodes have been found. Check the mesh outline'); @TODO
                 md.checkmessage('orphan nodes have been found. Check the mesh outline');
 			}
@@ -127,5 +127,6 @@
         } //}}}
 
-        //properties (SetAccess=public) 
+	//properties 
+	// {{{
         this.x                           = NaN;
         this.y                           = NaN;
@@ -139,5 +140,5 @@
         this.long                        = NaN;
         this.epsg                        = 0;
-		  this.scale_factor                = NaN;
+		this.scale_factor                = NaN;
 
         this.vertexonbase                = NaN;
@@ -161,3 +162,4 @@
         this.extractedvertices           = NaN;
         this.extractedelements           = NaN;
+	//}}}
 }
Index: /issm/trunk-jpl/src/m/classes/qmu.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/qmu.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/qmu.js	(revision 26300)
@@ -12,5 +12,7 @@
 		console.log(sprintf('   qmu parameters:'));
 
-		/*fielddisplay(this,'isdakota','is qmu analysis activated?');
+		fielddisplay(this,'isdakota','is qmu analysis activated?');
+		fielddisplay(this,'output','are we outputting ISSM results, default is 0');
+		/*
 		for (var i=0;i<this.variables.length;i++){
 			console.log(sprintf('         variables%s:  (arrays of each variable class)',...
@@ -106,4 +108,5 @@
 		this.marshall=function(md,prefix,fid) { //{{{
 			WriteData(fid,prefix,'object',this,'fieldname','isdakota','format','Boolean');
+			WriteData(fid,prefix,'object',this,'fieldname','output','format','Boolean');
 			if (!this.isdakota){
 				WriteData(fid,prefix,'data',0,'name','md.qmu.mass_flux_segments_present','format','Boolean');
@@ -129,4 +132,5 @@
 
 	this.isdakota                    = 0;
+	this.output                      = 0;
 	this.variables                   = []
 	this.responses                   = [];
Index: /issm/trunk-jpl/src/m/classes/slr.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/slr.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/slr.js	(revision 26300)
@@ -114,5 +114,6 @@
 		} //}}}
 		this.marshall=function(md,prefix,fid) { //{{{
-
+			console.log('WARNING: NOT MARHSALLING SLR FOR NOW.');
+			return;
 			WriteData(fid,prefix,'object',this,'fieldname','deltathickness','format','DoubleMat','mattype',2);
 			WriteData(fid,prefix,'object',this,'fieldname','sealevel','mattype',1,'format','DoubleMat','timeserieslength',md.mesh.numberofvertices+1,'yts',md.constants.yts);
Index: /issm/trunk-jpl/src/m/classes/stressbalance.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/stressbalance.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/stressbalance.js	(revision 26300)
@@ -158,11 +158,9 @@
 		WriteData(fid,prefix,'object',this,'class','stressbalance','fieldname','referential','format','DoubleMat','mattype',1);
 
-		var lx=NewArrayFill(this.loadingforce.length,0); for(var i=0;i<lx.length;i++)lx[i]=this.loadingforce[i][0];
-		var ly=NewArrayFill(this.loadingforce.length,0); for(var i=0;i<lx.length;i++)ly[i]=this.loadingforce[i][1];
-		var lz=NewArrayFill(this.loadingforce.length,0); for(var i=0;i<lx.length;i++)lz[i]=this.loadingforce[i][2];
-
-		WriteData(fid,prefix,'data',lx,'format','DoubleMat','mattype',1,'name','md.stressbalance.loadingforcex');
-		WriteData(fid,prefix,'data',ly,'format','DoubleMat','mattype',1,'name','md.stressbalance.loadingforcey');
-		WriteData(fid,prefix,'data',lz,'format','DoubleMat','mattype',1,'name','md.stressbalance.loadingforcez');
+		if (size(this.loadingforce,1)==3) {
+			WriteData(fid,prefix,'data',ArrayCol(this.loadingforce,0),'format','DoubleMat','mattype',1,'name','md.stressbalance.loadingforcex');
+			WriteData(fid,prefix,'data',ArrayCol(this.loadingforce,1),'format','DoubleMat','mattype',1,'name','md.stressbalance.loadingforcey');
+			WriteData(fid,prefix,'data',ArrayCol(this.loadingforce,2),'format','DoubleMat','mattype',1,'name','md.stressbalance.loadingforcez');
+		}
 
 		//process requested outputs
Index: /issm/trunk-jpl/src/m/classes/thermal.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/thermal.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/thermal.js	(revision 26300)
@@ -64,9 +64,9 @@
 		return "thermal";
 	}// }}}
-	this.extrude = function(md) {//{{{
-		this.spctemperature=project3d(md,'vector',this.spctemperature,'type','node','layer',md.mesh.numberoflayers,'padding',NaN);
-		if (md.initialization.temperature.length===md.mesh.numberofvertices) {
-			this.spctemperature = NewArrayFill(md.mesh.numberofvertices, NaN);
-			var pos=ArrayFindNot(md.mesh.vertexonsurface, 0);
+    this.extrude = function(md) {//{{{
+        this.spctemperature=project3d(md,'vector',this.spctemperature,'type','node','layer',md.mesh.numberoflayers,'padding',NaN);
+        if (md.initialization.temperature.length===md.mesh.numberofvertices) {
+            this.spctemperature = NewArrayFill(md.mesh.numberofvertices, NaN);
+            var pos=ArrayFindNot(md.mesh.vertexonsurface, 0);
 			// impose observed temperature on surface
 			for (var i=0,posIndex=0,count=0;i<md.initialization.temperature.length;i++){
@@ -89,8 +89,8 @@
 //				}
 //			}
-		}
+        }
 
-		return this;
-	}//}}}
+        return this;
+    }//}}}
 	this.checkconsistency = function(md,solution,analyses){ // {{{
 
@@ -104,5 +104,5 @@
 			checkfield(md,'fieldname','thermal.isdrainicecolumn','numel',[1],'values',[0, 1]);
 			checkfield(md,'fieldname','thermal.watercolumn_upperlimit','>=',0);
-
+			
 			for(var i=0;i<md.mesh.numberofvertices;i++){
 				for(var j=0;j<md.thermal.spctemperature[0].length;j++){
@@ -123,6 +123,6 @@
 					md.checkmessage('for a steadystate computation, thermal.reltol (relative convergence criterion) must be defined!');
 				}
+				checkfield(md,'fieldname','thermal.reltol','>',0.,'message','reltol must be larger than zero');
 			}
-			checkfield(md,'fieldname','thermal.reltol','>',0.,'message','reltol must be larger than zero');
 		}
 		checkfield(md,'fieldname','thermal.requested_outputs','stringrow',1);
@@ -163,18 +163,17 @@
 	//properties 
 	// {{{
-
-	this.spctemperature    = NaN;
-	this.penalty_threshold = 0;
-	this.stabilization     = 0;
-	this.reltol	   = 0;
-	this.maxiter           = 0;
-	this.penalty_lock      = 0;
-	this.penalty_factor    = 0;
-	this.isenthalpy        = 0;
-	this.isdynamicbasalspc = 0;
-	this.isdrainicecolumn  = 0;
-	this.watercolumn_upperlimit=0;
-	this.fe                = 'P1';
-	this.requested_outputs = [];
+	this.spctemperature    		= NaN;
+	this.penalty_threshold 		= 0;
+	this.stabilization     		= 0;
+	this.reltol			   		= 0;
+	this.maxiter           		= 0;
+	this.penalty_lock      		= 0;
+	this.penalty_factor    		= 0;
+	this.isenthalpy        		= 0;
+	this.isdynamicbasalspc 		= 0;
+	this.isdrainicecolumn  		= 0;
+	this.watercolumn_upperlimit = 0;
+	this.fe                		= 'P1';
+	this.requested_outputs 		= [];
 
 	this.setdefaultparameters();
Index: /issm/trunk-jpl/src/m/classes/timestepping.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/timestepping.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/timestepping.js	(revision 26300)
@@ -6,5 +6,5 @@
 function timestepping (){
 	//methods
-	this.setdefaultparameters = function(){// {{{
+	this.setdefaultparameters = function(){ //{{{
 		//time between 2 time steps
 		this.time_step=1./2.;
@@ -14,8 +14,8 @@
 
 		//should we interpolate forcings between timesteps?
-		this.interp_forcings=1;
+		this.interp_forcing=1;
 		this.cycle_forcing=0;
-	}// }}}
-	this.disp= function(){// {{{
+	} //}}}
+	this.disp= function(){ //{{{
 
 		var unit;
@@ -25,13 +25,13 @@
 		fielddisplay(this,'final_time','final time to stop the simulation ['+ unit + ']');
 		fielddisplay(this,'time_step','length of time steps [' +unit+ ']');
-		fielddisplay(this,'interp_forcings','interpolate in time between requested forcing values ? (0 or 1)');
+		fielddisplay(this,'interp_forcing','interpolate in time between requested forcing values ? (0 or 1)');
 		fielddisplay(this,'cycle_forcing','cycle through forcing ? (0 or 1)');
 		fielddisplay(this,'coupling_time','length of coupling time steps with ocean model [' +unit+ ']');
 
-	}// }}}
-	this.classname= function(){// {{{
+	} //}}}
+	this.classname= function(){ //{{{
 		return "timestepping";
 
-	}// }}}
+	} //}}}
 	this.checkconsistency = function(md,solution,analyses) { //{{{
 
@@ -39,9 +39,13 @@
 		checkfield(md,'fieldname','timestepping.final_time','numel',[1],'NaN',1,'Inf',1);
 		checkfield(md,'fieldname','timestepping.time_step','numel',[1],'>=',0,'NaN',1,'Inf',1);
-		checkfield(md,'fieldname','timestepping.interp_forcings','numel',[1],'values',[0,1]);
+		checkfield(md,'fieldname','timestepping.interp_forcing','numel',[1],'values',[0,1]);
 		checkfield(md,'fieldname','timestepping.cycle_forcing','numel',[1],'values',[0,1]);
 		checkfield(md,'fieldname','timestepping.coupling_time','numel',[1],'>=',0,'NaN',1,'Inf',1);
 		if (this.final_time-this.start_time<0){
 			md.checkmessage('timestepping.final_time should be larger than timestepping.start_time');
+		}
+		if (solution=='TransientSolution'){
+			checkfield(md,'fieldname','timestepping.time_step','numel',[1],'>',0,'NaN',1,'Inf',1);
+			checkfield(md,'fieldname','timestepping.time_step','numel',[1],'>=',0,'NaN',1,'Inf',1);
 		}
 	} // }}}
@@ -55,5 +59,5 @@
 		WriteData(fid,prefix,'object',this,'fieldname','final_time','format','Double','scale',scale);
 		WriteData(fid,prefix,'object',this,'fieldname','time_step','format','Double','scale',scale);
-		WriteData(fid,prefix,'object',this,'fieldname','interp_forcings','format','Boolean');
+		WriteData(fid,prefix,'object',this,'fieldname','interp_forcing','format','Boolean');
 		WriteData(fid,prefix,'object',this,'fieldname','cycle_forcing','format','Boolean');
 		WriteData(fid,prefix,'object',this,'fieldname','coupling_time','format','Double','scale',scale);
@@ -67,6 +71,6 @@
 	this.final_time      = 0.;
 	this.time_step       = 0.;
-	this.interp_forcings = 1;
-	this.cycle_forcing   = 0;
+	this.interp_forcing  = 1;
+	this.cycle_forcing   = 1;
 	this.coupling_time   = 0.;
 
Index: /issm/trunk-jpl/src/m/classes/trans.js
===================================================================
--- /issm/trunk-jpl/src/m/classes/trans.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/classes/trans.js	(revision 26300)
@@ -79,15 +79,15 @@
 			WriteData(fid,prefix,'object',this,'fieldname','issmb','format','Boolean');
 			WriteData(fid,prefix,'object',this,'fieldname','ismasstransport','format','Boolean');
+			WriteData(fid,prefix,'object',this,'fieldname','isoceantransport','format','Boolean');
 			WriteData(fid,prefix,'object',this,'fieldname','isstressbalance','format','Boolean');
 			WriteData(fid,prefix,'object',this,'fieldname','isthermal','format','Boolean');
 			WriteData(fid,prefix,'object',this,'fieldname','isgroundingline','format','Boolean');
-			WriteData(fid,prefix,'object',this,'fieldname','isgia','format','Boolean');
 			WriteData(fid,prefix,'object',this,'fieldname','isesa','format','Boolean');
 			WriteData(fid,prefix,'object',this,'fieldname','isdamageevolution','format','Boolean');
 			WriteData(fid,prefix,'object',this,'fieldname','ishydrology','format','Boolean');
 			WriteData(fid,prefix,'object',this,'fieldname','ismovingfront','format','Boolean');
-			WriteData(fid,prefix,'object',this,'fieldname','isslr','format','Boolean');
+			WriteData(fid,prefix,'object',this,'fieldname','issampling','format','Boolean');
+			WriteData(fid,prefix,'object',this,'fieldname','isslc','format','Boolean');
 			WriteData(fid,prefix,'object',this,'fieldname','isoceancoupling','format','Boolean');
-			WriteData(fid,prefix,'object',this,'fieldname','iscoupler','format','Boolean');
 			WriteData(fid,prefix,'object',this,'fieldname','amr_frequency','format','Integer');
 
Index: /issm/trunk-jpl/src/m/plot/applyoptions.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/applyoptions.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/plot/applyoptions.js	(revision 26300)
@@ -1,3 +1,5 @@
-function applyoptions(md, data, options, canvas){ //{{{
+'use strict';
+
+function applyoptions(md, data, options, canvas) {
 	//APPLYOPTIONS - apply colobar, text, cloud, and expdisp options to current plot
 	//
@@ -8,471 +10,358 @@
 
 	//{{{ colorbar
-	var gl = canvas.gl;
-	if (options.exist('colorbar')) {
-		if (options.getfieldvalue('colorbar')==1) {
-			//{{{ Variable options initialization
-			var caxis = options.getfieldvalue('caxis');
-			var ccanvasid, ctitleid, clabelsid, ccanvas, ctitle, clabels, ccontext, cmap, colorbar, cwidth, cheight, cgradient, color, y, x;
-			//}}}
-			//{{{ Create colorbar labels
-			var labels = [];
-			var cdivisions = options.getfieldvalue('colorbarnticks', 6);
-			var caxisdelta = caxis[1] - caxis[0];
-			var precision = options.getfieldvalue('colorbarprecision', 3);
-			var format = options.getfieldvalue('colorbarformat', 'f').toLowerCase();
-			if (options.getfieldvalue('log','off')!='off') {
-				for (var i=cdivisions; i >= 0; i--) {
-					var scale = (Math.log10(caxis[1])-Math.log10(caxis[0]))/Math.log10(options.getfieldvalue('log', 10));
-					if (format === 'f') {
-						labels[i] = (Math.pow(options.getfieldvalue('log', 10), Math.log10(caxis[0])/Math.log10(options.getfieldvalue('log', 10))+scale*(cdivisions-i)/cdivisions)).toFixed(precision);
-					}
-					else if (format === 'e') {
-						labels[i] = (Math.pow(options.getfieldvalue('log', 10), Math.log10(caxis[0])/Math.log10(options.getfieldvalue('log', 10))+scale*(cdivisions-i)/cdivisions)).toPrecision(precision);
-					}
-					else {
-						labels[i] = (Math.pow(options.getfieldvalue('log', 10), Math.log10(caxis[0])/Math.log10(options.getfieldvalue('log', 10))+scale*(cdivisions-i)/cdivisions)).toFixed(precision);
-					}
+	let gl = canvas.gl;
+	
+	if (options.getfieldvalue('colorbar', false)) {
+		//{{{ Create colorbar labels
+		let cAxis = options.getfieldvalue('caxis');
+		let labels = [];
+		let divisions = options.getfieldvalue('colorbarnticks', 6);
+		let cAxisDelta = cAxis[1] - cAxis[0];
+		let precision = options.getfieldvalue('colorbarprecision', 3);
+		let format = options.getfieldvalue('colorbarformat', 'f').toLowerCase();
+		if (options.getfieldvalue('log','off') !== 'off') {
+			for (let i = divisions; i >= 0; i--) {
+				let scale = (Math.log10(cAxis[1]) - Math.log10(cAxis[0])) / Math.log10(options.getfieldvalue('log', 10));
+				if (format === 'f') {
+					labels[i] = (Math.pow(options.getfieldvalue('log', 10), Math.log10(cAxis[0]) / Math.log10(options.getfieldvalue('log', 10)) + scale * (divisions - i) / divisions)).toFixed(precision);
+				} else if (format === 'e') {
+					labels[i] = (Math.pow(options.getfieldvalue('log', 10), Math.log10(cAxis[0]) / Math.log10(options.getfieldvalue('log', 10)) + scale * (divisions - i) / divisions)).toPrecision(precision);
+				} else {
+					labels[i] = (Math.pow(options.getfieldvalue('log', 10), Math.log10(cAxis[0]) / Math.log10(options.getfieldvalue('log', 10)) + scale * (divisions - i) / divisions)).toFixed(precision);
+				}
+			}
+		} else {
+			for (let i = divisions; i >= 0; i--) {
+				if (format === 'f') {
+					labels[i] = (cAxisDelta * (divisions - i) / divisions + cAxis[0]).toFixed(precision);
+				} else if (format === 'e') {
+					labels[i] = (cAxisDelta * (divisions - i) / divisions + cAxis[0]).toPrecision(precision);
+				} else {
+					labels[i] = (cAxisDelta * (divisions - i) / divisions + cAxis[0]).toFixed(precision);
+				}
+			}
+		} //}}}
+		//{{{ Initialize colorbar canvas
+		let cCanvasId = options.getfieldvalue('colorbarid', options.getfieldvalue('canvasid') + ('-colorbar-canvas'));
+		let cCanvasIdBase = cCanvasId.substring(0, cCanvasId.lastIndexOf('-canvas'));
+		let cCanvas = document.getElementById(cCanvasId);
+		let cWidth = cCanvas.width * options.getfieldvalue('colorbarwidth', 1);
+		let cHeight = cCanvas.height * options.getfieldvalue('colorbarheight', 1);
+		let cContext = cCanvas.getContext('2d');
+		let cMap = options.getfieldvalue('colormap', 'amp');
+		
+		// If value of cMap is of type array, assume we have a custom colorbar; otherwise, look up colormap by name from global letiable "colorbars"
+		let colorbar = null;
+		
+		if (vesl.arrays.isArray(cMap)) {
+			colorbar = cMap;
+		} else {
+    		for (let colormap in colorbars) {
+        		if (colormap === cMap) {
+			        colorbar = colorbars[cMap];
+			        break;
+                }
+            }
+            
+            if (colorbar === null) {
+                for (let colormap in cmoceanColormaps) {
+            		if (colormap === cMap) {
+    			        colorbar = cmoceanColormaps[cMap];
+    			        break;
+                    }
+                }
+            }
+		}
+		
+		let gradient = cContext.createLinearGradient(0, 0, 0, cHeight);
+		//}}}
+		//{{{ Draw colorbar gradient
+		// TODO: Allow for passing the opacity in as a fourth value of each array of a colormap?
+		let applyOpacityToColorbar  = options.getfieldvalue('applyOpacityToColorbar', false);
+		let color                   = null;
+		let background              = options.getfieldvalue('colorbarBackground', null);
+		let offset                  = 1 / (colorbar.length - 1) / 2;
+		let opacity                 = options.getfieldvalue('opacity', 1.0);
+		let position                = null;
+		let scaling                 = 1 - 2 * offset;
+		
+		if (!applyOpacityToColorbar) {
+    		opacity = 1.0;
+		}
+		
+		if (background !== null) {
+    		background = [background[0] * 255, background[1] * 255, background[2] * 255];
+    		$('#' + cCanvasId).css('background', 'rgba(' + background.toString() + ', 1.0');
+		}
+		
+		for (let i = 0; i < colorbar.length; i++) {
+			color = colorbar[colorbar.length - i - 1];
+			color = [Math.round(color[0] * 255), Math.round(color[1] * 255), Math.round(color[2] * 255)];
+			position = (i / (colorbar.length - 1) * scaling) + offset;
+			gradient.addColorStop(position, 'rgba(' + color.toString() + ', ' + opacity + ')');
+		}
+		
+		cContext.clearRect(0, 0, cWidth, cHeight);
+		cContext.beginPath();
+		cContext.fillStyle = gradient;
+		cContext.fillRect(0, 0, cWidth, cHeight);
+		//}}}
+		//{{{ Draw colorbar border
+		cContext.beginPath();
+		cContext.lineWidth = '1';
+		cContext.strokeStyle=options.getfieldvalue('colorbarfontcolor','black');
+		cContext.rect(0, 0, cWidth, cHeight);
+		cContext.stroke();
+		//}}}
+		//{{{ Draw colorbar labels
+		let cLabelsId = cCanvasIdBase + '-labels';
+		let cLabels = $('#' + cLabelsId);
+		let cLabelString = '';
+		let x, y;
+		cLabels.empty();
+		for (let i = 0; i <= divisions; i++) {
+			y = (i + 0.5) / (divisions + 1) * cHeight;
+			x = 0.2 * cWidth;
+			cLabelString += '<li><span>' + labels[i] + '</span></li>';
+			cContext.beginPath();
+			cContext.moveTo(0, y);
+			cContext.lineTo(x, y);
+			cContext.moveTo(cWidth - x, y);
+			cContext.lineTo(cWidth, y);
+			cContext.stroke();
+		}
+		cLabels.append(cLabelString);
+		//}}}
+		//{{{ Draw colorbar title
+		let cTitleId = cCanvasIdBase + '-heading';
+		let cTitle = $('#' + cTitleId);
+		if (options.exist('colorbartitle')) { cTitle.html(options.getfieldvalue('colorbartitle')); }
+		//}}}
+		//{{{ Setup texture/alpha canvases
+		let $canvas 	= $(canvas);
+		let tCanvasId 	= options.getfieldvalue('texturecanvasid', 'texturecanvas');
+		let aCanvasId 	= options.getfieldvalue('alphacanvasid', 'alphacanvas');
+		let tCanvas 	= document.getElementById(tCanvasId);
+		let aCanvas 	= document.getElementById(aCanvasId);
+		
+		if (tCanvas == null) {
+			$('<canvas id="' + tCanvasId + '" width="256" height="256" style="display: none;"></canvas>').insertAfter($canvas);
+			tCanvas = document.getElementById(tCanvasId);
+		}
+		
+		if (aCanvas == null) {
+			$('<canvas id="' + aCanvasId + '" width="256" height="256" style="display: none;"></canvas>').insertAfter($canvas);
+			aCanvas = document.getElementById(aCanvasId);
+		}
+	
+		//Set up canvas drawing contexes and gradients.
+		let tContext = tCanvas.getContext('2d');
+		let aContext = aCanvas.getContext('2d');
+		let tGradient = tContext.createLinearGradient(0, 0, 0, 256);
+		let aGradient = aContext.createLinearGradient(0, 0, 0, 256);
+		
+		//Determine where in gradient to start unit mesh transparency
+		let maskAlphaEnabled = options.getfieldvalue('maskAlphaEnabled', false);
+		let maskAlphaTolerance = options.getfieldvalue('maskAlphaTolerance', 0.1);
+		let maskAlphaValue = options.getfieldvalue('maskAlphaValue', 1.1);
+		let maskAlphaUseColor = options.getfieldvalue('maskAlphaUseColor', false);
+		let maskAlphaColor = options.getfieldvalue('maskAlphaColor', 'rgba(0.0, 0.0, 255, 1.0)');
+		let alphaValue = (maskAlphaValue - cAxis[0]) / cAxisDelta;
+		
+		//Apply transparency to alpha map that enables alpha to be read from texture, and to actual texture alpha.
+		for (let i = 0; i < colorbar.length; i++) {
+			color = colorbar[colorbar.length - i - 1];
+			color = [Math.round(color[0] * 255), Math.round(color[1] * 255), Math.round(color[2] * 255)];
+			let colorStop = i / (colorbar.length - 1);
+			if (maskAlphaEnabled && (colorStop > 1 - alphaValue || colorStop == colorbar.length - 1)) {
+				if (maskAlphaUseColor) {
+					tGradient.addColorStop(colorStop, maskAlphaColor);
+					aGradient.addColorStop(colorStop, 'rgb(255, 255, 255)');
+				} else {
+					tGradient.addColorStop(colorStop, 'rgba(' + color.toString() + ', 0.0)');
+					aGradient.addColorStop(colorStop, 'rgb(0, 0, 0)');
 				}
 			} else {
-				for (var i=cdivisions; i >= 0; i--) {
-					if (format === 'f') {
-						labels[i] = (caxisdelta*(cdivisions-i)/cdivisions+caxis[0]).toFixed(precision);
-					}
-					else if (format === 'e') {
-						labels[i] = (caxisdelta*(cdivisions-i)/cdivisions+caxis[0]).toPrecision(precision);
-					}
-					else {
-						labels[i] = (caxisdelta*(cdivisions-i)/cdivisions+caxis[0]).toFixed(precision);
-					}
-				}
-			} //}}}
-			//{{{ Initialize colorbar canvas
-			let colorbarSlug = options.getfieldvalue('colorbarSlug', options.getfieldvalue('canvasid') + '-colorbar');
-			ccanvasid = colorbarSlug + '-canvas';
-			ccanvas = $('#'+ccanvasid)[0];
-			cwidth = ccanvas.width*options.getfieldvalue('colorbarwidth', 1);
-			cheight = ccanvas.height*options.getfieldvalue('colorbarheight', 1);
-			ccontext = ccanvas.getContext('2d');
-			ccontext.clearRect(0, 0, cwidth, cheight);
-			ccontext.beginPath();
-			cmap = options.getfieldvalue('colormap','jet');
-			colorbar = colorbars[cmap];
-			cgradient = ccontext.createLinearGradient(0, 0, 0, cheight);
-			//}}}
-			//{{{ Draw colorbar gradient
-			var position;
-			var offset = 1 / (colorbar.length - 1) / 2;
-			var scaling = 1 - 2 * offset;
-			for (var i=0; i < colorbar.length; i++) {
-				color = colorbar[colorbar.length-i-1];
-				color = [Math.round(color[0]*255), Math.round(color[1]*255), Math.round(color[2]*255)];
-				position = (i / (colorbar.length - 1) * scaling) + offset;
-				cgradient.addColorStop(position, 'rgba(' + color.toString() + ', 1.0)');
-			}
-			ccontext.fillStyle=cgradient;
-			ccontext.fillRect(0, 0, cwidth, cheight);
-			//}}}
-			//{{{ Draw colorbar border
-			ccontext.beginPath();
-			ccontext.lineWidth='1';
-			ccontext.strokeStyle=options.getfieldvalue('colorbarfontcolor','black');
-			ccontext.rect(0, 0, cwidth, cheight);
-			ccontext.stroke();
-			//}}}
-			//{{{ Draw colorbar labels
-			clabelsid = colorbarSlug + '-labels';
-			clabels = $('#'+clabelsid);
-			var clabelstring = '';
-			clabels.empty();
-			for (var i=0; i <= cdivisions; i++) {
-				y = (i+0.5)/(cdivisions+1)*cheight;
-				x = 0.2*cwidth;
-				clabelstring += '<li><span>'+labels[i]+'</span></li>';
-				ccontext.beginPath();
-				ccontext.moveTo(0, y);
-				ccontext.lineTo(x, y);
-				ccontext.moveTo(cwidth-x, y);
-				ccontext.lineTo(cwidth, y);
-				ccontext.stroke();
-			}
-			clabels.append(clabelstring);
-			//}}}
-			//{{{ Draw colorbar title
-			ctitleid = colorbarSlug + '-heading';
-			ctitle = $('#'+ctitleid);
-			if (options.exist('colorbarHeader')) { ctitle.html(options.getfieldvalue('colorbarHeader')); }
-			//}}}
+				tGradient.addColorStop(colorStop, 'rgba(' + color.toString() + ', 1.0)');
+				aGradient.addColorStop(colorStop, 'rgb(255, 255, 255)');
+			}
+		}
+		
+		//Draw gradients to canvaes.
+		tContext.fillStyle = tGradient;
+		aContext.fillStyle = aGradient;
+		tContext.fillRect(0, 0, 256, 256);
+		aContext.fillRect(0, 0, 256, 256);
+		
+		//Allow for special texture colors, drawing each color in equal width vertical rectangles. The last rectanglar section is reserved for the colormap.
+		if (options.exist('maskregion')) {
+			let maskObject = options.getfieldvalue('maskregion',{'enabled':false});
+			if (maskObject.enabled && !vesl.helpers.isEmptyOrUndefined(maskObject.colors)) {
+				let x = 0;
+				let sections = Object.keys(maskObject.colors).length + 1;
+				let size = 256;
+				let width = Math.floor(1 / sections * size);
+				for (let color in maskObject.colors) {
+					tContext.fillStyle = maskObject.colors[color];
+					tContext.fillRect(x++ * width, 0, width, size);
+				}
+			}
+		}
+		
+		//Read canvases as images, and load as textures in Three.js
+		let tURL            = tCanvas.toDataURL();
+		let aURL            = aCanvas.toDataURL();
+		let textureMap      = new THREE.TextureLoader().load(tURL);
+		let alphaMap        = new THREE.TextureLoader().load(aURL);
+		let unitOptions 	= options.getfieldvalue('unitOptions', {'name' : 'unit'});
+		let unitName 		= unitOptions.name;
+		let unitSceneNode 	= canvas.unitNodes[unitName].sceneNode;
+		unitSceneNode.material.map          = textureMap;
+		unitSceneNode.material.emissiveMap  = textureMap;
+		unitSceneNode.material.color        = new THREE.Color(0xffffff);
+		unitSceneNode.material.needsUpdate  = true;
+		
+		//Only apply alpha map if enabled.
+		if (maskAlphaEnabled) {
+			unitSceneNode.material.alphaMap = alphaMap;
 		}
 	} //}}}
-	//{{{ texture canvas
-	var tcontext, tcanvas, tcanvasid, tURL, tgradient;
-	tcanvasid = 'texturecanvas';
-	var tcanvas = document.getElementById(tcanvasid);
-	if (tcanvas == null) {
-		$('<canvas id="texturecanvas" width="256" height="256" style="display: none;"></canvas>').insertAfter('#'+String(options.getfieldvalue('canvasid')));
-		tcanvas = document.getElementById(tcanvasid);
-	}
-	tcontext = tcanvas.getContext('2d');
-	tgradient = tcontext.createLinearGradient(0, 0, 0, 256);
-
-	var cmap = options.getfieldvalue('colormap','jet');
-	var colorbar = colorbars[cmap];
-	for (var i = 0; i < colorbar.length; i++) {
-		color = colorbar[colorbar.length - i - 1];
-		color = [Math.round(color[0] * 255), Math.round(color[1] * 255), Math.round(color[2] * 255)];
-		tgradient.addColorStop(i / (colorbar.length - 1), 'rgba(' + color.toString() + ', 1.0)');
-	}
-	tcontext.fillStyle = tgradient;
-	tcontext.fillRect(0, 0, 256, 256);
-
-	//Allow for special texture colors, drawing each color in equal width vertical rectangles. The last rectanglar section is reserved for the colormap.
-	if (options.exist('maskregion')) {
-		var maskObject = options.getfieldvalue('maskregion',{'enabled':false});
-		if (maskObject.enabled && !vesl.helpers.isEmptyOrUndefined(maskObject.colors)) {
-			var x = 0;
-			var sections = Object.keys(maskObject.colors).length + 1;
-			var size = 256;
-			var width = Math.floor(1 / sections * size);
-			for (var color in maskObject.colors) {
-				tcontext.fillStyle = maskObject.colors[color];
-				tcontext.fillRect(x++ * width, 0, width, size);
-			}
-		}
-	}
-
-	tURL = tcanvas.toDataURL();
-	if (options.getfieldvalue('clf','on')=='off') {
-		canvas.nodes['unit' + (Object.keys(canvas.nodes).length - 1)].texture = initTexture(canvas.gl, tURL);
-	} else {
-		canvas.nodes.unit.texture = initTexture(canvas.gl, tURL);
-	}
 	//}}}
-	//{{{ text display
-	var ctx;
-	var overlaycanvasid;
-	var overlaycanvas;
-	//Only intialize overlay canvas once by checking if it's already been defined
-	if (vesl.helpers.isEmptyOrUndefined(canvas.overlaycanvas)) {
-		//Get drawing context and save reference on main WebGL canvas
-		overlaycanvasid = options.getfieldvalue('overlayid', options.getfieldvalue('canvasid') + '-overlay')
-		overlaycanvas = $('#' + overlaycanvasid)[0];
-		ctx = overlaycanvas.getContext('2d');
-		canvas.overlaycanvas = overlaycanvas;
-	}
-	overlaycanvas = canvas.overlaycanvas;
-	ctx = overlaycanvas.getContext('2d');
-
-	if (options.exist('textlabels')) {//{{{
-		//Attatch new overlay handler to display text labels
-		var textLabels = options.getfieldvalue('textlabels',[]);
-		canvas.overlayHandlers['text'] = function(canvas) {
-			for (var i = 0; i < textLabels.length; i++) {
-				//Get text label to display
-				var textLabel = textLabels[i];
-				textLabel = {
-					position: defaultFor(textLabel.position, vec3.create()),
-					text: defaultFor(textLabel.text, ''),
-					fontSize: defaultFor(textLabel.fontSize, 18),
-					fontColor: defaultFor(textLabel.fontColor, 'black'),
-
-				};
-
-				// function declared in slr-gfm sim-front-end-controller.js
-				// if labels are behind the globe sphere then skip iteartion and do not display them
-				if (vesl.ui.isLabelVisible(textLabel)) {
-					//Transform from world space to viewport space
-					var screenPoint = vec3.transformMat4(vec3.create(), textLabel.position, canvas.camera.vpMatrix);
-					var x = (screenPoint[0] + 1.0) * (canvas.width / 2) + canvas.selector.offset().left;
-					var y = (-screenPoint[1] + 1.0) * (canvas.height / 2) + canvas.selector.offset().top;
-
-					//Draw text
-					ctx.font = 'bold ' + String(textLabel.fontSize) + 'px Arial Black, sans-serif';
-					ctx.fillStyle = textLabel.fontColor;
-					ctx.strokeStyle = 'white';
-					ctx.textAlign = 'center';
-					ctx.textBaseline = 'middle';
-					ctx.fillText(textLabel.text, x, y);
-					ctx.strokeText(textLabel.text, x, y);
-				}
-			}
-		}
-	}//}}}
-
-	//{{{ additional rendering nodes
-	if (options.exist('render')) {
-		var meshresults = processmesh(md, data, options);
-		var x = meshresults[0];
-		var y = meshresults[1];
-		var z = meshresults[2];
-		var elements = meshresults[3];
-		var is2d = meshresults[4];
-		var isplanet = meshresults[5];
-
-		var xlim = options.getfieldvalue('xlim', [ArrayMin(x), ArrayMax(x)]);
-		var ylim = options.getfieldvalue('ylim', [ArrayMin(y), ArrayMax(y)]);
-		var zlim = options.getfieldvalue('zlim', [ArrayMin(z), ArrayMax(z)]);
-
-		var global = vec3.length([(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, (zlim[0] + zlim[1]) / 2]) < 6371000/10; //tolerance for global models = center is 637100 meters away from center of earth
-		var translation = global ? [(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, (zlim[0] + zlim[1]) / 2] : canvas.view.position;
-
-		var renderObjects = options.getfieldvalue('render',{});
-
-		for (var renderObject in renderObjects) {
-			//Modify renderObejct?
-			var object = renderObjects[renderObject];
-			object = {
-				enabled: defaultFor(object.enabled, true),					//Toggle display of the render object node
-				scale: defaultFor(object.scale, 1),							//Model matrix scaling
-				x: defaultFor(object.x, [0.0, 1.0, 0.0, 0.0, 0.0, 0.0]),	//x coordinate array
-				y: defaultFor(object.y, [0.0, 0.0, 0.0, 1.0, 0.0, 0.0]),	//y coordinate array
-				z: defaultFor(object.z, [0.0, 0.0, 0.0, 0.0, 0.0, 1.0]),	//z coordinate array
-				indices: defaultFor(object.indices, []),					//indices array
-				name: defaultFor(object.name, 'NY'),						//Text to display for cities.
-				size: defaultFor(object.size, 1),							//Physical size of the object in meters
-				color: defaultFor(object.color, 'black'),					//Diffuse color of object
-				height: defaultFor(object.height, 25000),					//Height of object along y axis, currently for clouds only
-				range: defaultFor(object.range, 120000),					//Range of sz plane to spawn object, currently for clouds only
-				quantity: defaultFor(object.quantity, 15),					//Quantity of objects to display, currently for clouds only
-				source: defaultFor(object.source, 'NY'),					//Quantity of objects to display, currently for clouds only
-				targets: defaultFor(object.targets, ['NY'])					//Quantity of objects to display, currently for clouds only
-			};
-			if (!object.enabled) { continue; }
-			if ('sky' === renderObject && !('sky' in canvas.nodes)) {
-				var mesh = GL.Mesh.icosahedron({size: 6371000 * canvas.atmosphere.scaleHeight, subdivisions: 5});
-				var texture = initTexture(gl, canvas.assetsPath + '/textures/TychoSkymapII_t4_2k.jpg');
-				node = new Node(
-					'canvas', canvas,
-					'options', options,
-					'renderObject', object,
-					'name', 'sky',
-					'shaderName', 'SkyFromSpace',
-					'cullFace', gl.FRONT,
-					'mesh', mesh,
-					'texture',texture,
-					'translation',translation
-				);
-			}
-			if ('space' === renderObject && !('space' in canvas.nodes)) {
-				var mesh = GL.Mesh.sphere({size: 6371000 * 20});
-				var texture = initTexture(gl, canvas.assetsPath + '/textures/TychoSkymapII_t4_2k.jpg');
-				node = new Node(
-					'canvas', canvas,
-					'options', options,
-					'renderObject', object,
-					'name', 'space',
-					'shaderName', 'Textured',
-					'cullFace', gl.FRONT,
-					'drawOrder', 2,
-					'mesh', mesh,
-					'texture',texture,
-					'translation',translation
-				);
-			}
-			if ('coastlines' === renderObject) {
-				node = new Node(
-					'canvas', canvas,
-					'options', options,
-					'renderObject', object,
-					'name', 'coastlines',
-					'shaderName', 'Colored',
-					'drawMode', gl.LINE_STRIP,
-					'lineWidth', options.getfieldvalue('linewidth', 1),
-					'scale', [object.scale, object.scale, object.scale],
-					'rotation', [-90, 0, 0]
-				);
-				node.patch('Vertices', [object.x, object.y, object.z], 'FaceColor', 'none');
-			}
-			if ('graticule' === renderObject && !('graticule' in canvas.nodes)) {
-				node = new Node(
-					'canvas', canvas,
-					'options', options,
-					'renderObject', object,
-					'name', 'graticule',
-					'shaderName', 'Colored',
-					'drawMode', gl.LINE_STRIP,
-					'lineWidth', options.getfieldvalue('linewidth', 1),
-					'scale', [object.scale, object.scale, object.scale],
-					'rotation', [-90, 180, 0]
-				);
-				node.patch('Vertices', [object.x, object.y, object.z], 'FaceColor', 'none');
-			}
-			if ('cities' === renderObject && !('cities' in canvas.nodes)) {
-				var mesh = GL.Mesh.icosahedron({size: object.size, subdivisions: 1});
-				node = new Node(
-					'canvas', canvas,
-					'options', options,
-					'renderObject', object,
-					'name', 'cities',
-					'shaderName', 'ColoredDiffuse',
-					'diffuseColor', object.color,
-					'lineWidth', options.getfieldvalue('linewidth', 1),
-					'scale', [object.scale, object.scale, object.scale],
-					'rotation', [-90, 0, 0]
-				);
-				node.geometryShader('Mesh', mesh, 'Vertices', [object.x, object.y, object.z], 'Indices', object.indices);
-			}
-			if ('axis' === renderObject && !('axis' in canvas.nodes)) {
-				node = new Node(
-					'canvas', canvas,
-					'options', options,
-					'renderObject', object,
-					'name', 'axis',
-					'shaderName', 'Colored',
-					'drawMode', gl.LINES,
-					'diffuseColor', [1.0, 0.0, 0.0, 1.0],
-					'lineWidth', options.getfieldvalue('linewidth', 1),
-					'scale', [object.scale, object.scale, object.scale],
-					'rotation', [0, 0, 0]
-				);
-				node.patch('Vertices', [object.x, object.y, object.z], 'FaceColor', 'none');
-			}
-			if ('city' === renderObject && !vesl.helpers.isEmptyOrUndefined(overlaycanvas)) {
-				//city
-				var mesh = GL.Mesh.sphere({size: object.size});
-				node = new Node(
-					'canvas', canvas,
-					'options', options,
-					'renderObject', object,
-					'name', 'city',
-					'shaderName', 'ColoredDiffuse',
-					'diffuseColor', object.color,
-					'mesh', mesh,
-					'translation', [object.x, object.z, -object.y]
-				);
-				//Attatch new overlay handler to display city name
-				canvas.overlayHandlers['city'] = function(canvas) {
-					var node = canvas.nodes['city'];
-					var object = node.renderObject;
-					var screenPoint = vec3.transformMat4(vec3.create(), node.translation, canvas.camera.vpMatrix);
-					var x = (screenPoint[0] + 1.0) * (canvas.width / 2) + canvas.selector.offset().left;
-					var y = (-screenPoint[1] + 1.0) * (canvas.height / 2) + canvas.selector.offset().top;
-
-					ctx.font = 'bold ' + String(options.getfieldvalue('colorbarfontsize', 22))+'px Arial Black, sans-serif';
-					ctx.fillStyle = options.getfieldvalue('colorbarfontcolor','black');
-					ctx.strokeStyle = 'white';
-					ctx.textAlign = 'center';
-					ctx.textBaseline = 'middle';
-					ctx.fillText(object.name, x, y);
-					ctx.strokeText(object.name, x, y);
-				}
-			}
-			if ('clouds' === renderObject && !('clouds0' in canvas.nodes)) {
-				//clouds
-				var mesh = GL.Mesh.fromURL(canvas.assetsPath + '/obj/cloud.obj');
-				for (var i = 0; i < object.quantity; i++) {
-					//TODO: More options, less magic numbers. Add animation. Better shading.
-					var offset = [randomizeAxis(translation[0], object.range),
-												randomizeCloudHeight(translation[1], object),
-												randomizeAxis(translation[2], object.range)];
-					var randomSize = randomizeCloudSize(object.scale);
-					var randomColor = randomizeCloudColor();
-					node = new Node(
-						'canvas', canvas,
-						'options', options,
-						'renderObject', object,
-						'name', 'clouds' + i,
-						'shaderName', 'ColoredDiffuse',
-						'diffuseColor', [randomColor, randomColor, randomColor, 1.0],
-						'specularColor', [0.1, 0.1, 0.1, 1.0],
-						'mesh', mesh,
-						'scale', [randomSize, randomSize, randomSize],
-						'translation', offset
-					);
-				}
-			}
-			if ('citylines' === renderObject) {
-				//city
-				node = new Node(
-					'canvas', canvas,
-					'options', options,
-					'renderObject', object,
-					'name', 'citylines',
-					'shaderName', 'ColoredDiffuse',
-					'drawMode', gl.LINES,
-					'diffuseColor', object.color,
-					'lineWidth', options.getfieldvalue('linewidth', 1),
-					'scale', [object.scale, object.scale, object.scale],
-					'rotation', [0, 0, 0]
-				);
-
-				//For each target city, calculate the shortest line across the earth by performing a quaternion slerp.
-				//Treat source and target city as vectors to rotate to from the north pole.
-				//Then, slerp between the two rotations, and generate points across equidistance points on the earth to create the line.
-				var north = vec3.fromValues(0, 1, 0);
-				var source = object.source;
-				var sourceXYZ = vec3.fromValues(xcity[source], zcity[source], -ycity[source]);
-				var radius = vec3.length(sourceXYZ);
-				var lineSteps = 50;
-				var lineX = [];
-				var lineY = [];
-				var lineZ = [];
-				var lineXYZ = vec3.create();
-
-				for (var i = 0; i < object.targets.length; i++) {
-					var target = object.targets[i];
-					var targetXYZ = vec3.fromValues(xcity[target], zcity[target], -ycity[target]);
-					var axis = vec3.cross(vec3.create(), sourceXYZ, targetXYZ);
-					vec3.normalize(axis, axis);
-
-					//Get the total angle between the two cities.
-					var sourceXYZAxis = vec3.normalize(vec3.create(), sourceXYZ);
-					var targetXYZAxis = vec3.normalize(vec3.create(), targetXYZ);
-					var dotProduct = vec3.dot(sourceXYZAxis, targetXYZAxis);
-					var totalAngle = Math.acos(dotProduct); //theta = arccos(u . v / (||u|| * ||v||); in this case, ||u|| and ||v|| are 1, since u and v are unit vectors.
-
-					var lineQuat = quat.create();
-					for (var j = 1; j <= lineSteps; j++) {
-						//Calculate the partial rotation to obtain points on the line between the two cities.
-						var angle = j / lineSteps * totalAngle;
-						quat.setAxisAngle(lineQuat, axis, angle);
-						quat.normalize(lineQuat, lineQuat);
-						vec3.transformQuat(lineXYZ, sourceXYZ, lineQuat);
-						//GL.LINES needs 2 points for each line - at the beginning, just use the sourceXYZ.
-						//TODO: Eliminate this if statement.
-						if (j === 1) {
-							lineX.push(sourceXYZ[0]);
-							lineY.push(sourceXYZ[1]);
-							lineZ.push(sourceXYZ[2]);
-						} else {
-							lineX.push(lineX[lineX.length - 1]);
-							lineY.push(lineY[lineY.length - 1]);
-							lineZ.push(lineZ[lineZ.length - 1]);
-						}
-						lineX.push(lineXYZ[0]);
-						lineY.push(lineXYZ[1]);
-						lineZ.push(lineXYZ[2]);
-					}
-				}
-				node.patch('Vertices', [lineX, lineY, lineZ]);
-			}
-		}
-	} //}}}
+	//{{{ Data marker
+	// TODO: Default parameters are already being handled by /js/vesl/DataMarker.js::constructor, so perhaps we should be initializing this elsewhere and/or by some other manner	
+	if (vesl.helpers.isEmptyOrUndefined(canvas.dataMarker)) { // Only define data marker once
+		let dataMarker 			= {};
+		let dataMarkerOptions 	= options.getfieldvalue('dataMarker', {});
+		
+		dataMarker = {
+			enabled: defaultFor(dataMarkerOptions.enabled, false)
+		};
+		
+		// Only initialize data marker object if we have enabled data markers for this canvas
+		if (dataMarker.enabled) {
+			dataMarker.object = new vesl.DataMarker(
+				{
+					canvas 			: canvas,
+					hasMarker 		: defaultFor(dataMarkerOptions.hasMarker, true),
+					markerImgPath 	: defaultFor(dataMarkerOptions.markerImgPath, '/canvas/data-markers/data-marker.svg'),
+					hasTooltip		: defaultFor(dataMarkerOptions.hasTooltip, false),
+					tooltipFormat	: defaultFor(dataMarkerOptions.tooltipFormat, ''),
+					tooltipFields	: defaultFor(dataMarkerOptions.tooltipFields, null),
+					width			: defaultFor(dataMarkerOptions.width, 32),
+					height			: defaultFor(dataMarkerOptions.height, 32)
+				}
+			);
+		}
+		
+		canvas.dataMarker = dataMarker;
+	}
+	//}}}
+	//contours
+	if (options.exist('contourlevels')) {
+		plot_contour(md,data,options,canvas);
+	}
 } //}}}
-
-function randomizeCloudHeight(canvasGroundHeight, object) {
-		// -+7000 seems a reasonable range
-		var maxHeight = object.height + 7000;
-		var minHeigth = object.height - 7000;
-		var randomHeight = (Math.random() * (maxHeight - minHeigth)) + minHeigth;
-
-		return canvasGroundHeight + randomHeight;
-}
-
-// assumes that originAxisValue is the mid-value between min and max.
-function randomizeAxis(originAxisValue, range) {
-		return originAxisValue + (Math.random() - 0.5) * (range * 2);
-}
-
-function randomizeCloudSize(scale) {
-	var maxResize = 1.3;
-	var minResize = 0.5;
-	var randomizationFactor = Math.random() * (maxResize - minResize) + minResize;
-	return scale * randomizationFactor;
-}
-
-function randomizeCloudColor() {
-	var lighestColor = 1;
-	var darkestColor = 0.9;
-	var randomColor = Math.random() * (lighestColor - darkestColor) + darkestColor;
-	return randomColor;
-}
+function drawGroundingLines(md, canvas, options, renderObject, lines, colors) { //{{{
+	let renderObjects = options.getfieldvalue('render',{});
+	let state = canvas.state;
+	let scene = state.scene;
+	
+	let group = scene.getObjectByName('groundingLines');
+	if (group !== undefined) {
+		scene.remove(group); //Remove old group if already exists
+	}
+	group = new THREE.Group();
+	group.name = 'groundingLines';
+	
+	//Plot multiple grounding lines, each consisting of multiple polygons. 
+	for (let i = 0; i < lines.length; i++) {
+		let groundingLine = lines[i]; //of type Proxy (not object or array), thus must iterate without length attribute
+		let color = processColor(colors[i]);
+		let x = [];
+		let y = [];
+		let z = [];
+		//In order to show polygons correctly, must convert from line strip to line segments
+		for (let j in groundingLine) {
+			let polygon = groundingLine[j]
+			let lineStripX = polygon['x'];
+			let lineStripY = polygon['y'];
+			let lineStripZ = polygon['z'];
+			let lineSegmentsX = [lineStripX[0]];
+			let lineSegmentsY = [lineStripY[0]];
+			let lineSegmentsZ = [lineStripZ[0]];
+			//Must push same coordinates as end of previous segment and beginning of next segment
+			for (let k = 1; k < lineStripX.length - 1; k++) {
+				lineSegmentsX.push(lineStripX[k], lineStripX[k]);
+				lineSegmentsY.push(lineStripY[k], lineStripY[k]);
+				lineSegmentsZ.push(lineStripZ[k], lineStripZ[k]);
+			}
+			//Cap off last coordinate
+			lineSegmentsX.push(lineStripX[lineStripX.length - 1]);
+			lineSegmentsY.push(lineStripY[lineStripY.length - 1]);
+			lineSegmentsZ.push(lineStripZ[lineStripZ.length - 1]);
+			//Add polygon coordinates to existing groundingLine
+			x = x.concat(lineSegmentsX);
+			y = y.concat(lineSegmentsY);
+			if (renderObject.followsBed) {
+				z = z.concat(lineSegmentsZ);
+			} else {
+				z = z.concat(NewArrayFill(lineSegmentsX.length, 10 * i));
+			}
+		}
+		z = ArrayScale(z, options.getfieldvalue('heightscale', 1));
+		let vertices = [x, y, z];
+		//console.log('ArrayMin(x):', ArrayMin(x), 'ArrayMax(x):', ArrayMax(x), 'ArrayMin(y):', ArrayMin(y), 'ArrayMax(y):', ArrayMax(y));
+			
+		let node = new Node(
+			'canvas', canvas,
+			'options', options,
+			'renderObject', renderObject,
+			'name', 'groundingLine_' + String(i),
+			'shaderName', 'lines',
+			//'dashed', dashed,
+			'depthTest', false,
+			'diffuseColor', color,
+			'lineWidth', renderObject.lineWidth,
+			//'lineWidth', options.getfieldvalue('linewidth', 3),
+			'scale', [renderObject.scale, renderObject.scale, renderObject.scale]
+		);
+		node.patch('Vertices', vertices, 'group', group, 'ViewReset', false);
+	}
+	scene.add(group);
+	return group;
+} //}}}
+function drawText(state, options, textLabels) { //{{{
+	let camera = state.camera;
+	let overlayCanvas = state.overlayCanvas;
+	let ctx = overlayCanvas.getContext('2d');
+	let widthHalf = overlayCanvas.width / 2
+	let heightHalf = overlayCanvas.height / 2;
+	
+	for (let i = 0; i < textLabels.length; i++) {
+		let textLabel = textLabels[i];
+		let position = textLabel.position.clone();
+		let text = textLabel.text;
+		
+		//Project world coordinates to screenspace coordinates
+		position.project(camera);
+		let x = (position.x * widthHalf) + widthHalf;
+		let y = -(position.y * heightHalf) + heightHalf;
+		
+		ctx.font = 'bold ' + String(options.getfieldvalue('colorbarfontsize', 22))+'px Arial Black, sans-serif';
+		ctx.fillStyle = options.getfieldvalue('colorbarfontcolor','black');
+		ctx.strokeStyle = 'white';
+		ctx.textAlign = 'center';
+		ctx.textBaseline = 'middle';
+		ctx.fillText(text, x, y);
+		ctx.strokeText(text, x, y);
+	}
+} //}}}
+function processColor(color) { //{{{
+	//Update the diffuse color with an RGB color name or vec4 containing r, g, b, and opacity values from 0.0 to 1.0
+	if (typeof color === 'string') {
+		color = new THREE.Color(color);
+	} else if (Array.isArray(color)) {
+		color = new THREE.Color().fromArray(color);
+	}
+	return color;
+} //}}}
Index: /issm/trunk-jpl/src/m/plot/plot_contour.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plot_contour.js	(revision 26300)
+++ /issm/trunk-jpl/src/m/plot/plot_contour.js	(revision 26300)
@@ -0,0 +1,299 @@
+function plot_contour(md, datain, options, canvas) { //{{{
+	//PLOT_MESH - Function for plotting wireframe contour.
+	//
+	//   Usage:
+	//      plot_contour(md, options, canvas);
+	//
+	//   See also: PLOTMODEL, PLOT_MANAGER
+
+	//{{{
+	//Process data and model
+	//let meshresults = processmesh(md, [], options);
+	//let [x, y, z, index, is2d, isplanet, vertices, scale] = scaleMesh(md, meshresults, options);
+	let [x, y, z, index, is2d, isplanet]=processmesh(md,[],options);
+	options.removefield('log',0);;
+	let [data, datatype]=processdata(md,datain,options);
+	if (vesl.helpers.isEmptyOrUndefined(data)) error('data provided is empty');
+	
+	//check is2d
+	if (!is2d && !isplanet) {
+		error('plot_contour error message: contour not supported for 3d meshes, project on a layer');
+	}
+
+	//first, process data: must be on nodes
+	if (datatype==1) {
+		//elements -> take average
+		//data=averaging(md,data,0);
+		error('plot_contour error message: contour not supported for element data yet');
+	} else if (datatype==2) {
+		//nodes -> do nothing
+	} else if (datatype==3) {
+		//quiver -> take norm
+		//data=sqrt(sum(datain.*datain,2)); //(original)
+		//data=ArraySqrt(ArraySum(ArrayMultiply(datain, datain),2)); //js version
+		error('plot_contour error message: contour not supported for quiver data yet');
+	} else {
+		error('datatype not supported yet');
+	}
+
+	//prepare colors
+	if (options.exist('contouronly')) {
+		//remove the previous plots
+		//cla
+		error('contouronly not supported yet');
+	}
+	let color=options.getfieldvalue('contourcolor','yellow');
+	let linewidth=options.getfieldvalue('linewidth',1);
+
+	//get contours levels
+	let contourlevels=options.getfieldvalue('contourlevels');
+	let levels;
+	if (typeof(contourlevels) == 'number') {
+		//levels=round_ice(linspace(max(data),min(data),contourlevels),2);
+		error('numeric contourlevels not supported yet - must provide levels as an array');
+	} else {
+		//levels=sort(unique(levels),'descend');
+		levels=ArraySort(ArrayUnique(contourlevels)).reverse();
+	}
+	let numlevels=levels.length;
+
+	//initialization of some variables
+	let numberofelements=index.length; //same as size(index,1)
+	let elementslist=NewArrayFillIncrement(0,numberofelements,1); //1:numberofelements;
+	let c=[];
+	let h=[];;
+
+	//get unique edges in mesh
+	//1: list of edges
+	index = ArraySubtract2D(index, 1);
+	//edges=[index[:,[1,2]); index(:,[2,3]); index(:,[3,1])];
+	let edges=ArrayConcat(ArrayConcat(ArrayCol(index,[0,1]), ArrayCol(index,[1,2])), ArrayCol(index,[2,0]));
+	//2: find unique edges
+	//[edges,I,J]=unique(sort(edges,2),'rows');
+	[edges,I,J]=ArrayUnique(ArraySort(edges,2),'rows');
+	//3: unique edge numbers
+	let vec=J;
+	//4: unique edges numbers in each triangle (2 triangles sharing the same edge will have
+	//   the same edge number)
+	let edges_tria=ArrayTranspose([ArrayIndex(vec,elementslist), ArrayIndex(vec,ArrayAdd(elementslist,numberofelements)), ArrayIndex(vec,ArrayAdd(elementslist,2*numberofelements))]);
+
+	//segments [nodes1 nodes2]
+	let Seg1=ArrayCol(index,[0,1]);
+	let Seg2=ArrayCol(index,[1,2]);
+	let Seg3=ArrayCol(index,[2,0]);
+	
+	//segment numbers [1;4;6;...]
+	let Seg1_num=ArrayCol(edges_tria,0);
+	let Seg2_num=ArrayCol(edges_tria,1);
+	let Seg3_num=ArrayCol(edges_tria,2);
+
+	//value of data on each tips of the segments
+	let Data1=ArrayIndex(data,Seg1);
+	let Data2=ArrayIndex(data,Seg2);
+	let Data3=ArrayIndex(data,Seg3);
+
+	//get the ranges for each segment
+	let Range1=ArraySort(Data1,2);
+	let Range2=ArraySort(Data2,2);
+	let Range3=ArraySort(Data3,2);
+	
+	let hx = [];
+	let hy = [];
+	let hz = [];
+	for (let i=0; i<numlevels; i++) {
+		let level=levels[i];
+
+		//find the segments that contain this value
+		let pos1=ArrayAnd(ArrayLessThan(ArrayCol(Range1,0),level), ArrayGreaterThan(ArrayCol(Range1,1),level)); //pos1=(Range1(:,1)<level & Range1(:,2)>level);
+		let pos2=ArrayAnd(ArrayLessThan(ArrayCol(Range2,0),level), ArrayGreaterThan(ArrayCol(Range2,1),level)); //pos2=(Range2(:,1)<level & Range2(:,2)>level);
+		let pos3=ArrayAnd(ArrayLessThan(ArrayCol(Range3,0),level), ArrayGreaterThan(ArrayCol(Range3,1),level)); //pos3=(Range3(:,1)<level & Range3(:,2)>level);
+
+		//get elements
+		let poselem12=ArrayAnd(pos1, pos2);
+		let poselem13=ArrayAnd(pos1, pos3);
+		let poselem23=ArrayAnd(pos2, pos3);
+		let poselem=find(ArrayOr(ArrayOr(poselem12, poselem13), poselem23));
+		let numelems=length(poselem);
+
+		//if no element has been flagged, skip to the next level
+		if (numelems==0) {
+			continue;
+		}
+
+		//go through the elements and build the coordinates for each segment (1 by element)
+		let x1=zeros(numelems,1);
+		let x2=zeros(numelems,1);
+		let y1=zeros(numelems,1);
+		let y2=zeros(numelems,1);
+		let z1=zeros(numelems,1);
+		let z2=zeros(numelems,1);
+
+		let edge_l=zeros(numelems,2);
+
+		for (let j=0; j < numelems; j++) {
+
+			let weight1=(level-Data1[poselem[j]][0])/(Data1[poselem[j]][1]-Data1[poselem[j]][0]);
+			let weight2=(level-Data2[poselem[j]][0])/(Data2[poselem[j]][1]-Data2[poselem[j]][0]);
+			let weight3=(level-Data3[poselem[j]][0])/(Data3[poselem[j]][1]-Data3[poselem[j]][0]);
+
+			if (poselem12[poselem[j]]) {
+				x1[j]=x[Seg1[poselem[j]][0]]+weight1*(x[Seg1[poselem[j]][1]]-x[Seg1[poselem[j]][0]]);
+				x2[j]=x[Seg2[poselem[j]][0]]+weight2*(x[Seg2[poselem[j]][1]]-x[Seg2[poselem[j]][0]]);
+				y1[j]=y[Seg1[poselem[j]][0]]+weight1*(y[Seg1[poselem[j]][1]]-y[Seg1[poselem[j]][0]]);
+				y2[j]=y[Seg2[poselem[j]][0]]+weight2*(y[Seg2[poselem[j]][1]]-y[Seg2[poselem[j]][0]]);
+				z1[j]=z[Seg1[poselem[j]][0]]+weight1*(z[Seg1[poselem[j]][1]]-z[Seg1[poselem[j]][0]]);
+				z2[j]=z[Seg2[poselem[j]][0]]+weight2*(z[Seg2[poselem[j]][1]]-z[Seg2[poselem[j]][0]]);
+				edge_l[j][0]=Seg1_num[poselem[j]];
+				edge_l[j][1]=Seg2_num[poselem[j]];
+
+			} else if (poselem13[poselem[j]]) {
+                x1[j]=x[Seg1[poselem[j]][0]]+weight1*(x[Seg1[poselem[j]][1]]-x[Seg1[poselem[j]][0]]);
+				x2[j]=x[Seg3[poselem[j]][0]]+weight3*(x[Seg3[poselem[j]][1]]-x[Seg3[poselem[j]][0]]);
+				y1[j]=y[Seg1[poselem[j]][0]]+weight1*(y[Seg1[poselem[j]][1]]-y[Seg1[poselem[j]][0]]);
+				y2[j]=y[Seg3[poselem[j]][0]]+weight3*(y[Seg3[poselem[j]][1]]-y[Seg3[poselem[j]][0]]);
+				z1[j]=z[Seg1[poselem[j]][0]]+weight1*(z[Seg1[poselem[j]][1]]-z[Seg1[poselem[j]][0]]);
+				z2[j]=z[Seg3[poselem[j]][0]]+weight3*(z[Seg3[poselem[j]][1]]-z[Seg3[poselem[j]][0]]);
+				edge_l[j][0]=Seg1_num[poselem[j]];
+				edge_l[j][1]=Seg3_num[poselem[j]];
+
+			} else if (poselem23[poselem[j]]) {
+                x1[j]=x[Seg2[poselem[j]][0]]+weight2*(x[Seg2[poselem[j]][1]]-x[Seg2[poselem[j]][0]]);
+				x2[j]=x[Seg3[poselem[j]][0]]+weight3*(x[Seg3[poselem[j]][1]]-x[Seg3[poselem[j]][0]]);
+				y1[j]=y[Seg2[poselem[j]][0]]+weight2*(y[Seg2[poselem[j]][1]]-y[Seg2[poselem[j]][0]]);
+				y2[j]=y[Seg3[poselem[j]][0]]+weight3*(y[Seg3[poselem[j]][1]]-y[Seg3[poselem[j]][0]]);
+				z1[j]=z[Seg2[poselem[j]][0]]+weight2*(z[Seg2[poselem[j]][1]]-z[Seg2[poselem[j]][0]]);
+				z2[j]=z[Seg3[poselem[j]][0]]+weight3*(z[Seg3[poselem[j]][1]]-z[Seg3[poselem[j]][0]]);
+				edge_l[j][0]=Seg2_num[poselem[j]];
+				edge_l[j][1]=Seg3_num[poselem[j]];
+			} else {
+				//it shoud not go here
+			}
+			let test = new THREE.Vector3(x1[j], y1[j], z1[j]);
+			//console.log('helo', test.length());
+		}
+		//now that we have the segments, we must try to connect them...
+
+		//loop over the subcontours
+		while (!isempty(edge_l)) {
+			//take the right edge of the second segment and connect it to the next segments if any
+			let e1=edge_l[0][0];   let e2=edge_l[0][1];
+			let xc=[x1[0],x2[0]]; let yc=[y1[0],y2[0]]; let zc=[z1[0],z2[0]]; 
+
+			//erase the lines corresponding to this edge
+			//edge_l(1,:)=[];
+			//x1(1)=[]; x2(1)=[];
+			//y1(1)=[]; y2(1)=[];
+			//z1(1)=[]; z2(1)=[]
+			edge_l.splice(0,1);
+			x1.splice(0,1); x2.splice(0,1);
+			y1.splice(0,1); y2.splice(0,1);
+			z1.splice(0,1); z2.splice(0,1);
+			let [ro1,co1]=find(ArrayEqual(edge_l,e1));
+
+			while (!isempty(ro1)) {
+				if (co1==0) {
+					xc=[x2[ro1]].concat(xc); yc=[y2[ro1]].concat(yc); zc=[z2[ro1]].concat(zc);
+
+					//next edge:
+					e1=edge_l[ro1][1];
+
+				} else {
+					xc=[x1[ro1]].concat(xc); yc=[y1[ro1]].concat(yc); zc=[z1[ro1]].concat(zc);
+
+					//next edge:
+					e1=edge_l[ro1][0];
+				}
+
+				//erase the lines of this
+				edge_l.splice(ro1,1);
+				x1.splice(ro1,1); x2.splice(ro1,1);
+				y1.splice(ro1,1); y2.splice(ro1,1);
+				z1.splice(ro1,1); z2.splice(ro1,1);
+
+				//next connection
+				[ro1,co1]=find(ArrayEqual(edge_l,e1));
+			}
+
+			//same thing the other way (to the right)
+			let [ro2,co2]=find(ArrayEqual(edge_l,e2));
+
+			while (!isempty(ro2)) {
+
+				if (co2==0) {
+					xc=[x2[ro2]].concat(xc); yc=[y2[ro2]].concat(yc); zc=[z2[ro2]].concat(zc);
+
+					//next edge:
+					e2=edge_l[ro2][1];
+
+				} else {
+					xc=[x1[ro2]].concat(xc); yc=[y1[ro2]].concat(yc); zc=[z1[ro2]].concat(zc);
+
+					//next edge:
+					e2=edge_l[ro2][0];
+				}
+
+				//erase the lines of this
+				edge_l.splice(ro2,1);
+				x1.splice(ro2,1); x2.splice(ro2,1);
+				y1.splice(ro2,1); y2.splice(ro2,1);
+				z1.splice(ro2,1); z2.splice(ro2,1);
+
+				//next connection
+				[ro2,co2]=find(ArrayEqual(edge_l,e2));
+			}
+
+			//we now have one subcontour ready to be plotted
+			if (options.getfieldvalue('contouronly',0)) {
+				if (isplanet) {
+					hx = ArrayConcat(hx, ArrayConcat(xc, [NaN]));
+					hy = ArrayConcat(hy, ArrayConcat(yc, [NaN]));
+					hz = ArrayConcat(hz, ArrayConcat(zc, [NaN]));
+					//h=[h;patch('Xdata',[xc;NaN],'Ydata',[yc;NaN],'Zdata',[zc;NaN],'facecolor','none','linewidth',linewidth)];
+				} else {
+					hx = ArrayConcat(hx, ArrayConcat(xc, [NaN]));
+					hy = ArrayConcat(hy, ArrayConcat(yc, [NaN]));
+					hz = ArrayConcat(hz, ArrayConcat(zc, [NaN]));
+					//h=[h;patch('Xdata',[xc;NaN],'Ydata',[yc;NaN],'Zdata',zc,'Cdata',zc,'facecolor','none','edgecolor','flat','linewidth',linewidth)];
+				}
+				//hold on      
+			} else {
+				//dist = 5000;
+				dist = 0;
+				if (isplanet) {
+					if ((max(xc)-min(xc)+max(yc)-min(yc)+max(zc)-min(zc))<dist) { continue; }
+					hx = ArrayConcat(hx, ArrayConcat(xc, [NaN]));
+					hy = ArrayConcat(hy, ArrayConcat(yc, [NaN]));
+					hz = ArrayConcat(hz, ArrayConcat(zc, [NaN]));
+					//h=[h;patch('Xdata',[xc;NaN],'Ydata',[yc;NaN],'Zdata',[zc;NaN],'facecolor','none','edgecolor',color,'linewidth',linewidth)];
+					//c = horzcat([level, xc'; length(xc), yc'; length(xc), zc']);
+				} else {
+					if ((max(xc)-min(xc)+max(yc)-min(yc))<dist) { continue; }
+					hx = ArrayConcat(hx, ArrayConcat(xc, [NaN]));
+					hy = ArrayConcat(hy, ArrayConcat(yc, [NaN]));
+					hz = ArrayConcat(hz, ArrayConcat(zc, [NaN]));
+					//h=patch('Xdata',[xc;NaN],'Ydata',[yc;NaN],'facecolor','none','edgecolor',color,'linewidth',linewidth);
+					//c = horzcat([level, xc'; length(xc), yc']);
+				}
+				//clabel(c,h,'FontSize',10,'labelspacing',20000,'color',color);
+				//hold on
+			}
+			//break;
+		}
+	}
+
+	//Compute gl variables:
+	let node = new Node(
+		'canvas', canvas,
+		'options', options,
+		'md', md,
+		'name', 'contours',
+		'replaceNaN', false,
+		'shaderName', 'line_strip',
+		'lineWidth', options.getfieldvalue('linewidth', 1),
+		'scale', [1.0005, 1.0005, 1.0005]
+	);
+
+	node.patch('Vertices', [hx, hy, hz], 'FaceColor', 'none', 'EdgeColor', color);
+	//node.patch('Faces', elements, 'Vertices', vertices, 'FaceColor', 'none', 'EdgeColor', edgecolor);
+	//}}}
+} //}}}
Index: /issm/trunk-jpl/src/m/plot/plot_manager.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plot_manager.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/plot/plot_manager.js	(revision 26300)
@@ -1,130 +1,162 @@
-function plot_manager(md,options,subplotwidth,nlines,ncols,i){ //{{{
+'use strict';
+
+function plot_manager(md, options, subplotwidth, nlines, ncols, i) { //{{{
 	//PLOT__MANAGER - distribute the plots, called by plotmodel
 	//
 	//   Usage:
-	//      plot_manager(md,options,subplotwidth,i);
+	//      plot_manager(md, options, subplotwidth, i);
 	//
 	//   See also: PLOTMODEL, PLOT_UNIT
+	let canvas      = document.getElementById(options.getfieldvalue('canvasid'));
+	let data        = options.getfieldvalue('data'); // Get data to be displayed
+	let dataresults = null; 
+	let datatype    = 0;
+	let	data2       = null;
 			
-	//parse options and get a structure of options. 
-	checkplotoptions(md,options);
-	
-	//get data to be displayed
-	var data = options.getfieldvalue('data');
-	var canvas = initCanvas(options);
-	var gl = canvas.gl;
-
-	//figure out if this is a special plot
-	if (typeof data === 'string'){
-		
-		switch(data){
-
+	// Parse options and get a structure of options
+	checkplotoptions(md, options);
+
+	// Figure out if this is a special plot
+	if (typeof data === 'string') {
+		switch(data) {
 			case 'boundaries':
-				plot_boundaries(md,options,subplotwidth,i);
-				return;
+				plot_boundaries(md, options, subplotwidth, i);
+				return;
+				
 			case 'BC':
-				plot_BC(md,options,subplotwidth,i,data);
-				return;
+				plot_BC(md, options, subplotwidth, i, data);
+				return;
+				
 			case 'edges':
-				plot_edges(md,options,subplotwidth,i,data);
-				return;
+				plot_edges(md, options, subplotwidth, i, data);
+				return;
+				
 			case 'elementnumbering':
-				plot_elementnumbering(md,options,subplotwidth,i);
-				return;
+				plot_elementnumbering(md, options, subplotwidth, i);
+				return;
+				
 			case 'highlightelements':
-				plot_highlightelements(md,options,subplotwidth,i);
-				return;
+				plot_highlightelements(md, options, subplotwidth, i);
+				return;
+				
 			case 'qmumean':
-				plot_qmumean(md,options,nlines,ncols,i);
-				return;
+				plot_qmumean(md, options, nlines, ncols, i);
+				return;
+				
 			case 'qmustddev':
-				plot_qmustddev(md,options,nlines,ncols,i);
-				return;
+				plot_qmustddev(md, options, nlines, ncols, i);
+				return;
+				
 			case 'qmuhistnorm':
-				plot_qmuhistnorm(md,options,nlines,ncols,i);
-				return;
+				plot_qmuhistnorm(md, options, nlines, ncols, i);
+				return;
+				
 			case 'qmu_mass_flux_segments':
-				plot_qmu_mass_flux_segments(md,options,nlines,ncols,i);
-				return;
+				plot_qmu_mass_flux_segments(md, options, nlines, ncols, i);
+				return;
+				
 			case 'part_hist':
-				plot_parthist(md,options,nlines,ncols,i);
-				return;
+				plot_parthist(md, options, nlines, ncols, i);
+				return;
+				
 			case 'part_hist_n':
-				plot_parthistn(md,options,nlines,ncols,i);
-				return;
+				plot_parthistn(md, options, nlines, ncols, i);
+				return;
+				
 			case 'part_hist_w':
-				plot_parthistw(md,options,nlines,ncols,i);
-				return;
+				plot_parthistw(md, options, nlines, ncols, i);
+				return;
+				
 			case 'elements_type':
-				plot_elementstype(md,options,subplotwidth,i);
-				return;
+				plot_elementstype(md, options, subplotwidth, i);
+				return;
+				
 			case 'vertexnumbering':
-				plot_vertexnumbering(md,options,subplotwidth,i);
-				return;
+				plot_vertexnumbering(md, options, subplotwidth, i);
+				return;
+				
 			case 'highlightvertices':
-				plot_highlightvertices(md,options,subplotwidth,i);
-				return;
+				plot_highlightvertices(md, options, subplotwidth, i);
+				return;
+				
 			case 'basal_drag':
-				plot_basaldrag(md,options,subplotwidth,i,data);
-				return;
+				plot_basaldrag(md, options, subplotwidth, i, data);
+				return;
+				
 			case 'basal_dragx':
-				plot_basaldrag(md,options,subplotwidth,i,data);
-				return;
+				plot_basaldrag(md, options, subplotwidth, i, data);
+				return;
+				
 			case 'basal_dragy':
-				plot_basaldrag(md,options,subplotwidth,i,data);
-				return;
+				plot_basaldrag(md, options, subplotwidth, i, data);
+				return;
+				
 			case 'driving_stress':
-				plot_drivingstress(md,options,subplotwidth,i);
-				return;
+				plot_drivingstress(md, options, subplotwidth, i);
+				return;
+				
 			case 'mesh':
-				plot_mesh(md,options,canvas);
-				return;
+				plot_mesh(md, options, canvas);
+				return;
+				
 			case 'none':
-				//if (!(options.exist('overlay'))){
-				//	plot_none(md,options,nlines,ncols,i);
-				//}
-				return;
+				return;
+				
 			case 'penalties':
-				plot_penalties(md,options,subplotwidth,i);
-				return;
+				plot_penalties(md, options, subplotwidth, i);
+				return;
+				
 			case 'partition':
-				plot_partition(md,options,nlines,ncols,i);
-				return;
+				plot_partition(md, options, nlines, ncols, i);
+				return;
+				
 			case 'referential':
-				plot_referential(md,options,nlines,ncols,i);
-				return;
+				plot_referential(md, options, nlines, ncols, i);
+				return;
+				
 			case 'riftvel':
-				plot_riftvel(md,options,nlines,ncols,i);
-				return;
+				plot_riftvel(md, options, nlines, ncols, i);
+				return;
+				
 			case 'riftnumbering':
-				plot_riftnumbering(md,options,nlines,ncols,i);
-				return;
+				plot_riftnumbering(md, options, nlines, ncols, i);
+				return;
+				
 			case 'rifts':
-				plot_rifts(md,options,nlines,ncols,i);
-				return;
+				plot_rifts(md, options, nlines, ncols, i);
+				return;
+				
 			case 'riftrelvel':
-				plot_riftrelvel(md,options,nlines,ncols,i);
-				return;
+				plot_riftrelvel(md, options, nlines, ncols, i);
+				return;
+				
 			case 'riftpenetration':
-				plot_riftpenetration(md,options,nlines,ncols,i);
-				return;
+				plot_riftpenetration(md, options, nlines, ncols, i);
+				return;
+				
 			case 'riftfraction':
-				plot_riftfraction(md,options,nlines,ncols,i);
-				return;
+				plot_riftfraction(md, options, nlines, ncols, i);
+				return;
+				
 			case 'sarpwr':
-				plot_sarpwr(md,options,subplotwidth,i);
-				return;
+				plot_sarpwr(md, options, subplotwidth, i);
+				return;
+				
 			case 'time_dependant':
-				plot_vstime(md,options,nlines,ncols,i);
-				return;
+				plot_vstime(md, options, nlines, ncols, i);
+				return;
+				
 			case 'icefront':
-				plot_icefront(md,options,subplotwidth,i,data);
-				return;
+				plot_icefront(md, options, subplotwidth, i, data);
+				return;
+				
 			case 'segments':
-				plot_segments(md,options,subplotwidth,i,data);
-				return;
+				plot_segments(md, options, subplotwidth, i, data);
+				return;
+				
 			case 'quiver':
-				plot_quiver(md,options,canvas);
-				return;
+				plot_quiver(md, options, canvas);
+				return;
+				
 			case 'strainrate_tensor':
 			case 'strainrate':
@@ -145,23 +177,27 @@
 			case 'deviatoricstress_principalaxis2':
 			case 'deviatoricstress_principalaxis3':
-				plot_tensor(md,options,subplotwidth,i,data);
-				return;
+				plot_tensor(md, options, subplotwidth, i, data);
+				return;
+				
 			case 'thermaltransient_results':
-				plot_thermaltransient_results(md,options,subplotwidth,i);
-				return;
+				plot_thermaltransient_results(md, options, subplotwidth, i);
+				return;
+				
 			case 'transient_movie':
-				plot_transient_movie(md,options,canvas);
-				return;
+				plot_transient_movie(md, options, canvas);
+				return;
+				
 			case 'transient_results':
-				plot_transient_results(md,options,subplotwidth,i);
-				return;
+				plot_transient_results(md, options, subplotwidth, i);
+				return;
+				
 			case 'transient_field':
-				plot_transient_field(md,options,subplotwidth,i);
-				return;
+				plot_transient_field(md, options, subplotwidth, i);
+				return;
+				
 			default:
-				if (data in md){
-					data=md[data];
-				}
-				else{
+				if (data in md) {
+					data = md[data];
+				} else {
 					error('plot error message: data provided not supported yet. Type plotdoc for help');
 				}
@@ -169,40 +205,41 @@
 	}
 
-	//Figure out if this is a semi-transparent plot.
-	if (options.getfieldvalue('overlay','off')=='on'){
-		plot_overlay(md,data,options,canvas);
-	}
-
-	//Figure out if this is a semi-transparent plot.
-	if (options.exist('googlemaps')){
-		plot_googlemaps(md,data,options,nlines,ncols,i);
-		return;
-	}
-
-	//Figure out if this is a semi-transparent plot.
-	if (options.exist('gridded')){
-		plot_gridded(md,data,options,nlines,ncols,i);
-		return;
-	}
-
-	//Figure out if this is a Section plot
-	if (options.exist('sectionvalue')){
-		plot_section(md,data,options,nlines,ncols,i);
+	// Figure out if this is a semi-transparent plot.
+	if (options.getfieldvalue('overlay', 'off') === 'on') {
+		plot_overlay(md, data, options, canvas);
+		//return;
+	}
+
+	// Figure out if this is a semi-transparent plot.
+	if (options.exist('googlemaps')) {
+		plot_googlemaps(md, data, options, nlines, ncols, i);
+		return;
+	}
+
+	// Figure out if this is a semi-transparent plot.
+	if (options.exist('gridded')) {
+		plot_gridded(md, data, options, nlines, ncols, i);
+		return;
+	}
+
+	// Figure out if this is a Section plot
+	if (options.exist('sectionvalue')) {
+		plot_section(md, data, options, nlines, ncols, i);
 		return;
 	}
 
 	//Figure out if this is a Profile plot
-	if (options.exist('profile')){
-		plot_profile(md,data,options,nlines,ncols,i);
+	if (options.exist('profile')) {
+		plot_profile(md, data, options, nlines, ncols, i);
 		return;
 	}
 	
-	var	dataresults = processdata(md,data,options);
-	var	data2 = dataresults[0]; 
-	var	datatype = dataresults[1];
+	dataresults = processdata(md, data, options);
+	data2       = dataresults[0]; 
+	datatype    = dataresults[1];
 	
-	//plot unit
-	plot_unit(md,data2,datatype,options,canvas);
-
-	applyoptions(md,data2,options,canvas);
+	// Plot unit
+	plot_unit(md, data2, datatype, options, canvas);
+
+	applyoptions(md, data2, options, canvas);
 } //}}}
Index: /issm/trunk-jpl/src/m/plot/plot_mesh.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plot_mesh.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/plot/plot_mesh.js	(revision 26300)
@@ -7,64 +7,47 @@
 	//   See also: PLOTMODEL, PLOT_MANAGER
 
-	// If we already have the overlay and are using caching, short circuit
-	if ('mesh' in canvas.nodes && options.getfieldvalue('cachenodes', 'off') === 'on') {
-		return;
-	}
-	
-	/*
-		Local variables
-	*/
 	//{{{
 	//Process data and model
-	var meshresults = processmesh(md, [], options);
-	var x = meshresults[0]; 
-	var y = meshresults[1]; 
-	var z = meshresults[2]; 
-	var elements = meshresults[3]; 
-	var is2d = meshresults[4]; 
-	var isplanet = meshresults[5];
-		
-	//Compute coordinates and data range:
-	var xlim = options.getfieldvalue('xlim', [ArrayMin(x), ArrayMax(x)]);
-	var ylim = options.getfieldvalue('ylim', [ArrayMin(y), ArrayMax(y)]);
-	var zlim = options.getfieldvalue('zlim', [ArrayMin(z), ArrayMax(z)]);
-	
-	//Handle heightscale
-	var vertices, scale;
-	if (md.mesh.classname() !== 'mesh3dsurface') {
-		vertices = [x, y, z];
-		scale = [1, 1, options.getfieldvalue('heightscale', 1)];
-	}
-	else {
-		vertices = Node.prototype.scaleVertices(md, x, y, z, elements, options.getfieldvalue('heightscale', 1), options.getfieldvalue('maskregion',{'enabled':false}));
-		scale = [1, 1, 1];
-	}
+	let meshresults = processmesh(md, [], options);
+	meshresults = scaleMesh(md, meshresults, options);
+	let x = meshresults[0]; 
+	let y = meshresults[1]; 
+	let z = meshresults[2]; 
+	let elements = meshresults[3];
+	let is2d = meshresults[4]; 
+	let isplanet = meshresults[5];
+	let vertices = meshresults[6];
+	let scale = meshresults[7];
 	
 	//Compute gl variables:
-	var edgecolor = options.getfieldvalue('edgecolor', 'black');
-	var node = new Node(
+	let edgecolor = options.getfieldvalue('edgecolor', 'black');
+	let node = new Node(
 		'canvas', canvas,
 		'options', options,
+		'md', md,
 		'name', 'mesh',
-		'shaderName', 'Colored',
-		'alpha', options.getfieldvalue('alpha', 1.0),
-		//'center', [(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, md.mesh.classname() === 'mesh3dsurface' ? (zlim[0] + zlim[1]) / 2 : zlim[0]],
-		'center', [(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, (zlim[0] + zlim[1]) / 2],
-		'drawMode', canvas.gl.LINES,
-		'diffuseColor', edgecolor,
+		'cullFace', THREE.DoubleSide,
+		'shaderName', 'lines',
+		'opacity', options.getfieldvalue('opacity', 1.0),
 		'lineWidth', options.getfieldvalue('linewidth', 1),
-		'maskEnabled', options.getfieldvalue('innermask','off') == 'on',
-		'maskHeight', options.getfieldvalue('innermaskheight', 150.0) / options.getfieldvalue('heightscale', 1),
-		'maskColor', options.getfieldvalue('innermaskcolor',[0.0, 0.0, 1.0, 1.0]),
-		'rotation', [-90, 0, 0],
 		'scale', scale
 	);
 	//}}}
 	//{{{ node plot
-	if (elements[0].length==6){ //prisms
-	}
-	else if (elements[0].length==4){ //tetras
-	}
-	else{ //2D triangular elements
+	if (elements[0].length === 6) { //prisms
+		//We can skip bottom and every other side to avoid drawing edges twice.
+		let abc = elements.map(function(value, index) { return [value[0], value[1], value[2]]; }); //top
+		// let dfe = elements.map(function(value, index) { return [value[3], value[5], value[4]]; }); //bottom
+		// let aeb = elements.map(function(value, index) { return [value[0], value[4], value[1]]; }); //1st side upper right
+		let ade = elements.map(function(value, index) { return [value[0], value[3], value[4]]; }); //1st side lower left
+		// let bfc = elements.map(function(value, index) { return [value[1], value[5], value[2]]; }); //2nd side upper right
+		let bef = elements.map(function(value, index) { return [value[1], value[4], value[5]]; }); //2nd side lower left
+		// let cda = elements.map(function(value, index) { return [value[2], value[3], value[0]]; }); //3rd side upper right
+		let cfd = elements.map(function(value, index) { return [value[2], value[5], value[3]]; }); //3rd side lower left
+		let prismElements = abc.concat(ade, bef, cfd);
+		node.patch('Faces', prismElements, 'Vertices', vertices, 'FaceColor', 'none', 'EdgeColor', edgecolor);
+	} else if (elements[0].length === 4) { //tetras
+        // TODO: Implement handling for tetras
+	} else { // 2D triangular elements
 		node.patch('Faces', elements, 'Vertices', vertices, 'FaceColor', 'none', 'EdgeColor', edgecolor);
 	}
Index: /issm/trunk-jpl/src/m/plot/plot_overlay.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plot_overlay.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/plot/plot_overlay.js	(revision 26300)
@@ -1,98 +1,157 @@
-function plot_overlay(md, data, options, canvas){ //{{{
-	//PLOT_OVERLAY - Function for plotting a georeferenced image.  
-	//
-	//   Usage:
-	//      plot_overlay(md, data, options, canvas);
-	//
-	//   See also: PLOTMODEL, PLOT_MANAGER
+'use strict';
 
-	// If we already have the overlay and are using caching, short circuit
-	if ('overlay' in canvas.nodes && options.getfieldvalue('cachenodes', 'off') === 'on') {
-		return;
+/**
+ * Global Function: plotOverlay
+ *
+ * Plot a georeferenced image 
+ *
+ * Dependencies:
+ *      - /ext/proj4-<version>
+ *
+ * See Also:
+ *      - /js/plotmodel.js
+ *      - /js/plotmanager.js
+ */
+function plot_overlay(md, data, options, canvas) {
+    let elements        = null;
+    let meshresults     = null; // results from call to processmesh
+    let newX            = null; // scaled x-coordinates (array)
+    let newY            = null; // scaled y-coordinates (array)
+    let newZ            = null; // scaled z-coordinates (array)
+    let position        = null; // x-, y-, and z-coordinates of vertex (object)
+    let renderObject    = null; 
+    let reproject       = false; // default; whether or not to reproject overlay
+    let result          = null; // First, projection of radar overlay onto mesh; then, projection of overlay onto mesh
+    let scale           = null; // coordinate scalars (array)
+	let vertices        = null;
+    let x               = null; // x-coordinates from results (array)
+    let xlim            = null; // x-cooridinate limits (array)
+    let y               = null; // y-coordinates from results (array)
+    let ylim            = null; // y-cooridinate limits (array)
+    let z               = null; // z-coordinates from results (array)
+    
+	// Process data and model
+    meshresults = processmesh(md, [], options);
+	x           = meshresults[0]; 
+	y           = meshresults[1]; 
+	z           = meshresults[2]; 
+	elements    = meshresults[3];
+	
+	if (md.mesh.classname().startsWith('mesh2d')) {
+		if (vesl.helpers.isNaN(md.geometry.surface) || (md.geometry.surface[0] !== undefined && vesl.helpers.isNaN(md.geometry.surface[0]))) {
+			md.geometry.surface = NewArrayFill(x.length, 0);
+			z = NewArrayFill(x.length, 0);
+		} else {
+			z = md.geometry.surface;
+		}
 	}
 	
-	/*
-		Local variables
-	*/
-	//{{{
-	//Process data and model
-	var meshresults = processmesh(md, [], options);
-	var x = meshresults[0]; 
-	var y = meshresults[1]; 
-	var z = meshresults[2]; 
-	var elements = meshresults[3]; 
-	var is2d = meshresults[4]; 
-	var isplanet = meshresults[5];
-	if (md.mesh.classname() !== 'mesh3dsurface') z = md.geometry.surface;
+	// Compute coordinates and data range
+	xlim = options.getfieldvalue('xlim', [ArrayMin(x), ArrayMax(x)]);
+	ylim = options.getfieldvalue('ylim', [ArrayMin(y), ArrayMax(y)]);
 	
-	//Compute coordinates and data range:
-	var xlim = options.getfieldvalue('xlim', [ArrayMin(x), ArrayMax(x)]);
-	var ylim = options.getfieldvalue('ylim', [ArrayMin(y), ArrayMax(y)]);
-	var zlim = options.getfieldvalue('zlim', [ArrayMin(md.geometry.surface), ArrayMax(md.geometry.surface)]);
-	
-	//Handle radaroverlay
+	// Handle radaroverlay
 	if (md.radaroverlay.outerx) {
-		var result = Node.prototype.mergeVertices(x, y, z, elements, md.radaroverlay.outerx, md.radaroverlay.outery, md.radaroverlay.outerheight, md.radaroverlay.outerindex);
-		x = result.x;
-		y = result.y;
-		z = result.z;
-		elements = result.elements;
+		result      = Node.prototype.mergeVertices(x, y, z, elements, md.radaroverlay.outerx, md.radaroverlay.outery, md.radaroverlay.outerheight, md.radaroverlay.outerindex);
+		x           = result.x;
+		y           = result.y;
+		z           = result.z;
+		elements    = result.elements;
 	}
 	
-	//Handle heightscale
-	var vertices, scale;
-	if (md.mesh.classname() !== 'mesh3dsurface') {
-		vertices = [x, y, z];
-		scale = [1, 1, options.getfieldvalue('heightscale', 1)];
-	}
-	else {
-		vertices = Node.prototype.scaleVertices(md, x, y, z, elements, options.getfieldvalue('heightscale', 1), options.getfieldvalue('maskregion',{'enabled':false}));
-		scale = [1, 1, 1];
+	// Handle heightscale
+	renderObject = options.getfieldvalue('render', {});
+	
+	if ('overlay' in renderObject && renderObject.overlay.reproject !== undefined) {
+		reproject = renderObject.overlay.reproject;
 	}
 	
-	//Compute gl variables:
-	var edgecolor = options.getfieldvalue('edgecolor', 'black');
-	var texture = initTexture(gl, options.getfieldvalue('overlay_image'));
-	var node = new Node(
+	if (reproject) {
+		//NOTE: This is being hardcoded for now, as we want to display Josh's greenland-ice-retreat model on a globe.
+		// If mesh3dprisims do not need to be displayed on a globe, add an option to the view/render objects to specify the reprojection.
+		// Currently, we are also hardcoding the reprojection from EPSG:3413 (WGS 84 / NSIDC Sea Ice Polar Stereographic North) to EPSG:4326 (WGS 84, standard 2d spherical projection))
+		// projection definitions are taken from http://epsg.io/XXXXX.js, where XXXXX is the projection number.
+		// Can be dynamically taken and retrieved via md.mesh.epsg, but again, hardcoded for efficiency.
+		proj4.defs("EPSG:3413","+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs"); // http://epsg.io/3413.js
+		//Don't need to redefine EPSG:4326, as it is defined by default - however, this is still included just in case.
+		// proj4.defs("EPSG:4326","+proj=longlat +datum=WGS84 +no_defs"); // http://epsg.io/4326.js
+		//Create empty copies of XYZ to avoid rescaling md.geomtery.surface in the case that z = md.geomtery.surface
+		newX = NewArrayFill(x.length, 0);
+		newY = NewArrayFill(x.length, 0);
+		newZ = NewArrayFill(x.length, 0);
+		
+		for (let i = 0; i < x.length; ++i) {
+			result      = proj4('EPSG:3413','EPSG:4326', [x[i], y[i]]);
+			position    = vesl.geo.geographicToCartesian(vesl.EARTH_RADIUS + z[i], result[1], result[0], true);
+			newX[i]     = position.x;
+			newY[i]     = position.y;
+			newZ[i]     = position.z;
+		}
+		
+		x           = newX;
+		y           = newY;
+		z           = newZ;
+		vertices    = Node.prototype.scaleVertices(md, x, y, z, elements, options.getfieldvalue('heightscale', 1), options.getfieldvalue('maskregion',{'enabled':false, 'reproject':reproject}), true);
+		scale       = [1, 1, 1];
+    } else {
+		vertices    = Node.prototype.scaleVertices(md, x, y, z, elements, options.getfieldvalue('heightscale', 1), options.getfieldvalue('maskregion',{'enabled':false, 'reproject':reproject}), true);
+		scale       = [1, 1, 1];
+	}
+	
+	// Compute coordinates and data range
+	xlim = options.getfieldvalue('xlim', [ArrayMin(x), ArrayMax(x)]);
+	ylim = options.getfieldvalue('ylim', [ArrayMin(y), ArrayMax(y)]);
+	
+	// Compute GL variables
+	let texture         = new THREE.TextureLoader().load(options.getfieldvalue('overlay_image'));
+	let groundEnabled   = false;
+	let name            = 'overlay';
+	
+	if ('ground' in renderObject) {
+    	groundEnabled = renderObject.ground.enabled;
+    }
+    
+	if ('overlay' in renderObject) {
+    	name = renderObject.overlay.name;
+    }
+    
+	let shaderName = groundEnabled ? 'ground' : 'overlay';
+	
+	let node = new Node(
 		'canvas', canvas,
 		'options', options,
-		'name', 'overlay',
-		'shaderName', 'ground' in options.getfieldvalue('render', {}) ? 'GroundFromSpace' : 'TexturedDiffuse',
-		'alpha', options.getfieldvalue('outeralpha', 1.0),
-		//'center', [(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, md.mesh.classname() === 'mesh3dsurface' ? (zlim[0] + zlim[1]) / 2 : zlim[0]],
-		'center', [(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, (zlim[0] + zlim[1]) / 2],
-		'ambientColor', [0.1, 0.1, 0.1 ,1.0],
-		'diffuseColor', [1.0, 1.0, 1.0 ,1.0],
-		'specularStrength', 0.0,
-		'maskEnabled', options.getfieldvalue('outermask','off') == 'on',
-		'maskHeight', options.getfieldvalue('outermaskheight', 150.0),
-		'maskColor', options.getfieldvalue('outermaskcolor',[0.0, 0.0, 1.0, 1.0]),
+		'md', md,
+		'name', name,
+		'shaderName', shaderName,
+		'opacity', options.getfieldvalue('outeropacity', 1.0),
+		'cullFace', THREE.DoubleSide,
 		'texture', texture,
-		'rotation', [-90, 0, 0],
 		'scale', scale
 	);
-	//}}}
+	canvas.overlayNode = node;
 
-	var xRange = xlim[1] - xlim[0];
-	var yRange = ylim[1] - ylim[0];
-	var coordArray = [new Array(x.length), new Array(x.length)];
+	let xRange      = xlim[1] - xlim[0];
+	let yRange      = ylim[1] - ylim[0];
+	let coordArray  = [new Array(x.length), new Array(x.length)];
 	
-	//generate mesh:
-	if (md.mesh.classname() == 'mesh3dsurface') {
-		var xyz, magnitude;
-		for(var i = 0; i < x.length; i++){
-			xyz = vec3.fromValues(vertices[0][i], vertices[1][i], vertices[2][i]);
-			magnitude = vec3.length(xyz);
+	// Generate mesh
+	if (reproject || md.mesh.classname().startsWith('mesh3d')) {
+		let magnitude   = 0;
+		let xyz         = null;
 		
-			coordArray[0][i] = Math.atan2(xyz[1], xyz[0]) / (2 * Math.PI) + 0.5;
-			coordArray[1][i] = Math.asin(xyz[2] / magnitude) / Math.PI + 0.5;
+		for (let i = 0; i < x.length; ++i) {
+			xyz                 = vec3.fromValues(vertices[0][i], vertices[1][i], vertices[2][i]);
+			magnitude           = vec3.length(xyz);
+		
+			coordArray[0][i]    = Math.atan2(xyz[1], xyz[0]) / (2 * Math.PI) + 0.5;
+			coordArray[1][i]    = Math.asin(xyz[2] / magnitude) / Math.PI + 0.5;
 		}
-	}
-	else {
-		for(var i = 0; i < x.length; i++){
+	} else {
+		for (let i = 0; i < x.length; ++i) {
 			coordArray[0][i] = (vertices[0][i] - xlim[0]) / xRange;
 			coordArray[1][i] = (vertices[1][i] - ylim[0]) / yRange;
 		}
 	}
+	
 	node.patch('Faces', elements, 'Vertices', vertices, 'FaceVertexCData', coordArray, 'FaceColor', 'interp');
-} //}}}
+}
Index: /issm/trunk-jpl/src/m/plot/plot_quiver.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plot_quiver.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/plot/plot_quiver.js	(revision 26300)
@@ -21,5 +21,7 @@
 	var is2d = meshresults[4]; 
 	var isplanet = meshresults[5];
-	if (md.mesh.classname() !== 'mesh3dsurface') z = md.geometry.surface;
+	if (!md.mesh.classname().startsWith('mesh3d')) {
+		z = md.geometry.surface;
+	}
 		
 	//Compute coordinates and data range:
@@ -35,10 +37,10 @@
 	//Handle heightscale
 	var vertices, scale;
-	if (md.mesh.classname() !== 'mesh3dsurface') {
+	if (!md.mesh.classname().startsWith('mesh3d')) {
 		vertices = [x, y, z];
 		scale = [1, 1, options.getfieldvalue('heightscale', 1)];
 	}
 	else {
-		vertices = Node.prototype.scaleVertices(md, x, y, z, elements, options.getfieldvalue('heightscale', 1), options.getfieldvalue('maskregion',{'enabled':false}));
+		vertices = Node.prototype.scaleVertices(md, x, y, z, elements, options.getfieldvalue('heightscale', 1), options.getfieldvalue('maskregion',{'enabled':false}), true);
 		scale = [1, 1, 1];
 	}
@@ -51,5 +53,5 @@
 		'name', 'quiver',
 		'shaderName', 'Colored',
-		'alpha', options.getfieldvalue('alpha', 1.0),
+		'opacity', options.getfieldvalue('opacity', 1.0),
 		//'center', [(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, md.mesh.classname() === 'mesh3dsurface' ? (zlim[0] + zlim[1]) / 2 : zlim[0]],
 		'center', [(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, (zlim[0] + zlim[1]) / 2],
Index: /issm/trunk-jpl/src/m/plot/plot_transient_movie.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plot_transient_movie.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/plot/plot_transient_movie.js	(revision 26300)
@@ -1,119 +1,177 @@
-function plot_transient_movie(md, options, canvas) {//{{{
-	/**
-	 * PLOT_TRANSIENT_MOVIE - plot a transient result as a movie
-	 *
-	 * Usage:
-	 *	plot_transient_movie(md, options, canvas);
-	 *
-	 * See also: PLOTMODEL, PLOT_MANAGER
-	 */
+'use strict';
 
-	// Loop over the time steps
-	let data	 	= options.getfieldvalue('transient_field_data');
-	let dataType	= {};
-	let steps 		= new Array(data.length);
-
-	for (let i = 0, numSteps = steps.length; i < numSteps; ++i) {
+/**
+ * Global Function: plot_transient_movie
+ *
+ * Parameters:
+ *		options			- Object containing a number of property/value pairs.
+ *		animation		- Object containing a number of property/value pairs related to transient solution animation.
+ *		progressText	- Object containing a number of property/value pairs related to updating the playback slider text on frame change.
+ *		unit 			- String (default: '') which is appended to the frame index with a single white space before updating #playback-progress-text on frame change.
+ *		update			- Boolean (default: true) indicating whether or not to update #playback-progress-text on frame change with the frame index. Set this to 'false' if you would rather manage the update of #playback-progress-text via 'onMovieUpdate' handler (or by some other means).
+ * 
+ * See Also:
+ *		- /web/js/plotmodel.js
+ *		- /web/js/plot_manager.js
+ *
+ * TODO:
+ *		- Update function signature/handling to use ES6 default parameters for 'unit' and 'handling'.
+ */
+function plot_transient_movie(md, options, canvas) {
+	//loop over the time steps
+	let data = options.getfieldvalue('transient_field_data');
+	let steps = new Array(data.length);
+	
+	for (let i = 0; i < steps.length; i++) {
 		steps[i] = i;
 	}
-
-	// Calculate caxis
+	
+	//calculate caxis
 	if (!options.exist('caxis')) {
-		let range 			= [Infinity, -Infinity];
-		let	dataResults;
-
+		let range = [Infinity, -Infinity];
+		let dataresults;
 		for (let i in steps) {
-			dataResults = processdata(md, data[i], options);
-			range[0] = Math.min(range[0], ArrayMin(dataResults[1]));
-			range[1] = Math.max(range[1], ArrayMax(dataResults[1]));
+			dataresults = processdata(md, data[i], options);
+			range[0] = Math.min(range[0], ArrayMin(dataresults[1]));
+			range[1] = Math.max(range[1], ArrayMax(dataresults[1]));
 		}
-
-		dataType = dataResults[1];
 		options.addfielddefault('caxis', range);
 	}
+	
+	//Create unit node 
+	let dataresults = processdata(md, data[0], options);
+	let data2 = dataresults[0]; 
+	let datatype = dataresults[1];
+	plot_unit(md, data2, datatype, options, canvas);
+	let unitOptions = options.getfieldvalue('unitOptions', {'name' : 'unit'});
+	let unitName 	= unitOptions.name;
+	let node 		= canvas.unitNodes[unitName];
+	
+	//process data
+	let processedData = [];
+	for (let i in steps) {
+		dataresults = processdata(md, data[i].slice(), options);
+		processedData[i] = dataresults[0];
+	}
+	
+	//process mesh
+	let meshresults = processmesh(md, data2, options);
+	meshresults = scaleMesh(md, meshresults, options);
+	let x = meshresults[0]; 
+	let y = meshresults[1]; 
+	let z = meshresults[2]; 
+	let elements = meshresults[3];
+	let is2d = meshresults[4]; 
+	let isplanet = meshresults[5];
+	let vertices = meshresults[6];
+	let scale = meshresults[7];
+	
+	//process options
+	if (canvas.animation !== undefined && canvas.animation.handler !== undefined) {
+		clearInterval(canvas.animation.handler);
+	}
+	let animation = options.getfieldvalue('animation', {});
+	
+	let progressText = null;
+	if (!vesl.helpers.isEmptyOrUndefined(animation.progressText)) {
+		progressText = {
+					update	: defaultFor(animation.progressText.update, true),
+					unit	: defaultFor(animation.progressText.unit,  	'')
+				};
+	} else {
+		progressText = {
+					update	: false,
+					unit	: ''
+				};
+	}
+	canvas.animation = {
+		frame: defaultFor(animation.frame, 			0),
+		play: defaultFor(animation.play, 			true),
+		fps: defaultFor(animation.fps, 				4),
+		interval: defaultFor(animation.interval, 	1000 / defaultFor(animation.fps, 4)),
+		loop: defaultFor(animation.loop, 			true),
+		handler: 						{}, // Initialized below
+		progressText: 						progressText
+	}
+	//display movie
+	canvas.unitMovieData = processedData;
+	canvas.animation.totalFrames = processedData.length;
+	canvas.animation.frame = 0;
 
-	// Create unit node if it does not already exist
-	if (!('unit' in canvas.nodes)) {
-		let	dataResults = processdata(md, data[0],options);
-		let	data2 		= dataResults[0];
-		let	dataType 	= dataResults[1];
-
-		// Plot unit
-		plot_unit(md,data2,dataType,options,canvas);
+	let slider = null;
+	
+	if (!vesl.helpers.isEmptyOrUndefined(canvas.playbackControls)) {
+		if (!vesl.helpers.isEmptyOrUndefined(canvas.playbackControls.slider)) {
+			slider = canvas.playbackControls.slider;
+		}
+		
+		
+		if (!vesl.helpers.isEmptyOrUndefined(canvas.playbackControls.progressText)) {
+			progressText = canvas.playbackControls.progressText;
+		}
 	}
-
-	// Setup rendering node
-	let node = canvas.nodes.unit;
-
-	node.options 	= options;
-	node.alpha 		= options.getfieldvalue('alpha', 1.0);
-	node.caxis 		= options.getfieldvalue('caxis');
-	node.enabled 	= options.getfieldvalue('nodata', 'off') === 'off';
-	node.log 		= options.getfieldvalue('log', false);
-
-	// Process data
-	let	dataResults;
-	let processedData = [];
-
-	for (let i in steps) {
-		dataResults 		= processdata(md, data[i].slice(), options);
-		processedData[i] 	= dataResults[0];
-	}
-
-	// Display movie
-	canvas.unitMovieData		= processedData;
-	canvas.animation.frame 		= 0;
-	canvas.animation.handler 	= setInterval(function () {
+	
+	clearInterval(canvas.animation.handler);
+	canvas.animation.handler = setInterval(function() {
 		// Update current animation frame
-		let frame 		= canvas.animation.frame;
-		let numFrames 	= steps.length;
-
+		let frame = canvas.animation.frame;
+		
 		if (canvas.animation.play) {
-			if (canvas.animation.increment) {
-				if (frame >= numFrames - 1) {
-					if (canvas.animation.loop) {
-						frame = 0;
-					} else {
-						toggleMoviePlay(canvas);
-					}
+			if (frame >= steps.length - 1) {
+				if (canvas.animation.loop) {
+					frame = 0;
 				} else {
-					frame = (frame + 1) % numFrames;
+					canvas.animation.play = !canvas.animation.play;
 				}
+			} else {
+				frame = (frame + 1) % steps.length;
 			}
 		}
-
-		// If frame has changed, update unit node and data marker display
+		
+		//If frame has changed, update unit node and data marker display.
 		if (frame !== canvas.animation.lastFrame) {
+			canvas.state.dispatchEvent('onMovieUpdate', canvas, frame, node);
+			
+			//Rescale in case surface has changed
+			if (md.mesh.classname() === 'mesh3dsurface' || md.mesh.classname() === 'mesh3dprisms') {
+				vertices = Node.prototype.scaleVertices(md, x, y, z, elements, options.getfieldvalue('heightscale', 1), options.getfieldvalue('maskregion',{'enabled':false, 'reproject':false}), true);
+			} else {
+				vertices = Node.prototype.scaleVertices(md, x, y, z, elements, options.getfieldvalue('heightscale', 1), options.getfieldvalue('maskregion',{'enabled':false, 'reproject':false}), true);
+				//vertices = [x, y, z];
+			}
+	
+			node.updateBuffer('Vertices', vertices);
 			node.updateBuffer('Coords', processedData[frame]);
-			canvas.unitData = processedData[frame];
-
+			canvas.unitData[unitName] = processedData[frame];
 			if (canvas.graph.enabled) {
 				vesl.graph.draw(canvas);
 			}
-
-			if (!vesl.helpers.isEmptyOrUndefined(canvas.playbackControls.slider)) {
-				canvas.playbackControls.slider.val(frame);
+			if (slider != null) {
+				slider.val(frame);
 			}
-
-			if (!vesl.helpers.isEmptyOrUndefined(canvas.playbackControls.progressText)) {
-				canvas.playbackControls.progressText.html(steps[frame].toFixed(0) + ' ' + options.getfieldvalue('movietimeunit', 'yr'));
+			if (!vesl.helpers.isEmptyOrUndefined(progressText) && canvas.animation.progressText.update) {
+				if (canvas.animation.progressText.unit === '') {
+					progressText.html(steps[frame].toFixed(0));
+				} else {
+					progressText.html(steps[frame].toFixed(0) + ' ' + canvas.animation.progressText.unit);
+				}
 			}
-
-			if (!vesl.helpers.isEmptyOrUndefined(canvas.nodes.quiver)) {
-				plot_quiver(md, options, canvas, false);
-			}
+			//if (!vesl.helpers.isEmptyOrUndefined(canvas.nodes.quiver)) {
+			//	plot_quiver(md,options,canvas,false);
+			//}
+			
 		}
-
-		// Save new frame info
-		canvas.animation.lastFrame 	= canvas.animation.frame;
-		canvas.animation.frame 		= frame;
+		
+		//Save new frame info.
+		canvas.animation.lastFrame = canvas.animation.frame;
+		canvas.animation.frame = frame;
 	}, canvas.animation.interval);
-
-	// Update progress bar with new frame info
-	if (!vesl.helpers.isEmptyOrUndefined(canvas.playbackControls.slider)) {
-		canvas.playbackControls.slider.val(canvas.animation.frame);
-		canvas.playbackControls.slider.setUpperBound(steps.length - 1);
+	
+	//Update progress bar with new frame info.
+	if (!vesl.helpers.isEmptyOrUndefined(slider)) {
+		slider.max(steps.length - 1);
+		slider.val(canvas.animation.frame);
 	}
-
+				
 	applyoptions(md, [], options, canvas);
-}//}}}
+}
Index: /issm/trunk-jpl/src/m/plot/plot_unit.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plot_unit.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/plot/plot_unit.js	(revision 26300)
@@ -1,2 +1,4 @@
+'use strict';
+
 function plot_unit(md, data, datatype, options, canvas) { //{{{
 	//PLOT_UNIT - unit plot, display data
@@ -6,121 +8,123 @@
 	//
 	//   See also: PLOTMODEL, PLOT_MANAGER
-
-	var name = 'unit';
-	if ('unit' in canvas.nodes) {
-		if (options.getfieldvalue('clf','on')=='on') {
-			for (var node in canvas.nodes) {
-				if (vesl.string.startsWith(node, 'unit')) {
-					delete canvas.octrees[node];
-					delete canvas.nodes[node];
-				}
+	
+	//{{{
+	// Process data and model
+	let meshresults = processmesh(md, data, options);
+	meshresults = scaleMesh(md, meshresults, options);
+	let x = meshresults[0]; 
+	let y = meshresults[1]; 
+	let z = meshresults[2]; 
+	let elements = meshresults[3];
+	let is2d = meshresults[4]; 
+	let isplanet = meshresults[5];
+	let vertices = meshresults[6];
+	let scale = meshresults[7];
+	
+	//Compute gl variables:
+	let edgecolor = options.getfieldvalue('edgecolor', [1.0, 1.0, 1.0, 1.0]);
+	let maskzeros = options.getfieldvalue('maskzeros', {});
+	
+	let unitOptions = options.getfieldvalue('unitOptions', {'name' : 'unit'});
+	let unitName 	= unitOptions.name;
+	
+	let node = new Node(
+		'canvas', canvas,
+		'options', options,
+		'md', md,
+		'name', unitName,
+		'shaderName', 'unit',
+		'opacity', options.getfieldvalue('opacity', 1.0),
+		'caxis', options.getfieldvalue('caxis',[ArrayMin(data), ArrayMax(data)]),
+		'cullFace', THREE.FrontSide,
+		'enabled', options.getfieldvalue('nodata', 'off') === 'off',
+		'log', options.getfieldvalue('log',false),
+		'maskObject', options.getfieldvalue('maskregion', {'enabled': false}),
+		'maskZerosColor', defaultFor(maskzeros.color, [1.0, 1.0, 1.0, 1.0]),
+		'maskZerosEnabled', defaultFor(maskzeros.enabled, false),
+		'maskZerosTolerance', defaultFor(maskzeros.tolerance, 1e-3),
+		'maskZerosZeroValue', defaultFor(maskzeros.zeroValue, 0.5),
+		'scale', scale
+	);
+	//}
+	
+	if (vesl.helpers.isEmptyOrUndefined(canvas.unitNodes)) {
+		canvas.unitNodes = {};
+	}
+	
+	if (vesl.helpers.isEmptyOrUndefined(canvas.unitData)) {
+		canvas.unitData = {};
+	}
+	
+	// If option 'clf' is on, remove all cached data associated with this unit
+	if (options.getfieldvalue('clf', 'on') === 'on') {
+		delete canvas.unitNodes[unitName];
+		canvas.unitData[unitName] = data;
+		
+		for (let i = 0, numUnits = canvas.state.scene.children.length; i < numUnits; ++i) {
+			if (canvas.state.scene.children[i].name === unitName) {
+				canvas.state.scene.children.splice(i, 1);
+				break;
 			}
 		}
-		else {
-			name = 'unit' + Object.keys(canvas.nodes).length;
-		}
-	}
-
-	//{{{ declare variables:
-	//Process data and model
-	var meshresults = processmesh(md, data, options);
-	var x = meshresults[0]; 
-	var y = meshresults[1]; 
-	var z = meshresults[2]; 
-	var elements = meshresults[3];
-	var is2d = meshresults[4]; 
-	var isplanet = meshresults[5];
-	if (md.mesh.classname() !== 'mesh3dsurface') {
-		if (vesl.helpers.isNaN(md.geometry.surface) || (md.geometry.surface[0] !== undefined && vesl.helpers.isNaN(md.geometry.surface[0]))) {
-			md.geometry.surface = NewArrayFill(x.length, 0);
-		}
-		z = md.geometry.surface;
 	}
 	
-	//Compute coordinates and data range:
-	var xlim = options.getfieldvalue('xlim', [ArrayMin(x), ArrayMax(x)]);
-	var ylim = options.getfieldvalue('ylim', [ArrayMin(y), ArrayMax(y)]);
-	var zlim = options.getfieldvalue('zlim', [ArrayMin(z), ArrayMax(z)]);
+	canvas.unitNodes[unitName] = node;
 	
-	//Handle heightscale
-	var vertices, scale;
-	if (md.mesh.classname() !== 'mesh3dsurface') {
-		vertices = [x, y, z];
-		scale = [1, 1, options.getfieldvalue('heightscale', 1)];
-	}
-	else {
-		vertices = Node.prototype.scaleVertices(md, x, y, z, elements, options.getfieldvalue('heightscale', 1), options.getfieldvalue('maskregion',{'enabled':false}));
-		scale = [1, 1, 1];
-	}
-
-	//Compute gl variables:
-	var edgecolor = options.getfieldvalue('edgecolor', [1.0, 1.0, 1.0 ,1.0]);
-	var maskzeros = options.getfieldvalue('maskzeros', {});
-	var render = options.getfieldvalue('render', {});
-	var cullFace = ("unit" in render) ? canvas.gl[render.unit.cullFace] : canvas.gl.BACK;
-	var node = new Node(
-		'canvas', canvas,
-		'options', options,
-		'name', name,
-		'shaderName', 'TexturedDiffuse',
-		'alpha', options.getfieldvalue('alpha', 1.0),
-		'caxis', options.getfieldvalue('caxis',[ArrayMin(data), ArrayMax(data)]),
-		'center', [(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, (zlim[0] + zlim[1]) / 2],
-		'cullFace', cullFace,
-		'lightingBias', canvas.view.lightingBias,
-		'diffuseColor', edgecolor,
-		'specularStrength', 0.0,
-		'enabled', options.getfieldvalue('nodata','off') == 'off' || (("unit" in render) && render.unit.enabled),
-		'log', options.getfieldvalue('log',false),
-		'maskEnabled', options.getfieldvalue('innermask','off') == 'on',
-		'maskHeight', options.getfieldvalue('innermaskheight', 150.0) / options.getfieldvalue('heightscale', 1),
-		'maskColor', options.getfieldvalue('innermaskcolor',[0.0, 0.0, 1.0, 1.0]),
-		'maskObject', options.getfieldvalue('maskregion',{'enabled':false}),
-		'maskZerosColor', defaultFor(maskzeros.color,[1.0, 1.0, 1.0, 1.0]),
-		'maskZerosEnabled', defaultFor(maskzeros.enabled,false),
-		'maskZerosTolerance', defaultFor(maskzeros.tolerance,1e-3),
-		'maskZerosZeroValue', defaultFor(maskzeros.zeroValue,0.5),
-		'rotation', [-90, 0, 0],
-		'scale', ("unit" in render) ? [render.unit.scale, render.unit.scale, render.unit.scale] : scale
-	);
-	//}
-	if (options.getfieldvalue('clf','on')=='on') {
-		canvas.unitNode = node;
-		canvas.unitData = data;
-	}
 	//}}}
-	switch(datatype){
+	switch(datatype) {
 		//{{{ element plot
 		case 1:
 			//WARNING: NaN are not properly found (NaN != NaN = true)
-			pos=ArrayFindNot(data, NaN); //needed for element on water
-			if (elements[0].length==6){ //prisms
-			}
-			else if (elements[0].length==4){ //tetras
-			}
-			else{ //triangular elements
+			let pos = ArrayFindNot(data, NaN); //needed for element on water
+			
+			if (elements[0].length === 6) { // prisms
+				let abc = elements.map(function(value, index) { return [value[0], value[1], value[2]]; }); //top
+				let dfe = elements.map(function(value, index) { return [value[3], value[5], value[4]]; }); //bottom
+				let aeb = elements.map(function(value, index) { return [value[0], value[4], value[1]]; }); //1st side upper right
+				let ade = elements.map(function(value, index) { return [value[0], value[3], value[4]]; }); //1st side lower left
+				let bfc = elements.map(function(value, index) { return [value[1], value[5], value[2]]; }); //2nd side upper right
+				let bef = elements.map(function(value, index) { return [value[1], value[4], value[5]]; }); //2nd side lower left
+				let cda = elements.map(function(value, index) { return [value[2], value[3], value[0]]; }); //3rd side upper right
+				let cfd = elements.map(function(value, index) { return [value[2], value[5], value[3]]; }); //3rd side lower left
+				let prismElements = abc.concat(dfe, aeb, ade, bfc, bef, cda, cfd);
+				node.patch('Faces', prismElements, 'Vertices', vertices, 'FaceVertexCData', data, 'FaceColor', 'flat', 'EdgeColor', edgecolor);
+			} else if (elements[0].length === 4) { // tetras
+    			// TODO: Implement handling for tetras
+			} else { // triangular elements
 				node.patch('Faces', elements, 'Vertices', vertices, 'FaceVertexCData', data, 'FaceColor', 'flat', 'EdgeColor', edgecolor);
 			}
+			
 			break;
 		//}}}
 		//{{{ node plot
 		case 2:
-			if (elements[0].length==6){ //prisms
-			}
-			else if (elements[0].length==4){ //tetras
-			}
-			else{ //triangular elements	
+			if (elements[0].length === 6) { // prisms
+				let abc = elements.map(function(value, index) { return [value[0], value[1], value[2]]; }); //top
+				let dfe = elements.map(function(value, index) { return [value[3], value[5], value[4]]; }); //bottom
+				let aeb = elements.map(function(value, index) { return [value[0], value[4], value[1]]; }); //1st side upper right
+				let ade = elements.map(function(value, index) { return [value[0], value[3], value[4]]; }); //1st side lower left
+				let bfc = elements.map(function(value, index) { return [value[1], value[5], value[2]]; }); //2nd side upper right
+				let bef = elements.map(function(value, index) { return [value[1], value[4], value[5]]; }); //2nd side lower left
+				let cda = elements.map(function(value, index) { return [value[2], value[3], value[0]]; }); //3rd side upper right
+				let cfd = elements.map(function(value, index) { return [value[2], value[5], value[3]]; }); //3rd side lower left
+				let prismElements = abc.concat(dfe, aeb, ade, bfc, bef, cda, cfd);
+				node.patch('Faces', prismElements, 'Vertices', vertices, 'FaceVertexCData', data, 'FaceColor', 'interp', 'EdgeColor', edgecolor);
+			} else if (elements[0].length === 4) { // tetras
+    			// TODO: Implement handling for tetras
+			} else { // triangular elements	
 				node.patch('Faces', elements, 'Vertices', vertices, 'FaceVertexCData', data, 'FaceColor', 'interp', 'EdgeColor', edgecolor);
 			}
+			
 			break;
 		//}}}
 		//{{{ quiver plot 
 		case 3:
-			if (is2d){
+			if (is2d) {
 				//plot_quiver(x, y, data(:, 1), data(:, 2), options);
-			}
-			else{
+			} else {
 				//plot_quiver3(x, y, z, data(:, 1), data(:, 2), data(:, 3), options);
 			}
+			
 			break;
 		//}}}
Index: /issm/trunk-jpl/src/m/plot/plotdoc.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plotdoc.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/plot/plotdoc.js	(revision 26300)
@@ -24,5 +24,5 @@
 	console.log('       	"height": height to spawn clouds at (ex: 7500)');
 	console.log('       	"quantity": quantity of clouds to spawn (ex: 10)');
-	console.log('       "caxis": modify  colorbar range. (array of type [a, b] where b>=a)');
+	console.log('       "caxis": modify colorbar range (array of type [a, b] where b>=a)');
 	console.log('       "colorbar": add colorbar (default "off", ex: "on", "off")');
 	console.log('       "colorbarid": colorbar canvas id (string)');
@@ -39,9 +39,10 @@
 	console.log('       "dataMarker": object cotaining data marker parameters. See webgl.js for defaults. (ex: {"enabled":true,"format":["<div id="sim-plot"></div>"],"labels":["thickness","velocity","value"],"animated":true})');
 	console.log('       	"enabled": toggle data marker displays (default true, ex: false)');
-	console.log('       	"image": image used for marking the clicked point (ex: "/canvas/data-markers/data-marker.svg")');
-	console.log('       	"labels": when displaying a sim-plot graph, display these model fields. (ex: ["thickness","velocity","value"])');
-	console.log('       	"font": font to be used for display (ex: "24px "Comic Sans MS", cursive")');
-	console.log('       	"format": an array compatible with sprintf that will be displayed as html (ex: ["X: %.2e<br>Y: %.2e<br>Z: %.2e]<br>Value: %0.1f","x","y","z","value"])');
-	console.log('      		"size": specifiy the width and height of the data markers (default [32,32], ex: [24,32], [105,10])');
+	console.log('       	"hasMarker": whether or not the data marker has a marker (default true, ex: false)');
+	console.log('       	"markerImgPath": path to image to use for marker (default "/canvas/data-markers/data-marker.svg")');
+	console.log('       	"hasTooltip": whether or not the data marker has a tooltip (default false, ex: true)');
+	console.log('       	"tooltipFormat": C-style format string to be used when on calls to vesl.DataMarker.tooltipContent to format passed data points (default "", ex: ["X: %.2e<br>Y: %.2e<br>Z: %.2e])');	
+	console.log('       	"width": width, in pixels, of the data marker (default 32)');	
+	console.log('       	"height": height, in pixels, of the data marker (default 32)');
 	console.log('       "displayview": print view value to console (default "off", ex: "on", "off")');
 	console.log('       "displayzoom": print zoom value to console (default "off", ex: "on", "off")');
@@ -60,8 +61,8 @@
 	console.log('       "outermask*": Special mask that colors all parts of a overlay mesh below a height a certain color. provide outermaskheight and outermaskcolor options also (default "off", ex: "on", "off")');
 	console.log('       "overlay": overlay a radar amplitude image behind (default "off", ex: "on", "off")');
-	console.log('       "overlay_image": path to overlay image (default "", ex: "./images/radar.png")');
+	console.log('       "overlay_image": path to overlay image (default "", ex: "./img/radar.png")');
 	console.log('       "quiver": add quiver plot overlay for velocities. (default "off", ex: "on", "off")');
 	console.log('       "scaling": scaling factor used by quiver plots. Default is 0.4');
-	console.log('       "alpha": transparency coefficient 0.0 to 1.0, the lower, the more transparent. (default 1.0, ex: 0.5, 0.25)');
+	console.log('       "opacity": transparency coefficient 0.0 to 1.0, the lower, the more transparent. (default 1.0, ex: 0.5, 0.25)');
 	console.log('       "render": a object containing a list of default object to render. (default {}, ex: {"sky", "space"})');
 	console.log('       	"sky": render the atmosphere. (ex: {"enabled":true})');
Index: /issm/trunk-jpl/src/m/plot/plotmodel.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plotmodel.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/plot/plotmodel.js	(revision 26300)
@@ -1,45 +1,54 @@
-function plotmodel(md){ //{{{
-	//Convert arguments to array: 
-	var args = Array.prototype.slice.call(arguments);
+'use strict';
 
-	//First process options
-	var options = new plotoptions(args.slice(1,args.length));
+function plotmodel(md) { //{{{
+	let args            = Array.prototype.slice.call(arguments); // Convert arguments to array 
+    let canvas          = null;
+    let ncols           = 0;
+    let nlines          = 0;
+    let numberofplots   = 0;
+    let options         = new plotoptions(args.slice(1, args.length)); // Process options
+    let subplotwidth    = Math.ceil(Math.sqrt(options.numberofplots)); // Get number of subplots
+    
+	// Get figure number and number of plots
+	numberofplots = options.numberofplots;
+
+	// If nlines and ncols specified, then bypass.
+	if (options.list[0].exist('nlines')) {
+		nlines = options.list[0].getfieldvalue('nlines');
+	} else {
+		nlines = Math.ceil(numberofplots / subplotwidth);
+	}
 	
-	//get number of subplots
-	subplotwidth=Math.ceil(Math.sqrt(options.numberofplots)); 
-	
-	//Get figure number and number of plots
-	numberofplots=options.numberofplots;
-
-	//if nlines and ncols specified, then bypass.
-	var nlines,ncols;
-	if (options.list[0].exist('nlines')){
-		nlines=options.list[0].getfieldvalue('nlines');
-	}
-	else {
-		nlines=Math.ceil(numberofplots/subplotwidth);
-	}
 	if (options.list[0].exist('ncols')){
-		ncols=options.list[0].getfieldvalue('ncols');
-	}
-	else {
-		ncols=subplotwidth;
+		ncols = options.list[0].getfieldvalue('ncols');
+	} else {
+		ncols = subplotwidth;
 	}
 	
 	//check that nlines and ncols were given at the same time!
-	if ((options.list[0].exist('ncols') & !options.list[0].exist('nlines')) | (options.list[0].exist('nlines') & !options.list[0].exist('ncols'))) throw Error('plotmodel error message: nlines and ncols need to be specified together, or not at all');
+	if ((options.list[0].exist('ncols') && !options.list[0].exist('nlines')) 
+	    || ((options.list[0].exist('nlines') && !options.list[0].exist('ncols')))) {
+    	    throw Error('plotmodel error message: nlines and ncols need to be specified together, or not at all');
+    }
 
-	//go through subplots
-	if (numberofplots){
-		//Reinitialize all canvases
-		for (var i=0;i<numberofplots;i++){
-			if (options.list[i].getfieldvalue('clf','on')!='off') document.getElementById(options.list[i].getfieldvalue('canvasid')).initialized = false;
+	// Go through subplots
+	if (numberofplots) {
+		// Go through all data plottable and close window if an error occurs
+
+		let canvases = [];
+		for (let i = 0; i < numberofplots; ++i) {
+			canvases[i] = document.getElementById(options.list[i].getfieldvalue('canvasid'));
+			canvases[i].state.dispatchEvent('onPlotModelStart', canvases[i], options.list[i]);
 		}
-		//Go through all data plottable and close window if an error occurs
-		for (var i=0;i<numberofplots;i++){
-			plot_manager(options.list[i].getfieldvalue('model',md),options.list[i],subplotwidth,nlines,ncols,i);
+		for (let i = 0; i < numberofplots; ++i) {
+			canvases[i].state.dispatchEvent('onPlotManagerStart', canvases[i], options.list[i]);
+			plot_manager(options.list[i].getfieldvalue('model',md), options.list[i], subplotwidth, nlines, ncols, i);
+			canvases[i].state.dispatchEvent('onPlotManagerEnd', canvases[i], options.list[i]);
 
-			//List all unused options
+			// List all unused options
 			options.list[i].displayunused();
+		}
+		for (let i = 0; i < numberofplots; ++i) {
+			canvases[i].state.dispatchEvent('onPlotModelEnd', canvases[i], options.list[i]);
 		}
 	}
Index: /issm/trunk-jpl/src/m/solve/WriteData.js
===================================================================
--- /issm/trunk-jpl/src/m/solve/WriteData.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/solve/WriteData.js	(revision 26300)
@@ -1,3 +1,3 @@
-function WriteData(fid,prefix){
+function WriteData(fid,prefix){ //{{{
 //WRITEDATA - write model field into binary buffer 
 //
@@ -74,4 +74,9 @@
 	}
 
+	let recordlengthtype = 'int';
+	if (svnversion >= 22708) {
+		recordlengthtype = 'long long';
+	}
+
 	//Step 1: write the name to identify this record uniquely
 	fid.fwrite(name.length,'int'); 
@@ -83,5 +88,5 @@
 
 		//first write length of record
-		fid.fwrite(4+4,'int');  //1 bool (disguised as an int)+code
+		fid.fwrite(4+4,recordlengthtype);  //1 bool (disguised as an int)+code
 
 		//write data code: 
@@ -95,5 +100,5 @@
 
 		//first write length of record
-		fid.fwrite(4+4,'int');  //1 integer + code
+		fid.fwrite(4+4,recordlengthtype);  //1 integer + code
 
 		//write data code: 
@@ -107,5 +112,5 @@
 
 		//first write length of record
-		fid.fwrite(8+4,'int');  //1 double+code
+		fid.fwrite(8+4,recordlengthtype);  //1 double+code
 
 		//write data code: 
@@ -117,5 +122,5 @@
 	else if (format == 'String'){ // {{{
 		//first write length of record
-		fid.fwrite(data.length+4+4,'int');  //string + string size + code
+		fid.fwrite(data.length+4+4,recordlengthtype);  //string + string size + code
 
 		//write data code: 
@@ -141,5 +146,5 @@
 
 		//first write length of record
-		fid.fwrite(4+4+8*s[0]*s[1]+4+4,'int');  //2 integers (32 bits) + the double matrix + code + matrix type
+		fid.fwrite(4+4+8*s[0]*s[1]+4+4,recordlengthtype);  //2 integers (32 bits) + the double matrix + code + matrix type
 
 		//write data code and matrix type: 
@@ -167,5 +172,5 @@
 
 		//first write length of record
-		fid.fwrite(4+4+8*s[0]*s[1]+4+4,'int');  //2 integers (32 bits) + the double matrix + code + matrix type
+		fid.fwrite(4+4+8*s[0]*s[1]+4+4,recordlengthtype);  //2 integers (32 bits) + the double matrix + code + matrix type
 
 		//write data code and matrix type: 
@@ -197,5 +202,5 @@
 		var recordlength=4+4+8*s[0]*s[1]+4+4; //2 integers (32 bits) + the double matrix + code + matrix type
 		if (recordlength>Math.pow(2,31)) throw Error(sprintf("field '%s' cannot be marshalled because it is larger than 2^31 bytes!",name));
-		fid.fwrite(recordlength,'int');
+		fid.fwrite(recordlength,recordlengthtype);
 
 		//write data code and matrix type: 
@@ -224,5 +229,5 @@
 
 		//write length of record
-		fid.fwrite(recordlength,'int'); 
+		fid.fwrite(recordlength,recordlengthtype); 
 
 		//write data code: 
@@ -259,5 +264,5 @@
 
 		//write length of record
-		fid.fwrite(recordlength,'int'); 
+		fid.fwrite(recordlength,recordlengthtype); 
 
 		//write data code: 
@@ -278,6 +283,5 @@
 					format.toString(),name));
 	}
-}
-
+} //}}}
 function FormatToCode(format){ // {{{
 	//This routine takes the format string, and hardcodes it into an integer, which 
Index: /issm/trunk-jpl/src/m/solve/solve.js
===================================================================
--- /issm/trunk-jpl/src/m/solve/solve.js	(revision 26299)
+++ /issm/trunk-jpl/src/m/solve/solve.js	(revision 26300)
@@ -1,90 +1,116 @@
-function solve(md, solutionstring) {//{{{
-/**
- * SOLVE - apply solution sequence for this model
- *
- *	Usage:
- *		solve(md, solutionstring, varargin);
- *		where varargin is a list of paired arguments of string OR enums
- *
- *	solution types available comprise:
- *		- 'Stressbalance'		or 'sb'
- *		- 'Masstransport'		or 'mt'
- *		- 'Thermal'				or 'th'
- *		- 'Steadystate'			or 'ss'
- *		- 'Transient'			or 'tr'
- *		- 'Balancethickness'	or 'mc'
- *		- 'Balancevelocity'		or 'bv'
- *		- 'BedSlope'			or 'bsl'
- *		- 'SurfaceSlope'		or 'ssl'
- *		- 'Hydrology'			or 'hy'
- *		- 'DamageEvolution'		or 'da'
- *		- 'Gia'					or 'gia'
- *		- 'Sealevelchange'		or 'slc'
- *
- *	extra options:
- *		- loadonly    		: do not solve, only load results
- *		- runtimename 		: true or false (default is true), makes name unique
- *		- checkconsistency 	: 'yes' or 'no' (default is 'yes'), ensures checks on consistency of model
- *		- restart			: directory name (relative to the execution directory) where the restart file is located
- *		- successCallback	: callback function to be called on success
- *		- errorCallback		: callback function to be called on error
- *
- *	reporting:
- *		With no optional arguments for reporting, progress and error reporting is written to the DOM element with ID 'solve-button'.
- *		- solveButtonId		: overrides default solve button ID
- *		- callout			: Callout to report progress/errors to; overrides reporting to solve button
- *		- withProgressBar	: reports progress of certain solution stages with a progress bar; will not display if a Callout has not been provided
- *
- *	Examples:
- *		md = solve(md, 'Stressbalance');
- *		md = solve(md, 'sb');
- */
-	if (typeof solutionstring !== 'string') {
-		throw Error(sprintf("%s\n", "ISSM's solve function only accepts strings for solution sequences. Type help solve to get a list of supported solutions."));
-	}
-
-	//recover and process solve options
-	let solutionStringLowerCase = solutionstring.toLowerCase();
-	
-	if ((solutionStringLowerCase === 'sb') || (solutionStringLowerCase === 'stressbalance')) {
+async function solve(md, solutionstring, ...varargin) { //{{{
+/*
+SOLVE - apply solution sequence for this model
+
+Usage:
+	solve(md, solutionstring[, ...]);
+
+where varargin is a list of paired arguments of string OR enums
+
+Solution types available comprise:
+- 'Stressbalance'			or 'sb'
+- 'Masstransport'			or 'mt'
+- 'Oceantransport'			or 'oceant'
+- 'Thermal'					or 'th'
+- 'Steadystate'				or 'ss'
+- 'Transient'				or 'tr'
+- 'Balancethickness'		or 'mc'
+- 'BalancethicknessSoft'	or 'mcsoft'
+- 'Balancevelocity'			or 'bv'
+- 'BedSlope'				or 'bsl'
+- 'SurfaceSlope'			or 'ssl'
+- 'Hydrology'				or 'hy'
+- 'DamageEvolution'			or 'da'
+- 'Gia'						or 'gia'
+- 'Love'					or 'lv'
+- 'Esa'						or 'esa'
+- 'Sampling'				or 'smp'
+- 'Gmsh'
+
+Extra options (these all need to be passed in via the third parameter, which is 
+a rest parameter):
+- loadonly    		: do not solve, only load results
+- runtimename 		: true or false (default is true); makes name unique
+- checkconsistency 	: true or false (default is true); checks consistency of model
+- restart			: directory name (relative to the execution directory) where the restart file is located
+
+Examples:
+	md = solve(md, 'Stressbalance');
+	md = solve(md, 'sb');
+	
+NOTE:
+- We do not strictly need to return md as objects are passed by reference in 
+JavaScript, but we do so to mirror MATLAB and Python APIs.
+
+TODO:
+- Refactor UI reporting structure so we do not have to check if it is defined
+*/	
+ 
+/*
+	// Check that md exists and that it is a model
+ 	if (md === null || md === undefined || md.constructor.name !== 'model') {
+ 		throw new Error('md needs to be an instance of the model class');
+ 	}
+*/
+	
+	if (typeof(solutionstring) !== 'string') {
+		throw new Error('ISSM\'s solve function only accepts strings for solution sequences. Type help solve to get a list of supported solutions.');
+	}
+
+	// Recover and process solve options
+	if (vesl.strings.strcmpi(solutionstring, 'sb') || vesl.strings.strcmpi(solutionstring, 'Stressbalance')) {
 		solutionstring = 'StressbalanceSolution';
-	} else if ((solutionStringLowerCase === 'mt') || (solutionStringLowerCase === 'masstransport')) {
+	} else if (vesl.strings.strcmpi(solutionstring, 'mt') || vesl.strings.strcmpi(solutionstring, 'Masstransport')) {
 		solutionstring = 'MasstransportSolution';	
-	} else if ((solutionStringLowerCase === 'th') || (solutionStringLowerCase === 'thermal')) {
+	} else if (vesl.strings.strcmpi(solutionstring, 'oceant') || vesl.strings.strcmpi(solutionstring, 'Oceantransport')) {
+		solutionstring = 'OceantransportSolution';	
+	} else if (vesl.strings.strcmpi(solutionstring, 'th') || vesl.strings.strcmpi(solutionstring, 'Thermal')) {
 		solutionstring = 'ThermalSolution';
-	} else if ((solutionStringLowerCase === 'st') || (solutionStringLowerCase === 'steadystate')) {
+	} else if (vesl.strings.strcmpi(solutionstring, 'st') || vesl.strings.strcmpi(solutionstring, 'Steadystate')) {
 		solutionstring = 'SteadystateSolution';
-	} else if ((solutionStringLowerCase === 'tr') || (solutionStringLowerCase === 'transient')) {
+	} else if (vesl.strings.strcmpi(solutionstring, 'tr') || vesl.strings.strcmpi(solutionstring, 'Transient')) {
 		solutionstring = 'TransientSolution';
-	} else if ((solutionStringLowerCase === 'mc') || (solutionStringLowerCase === 'balancethickness')) {
+	} else if (vesl.strings.strcmpi(solutionstring, 'mc') || vesl.strings.strcmpi(solutionstring, 'Balancethickness')) {
 		solutionstring = 'BalancethicknessSolution';
-	} else if ((solutionStringLowerCase === 'bv') || (solutionStringLowerCase === 'balancevelocity')) {
+	} else if (vesl.strings.strcmpi(solutionstring, 'mcsoft') || vesl.strings.strcmpi(solutionstring, 'BalancethicknessSoft')) {
+		solutionstring = 'BalancethicknessSoftSolution';
+	} else if (vesl.strings.strcmpi(solutionstring, 'bv') || vesl.strings.strcmpi(solutionstring, 'Balancevelocity')) {
 		solutionstring = 'BalancevelocitySolution';
-	} else if ((solutionStringLowerCase === 'bsl') || (solutionStringLowerCase === 'bedslope')) {
+	} else if (vesl.strings.strcmpi(solutionstring, 'bsl') || vesl.strings.strcmpi(solutionstring, 'BedSlope')) {
 		solutionstring = 'BedSlopeSolution';
-	} else if ((solutionStringLowerCase === 'ssl') || (solutionStringLowerCase === 'surfaceslope')) {
+	} else if (vesl.strings.strcmpi(solutionstring, 'ssl') || vesl.strings.strcmpi(solutionstring, 'SurfaceSlope')) {
 		solutionstring = 'SurfaceSlopeSolution';
-	} else if ((solutionStringLowerCase === 'hy') || (solutionStringLowerCase === 'hydrology')) {
+	} else if (vesl.strings.strcmpi(solutionstring, 'hy') || vesl.strings.strcmpi(solutionstring, 'Hydrology')) {
 		solutionstring = 'HydrologySolution';
-	} else if ((solutionStringLowerCase === 'da') || (solutionStringLowerCase === 'damageevolution')) {
+	} else if (vesl.strings.strcmpi(solutionstring, 'da') || vesl.strings.strcmpi(solutionstring, 'DamageEvolution')) {
 		solutionstring = 'DamageEvolutionSolution';
-	} else if ((solutionStringLowerCase === 'gia') || (solutionStringLowerCase === 'gia')) {
+	} else if (vesl.strings.strcmpi(solutionstring, 'gia') || vesl.strings.strcmpi(solutionstring, 'Gia')) {
 		solutionstring = 'GiaSolution';
-	} else if ((solutionStringLowerCase === 'slc') || (solutionStringLowerCase === 'sealevelchange')) {
-		solutionstring = 'SealevelriseSolution';
+	} else if (vesl.strings.strcmpi(solutionstring, 'lv') || vesl.strings.strcmpi(solutionstring, 'Love')) {
+		solutionstring = 'LoveSolution';
+	} else if (vesl.strings.strcmpi(solutionstring, 'Esa')) {
+		solutionstring = 'EsaSolution';
+	} else if (vesl.strings.strcmpi(solutionstring, 'smp') || vesl.strings.strcmpi(solutionstring, 'Sampling')) {
+		solutionstring = 'SamplingSolution';
+	} else if (vesl.strings.strcmpi(solutionstring, 'gmsh')) {
+		solutionstring = 'GmshSolution';
+	} else if (vesl.strings.strcmpi(solutionstring, 'gmt')) {
+		solutionstring = 'GmtSolution';
 	} else {
-		throw Error(sprintf("%s%s%s\n",'solutionstring ',solutionstring,' not supported!'));
-	}
-	
-	// Process options
-	let args 	= Array.prototype.slice.call(arguments);
-	let options = new pairoptions(args.slice(2, args.length));
-	options.addfield('solutionstring', solutionstring);
-
-	// recover some fields
+		throw new Error('solutionstring ' + solutionstring + ' not supported!');
+	}
+	let options = new pairoptions(varargin, 'solutionstring', solutionstring);
+	
+	// Recover some fields
 	md.priv.solution 	= solutionstring;
-	cluster 			= md.cluster;
-
-	//check model consistency
+	let cluster 		= md.cluster;
+	
+	// NOTE: Batch scripts are not currently implemented
+	let batch = 0; 
+	if (options.getfieldvalue('batch', 'no') === 'yes') {
+		batch = 1;
+	}
+	
+	// Check model consistency
 	if (options.getfieldvalue('checkconsistency', 'yes') === 'yes') {
 		if (md.verbose.solution) {
@@ -94,28 +120,30 @@
 		ismodelselfconsistent(md);
 	}
-
+	
 	// If we are restarting, actually use the provided runtime name:
 	restart = options.getfieldvalue('restart', '');
 
 	// First, build a runtime name that is unique
-	if (restart === 1 ) {
+	if (restart === 1) {
 		// Leave the runtimename as is
 	} else {
-		if (!(restart === '')) {
-			md.priv.runtimename=restart;
-		} else if (options.getfieldvalue('runtimename',true)) {
-			let c = new Date().getTime();
-			md.priv.runtimename = sprintf('%s-%g', md.miscellaneous.name, c);
+		if (restart !== '') {
+			md.priv.runtimename = restart;
 		} else {
-			md.priv.runtimename = md.miscellaneous.name;
-		}
-	}
-
-	// If running qmu analysis, some preprocessing of dakota files using models fields needs to be carried out
+			if (options.getfieldvalue('runtimename', true)) {
+				let c = new Date().getTime();
+				md.priv.runtimename = sprintf('%s-%g', md.miscellaneous.name, c);
+			} else {
+				md.priv.runtimename = md.miscellaneous.name;
+			}
+		}
+	}
+
+	// If running QMU analysis, some preprocessing of Dakota files using model fields needs to be carried out
 	if (md.qmu.isdakota) {
-		throw Error("solve error message: qmu runs not supported yet!");
-		//md.preqmu(options);
-	}
-
+		throw new Error("QMU not supported yet!");
+		//md = preqmu(md, options);
+	}
+	
 	// Do we load results only?
 	if (options.getfieldvalue('loadonly', false)){
@@ -124,97 +152,53 @@
 	}
 
-	// Marshall into a binary array (fid) all the fields of model
-	let fid = marshall(md); // bin file
-	
-	//deal with toolkits options: 
-	toolkitsstring = md.toolkits.ToolkitsFile(md.miscellaneous.name + '.toolkits'); // toolkits file
-
-
 	/*
-		Set success callback function
-		
-		Default: do nothing if no success callback function requested
+	Write all input arrays (as opposed to, under MATLAB/Python, input binary 
+	files)
+	
+	NOTE: The JavaScript implementation diverges significantly from the 
+		  MATLAB/Python APIs here.
 	*/
-	//{{{
-	function successCallbackDefault() {
-		solving = false;
-	}; 
-	
-	let successCallback = options.getfieldvalue('successCallback', successCallbackDefault);
-	//}}}
-	
-	
-	/*
-		Set error callback function
-		
-		Default: do nothing if no error callback function requested
-	*/
-	//{{{ 
-	function errorCallbackDefault() {
-		solving = false;
-	}; 
-	
-	let errorCallback = options.getfieldvalue('errorCallback', errorCallbackDefault);
-	//}}}
-	
-	
-	/*
-		Set solve button ID
-		
-		Default: update #solve-button element with progress updates
-	*/
-	//{{{
-	let solveButtonId = options.getfieldvalue('solveButtonId', '#solve-button');
-	//}}}
-	
-	
-	/*
-		Set Callout
-	*/
-	//{{{
-	var callout = {};
-	
-	// Default: Callout is an empty object
-	callout = options.getfieldvalue('callout', {});
-	//}}}
-	
-	
-	/*
-		Set progress bar display boolean
-		
-		Default: no progress bar
-		
-		NOTE: must have supplied a callout for progress bar to display
-	*/
-	//{{{
-	let withProgressBar = options.getfieldvalue('withProgressBar', false);
-	//}}}
-
+	let fid = null; // bin file equivalent
+	//TODO: FIND A BETTER WAY TO DO THIS! (IE, SYNC UP WRITEDATA AND HAVE A FULL DEMARSHALL/READMODEL IN PYTHON
+	if (solutionstring === 'GmshSolution') {
+		//open file for binary writing
+		fid = new fileptr('mode','w');
+	} else if (solutionstring === 'GmtSolution') {
+		//open file for binary writing
+		fid = new fileptr('mode','w');
+		let prefix='md.mesh';
+		WriteData(fid,prefix,'object',md.mesh,'fieldname','lat','format','DoubleMat','mattype',1);
+		WriteData(fid,prefix,'object',md.mesh,'fieldname','long','format','DoubleMat','mattype',1);
+	} else {
+		// Marshall into a binary array (fid) all the fields of model
+		fid = marshall(md); // bin file
+	}
+	let toolkitsstring = md.toolkits.ToolkitsFile(md.miscellaneous.name + '.toolkits'); // toolkits file equivalent
 
 	if (cluster.classname() === 'local') {//{{{
 
-		// We are running locally on the machine, using the issm module
+		// We are running locally on the machine, using the ISSM module
 		console.log('running issm locally');
 		
-		// Call issm
+		// Call ISSM
 		let outputs = issm(fid, toolkitsstring, solutionstring, md.miscellaneous.name); 
 		
-		// Recover output arguments:
+		// Recover output
 		let outputbuffer 		= outputs[0]; 
 		let outputbuffersize 	= outputs[1];
 			
 		// Load results 
-		md = loadresultsfrombuffer(md, outputbuffer, outputbuffersize); 
-		
-		// Call success callback 
-		successCallback();
-
-		return md;
+		md = loadresultsfrombuffer(md, outputbuffer, outputbuffersize); // TODO: Pass reporting construct to loadresultsfrombuffer
+		
+		// Call success callback
+		if (vesl.helpers.isFunction(vesl.ui.reporting.success_callback)) {
+			vesl.ui.reporting.success_callback();
+		}
 	//}}}
-	} else {//{{{
+	} else { //{{{
 		// We are running somewhere else on a computational server. Send the buffer to that server and retrieve output.
 		console.log('running issm remotely');
 		
-		cluster.UploadAndRun(
+		await cluster.uploadandrun(
 			md, 
 			fid, 
@@ -223,13 +207,23 @@
 			md.miscellaneous.name, 
 			md.priv.runtimename,
-			successCallback, 
-			errorCallback, 
-			solveButtonId, 
-			callout, 
-			withProgressBar
-		);
-
+			options
+		);/*
+.catch(function(e) {
+			if (vesl.helpers.isDefined(vesl.ui) && vesl.helpers.isDefined(vesl.ui.reporting) && vesl.helpers.isFunction(vesl.ui.reporting.error_callback)) {
+				vesl.ui.reporting.error_callback(e);
+			}
+		}).catch(function(e) {
+			// Handle unexpected errors (source: http://thecodebarbarian.com/async-await-error-handling-in-javascript.html)
+			console.log(e);
+		});
+			
+		if (vesl.helpers.isDefined(vesl.ui) && vesl.helpers.isDefined(vesl.ui.reporting) && vesl.helpers.isFunction(vesl.ui.reporting.success_callback)) {
+			vesl.ui.reporting.success_callback(md);
+		}
+*/
+		
+		// Why is md undefined at vesl.ui.reporting.success_callback(md)? See issm-refactor
+		
 		return md;
-	}//}}}
-}//}}}
-
+	} //}}}
+} //}}}
