/*
	This heading must remain intact at all times.
	Copyright (c) 2009 Mark Mason. All rights reserved.

	File:	Q-Cogo-Cogo.js
	Use:	To provide coordinate geometry operations for Q-Cogo, <http://www.q-cogo.com/>.
	Ver:	1.1 (Beta)

	Created by Mark Mason. Latest version available from <http://www.q-cogo.com/>.



	This file is part of Q-Cogo.
	
	Q-Cogo is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	Q-Cogo is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	
	You should have received a copy of the GNU General Public License
	along with Q-Cogo.  If not, see <http://www.gnu.org/licenses/>.



	FUNCTION LISTING

	Area
	Transform
	Inverse
	PtLineInverse
	PtPtInverse
	Intersect
	BrgBrg
	BrgDist
	DistDist
	Traverse
*/



// *******************************************************************************************************************************

function Area() {

// Finds the area contained by a list of points that define straight lines and curves
// Input:  None
// Output: The resulting area and a sketch of the operation

// Find the contents of the input text boxes

	var PtList = document.getElementById('AreaPts').value;

// Check to make sure fields are't blank

	if (!CheckBlank(PtList, ' list of points bounding the area')) {

		return;

	}

// Get area point matrix and check

	if (!(PtList = GetPointList(PtList, 1, 1))) {

		return;

	}

// Make sure points matrix contains at least three points

	if (PtList.length < 3) {

		alert('Enter at least three points bounding the area');
		return;

	}

// Perform straight line area calculations, check that any radial distances are equal and isolated, find direction of entry (CW or CCW)

	PtList[PtList.length] = PtList[0];
	var Total = 0;
	var intAng = -2;
	var intTest = 0;

	for (var i=0; i<PtList.length-1; i++) {

		var indexM = ((i == 0) ? PtList.length - 2 : i - 1);

		if (PtList[i][0].search(/\*/) >= 0) {

			var radC = Math.abs(PtPtInverse(0, PtList[i][0].replace(/\*/g,''), PtList[i + 1][0])[0] - PtPtInverse(0, PtList[i][0].replace(/\*/g,''), PtList[indexM][0])[0]);

			if (Round(radC, parseInt(document.getElementById('DPrecision').value)) > Math.pow(10, -1 * parseInt(document.getElementById('DPrecision').value))) {

				alert('Distances from radial point "' + PtList[i][0].replace(/\*/g,'') + '" to BC and EC are unequal!');
				return;

			}

			if (PtList[i + 1][0].search(/\*/) >= 0 || PtList[indexM][0].search(/\*/) >= 0) {

				alert('Radial points must be separated by at least one non-radial point');
				return;

			}

		}

		else {

			var indexP = i + 1;
			indexP = ((PtList[indexP][0].search(/\*/) >= 0) ? indexP + 1 : indexP);
			indexP = ((indexP > PtList.length - 1) ? 1 : indexP);

			Total += parseFloat(PtList[i][1]) * parseFloat(PtList[indexP][2]);
			Total -= parseFloat(PtList[indexP][1]) * parseFloat(PtList[i][2]);

			intAng += 1;

			indexM = ((PtList[indexM][0].search(/\*/) >= 0) ? indexM - 1 : indexM);
			indexM = ((indexM < 0) ? PtList.length - 2 : indexM);

			intTest += (PtPtInverse(0, PtList[i][0], PtList[indexM][0])[2] - PtPtInverse(0, PtList[i][0], PtList[indexP][0])[2] + 2 * Math.PI) % (2 * Math.PI);

		}

	}

	Total = Math.abs(Total / 2);

	var CW = ((parseInt(intAng * Math.PI) != parseInt(intTest)) ? -1 : 1);

// Add or subtract any segment areas from total as appropriate

	for (i=0; i<PtList.length-1; i++) {

		if (PtList[i][0].search(/\*/) >= 0) {

			indexM = ((i == 0) ? PtList.length - 2 : i - 1);

			var R = PtPtInverse(0, PtList[i][0].replace(/\*/g,''), PtList[indexM][0])[0];
			var Theta = (PtPtInverse(0, PtList[i][0].replace(/\*/g,''), PtList[indexM][0])[2] - PtPtInverse(0, PtList[i][0].replace(/\*/g,''), PtList[i + 1][0])[2] + 2 * Math.PI) % (2 * Math.PI);
			Theta = ((Theta > Math.PI) ? 2 * Math.PI - Theta : Theta);
			var segArea = Math.pow(R, 2.0) * (Theta - Math.sin(Theta)) / 2;
			var LR = PtLineInverse(0, PtList[i][0].replace(/\*/g,''), PtList[indexM][0], PtList[i + 1][0])[0];
			LR = ((LR.search(/right/i) >= 0) ? 1 : -1);

// If the point is preceded by an asterisk (*Pt), change segment area and left / right flag appropriately

			segArea = ((PtList[i][0].search(/\*/) == 0) ? Math.PI * Math.pow(R, 2.0) - segArea : segArea);
			LR *= ((PtList[i][0].search(/\*/) == 0) ? -1 : 1);

			Total += CW * LR * segArea;

		}

	}

	Total = Math.abs(Total);

// Output results

	SketchArea(PtList, 'AreaCanvas');

	PtList.splice(PtList.length - 1, 1);

	var PtString = FormatPtList(PtList, 0);

	var Results = '\n\n\n\n\n';
	Results += 'By Perimeter: ' + PtString + '\n';

	Results += '\n       Area:     ' + FormatArea(Total + '') + '\n';

	OutputCogo(Results, 'AreaLog');

}

// *******************************************************************************************************************************

function Transform() {

// Scales, shifts, and rotates point list as indicated by the input fields
// Input:  None
// Output: The solution of the transformation, a sketch of the operation, and the updated points

// Find the contents of the input text boxes

	var PtList = document.getElementById('TransPts').value;
	var AboutPt = document.getElementById('TransAbout').value;
	var SF = document.getElementById('TransSF').value;
	var NShift = document.getElementById('TransN').value;
	var EShift = document.getElementById('TransE').value;
	var ZShift = document.getElementById('TransZ').value;
	var Rot = document.getElementById('TransHA').value;

// Check to make sure fields are't blank as appropriate

	if (!CheckBlank(PtList, ' list of points to transform')) {

		return;

	}

	if (!SF && !NShift && !EShift && !ZShift && !Rot) {

		alert('Enter at least one transformation parameter');
		return;

	}

	if (!AboutPt && (SF || Rot)) {

		alert('Enter a point to transform about');
		return;

	}

// Format input to match values in point records

	AboutPt = FormatString(AboutPt, 1, 0);

// Get transform point matrix and about point vector from points record and check

	if (!(PtList = GetPointList(PtList, 1, 0)) || !(AboutPt = GetPointList(AboutPt, 0, 0))) {

		return;

	}

	if (Rot) {

		Rot = ParseDMS(Rot);

	}

// Check all values, if entered

	if ((SF && !CheckDecimal(SF)) || (NShift && !CheckDecimal(NShift)) || (EShift && !CheckDecimal(EShift)) || (ZShift && !CheckDecimal(ZShift)) || (Rot && Rot == 'X')) {

		return;

	}

// Find coordinates of four corners of extents of points to be transformed and add to point list two times (one set to be transformed, one not)

	var Ymax = -99999999999999999999999;
	var Xmax = -99999999999999999999999;
	var Ymin = 99999999999999999999999;
	var Xmin = 99999999999999999999999;

	for (var h=0; h<PtList.length; h++) {

		if (parseFloat(PtList[h][1]) > Ymax) { Ymax = parseFloat(PtList[h][1]); }
		if (parseFloat(PtList[h][1]) < Ymin) { Ymin = parseFloat(PtList[h][1]); }
		if (parseFloat(PtList[h][2]) > Xmax) { Xmax = parseFloat(PtList[h][2]); }
		if (parseFloat(PtList[h][2]) < Xmin) { Xmin = parseFloat(PtList[h][2]); }

	}

	for (var g=0; g<2; g++) {

		PtList[PtList.length] = ['NW_Extents', Ymax, Xmin, 0, 'NW_Extents'];
		PtList[PtList.length] = ['NE_Extents', Ymax, Xmax, 0, 'NE_Extents'];
		PtList[PtList.length] = ['SW_Extents', Ymin, Xmin, 0, 'SW_Extents'];
		PtList[PtList.length] = ['SE_Extents', Ymin, Xmax, 0, 'SE_Extents'];

	}

	var dN = 0;
	var dE = 0;

// Perform Scale, if necessary

	if (SF) {

		for (var f=0; f<PtList.length-4; f++) {

			dN = (parseFloat(PtList[f][1]) - parseFloat(AboutPt[1])) * parseFloat(SF);
			dE = (parseFloat(PtList[f][2]) - parseFloat(AboutPt[2])) * parseFloat(SF);

			PtList[f][1] = parseFloat(AboutPt[1]) + dN;
			PtList[f][2] = parseFloat(AboutPt[2]) + dE;

		}

	}

	else {

		SF = '1';

	}

// Perform Rotation, if necessary

	if (Rot) {

		for (var d=0; d<PtList.length-4; d++) {

			var DN1 = parseFloat(PtList[d][1]) - parseFloat(AboutPt[1]);
			var DE1 = parseFloat(PtList[d][2]) - parseFloat(AboutPt[2]);

			var DistAtoD = Math.sqrt(Math.pow(DN1, 2) + Math.pow(DE1, 2));

			var AzAtoD = Math.atan2(DE1, DN1);

			if (AzAtoD < 0) {

				AzAtoD = AzAtoD + 2 * Math.PI;

			}

			AzAtoD += Rot;

			dN = DistAtoD * Math.cos(AzAtoD);
			dE = DistAtoD * Math.sin(AzAtoD);

			PtList[d][1] = parseFloat(AboutPt[1]) + dN;
			PtList[d][2] = parseFloat(AboutPt[2]) + dE;

		}

	}

	else {

		Rot = '0';

	}

// Perform Shifts, if necessary

	if (NShift) {

		for (var i=0; i<PtList.length-4; i++) {

			PtList[i][1] = (parseFloat(PtList[i][1]) + parseFloat(NShift)) + '';

		}

	}

	else {

		NShift = '0';

	}

	if (EShift) {

		for (var j=0; j<PtList.length-4; j++) {

			PtList[j][2] = (parseFloat(PtList[j][2]) + parseFloat(EShift)) + '';

		}

	}

	else {

		EShift = '0';

	}

	if (ZShift) {

		for (var k=0; k<PtList.length-4; k++) {

			PtList[k][3] = (parseFloat(PtList[k][3]) + parseFloat(ZShift)) + '';

		}

	}

	else {

		ZShift = '0';

	}

// Confirm before changing any points

	var SFDisp = FormatDecimal(SF, 1);
	var NShiftDisp = FormatDecimal(NShift, 0);
	var EShiftDisp = FormatDecimal(EShift, 0);
	var ZShiftDisp = FormatDecimal(ZShift, 0);
	var RotDisp = FormatDMS(Rot);

	var Plural = ((PtList.length > 9) ? 's' : '');

	var Message = PtList.length-8 + ' point' + Plural + ' will be edited!\n\n';
	Message += '     +N:\t\t' + NShiftDisp.replace(/\s+/g,'') + '\n';
	Message += '     +E:\t\t' + EShiftDisp.replace(/\s+/g,'') + '\n';
	Message += '     +Z:\t\t' + ZShiftDisp.replace(/\s+/g,'') + '\n';
	Message += '     SF:\t\t' + SFDisp.replace(/\s+/g,'') + '\n';
	Message += '     Rot:\t\t' + RotDisp.replace(/\s+/g,'') + '\n\n';
	Message += 'Edit point' + Plural + '?';

	check = confirm(Message);

	if (check == 1) {

	
// Update points matrix with new values

		var pointsMatrix = ParsePoints();

		for (var l=0; l<PtList.length; l++) {

			for (var m=0; m<pointsMatrix.length; m++) {

				if (pointsMatrix[m][0] == PtList[l][0]) {

					pointsMatrix[m] = PtList[l];

				}

			}

		}

		lP = pointsMatrix.length-2;
		OutputPoints(pointsMatrix, lP);

// Clear point sorting labels

		ClearSort();

// Output results and sketch transformation

		PtList[PtList.length] = [0];
		SketchTransform(PtList, 'TransformCanvas');

		PtList.splice(PtList.length - 9, 9);

		var PtString = FormatPtList(PtList, 1);

		var Results = '\n\n\n\n\n';
		Results += 'Transformed: ' + PtString + '\n';

		if (AboutPt[0]) {

			Results += 'About:       ' + AboutPt[0] + '\n';

		}

		Results += '\n       +N:  ' + NShiftDisp + '       SF:  ' + SFDisp + '\n';
		Results += '       +E:  ' + EShiftDisp + '       Rot:     ' + RotDisp + '\n';
		Results += '       +Z:  ' + ZShiftDisp + '\n';

		OutputCogo(Results, 'TransformLog');

	}

}

// *******************************************************************************************************************************

function Inverse() {

// Finds input type of inverse and calls appropriate inverse function
// Input:  None
// Output: The solution of the inverse, through the various inverse functions

// Read title to find out which intersection to perform

	var Title = document.getElementById('InvType');

// Call appropriate inverse function

	if (Title.innerHTML == 'Point') {

		PtPtInverse(1, 0, 0);

	}

	else if (Title.innerHTML == 'Line') {

		PtLineInverse(1, 0, 0, 0);

	}

}

// *******************************************************************************************************************************

function PtLineInverse(Output, PtPt, StartPt, EndPt) {

// Computes an inverse between a point and a line defined by two points and adds the result to the inverse log
// Input:  None
// Output: The values of the inverse log text box

// If no o/s point, start point, or end point requested, get point names from input boxes

	if (!PtPt || !StartPt || !EndPt) {

		PtPt = document.getElementById('InvPtPt').value;
		StartPt = document.getElementById('FromPt').value;
		EndPt = document.getElementById('ToPt').value;

	}

// Check to make sure fields are't blank

	if (!CheckBlank(PtPt, ' point to inverse from') || !CheckBlank(StartPt, ' point at the start of line') || !CheckBlank(EndPt, ' point at the end of line')) {
		return;

	}

// Format input to match values in point records

	PtPt = FormatString(PtPt, 1, 0);
	StartPt = FormatString(StartPt, 1, 0);
	EndPt = FormatString(EndPt, 1, 0);

// Get point vectors from points record and check

	if (!(PtPt = GetPointList(PtPt, 0, 0)) || !(StartPt = GetPointList(StartPt, 0, 0)) || !(EndPt = GetPointList(EndPt, 0, 0)) || SamePoint(PtPt[0], StartPt[0], 'From and start') || SamePoint(PtPt[0], EndPt[0], 'From and end') || SamePoint(StartPt[0], EndPt[0], 'Start and end') || SameCoords(PtPt, StartPt, 'From and start') || SameCoords(PtPt, EndPt, 'From and end') || SameCoords(StartPt, EndPt, 'Start and end')) {

		return;

	}

// Compute inverse northing and easting results by brg-brg intersection

	var Az1 = PtPtInverse(0, StartPt[0], EndPt[0])[2];
	var Az2 = Az1 + Math.PI / 2;

	var Np = BrgBrg(0, 0, StartPt, Az1, PtPt, Az2, '', '')[0];
	var Ep = BrgBrg(0, 0, StartPt, Az1, PtPt, Az2, '', '')[1];

// Compute inverse elevation and stationing results

	var Station = Math.sqrt(Math.pow(Np - StartPt[1], 2) + Math.pow(Ep - StartPt[2], 2));

	var Az4 = Math.atan2(Ep - StartPt[2], Np - StartPt[1]);
	Az4 = ((Az4 < 0) ? Az4 + 2 * Math.PI : Az4);

	Station *= ((Round(Az4, 1) != Round(Az1, 1)) ? -1 : 1);

	var Zp = parseFloat(StartPt[3]) + Station * (EndPt[3] - StartPt[3]) / PtPtInverse(0, StartPt[0], EndPt[0])[0];

	var HD = Math.sqrt(Math.pow(Np - PtPt[1], 2) + Math.pow(Ep - PtPt[2], 2));
	var DZ = Zp - PtPt[3];
	var SD = Math.sqrt(Math.pow(HD, 2) + Math.pow(DZ, 2));

	var Az = Math.atan2(Ep - PtPt[2], Np - PtPt[1]);
	Az = ((Az < 0) ? Az + 2 * Math.PI : Az);
	
	var AzDiff = (PtPtInverse(0, StartPt[0], PtPt[0])[2] - Az1 + 2 * Math.PI) % (2 * Math.PI);

	var LR = ((AzDiff > 0 && AzDiff < Math.PI) ? 'RIGHT of line' : (AzDiff > Math.PI && AzDiff < 2 * Math.PI) ? 'LEFT of line' : 'ON LINE');

	Az = ((LR == 'ON LINE') ? 0 : Az);

// If output requested, form points matrix for sketching routine

	if (Output) {

		var pointsMatrix = new Array(5);
		pointsMatrix[0] = StartPt;
		pointsMatrix[1] = EndPt;
		pointsMatrix[2] = PtPt;
		pointsMatrix[3] = ['Calc_Pt', Np, Ep, Zp, 'Calc_Pt'];
		pointsMatrix[4] = [0];

// Form line vector of points (in points matrix) that are furthest apart, so no gaps exist in the line

		var HD1 = Math.sqrt(Math.pow(EndPt[1] - StartPt[1], 2) + Math.pow(EndPt[2] - StartPt[2], 2));
		var HD2 = Math.sqrt(Math.pow(EndPt[1] - Np, 2) + Math.pow(EndPt[2] - Ep, 2));
		var MaxHD = Math.max(Station, HD1, HD2);

		var Line = ((MaxHD == Station) ? [0, 3] : (MaxHD == HD1) ? [0, 1] : [1, 3]);

		SketchPtLine(pointsMatrix, 'InverseCanvas', Line);

		var StationDisp = FormatStn(Station + '');
		var HDDisp = FormatDecimal(HD + '', 0);
		var SDDisp = FormatDecimal(SD + '', 0);
		var VDDisp = FormatDecimal(DZ + '', 0);
		var AzDisp = FormatDMS(Az + '');
		var GradeDisp = FormatGrade(DZ, HD);

// Sketch and output results to inverse log

		var Results = '\n\n\n\n\n';
		Results += PtPt[0] + '(' + PtPt[4] + ') to Line ' + StartPt[0] + '(' + StartPt[4] + ') --> ' + EndPt[0] + '(' +  EndPt[4] + '): \n\n';
		Results += LR + ', Stn:  ' + StationDisp + '\n\n';
		Results += '       HD: ' + HDDisp + '        Az:     ' + AzDisp + '\n';

		if (document.getElementById('InvDim').innerHTML == '3D') {

			Results += '       SD: ' + SDDisp + '        Grd: ' + GradeDisp + '\n       VD: ' + VDDisp + '\n';

		}

		OutputCogo(Results, 'InverseLog');

	}

// If output not requested, return calculated values

	else {

		return [LR, HD, SD, Az];

	}

}

// *******************************************************************************************************************************

function PtPtInverse(Output, FromPt, ToPt) {

// Computes an inverse between two points and adds the result to the inverse log, or returns a vector of results
// Input:  Output flag (1 to request textarea output) and points to calculate (0's to get points from input boxes)
// Output: The values of the inverse log text box, or the vector of results

// If no from or to point requested, get point names from input boxes

	if (!FromPt || !ToPt) {

		FromPt = document.getElementById('FromPt').value;
		ToPt = document.getElementById('ToPt').value;

	}

// Check to make sure fields are't blank

	if (!CheckBlank(FromPt, ' point to inverse from') || !CheckBlank(ToPt, ' point to inverse to'))  {

		return;

	}

// Format input to match values in point records

	FromPt = FormatString(FromPt, 1, 0);
	ToPt = FormatString(ToPt, 1, 0);

// Get point vectors from points record and check

	if (!(FromPt = GetPointList(FromPt, 0, 0)) || !(ToPt = GetPointList(ToPt, 0, 0)) || SamePoint(FromPt[0], ToPt[0], 'From and to')) {

		return;

	}

// Compute inverse results

	var DN = parseFloat(ToPt[1]) - parseFloat(FromPt[1]);
	var DE = parseFloat(ToPt[2]) - parseFloat(FromPt[2]);
	var DZ = parseFloat(ToPt[3]) - parseFloat(FromPt[3]);

	var HD = Math.sqrt(Math.pow(DN, 2) + Math.pow(DE, 2));
	var SD = Math.sqrt(Math.pow(HD, 2) + Math.pow(DZ, 2));

	var Az = Math.atan2(DE, DN);

	Az = ((Az < 0) ? Az + 2 * Math.PI : Az);

	var HDDisp = FormatDecimal(HD + '', 0);
	var SDDisp = FormatDecimal(SD + '', 0);
	var VDDisp = FormatDecimal(DZ + '', 0);
	var AzDisp = FormatDMS(Az + '');
	var GradeDisp = FormatGrade(DZ, HD);

// If output requested, sketch and output results to inverse log

	if (Output) {

		var pointsMatrix = new Array(3);
		pointsMatrix[0] = FromPt;
		pointsMatrix[1] = ToPt;
		pointsMatrix[2] = [0];

		SketchPtPt(pointsMatrix, 'InverseCanvas');

		var Results = '\n\n\n\n\n';
		Results += FromPt[0] + '(' + FromPt[4] + ') to ' + ToPt[0] + '(' + ToPt[4] + '): \n\n';
		Results += '       HD: ' + HDDisp + '        Az:     ' + AzDisp + '\n';

		if (document.getElementById('InvDim').innerHTML == '3D') {

			Results += '       SD: ' + SDDisp + '        Grd: ' + GradeDisp + '\n       VD: ' + VDDisp + '\n';

		}

		OutputCogo(Results, 'InverseLog');

	}

// If output not requested, return calculated values

	else {

		return [HD, SD, Az];

	}

}

// *******************************************************************************************************************************

function Intersect(Store) {

// Finds input type of intersection and calls appropriate intersection function
// Input:  Store point switch, 0 for no, 1 for yes
// Output: The solution of the intersection, through the various intersection functions

// Find the contents of the input text boxes

	var FromPt = document.getElementById('IntFrom').value;
	var ValueFrom = document.getElementById('IntValueFrom').value;
	var ToPt = document.getElementById('IntTo').value;
	var ValueTo = document.getElementById('IntValueTo').value;
	var Pt = document.getElementById('IntPt').value;
	var Desc = document.getElementById('IntDesc').value;

// Read title to find out which intersection to perform

	var Title = document.getElementById('IntTitle');

// Check to make sure points fields aren't blank

	if (!CheckBlank(FromPt, ' point to intersect from') || !CheckBlank(ToPt, ' point to intersect to'))  {

		return;

	}

// Format input to match values in point records

	FromPt = FormatString(FromPt, 1, 0);
	ToPt = FormatString(ToPt, 1, 0);

// Get from point vectors from points record and check

	if (!(FromPt = GetPointList(FromPt, 0, 0)) || !(ToPt = GetPointList(ToPt, 0, 0)) || SamePoint(FromPt[0], ToPt[0], 'From and to') || SameCoords(FromPt, ToPt,'From and to')) {

		return;

	}

// Call appropriate intersection function

	if (Title.innerHTML == 'Brg-Brg') {

		BrgBrg(1, Store, FromPt, ValueFrom, ToPt, ValueTo, Pt, Desc);

	}

	else if (Title.innerHTML == 'Brg-Dist') {

		BrgDist(Store, FromPt, ValueFrom, ToPt, ValueTo, Pt, Desc);

	}

	else if (Title.innerHTML == 'Dist-Dist') {

		DistDist(Store, FromPt, ValueFrom, ToPt, ValueTo, Pt, Desc);

	}

}

// *******************************************************************************************************************************

function BrgBrg(Output, Store, FromPt, AzFrom, ToPt, AzTo, Pt, Desc) {

// Performs bearing-bearing intersection to create new point coordinates at zero elevation, storing if necessary, or returns a vector of results
// Input:  Store point switch, from point and azimuth, to point and azimuth, store point and description, output switch
// Output: The values of the intersection record text box and stored point, through the AddPoint function

// If output is requested, check to make sure appropriate fields aren't blank and parse DMS input (if not, input will be in radians)

	if (Output) {

		if (!CheckBlank(AzFrom, ' "from" azimuth') || !CheckBlank(AzTo, ' "to" azimuth') || (Store == 1 && (!CheckBlank(Pt, ' point name') || !CheckBlank(Desc, ' point description'))))  {

			return;

		}

		AzFrom = ParseDMS(AzFrom);

		if (AzFrom != 'X') {

			AzTo = ParseDMS(AzTo);

		}

		else {

			return;

		}

	}

// Check to ensure inputs are acceptable, then compute results, output results, and add point (if necessary)

	if (AzTo != 'X') {

// Check that Azimuths are not parallel

		if (AzFrom == AzTo || Round(AzFrom - Math.PI, 10) == Round(AzTo, 10) || Round(AzFrom, 10) == Round(AzTo - Math.PI, 10)) {

			alert ('Azimuths do not intersect!');
			return;

		}

// Calculate unique solution

		var AzAB = PtPtInverse(0, FromPt[0], ToPt[0])[2];

		var A = AzFrom - AzAB;
		var B = AzAB + Math.PI - AzTo;
		var P = Math.PI - A - B;

		var AP = PtPtInverse(0, FromPt[0], ToPt[0])[0] * Math.sin(B) / Math.sin(P);

		var Yp = parseFloat(FromPt[1]) + AP * Math.cos(AzFrom);
		var Xp = parseFloat(FromPt[2]) + AP * Math.sin(AzFrom);

// Format display values

		AzFrom = FormatDMS(AzFrom + '');
		AzTo = FormatDMS(AzTo + '');
		var N = FormatDecimal(Yp + '', 0);
		var E = FormatDecimal(Xp + '', 0);

// Create precise store point values

		var Np = Yp + '';
		var Ep = Xp + '';
		var Zp = 0 + '';

		if (Output) {

			var pointsMatrix = new Array(3);
			pointsMatrix[0] = FromPt;
			pointsMatrix[1] = ToPt;
			pointsMatrix[2] = ['Calc_Pt', Np, Ep, Zp, 'Calc_Pt'];
			pointsMatrix[3] = [0];

			var Results ='\n\n\n\n';
			Results += 'Brg-Brg from ' + FromPt[0] + '(' + FromPt[4] + ') to ' + ToPt[0] + '(' + ToPt[4] + '):\n\n';
			Results += '       From Az: ' + AzFrom + '        To Az: ' + AzTo + '\n\n';

			if (Store == 0) {

				Results += 'Calculated Point (NOT STORED):\n\n';

			}

			else if (Store == 1 && CheckPoint(Pt)) {

				Pt = FormatString(Pt, 1, 0);
				Desc = FormatString(Desc, 1, 0);

				pointsMatrix[2] = [Pt, Np, Ep, Zp, Desc];
				
				Results += 'STORED ' + Pt + '(' + Desc + '):\n\n'

				AddPoint(Pt, Np, Ep, Zp, Desc);

				document.getElementById('IntPt').value = '';

			}

			else {

				return;

			}

			Results += '       N:  ' + N + '\n       E:  ' + E + '\n';

			OutputCogo(Results, 'IntersectLog');
			SketchBrgBrg(pointsMatrix, 'IntersectCanvas');

		}

		else {

			return [Yp, Xp];

		}	

	}

}

// *******************************************************************************************************************************

function BrgDist(Store, FromPt, AzFrom, ToPt, DistTo, Pt, Desc) {

// Performs bearing-distance intersection to create new point coordinates at zero elevation, storing if necessary, prompting user for solution point
// Input:  Store point switch, from point and azimuth, to point and distance, store point and description
// Output: The values of the intersection record text box and stored point, through the AddPoint function

// Check to make sure appropriate fields aren't blank

	if (!CheckBlank(AzFrom, ' "from" azimuth') || !CheckBlank(DistTo, ' "to" distance') || (Store == 1 && (!CheckBlank(Pt, ' point name') || !CheckBlank(Desc, ' point description'))))  {

		return;

	}

	AzFrom = ParseDMS(AzFrom);

// Check to ensure inputs are acceptable, then compute results, output results, and add point (if necessary)

	if (AzFrom != 'X' && CheckDecimal(DistTo)) {

	DistTo = parseFloat(DistTo);

// Get point coordinates from points vector

		var Ya = parseFloat(FromPt[1]);
		var Xa = parseFloat(FromPt[2]);
		var Yb = parseFloat(ToPt[1]);
		var Xb = parseFloat(ToPt[2]);

// Calculate both solution points

		var AzAB = Math.atan2(Xb - Xa, Yb - Ya);

		if (AzAB < 0) {
	
			AzAB = AzAB + 2 * Math.PI;

		}

		var A = AzFrom - AzAB;
		var AB = Math.sqrt(Math.pow(Ya - Yb, 2) + Math.pow(Xa - Xb, 2));

		var AP1 = (2 * (AB) * Math.cos(A) + Math.sqrt(Math.pow(2 * AB * Math.cos(A), 2) - 4 * (Math.pow(AB, 2) - Math.pow(DistTo, 2)))) / 2;
		var AP2 = (2 * (AB) * Math.cos(A) - Math.sqrt(Math.pow(2 * AB * Math.cos(A), 2) - 4 * (Math.pow(AB, 2) - Math.pow(DistTo, 2)))) / 2;

		var Xp1 = Xa + AP1 * Math.sin(AzFrom);
		var Yp1 = Ya + AP1 * Math.cos(AzFrom);
		var Xp2 = Xa + AP2 * Math.sin(AzFrom);
		var Yp2 = Ya + AP2 * Math.cos(AzFrom);

// Alert and exit if no solution exists

		if (isNaN(Xp1) || isNaN(Yp1) || isNaN(Xp2) || isNaN(Yp2)) {

			alert('Azimuth and distance do not intersect!');
			return;

		}

// Sketch both solutions and prompt user to choose correct point

		var pointsMatrix = new Array(5);
		pointsMatrix[0] = FromPt;
		pointsMatrix[1] = ToPt;
		pointsMatrix[2] = ['Sol_1', Yp1, Xp1, 0, 'Sol_1'];
		pointsMatrix[3] = ['Sol_2', Yp2, Xp2, 0, 'Sol_2'];
		pointsMatrix[4] = [0];

		var HD1 = Math.sqrt(Math.pow(Ya - Yp1, 2) + Math.pow(Xa - Xp1, 2));
		var HD2 = Math.sqrt(Math.pow(Ya - Yp2, 2) + Math.pow(Xa - Xp2, 2));
		var HD3 = Math.sqrt(Math.pow(Yp1 - Yp2, 2) + Math.pow(Xp1 - Xp2, 2));
		var MaxHD = Math.max(HD1, HD2, HD3);

		var Line = ((MaxHD == HD1) ? [0, 2] : (MaxHD == HD2) ? [0, 3] : [2, 3]);

		SketchBrgDist(pointsMatrix, 'IntersectCanvas', 1, Line);

		var N = FormatDecimal(Yp1, 0);
		N = N.replace(/\s+/g,'');
		var E = FormatDecimal(Xp1, 0);
		E = E.replace(/\s+/g,'');

		Message = 'Solution 1:\n\n';
		Message += '     N:\t\t' + N + '\n';
		Message += '     E:\t\t' + E + '\n\n';
		Message += 'Accept this point?';

		var check1 = confirm(Message);

		if (!check1) {

			var N = FormatDecimal(Yp2, 0);
			N = N.replace(/\s+/g,'');
			var E = FormatDecimal(Xp2, 0);
			E = E.replace(/\s+/g,'');

			Message = 'Solution 2:\n\n';
			Message += '     N:\t\t' + N + '\n';
			Message += '     E:\t\t' + E + '\n\n';
			Message += 'Accept this point?';

			var check2 = confirm(Message);

		}

		if (check1) {

			var Xp = Xp1;
			var Yp = Yp1;

		}

		else if (check2) {

			var Xp = Xp2;
			var Yp = Yp2;

		}

		else {

			alert('No solution chosen!');
			return;

		}


// Format display values

		AzFrom = FormatDMS(AzFrom + '');
		DistTo = FormatDecimal(DistTo + '', 0);
		var N = FormatDecimal(Yp + '', 0);
		var E = FormatDecimal(Xp + '', 0);

// Create precise store point values

		var Np = Yp + '';
		var Ep = Xp + '';
		var Zp = 0 + '';

// Refresh from and to point coordinates, then form new diminished points matrix

		FromPt = GetPointList(FromPt[0], 0, 0);
		ToPt = GetPointList(ToPt[0], 0, 0);

		pointsMatrix = new Array(4);
		pointsMatrix[0] = FromPt;
		pointsMatrix[1] = ToPt;
		pointsMatrix[2] = ['Calc_Pt', Np, Ep, Zp, 'Calc_Pt'];
		pointsMatrix[3] = [0];

		var Results ='\n\n\n\n';
		Results += 'Brg-Dist from ' + FromPt[0] + '(' + FromPt[4] + ') to ' + ToPt[0] + '(' + ToPt[4] + '):\n\n';
		Results += '       From Az: ' + AzFrom + '        To Dist: ' + DistTo + '\n\n';

		if (Store == 0) {

			Results += 'Calculated Point (NOT STORED):\n\n';

		}

		else if (Store == 1 && CheckPoint(Pt)) {

			Pt = FormatString(Pt, 1, 0);
			Desc = FormatString(Desc, 1, 0);

			Results += 'STORED ' + Pt + '(' + Desc +'):\n\n';

			pointsMatrix[2] = [Pt, Yp, Xp, 0, Pt];

			AddPoint(Pt, Np, Ep, Zp, Desc);

			document.getElementById('IntPt').value = '';

		}

		else {

			return;

		}

		Results += '       N:  ' + N + '\n       E:  ' + E + '\n';

		OutputCogo(Results, 'IntersectLog');
		SketchBrgDist(pointsMatrix, 'IntersectCanvas', 0, '');

	}

}

// *******************************************************************************************************************************

function DistDist(Store, FromPt, DistFrom, ToPt, DistTo, Pt, Desc) {

// Performs distance-distance intersection to create new point coordinates at zero elevation, storing if necessary, prompting user for solution point
// Input:  Store point switch, from point and distance, to point and distance, store point and description
// Output: The values of the intersection record text box and stored point, through the AddPoint function

// Check to make sure appropriate fields are't blank

	if (!CheckBlank(DistFrom, ' "from" distance') || !CheckBlank(DistTo, ' "to" distance') || (Store == 1 && (!CheckBlank(Pt, ' point name') || !CheckBlank(Desc, ' point description'))))  {

		return;

	}

// Check to ensure inputs are acceptable, then compute results, output results, and add point (if necessary)

	if (CheckDecimal(DistFrom) && CheckDecimal(DistTo)) {

		DistFrom = parseFloat(DistFrom);
		DistTo = parseFloat(DistTo);

// Calculate both solution points

		var AB = PtPtInverse(0, FromPt[0], ToPt[0])[0];
		var AzAB = PtPtInverse(0, FromPt[0], ToPt[0])[2];
		var A = Math.acos((Math.pow(AB, 2) + Math.pow(DistFrom, 2) - Math.pow(DistTo, 2)) / (2 * AB * DistFrom));

		var Xp1 = parseFloat(FromPt[2]) + DistFrom * Math.sin(AzAB + A);
		var Yp1 = parseFloat(FromPt[1]) + DistFrom * Math.cos(AzAB + A);
		var Xp2 = parseFloat(FromPt[2]) + DistFrom * Math.sin(AzAB - A);
		var Yp2 = parseFloat(FromPt[1]) + DistFrom * Math.cos(AzAB - A);

// Alert and exit if no solution exists

		if (isNaN(Xp1) || isNaN(Yp1) || isNaN(Xp2) || isNaN(Yp2)) {

			alert('Distances do not intersect!');
			return;

		}

// Sketch both solutions and prompt user to choose correct point

		var pointsMatrix = new Array(5);
		pointsMatrix[0] = FromPt;
		pointsMatrix[1] = ToPt;
		pointsMatrix[2] = ['Sol_1', Yp1, Xp1, 0, 'Sol_1'];
		pointsMatrix[3] = ['Sol_2', Yp2, Xp2, 0, 'Sol_2'];
		pointsMatrix[4] = [0];

		SketchDistDist(pointsMatrix, 'IntersectCanvas', 1);

		var N = FormatDecimal(Yp1, 0);
		N = N.replace(/\s+/g,'');
		var E = FormatDecimal(Xp1, 0);
		E = E.replace(/\s+/g,'');

		Message = 'Solution 1:\n\n';
		Message += '     N:\t\t' + N + '\n';
		Message += '     E:\t\t' + E + '\n\n';
		Message += 'Accept this point?';

		var check1 = confirm(Message);

		if (!check1) {

			var N = FormatDecimal(Yp2, 0);
			N = N.replace(/\s+/g,'');
			var E = FormatDecimal(Xp2, 0);
			E = E.replace(/\s+/g,'');

			Message = 'Solution 2:\n\n';
			Message += '     N:\t\t' + N + '\n';
			Message += '     E:\t\t' + E + '\n\n';
			Message += 'Accept this point?';

			var check2 = confirm(Message);

		}

		if (check1) {

			var Xp = Xp1;
			var Yp = Yp1;

		}

		else if (check2) {

			var Xp = Xp2;
			var Yp = Yp2;

		}

		else {

			alert('No solution chosen!');
			return;

		}


// Format display values

		DistFrom = FormatDecimal(DistFrom + '', 0);
		DistTo = FormatDecimal(DistTo + '', 0);
		var N = FormatDecimal(Yp + '', 0);
		var E = FormatDecimal(Xp + '', 0);

// Create precise store point values

		var Np = Yp + '';
		var Ep = Xp + '';
		var Zp = 0 + '';

// Refresh from and to point coordinates, then form new diminished points matrix

		FromPt = GetPointList(FromPt[0], 0, 0);
		ToPt = GetPointList(ToPt[0], 0, 0);

		pointsMatrix = new Array(4);
		pointsMatrix[0] = FromPt;
		pointsMatrix[1] = ToPt;
		pointsMatrix[2] = ['Calc_Pt', Np, Ep, Zp, 'Calc_Pt'];
		pointsMatrix[3] = [0];

		var Results ='\n\n\n\n';
		Results += 'Dist-Dist from ' + FromPt[0] + '(' + FromPt[4] + ') to ' + ToPt[0] + '(' + ToPt[4] + '):\n\n';
		Results += '       From Dist: ' + DistFrom + '        To Dist: ' + DistTo + '\n\n';

		if (Store == 0) {

			Results += 'Calculated Point (NOT STORED):\n\n';

		}

		else if (Store == 1 && CheckPoint(Pt)) {

			Pt = FormatString(Pt, 1, 0);
			Desc = FormatString(Desc, 1, 0);

			Results += 'STORED ' + Pt + '(' + Desc +'):\n\n';

			pointsMatrix[2] = [Pt, Yp, Xp, 0, Pt];

			AddPoint(Pt, Np, Ep, Zp, Desc);

			document.getElementById('IntPt').value = '';

		}

		else {

			return;

		}

		Results += '       N:  ' + N + '\n       E:  ' + E + '\n';

		OutputCogo(Results, 'IntersectLog');
		SketchDistDist(pointsMatrix, 'IntersectCanvas', 0);

	}

}

// *******************************************************************************************************************************

function Traverse(Store, Increment) {

// Traverses to create new coordinates, storing a point if necessary (2D choice will result in same elevation)
// Input:  Store point switch, 0 for no, 1 for yes
// Output: The values of the traverse record text box and stored point, through the AddPoint function

// Find the contents of the input text boxes

	var FromPt = document.getElementById('TravFrom').value;
	var BSPt = document.getElementById('TravBS').value;
	var Ang = document.getElementById('TravAng').value;
	var Dist = document.getElementById('TravDist').value;
	var ZA = document.getElementById('TravZA').value;
	var Pt = document.getElementById('TravPt').value;
	var Desc = document.getElementById('TravDesc').value;

	var Dim = document.getElementById('TravDim').innerHTML;
	var Type = document.getElementById('TravType').innerHTML;

	var HI = parseFloat(document.getElementById('HI').value);
	var HT = parseFloat(document.getElementById('HT').value);

// Check to make sure appropriate fields aren't blank (HA case) and format BS point if necessary

	var AngBlankError = 'n azimuth';
	var BSRecord = '';
	var HIHTRecord = '';
	var AngRecord = '        Az:     ';

	if (Type == 'Angle') {

		if (!CheckBlank(BSPt, ' backsight point')) {

			return;

		}

		BSPt = FormatString(BSPt, 1, 0);

		if (!(BSPt = GetPointList(BSPt, 0, 0))) {

			return;

		}

		AngBlankError = ' horizontal angle';
		BSRecord = ' BS ' + BSPt[0] + '(' + BSPt[4] + ')';
		AngRecord = '        HA:     ';

	}

// Check to make sure appropriate fields aren't blank (2D and 3D cases)

	if (Dim == '2D' && (!CheckBlank(FromPt, ' point to traverse from') || !CheckBlank(Ang, AngBlankError) || !CheckBlank(Dist, ' horizontal distance') || (Store == 1 && (!CheckBlank(Pt, ' point name') || !CheckBlank(Desc, ' point description')))))  {

		return;

	}

	else if (Dim == '3D' && (!CheckBlank(FromPt, ' point to traverse from') || !CheckBlank(Ang, AngBlankError) || !CheckBlank(Dist, ' slope distance') || !CheckBlank(ZA, ' zenith angle') || (Store == 1 && (!CheckBlank(Pt, ' point name') || !CheckBlank(Desc, ' point description')))))  {

		return;

	}

// Format input to match values in point records

	FromPt = FormatString(FromPt, 1, 0);

// Get from point vector from points record and check

	if (!(FromPt = GetPointList(FromPt, 0, 0)) || (Type == 'Angle' && SamePoint(FromPt[0], BSPt[0], 'From and BS'))) {

		return;

	}

	Ang = ParseDMS(Ang);
	Ang2 = Ang;

	if (Type == 'Angle') {

		Ang = Ang + PtPtInverse(0, FromPt[0], BSPt[0])[2];

	}

	if (Dim == '3D') {

		ZA = ParseDMS(ZA);

		HIdisp = FormatDecimal(HI + '');
		HIdisp = HIdisp.replace(/\s+/g,'');
		HTdisp = FormatDecimal(HT + '');
		HTdisp = HTdisp.replace(/\s+/g,'');

		HIHTRecord = ' [HI=' + HIdisp + ', HT=' + HTdisp + ']';

	}

// Check to ensure inputs are acceptable, then compute results, output results, and add point (if necessary)

	if (Ang != 'X' && CheckDecimal(Dist) && ((Dim == '2D') || (Dim == '3D' && ZA != 'X'))) {

// Format zenith angle for calculations

		if (Dim == '2D') {

			ZA = Math.PI / 2;
			HI = 0;
			HT = 0;

		}

		if (Dim == '3D' && ZA > Math.PI) {

			ZA = Math.PI * 2 - ZA;

		}

		Dist = parseFloat(Dist);

		HD = Math.sin(ZA) * Dist;
		VD = Math.cos(ZA) * Dist + HI - HT;

		var Np = HD * Math.cos(Ang) + parseFloat(FromPt[1]);
		var Ep = HD * Math.sin(Ang) + parseFloat(FromPt[2]);
		var Zp = VD + parseFloat(FromPt[3]);

// Format display values

		Ang = FormatDMS(Ang2 + '');
		ZA = FormatDMS(ZA + '');
		Dist = FormatDecimal(Dist + '', 0);
		var N = FormatDecimal(Np + '', 0);
		var E = FormatDecimal(Ep + '', 0);
		var Z = FormatDecimal(Zp + '', 0);

// Create precise store point values

		Np = Np + '';
		Ep = Ep + '';
		Zp = Zp + '';

// Create calc output and sketch traverse

		var ZAline = '';
		var Zline = '';
		var distLabel = 'HD:';

		if (Dim == '3D') {

			var Spacer = '           ';

				for (var j=0; j<Dist.length; j++) {

					Spacer += ' ';

				}

			ZAline= Spacer + '        ZA:     ' + ZA + '\n';
			Zline= '       Z:  ' + Z + '\n';
			distLabel = 'SD:';

		}

		var Results ='\n\n\n\n';
		Results += Dim + ' by ' + Type + ' from ' + FromPt[0] + '(' + FromPt[4] + ')' + BSRecord + HIHTRecord + ':\n\n';
		Results += '       ' + distLabel +' ' + Dist + AngRecord + Ang + '\n';
		Results += ZAline;

		if (Store == 0) {

			Results += '\nCalculated Point (NOT STORED):\n\n'

			Pt = 'Calc_Pt';

		}

		else if (Store == 1 && CheckPoint(Pt)) {

			Pt = FormatString(Pt, 1, 0);
			Desc = FormatString(Desc, 1, 0);

			Results += '\nSTORED ' + Pt + '(' + Desc +'):\n\n'

			AddPoint(Pt, Np, Ep, Zp, Desc);

			if (Increment) {

				document.getElementById('TravBS').value = document.getElementById('TravFrom').value;
				document.getElementById('TravFrom').value = Pt;

			}

			document.getElementById('TravPt').value = '';

		}

		else {

			return;

		}

		Results += '       N:  ' + N + '\n       E:  ' + E + '\n' + Zline;

		if (Type == 'Azimuth') {

			var pointsMatrix = new Array(3);
			pointsMatrix[0] = FromPt;
			pointsMatrix[1] = [Pt, Np, Ep, Zp, Desc];
			pointsMatrix[2] = [0];

		}

		else {

			var pointsMatrix = new Array(3);
			pointsMatrix[0] = BSPt;
			pointsMatrix[1] = FromPt;
			pointsMatrix[2] = [Pt, Np, Ep, Zp, Desc];
			pointsMatrix[3] = [0];

		}

		SketchPtPt(pointsMatrix, 'TraverseCanvas');
		OutputCogo(Results, 'TraverseLog');

	}

}

