PageSourceSearch

https://www.provisualizer.com/3dplanner.js

js provisualizer.com collected 2026-10-05 03:43:13 UTC 231,083 bytes, 6,967 lines download raw bytes

1var viewer; // Cesium Viewer
2var handler; // Cesium Handler
3var mapMode = 2; // 1 = 2D, 2 = 3D
4var terrainMode = 1; // 1 = flat, 2 = hills
5var terrainProvider;
6var mapMarkers = [];
7var mapMarkers2 = [];
8var mapLines = [];
9var mapLines2 = [];
10var mapCircles = [];
11var mode = 0;
12var dispersionMode = 0;
13var initName = "";
14var initHole = 1;
15var distLat1 = 0;
16var distLon1 = 0;
17var distLat2 = 0;
18var distLon2 = 0;
19var elevationReqTimestamp = [];
20var elevationLat = [];
21var elevationLon = [];
22var elevation = [];
23var elevation2Lat = [];
24var elevation2Lon = [];
25var elevation2 = [];
26var elevationTimestamp = 0;
27var viewTimestamp = 0;
28var teeGridCount = 0;
29var greenGridCount = 0;
30var greenSlopesCount = 0;
31var greenSlopesBearing = 0;
32var greenContoursCount = 0;
33var distCount = 0;
34var coneCount = 0;
35var bestClubID = [];
36	
37async function bodyOnLoadHandler()
38{
39	if (!PV.auth.cookieConsentIsValid())       { window.location.href = "support/privacypolicy.php"; return; }
40	if (!PV.auth.localStorageConsentIsValid()) { window.location.href = "support/privacypolicy.php"; return; }
41	if (!PV.auth.termsAgreementIsValid())      { window.location.href = "support/terms.php"; return; }
42	initProfile();
43	initCourseData(0);
44	populateCourseSelect();
45	initSelectedUnits();
46	initTeeCircles();
47	initLineColor();
48	initLineBrightness();
49	initCircleColor();
50	initCircleBrightness();
51	initAutoRotate();
52	initCompass();
53	initDispersion();
54	initCones();
55	processLinkParameters();
56	document.getElementById("saveasinput").value = initName;
57	document.getElementById("layerselect").selectedIndex = 0;
58	document.getElementById("terrainselect").selectedIndex = 0;
59	document.getElementById("geheightselect").selectedIndex = 0;
60	document.getElementById("greenspeedselect").selectedIndex = 4;
61	document.getElementById("greenslopecolorselect").selectedIndex = 0;
62	document.getElementById("greenslopesizeselect").selectedIndex = 1;
63	document.getElementById("greenslopebrightnessselect").selectedIndex = 4;
64	document.getElementById("greencontourselect").selectedIndex = 1;
65	document.getElementById("greencontourminselect").selectedIndex = 0;
66	document.getElementById("greencontourbrightnessselect").selectedIndex = 4;
67	document.getElementById("puttingspeedselect").selectedIndex = 0;
68	setSelectedHole(initHole);
69	var p = navigator.platform;
70	if (p == "iPad" || p == "iPhone")
71	{
72		document.getElementById('maptd').style.width = '65%';
73		document.getElementById('buttontd').style.width = '25%';
74	}
75	bodyOnResizeHandler();
76	var imageryProvider = await PV.auth.newCesiumBingMapsImageryProviderSatellite();
77	var baseLayer = new Cesium.ImageryLayer(imageryProvider);
78	terrainProvider = new Cesium.EllipsoidTerrainProvider();
79	viewer = new Cesium.Viewer(
80		'mapdiv',
81		{
82			baseLayer : baseLayer,
83			terrainProvider : terrainProvider,
84			animation : false,
85			baseLayerPicker : false,
86			fullscreenButton : false,
87			vrButton : false,
88			geocoder : false,
89			homeButton : false,
90			infoBox : true,
91			sceneModePicker : false,
92			selectionIndicator : false,
93			timeline : false,
94			useDefaultRenderLoop : true
95		});
96	viewer.scene.globe.maximumScreenSpaceError = 2.0;
97	viewer.scene.globe.showGroundAtmosphere = false;
98	viewer.scene.screenSpaceCameraController._zoomFactor = 50/minWheel;
99	viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
100	viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
101	viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK);
102	handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
103	handler.setInputAction(mapLeftClickHandler, Cesium.ScreenSpaceEventType.LEFT_CLICK);
104	handler.setInputAction(mapRightClickHandler, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
105	handler.setInputAction(mapMouseMoveHandler, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
106	handler.setInputAction(mapWheelHandler, Cesium.ScreenSpaceEventType.WHEEL);
107	if (mapMode == 2 && getCourseHoles(currentCourseID()) < 1) toggle3D();
108	zoomToCourseHole(0, initHole, true, true, 0);
109	window.setTimeout(compassRenderLoop, 1000);
110	window.setTimeout(refreshElevation, 3000);
111	window.setTimeout(viewerRenderLoop, 5000);
112	window.setTimeout(satelliteProviderLoop1, 5000);
113	startHeartbeat("3dplanner", 60);
114}
115
116var minWheel = 1000000;
117function mapWheelHandler(wheel)
118{
119	var w = Math.abs(wheel);
120	if (w < 1.0) w = 1.0;
121	if (w < minWheel)// || w > 100*minWheel)
122	{
123		minWheel = w;
124		viewer.scene.screenSpaceCameraController._zoomFactor = 50/minWheel;
125	}
126}
127
128var fastRenderStopTime = 0;
129function fastRender(seconds)
130{
131	var t = Date.now() + Math.round(1000*seconds) + 500;
132	if (fastRenderStopTime < t)
133		fastRenderStopTime = t;
134}
135
136var showTileCount = false;
137function viewerRenderLoop()
138{
139	viewer.useDefaultRenderLoop = false;
140	viewer.resize();
141	viewer.render();
142	if (showTileCount)
143	{
144		var s =   "Bing Tile Count = "+PV.auth.BingTileCount.toString()+", unique = "+PV.auth.BingTileCounts.size.toString();
145		s += "<br/>Esri Tile Count = "+PV.auth.EsriTileCount.toString()+", unique = "+PV.auth.EsriTileCounts.size.toString();
146		s += "<br/>NASA Tile Count = "+PV.auth.NASATileCount.toString()+", unique = "+PV.auth.NASATileCounts.size.toString();
147		s += "<br/>OSM Tile Count = "+PV.auth.OSMTileCount.toString()+", unique = "+PV.auth.OSMTileCounts.size.toString();
148		document.getElementById('coordsdiv').innerHTML = s;
149	}
150	var renderDelay = 50;
151	if (Date.now() < fastRenderStopTime)
152	{
153		viewer.scene.screenSpaceCameraController._zoomFactor = 20;
154		renderDelay = 20;
155	}
156	else
157	{
158		viewer.scene.screenSpaceCameraController._zoomFactor = 50;
159	}
160	window.setTimeout(viewerRenderLoop, renderDelay);
161}
162
163function flyToHeightAboveGround(lat, lon, hlat, hlon, heightAboveGround, heading, angle, durationSeconds)
164{
165	var h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(hlat, hlon));
166	flyToHeightAboveEllipsoid(lat, lon, h + heightAboveGround, heading, angle, durationSeconds);
167}
168
169function flyToHeightAboveEllipsoid(lat, lon, heightAboveEllipsoid, heading, angle, durationSeconds)
170{
171	fastRender(durationSeconds);
172	viewer.camera.flyTo({
173		destination : Cesium.Cartesian3.fromDegrees(lon, lat, heightAboveEllipsoid),
174		orientation : {
175			heading : heading * Math.PI / 180,
176			pitch : angle * Math.PI / 180,
177			roll : 0
178		},
179		duration : durationSeconds
180	});
181}
182
183var compassCanvas = null;
184var compassCtx = null;
185var weatherCanvas = null;
186var weatherCtx = null;
187
188function initCompass()
189{
190	compassCanvas = document.getElementById("compasscanvas");
191	compassCtx = compassCanvas.getContext("2d");
192	weatherCanvas = document.getElementById("weathercanvas");
193	weatherCtx = weatherCanvas.getContext("2d");
194}
195
196function compassRenderLoop()
197{
198	var cameraHeading = viewer.camera.heading;
199	var theta = -cameraHeading;
200	var c = Math.cos(theta);
201	var s = Math.sin(theta);
202	var x = []; var y = [];
203	// Cross
204	x[0] = -6; y[0] = 0;
205	x[1] = 6; y[1] = 0;
206	x[2] = 0; y[2] = -6;
207	x[3] = 0; y[3] = 6;
208	// North
209	x[4] = -3; y[4] = 8;
210	x[5] = -3; y[5] = 17;
211	x[6] = 3; y[6] = 9;
212	x[7] = 3; y[7] = 18;
213	// South
214	x[8] = -3; y[8] = -16;
215	x[9] = -3; y[9] = -17;
216	x[10] = 2; y[10] = -17;
217	x[11] = 3; y[11] = -17;
218	x[12] = 3; y[12] = -13;
219	x[13] = 2; y[13] = -13;
220	x[14] = -2; y[14] = -13;
221	x[15] = -3; y[15] = -13;
222	x[16] = -3; y[16] = -9;
223	x[17] = -2; y[17] = -9;
224	x[18] = 3; y[18] = -9;
225	x[19] = 3; y[19] = -10;
226	// East
227	x[20] = 16; y[20] = -4;
228	x[21] = 10; y[21] = -4;
229	x[22] = 10; y[22] = 4;
230	x[23] = 16; y[23] = 4;
231	x[24] = 10; y[24] = 0;
232	x[25] = 16; y[25] = 0;
233	// West
234	x[26] = -16; y[26] = 4;
235	x[27] = -15; y[27] = -4;
236	x[28] = -13; y[28] = 0;
237	x[29] = -11; y[29] = -4;
238	x[30] = -10; y[30] = 4;
239	var x2 = []; var y2 = [];
240	for (var i = 0; i <= 30; ++i)
241	{
242		x2[i] = 20 + 0.9*(c*x[i] + s*y[i]);
243		y2[i] = 20 + 0.9*(s*x[i] - c*y[i]);
244	}
245	compassCtx.clearRect(0,0,40,40);
246	compassCtx.lineWidth = 1;
247	compassCtx.strokeStyle = "#FF0000";
248	compassCtx.beginPath();
249	// North
250	compassCtx.moveTo(x2[4],y2[4]);
251	compassCtx.lineTo(x2[5],y2[5]);
252	compassCtx.lineTo(x2[6],y2[6]);
253	compassCtx.lineTo(x2[7],y2[7]);
254	compassCtx.stroke();
255	compassCtx.strokeStyle = "#888888";
256	compassCtx.beginPath();
257	// Cross
258	compassCtx.moveTo(x2[0],y2[0]);
259	compassCtx.lineTo(x2[1],y2[1]);
260	compassCtx.moveTo(x2[2],y2[2]);
261	compassCtx.lineTo(x2[3],y2[3]);
262	// South
263	compassCtx.moveTo(x2[8],y2[8]);
264	compassCtx.lineTo(x2[9],y2[9]);
265	compassCtx.lineTo(x2[10],y2[10]);
266	compassCtx.lineTo(x2[11],y2[11]);
267	compassCtx.lineTo(x2[12],y2[12]);
268	compassCtx.lineTo(x2[13],y2[13]);
269	compassCtx.lineTo(x2[14],y2[14]);
270	compassCtx.lineTo(x2[15],y2[15]);
271	compassCtx.lineTo(x2[16],y2[16]);
272	compassCtx.lineTo(x2[17],y2[17]);
273	compassCtx.lineTo(x2[18],y2[18]);
274	compassCtx.lineTo(x2[19],y2[19]);
275	// East
276	compassCtx.moveTo(x2[20],y2[20]);
277	compassCtx.lineTo(x2[21],y2[21]);
278	compassCtx.lineTo(x2[22],y2[22]);
279	compassCtx.lineTo(x2[23],y2[23]);
280	compassCtx.moveTo(x2[24],y2[24]);
281	compassCtx.lineTo(x2[25],y2[25]);
282	// West
283	compassCtx.moveTo(x2[26],y2[26]);
284	compassCtx.lineTo(x2[27],y2[27]);
285	compassCtx.lineTo(x2[28],y2[28]);
286	compassCtx.lineTo(x2[29],y2[29]);
287	compassCtx.lineTo(x2[30],y2[30]);
288	compassCtx.stroke();
289	if (weatherIndex >= 0)
290		renderWind(cameraHeading);
291	var renderDelay = 500;
292	if (Date.now() < fastRenderStopTime)
293		renderDelay = 100;
294	window.setTimeout(compassRenderLoop, renderDelay);
295}
296
297function renderWind(cameraHeading)
298{
299	var theta = weatherWindBearing*(Math.PI/180.0) - cameraHeading;
300	var k = (weatherWindSpeed > 0 ? 1.4 : 0.0);
301	var c = Math.cos(theta);
302	var s = Math.sin(theta);
303	var x = []; var y = [];
304	x[0] =  0; y[0] =  10;
305	x[1] = -3; y[1] =   6;
306	x[2] =  3; y[2] =   6;
307	x[3] =  0; y[3] = -10;
308	var x2 = []; var y2 = [];
309	for (var i = 0; i <= 3; ++i)
310	{
311		x2[i] = 20 + k*(c*x[i] + s*y[i]);
312		y2[i] = 20 + k*(s*x[i] - c*y[i]);
313	}
314	weatherCtx.clearRect(0,0,40,40);
315	weatherCtx.lineWidth = 1;
316	weatherCtx.strokeStyle = "#888888";
317	weatherCtx.beginPath();
318	weatherCtx.moveTo(x2[0],y2[0]);
319	weatherCtx.lineTo(x2[1],y2[1]);
320	weatherCtx.moveTo(x2[0],y2[0]);
321	weatherCtx.lineTo(x2[2],y2[2]);
322	weatherCtx.moveTo(x2[0],y2[0]);
323	weatherCtx.lineTo(x2[3],y2[3]);
324	weatherCtx.stroke();
325}
326
327function bodyOnResizeHandler()
328{
329	var x = document.getElementById("mapdiv");
330	x.style.height = Math.round(window.innerHeight - 10).toString() + 'px';
331}
332
333function layerHelp()
334{
335	alert("Use this drop-down to select the imagery to display. The 'Terrain' overlay is based on the SRTM 1 Arc-Second Global Dataset between 60-degrees north and 56-degrees south, and the ASTER GDEM V3 Dataset above 60-degrees north and below 56-degrees south. Note that the bright lines on the Terrain overlay are 10-metre contours. The 'Lidar' overlay is currently only available in the United States and England. Note that the Lidar imagery is generated on-demand and typically takes around 10-20 seconds to load. If it has not loaded after 30 seconds, it probably means it is not available in your region.");
336}
337
338async function layerSelectHandler()
339{
340	setMode(0);
341	var i = document.getElementById("layerselect").selectedIndex;
342	var layers = viewer.scene.imageryLayers;
343	while (layers.length > 1) layers.remove(layers.get(layers.length - 1), true);
344	satelliteProvider = 0;
345	if (i == 1)
346	{
347		terrainModel = 1;
348		var s = document.getElementById('saveasinput').value;
349		if (s != null && s.indexOf('!!dsm') == 0) { terrainModel = 2; }
350		var courseID = currentCourseID();
351		var courseName = getCourseNameFromID(courseID);
352		if (courseName == "") courseName = cleanString(document.getElementById("saveasinput").value);
353		var minLat = getCourseMinLat(courseID);
354		var minLon = getCourseMinLon(courseID);
355		var maxLat = getCourseMaxLat(courseID);
356		var maxLon = getCourseMaxLon(courseID);
357		getProVisualizerTerrainData1(minLat, minLon, maxLat, maxLon, 200, viewer, courseName);
358	}
359	else if (i == 2)
360	{
361		var courseID = currentCourseID();
362		var minLat1 = getCourseMinLat(courseID);
363		var minLon1 = getCourseMinLon(courseID);
364		var maxLat1 = getCourseMaxLat(courseID);
365		var maxLon1 = getCourseMaxLon(courseID);
366		var minLat = Math.round(moveLat(minLat1, minLon1, 200, 180)*10000)/10000;
367		var minLon = Math.round(moveLon(minLat1, minLon1, 200, 270)*10000)/10000;
368		var maxLat = Math.round(moveLat(maxLat1, maxLon1, 200,   0)*10000)/10000;
369		var maxLon = Math.round(moveLon(maxLat1, maxLon1, 200,  90)*10000)/10000;
370		var azimuth = 0.0;
371		var altitude = 40.0;
372		var zFactor = 1.0;
373		var degToRad = Math.PI / 180.0;
374		var dx = 2*geogDist(minLat, minLon, minLat, maxLon)/Math.cos(((minLat+maxLat)/2)*degToRad);
375		var dy = 2*geogDist(minLat, minLon, maxLat, minLon);
376		var k = Math.sqrt(1+dx*dy)/3500.0;
377		if (k > 1.0) { dx /= k; dy /= k; }
378		var s = document.getElementById('saveasinput').value;
379		if (s == null) s = "";
380		if (s.indexOf("!!dsmEN") == 0)
381		{
382			// England DSM
383			var url = 'https://environment.data.gov.uk/geoservices/datasets/df4e3ec3-315e-48aa-aaaf-b5ae74d7b2bb/wms?';
384			url += 'VERSION=1.3.0';
385			url += '&SERVICE=WMS';
386			url += '&REQUEST=GetMap';
387			url += '&LAYERS=Lidar_Composite_Hillshade_FZ_DSM_1m';
388			url += '&STYLES=hillshade';
389			url += '&CRS=CRS%3A84';
390			url += '&BBOX='+minLon.toString()+'%2C'+minLat.toString()+'%2C'+maxLon.toString()+'%2C'+maxLat.toString();
391			url += '&WIDTH='+Math.round(dx).toString();
392			url += '&HEIGHT='+Math.round(dy).toString();
393			url += '&FORMAT=image%2Fpng';
394			url += '&TRANSPARENT=true';
395			url += '&BGCOLOR=0x000000';
396			layers.addImageryProvider(new Cesium.SingleTileImageryProvider(
397			{
398				url : url,
399				rectangle : new Cesium.Rectangle(minLon*degToRad, minLat*degToRad, maxLon*degToRad, maxLat*degToRad)
400			}));
401		}
402		else if (s.indexOf("!!dsmWA") == 0)
403		{
404			// Wales DSM
405			var url = 'https://datamap.gov.wales/geoserver/ows?';
406			url += 'VERSION=1.3.0';
407			url += '&SERVICE=WMS';
408			url += '&REQUEST=GetMap';
409			url += '&LAYERS=geonode%3Awales_lidar_dsm_1m_hillshade_cog';
410			url += '&STYLES=';
411			url += '&CRS=CRS%3A84';
412			url += '&BBOX='+minLon.toString()+'%2C'+minLat.toString()+'%2C'+maxLon.toString()+'%2C'+maxLat.toString();
413			url += '&WIDTH='+Math.round(dx).toString();
414			url += '&HEIGHT='+Math.round(dy).toString();
415			url += '&FORMAT=image%2Fpng';
416			url += '&TILED=true';
417			url += '&TRANSPARENT=true';
418			url += '&BGCOLOR=0x000000';
419			layers.addImageryProvider(new Cesium.SingleTileImageryProvider(
420			{
421				url : url,
422				rectangle : new Cesium.Rectangle(minLon*degToRad, minLat*degToRad, maxLon*degToRad, maxLat*degToRad)
423			}));
424		}
425		else if (s.indexOf("!!dtmSC") == 0)
426		{
427			// Scotland DTM
428			var phase = "1";
429			if (s.indexOf("!!dtmSC2") == 0) phase = "2";
430			if (s.indexOf("!!dtmSC3") == 0) phase = "3";
431			if (s.indexOf("!!dtmSC4") == 0) phase = "4";
432			if (s.indexOf("!!dtmSC5") == 0) phase = "5";
433			if (s.indexOf("!!dtmSC6") == 0) phase = "6";
434			layers.addImageryProvider(new Cesium.SingleTileImageryProvider(
435			{
436				url : 'https://srsp-ows.jncc.gov.uk/ows?service=WMS&request=GetMap&layers=scotland%3Ascotland-lidar-'+phase+'-dtm&styles=scotland%3Alidar-dem&format=image%2Fpng&transparent=true&version=1.1.1&tiled=true&bbox='+minLon.toString()+'%2C'+minLat.toString()+'%2C'+maxLon.toString()+'%2C'+maxLat.toString()+'&srs=EPSG%3A4326&width='+Math.round(dx).toString()+'&height='+Math.round(dy).toString(),
437				rectangle : new Cesium.Rectangle(minLon*degToRad, minLat*degToRad, maxLon*degToRad, maxLat*degToRad)
438			}));
439		}
440		else if (s.indexOf("!!dsmSC") == 0)
441		{
442			// Scotland DSM
443			var phase = "1";
444			if (s.indexOf("!!dsmSC2") == 0) phase = "2";
445			if (s.indexOf("!!dsmSC3") == 0) phase = "3";
446			if (s.indexOf("!!dsmSC4") == 0) phase = "4";
447			if (s.indexOf("!!dsmSC5") == 0) phase = "5";
448			if (s.indexOf("!!dsmSC6") == 0) phase = "6";
449			layers.addImageryProvider(new Cesium.SingleTileImageryProvider(
450			{
451				url : 'https://srsp-ows.jncc.gov.uk/ows?service=WMS&request=GetMap&layers=scotland%3Ascotland-lidar-'+phase+'-dsm&styles=scotland%3Alidar-dem&format=image%2Fpng&transparent=true&version=1.1.1&tiled=true&bbox='+minLon.toString()+'%2C'+minLat.toString()+'%2C'+maxLon.toString()+'%2C'+maxLat.toString()+'&srs=EPSG%3A4326&width='+Math.round(dx).toString()+'&height='+Math.round(dy).toString(),
452				rectangle : new Cesium.Rectangle(minLon*degToRad, minLat*degToRad, maxLon*degToRad, maxLat*degToRad)
453			}));
454		}
455		else if (minLat > 49 && maxLat < 56 && minLon > -8 && maxLon < 3 && dx < 10000 && dy < 10000)
456		{
457			// England DTM
458			var url = 'https://environment.data.gov.uk/geoservices/datasets/13787b9a-26a4-4775-8523-806d13af58fc/wms?';
459			url += 'VERSION=1.3.0';
460			url += '&SERVICE=WMS';
461			url += '&REQUEST=GetMap';
462			url += '&LAYERS=Lidar_Composite_Hillshade_DTM_1m';
463			url += '&STYLES=hillshade';
464			url += '&CRS=CRS%3A84';
465			url += '&BBOX='+minLon.toString()+'%2C'+minLat.toString()+'%2C'+maxLon.toString()+'%2C'+maxLat.toString();
466			url += '&WIDTH='+Math.round(dx).toString();
467			url += '&HEIGHT='+Math.round(dy).toString();
468			url += '&FORMAT=image%2Fpng';
469			url += '&TRANSPARENT=true';
470			url += '&BGCOLOR=0x000000';
471			layers.addImageryProvider(new Cesium.SingleTileImageryProvider(
472			{
473				url : url,
474				rectangle : new Cesium.Rectangle(minLon*degToRad, minLat*degToRad, maxLon*degToRad, maxLat*degToRad)
475			}));
476		}
477		else if (minLat > 0 && maxLat < 90 && minLon > -180 && maxLon < -20 && dx < 10000 && dy < 10000)
478		{
479			// United Statesa DTM
480			var url = 'https://elevation.nationalmap.gov/arcgis/rest/services/3DEPElevation/ImageServer/exportImage?';
481			url += 'f=image';
482			url += '&bandIds=';
483			url += '&renderingRule=' + encodeURIComponent('%7B"rasterFunction"%3A"Hillshade","rasterFunctionArguments"%3A%7B"Azimuth"%3A'+azimuth.toString()+',"Altitude"%3A'+altitude.toString()+',"ZFactor"%3A'+zFactor.toString()+'%7D,"variableName"%3A"DEM"%7D');
484			url += '&bbox='+minLon.toString()+'%2C'+minLat.toString()+'%2C'+maxLon.toString()+'%2C'+maxLat.toString();
485			url += '&imageSR=4326&bboxSR=4326&size='+Math.round(dx).toString()+'%2C'+Math.round(dy).toString();
486			layers.addImageryProvider(new Cesium.SingleTileImageryProvider(
487			{
488				url : url,
489				rectangle : new Cesium.Rectangle(minLon*degToRad, minLat*degToRad, maxLon*degToRad, maxLat*degToRad)
490			}));
491		}
492	}
493	else if (i == 3)
494	{
495		var s = document.getElementById('saveasinput').value;
496		if (s != null && s.indexOf('!!esri12') == 0) { layers.addImageryProvider(await PV.auth.newCesiumEsriImageryProviderSatellite(12)); return; }
497		if (s != null && s.indexOf('!!esri13') == 0) { layers.addImageryProvider(await PV.auth.newCesiumEsriImageryProviderSatellite(13)); return; }
498		if (s != null && s.indexOf('!!esri14') == 0) { layers.addImageryProvider(await PV.auth.newCesiumEsriImageryProviderSatellite(14)); return; }
499		if (s != null && s.indexOf('!!esri15') == 0) { layers.addImageryProvider(await PV.auth.newCesiumEsriImageryProviderSatellite(15)); return; }
500		if (s != null && s.indexOf('!!esri16') == 0) { layers.addImageryProvider(await PV.auth.newCesiumEsriImageryProviderSatellite(16)); return; }
501		if (s != null && s.indexOf('!!esri17') == 0) { layers.addImageryProvider(await PV.auth.newCesiumEsriImageryProviderSatellite(17)); return; }
502		if (s != null && s.indexOf('!!esri18') == 0) { layers.addImageryProvider(await PV.auth.newCesiumEsriImageryProviderSatellite(18)); return; }
503		if (s != null && s.indexOf('!!esri') == 0) { layers.addImageryProvider(await PV.auth.newCesiumEsriImageryProviderSatellite(19)); return; }
504		if (s != null && s.indexOf('!!nasa+bm+osm') == 0)
505		{
506			layers.addImageryProvider(newNasaImageryProviderTerrain());
507			var bmLayer = layers.addImageryProvider(PV.auth.newCesiumSingleTileImageryProviderNASABlueMarble()); bmLayer.alpha = 0.5;
508			var osmLayer = layers.addImageryProvider(await PV.auth.newCesiumOSMImageryProviderMap(18)); osmLayer.alpha = 0.65;
509			return;
510		}
511		if (s != null && s.indexOf('!!nasa+osm') == 0)
512		{
513			layers.addImageryProvider(newNasaImageryProviderTerrain());
514			var osmLayer = layers.addImageryProvider(await PV.auth.newCesiumOSMImageryProviderMap(18)); osmLayer.alpha = 0.65;
515			return;
516		}
517		if (s != null && s.indexOf('!!bm+osm') == 0)
518		{
519			layers.addImageryProvider(PV.auth.newCesiumSingleTileImageryProviderNASABlueMarble());
520			var osmLayer = layers.addImageryProvider(await PV.auth.newCesiumOSMImageryProviderMap(18)); osmLayer.alpha = 0.65;
521			return;
522		}
523		if (s != null && s.indexOf('!!nasa') == 0) { layers.addImageryProvider(newNasaImageryProviderTerrain()); return; }
524		if (s != null && s.indexOf('!!bm') == 0) { layers.addImageryProvider(PV.auth.newCesiumSingleTileImageryProviderNASABlueMarble()); return; }
525		if (s != null && s.indexOf('!!osm') == 0)
526		{
527			var zMax = 18;
528			if (s.length > 5 && !isNaN(parseInt(s.substring(5)))) zMax = parseInt(s.substring(5));
529			if (zMax < 0) zMax = 0;
530			if (zMax > 20) zMax = 20;
531			PV.auth.clearOSMTileRanges();
532			var courseID = currentCourseID();
533			for (var hole = 1; hole <= 18; ++hole)
534			{
535				for (var j = 1; j <= 5; ++j)
536				{
537					var lat = 0;
538					var lon = 0;
539					if (j == 1) { lat = getCourseHoleTeeLat(courseID, hole); lon = getCourseHoleTeeLon(courseID, hole); }
540					if (j == 2) { lat = getCourseHolePinLat(courseID, hole); lon = getCourseHolePinLon(courseID, hole); }
541					if (j == 3) { lat = getCourseHoleTargetLat(courseID, hole, 1); lon = getCourseHoleTargetLon(courseID, hole, 1); }
542					if (j == 4) { lat = getCourseHoleTargetLat(courseID, hole, 2); lon = getCourseHoleTargetLon(courseID, hole, 2); }
543					if (j == 5) { lat = getCourseHoleTargetLat(courseID, hole, 3); lon = getCourseHoleTargetLon(courseID, hole, 3); }
544					if (lat != 0 || lon != 0)
545					{
546						var buffer = 200;
547						minLat = moveLat(lat, lon, buffer, 180);
548						minLon = moveLon(lat, lon, buffer, 270);
549						maxLat = moveLat(lat, lon, buffer,   0);
550						maxLon = moveLon(lat, lon, buffer,  90);
551						PV.auth.addOSMTileRange(zMax, minLat, minLon, maxLat, maxLon);
552						for (var z = zMax - 1; z >= 15; --z)
553							PV.auth.addOSMTileRange(z, minLat, minLon, maxLat, maxLon);
554					}
555				}
556			}
557			if (zMax > 15) layers.addImageryProvider(await PV.auth.newCesiumOSMImageryProviderMap(15));
558			layers.addImageryProvider(await PV.auth.newCesiumOSMImageryProviderMap(zMax));
559			return;
560		}
561		var imageryProvider = await PV.auth.newCesiumBingMapsImageryProviderMap();
562		var layer = new Cesium.ImageryLayer(imageryProvider);
563		layers.add(layer);
564	}
565	else if (i == 4)
566	{
567		var courseID = currentCourseID();
568		var minLat1 = getCourseMinLat(courseID);
569		var minLon1 = getCourseMinLon(courseID);
570		var maxLat1 = getCourseMaxLat(courseID);
571		var maxLon1 = getCourseMaxLon(courseID);
572		var buffer = 100.0;
573		var minLat = Math.round(moveLat(minLat1, minLon1, buffer, 180)*1000000)/1000000;
574		var minLon = Math.round(moveLon(minLat1, minLon1, buffer, 270)*1000000)/1000000;
575		var maxLat = Math.round(moveLat(maxLat1, maxLon1, buffer,   0)*1000000)/1000000;
576		var maxLon = Math.round(moveLon(maxLat1, maxLon1, buffer,  90)*1000000)/1000000;
577		customImageryFile = null;
578		customImageryFilename = "";
579		document.getElementById('customimageryminlat').value = minLat.toString();
580		document.getElementById('customimageryminlon').value = minLon.toString();
581		document.getElementById('customimagerymaxlat').value = maxLat.toString();
582		document.getElementById('customimagerymaxlon').value = maxLon.toString();
583		document.getElementById('customimagefilename').value = "";
584		document.getElementById('customimageryselect').value = "";
585		document.getElementById('customimagerydiv').style.display = '';
586	}
587}
588
589function copyCustomImageFilenameToClipboard()
590{
591	var filename = document.getElementById('customimagefilename').value;
592	if (filename == null) filename = '';
593	navigator.clipboard.writeText(filename);
594}
595
596function getCustomImageryBBoxKML()
597{
598	var minLat = parseFloat(document.getElementById('customimageryminlat').value);
599	var minLon = parseFloat(document.getElementById('customimageryminlon').value);
600	var maxLat = parseFloat(document.getElementById('customimagerymaxlat').value);
601	var maxLon = parseFloat(document.getElementById('customimagerymaxlon').value);
602	if (isNaN(minLat) || minLat < -90  || minLat > +90)  { alert("Please enter valid Min Latitude in range -90 .. +90"); return; }
603	if (isNaN(maxLat) || maxLat < -90  || maxLat > +90)  { alert("Please enter valid Max Latitude in range -90 .. +90"); return; }
604	if (isNaN(minLon) || minLon < -180 || minLon > +180) { alert("Please enter valid Min Longitude in range -180 .. +180"); return; }
605	if (isNaN(maxLon) || maxLon < -180 || maxLon > +180) { alert("Please enter valid Max Longitude in range -180 .. +180"); return; }
606	if (maxLat <= minLat)   { alert("Max Latitude must be greater than Min Latitude"); return; }
607	if (maxLat >= minLat+1) { alert("Max Latitude must be less than Min Latitude + 1 degree"); return; }
608	while (maxLon > minLon + 180) maxLon -= 360;
609	while (maxLon < minLon - 180) maxLon += 360;
610	if (Math.abs(maxLon - minLon) > 1) { alert("Max Longitude must be less than Min Longitude + 1 degree"); return; }
611	var courseID = currentCourseID();
612	var courseName = getCourseNameFromID(courseID);
613	if (courseName == null || courseName == "") courseName = cleanString(document.getElementById("saveasinput").value);
614	if (courseName == null || courseName == "") courseName = "CourseName";
615	var filename = courseName
616		+"_"+minLat.toString()
617		+"_"+maxLat.toString()
618		+"_"+minLon.toString()
619		+"_"+maxLon.toString()
620		+".jpg";
621	var x = document.getElementById('customimagefilename');
622	x.value = filename;
623	window.location.href = "bboxkml.php?"
624				+"minlat="+minLat.toString()
625				+"&minlon="+minLon.toString()
626				+"&maxlat="+maxLat.toString()
627				+"&maxlon="+maxLon.toString();
628}
629
630function cancelCustomImageryDialog()
631{
632	document.getElementById('customimagerydiv').style.display = 'none';
633	document.getElementById("layerselect").selectedIndex = 0;
634	layerSelectHandler();
635}
636
637function confirmCustomImageryDialog()
638{
639	var x = document.getElementById("customimageryselect");
640	if (x.files == null || x.files.length != 1) { alert("Please select image."); return; }
641	var f = x.files[0];
642	var fname = f.name;
643	var cpnts = fname.split("_");
644	var n = cpnts.length;
645	if (n < 5) { alert("Filename does not contain bounding box coordinates. Please select valid image file."); return; }
646	var minLat = parseFloat(cpnts[n - 4]);
647	var maxLat = parseFloat(cpnts[n - 3]);
648	var minLon = parseFloat(cpnts[n - 2]);
649	var maxLon = parseFloat(cpnts[n - 1]);
650	if (isNaN(minLat) || minLat < -90  || minLat > +90)  { alert("Min Latitude must be in range -90 .. +90"); return; }
651	if (isNaN(maxLat) || maxLat < -90  || maxLat > +90)  { alert("Max Latitude must be in range -90 .. +90"); return; }
652	if (isNaN(minLon) || minLon < -180 || minLon > +180) { alert("Min Longitude must be in range -180 .. +180"); return; }
653	if (isNaN(maxLon) || maxLon < -180 || maxLon > +180) { alert("Max Longitude must be in range -180 .. +180"); return; }
654	if (maxLat <= minLat)   { alert("Max Latitude must be greater than Min Latitude"); return; }
655	if (maxLat >= minLat+1) { alert("Max Latitude must be less than Min Latitude + 1 degree"); return; }
656	while (maxLon > minLon + 180) maxLon -= 360;
657	while (maxLon < minLon - 180) maxLon += 360;
658	if (Math.abs(maxLon - minLon) > 1) { alert("Max Longitude must be less than Min Longitude + 1 degree"); return; }
659	document.getElementById('customimagerydiv').style.display = 'none';
660	var reader = new FileReader();
661	reader.onload = readerEvent => {
662		var degToRad = Math.PI / 180.0;
663		var content = readerEvent.target.result;
664		var layers = viewer.scene.imageryLayers;
665		while (layers.length > 1) layers.remove(layers.get(layers.length - 1), true);
666		satelliteProvider = 0;
667		layers.addImageryProvider(new Cesium.SingleTileImageryProvider(
668		{
669			url : content,
670			rectangle : new Cesium.Rectangle(minLon*degToRad, minLat*degToRad, maxLon*degToRad, maxLat*degToRad)
671		}));
672	}
673	reader.readAsDataURL(f);
674}
675
676function showGreenSlopePanel()
677{
678	document.getElementById('greenspeeddiv').style.display = '';
679	document.getElementById('greenslopediv').style.display = '';
680	document.getElementById('greencontourdiv').style.display = '';
681}
682
683function hideGreenSlopePanel()
684{
685	document.getElementById('greenspeeddiv').style.display = 'none';
686	document.getElementById('greenslopediv').style.display = 'none';
687	document.getElementById('greencontourdiv').style.display = 'none';
688}
689
690function terrainSelectHandler()
691{
692	hideGreenSlopePanel();
693	terrainModel = 1;
694	var s = document.getElementById('saveasinput').value;
695	if (s != null && s.indexOf('!!dsm') == 0) { terrainModel = 2; }
696	terrainMode = 1 + document.getElementById("terrainselect").selectedIndex;
697	if (terrainMode == 2)
698	{
699		var courseID = currentCourseID();
700		var courseName = getCourseNameFromID(courseID);
701		if (courseName == "") courseName = cleanString(document.getElementById("saveasinput").value);
702		var minLat = getCourseMinLat(courseID);
703		var minLon = getCourseMinLon(courseID);
704		var maxLat = getCourseMaxLat(courseID);
705		var maxLon = getCourseMaxLon(courseID);
706		if (minLat == 0 && minLon == 0 && maxLat == 0 && maxLon == 0)
707		{
708			alert('Please select course.');
709			document.getElementById("terrainselect").selectedIndex = 0;
710			terrainMode = 1;
711			return;
712		}
713		getProVisualizerTerrainData1(minLat, minLon, maxLat, maxLon, 200, null, courseName);
714		getProVisualizerTerrainData2(minLat, minLon, maxLat, maxLon, 10000, null);
715		getProVisualizerTerrainData3(minLat, minLon, maxLat, maxLon, 50000, null);
716		getNewProVisualizerTerrainProvider();
717	}
718	else
719	{
720		terrainProvider = new Cesium.EllipsoidTerrainProvider();
721		viewer.terrainProvider = terrainProvider;
722		zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 3);
723	}
724}
725
726function getNewProVisualizerTerrainProvider()
727{
728	if (terrainMode != 2) return;
729	if (!terrain1Ready || !terrain2Ready || !terrain3Ready)
730	{
731		window.setTimeout(getNewProVisualizerTerrainProvider, 500);
732		return;
733	}
734	terrainProvider = newProVisualizerTerrainProvider();
735	viewer.terrainProvider = terrainProvider;
736	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 3);
737	if (terrain1AllowSlopes)
738		showGreenSlopePanel();
739}
740
741function zoomInClickHandler()
742{
743	setMode(0);
744	var p = viewer.camera.positionCartographic;
745	var k = 180.0/Math.PI;
746	var h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(p.latitude*k, p.longitude*k));
747	var dh = p.height - h;
748	viewer.camera.moveForward(0.2*dh);
749}
750
751function zoomOutClickHandler()
752{
753	setMode(0);
754	var p = viewer.camera.positionCartographic;
755	var k = 180.0/Math.PI;
756	var h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(p.latitude*k, p.longitude*k));
757	var dh = p.height - h;
758	viewer.camera.moveBackward(0.25*dh);
759}
760
761function rotateLeftClickHandler()  { rotateClickHandler( 11.25); }
762function rotateRightClickHandler() { rotateClickHandler(-11.25); }
763
764var rotateHeight = 0;
765var rotateRadius = 0;
766var rotateLat = 0;
767var rotateLon = 0;
768var rotatePrevLat = -1;
769var rotatePrevLon = 0;
770var rotatePrevH = 0;
771var rotatePrevB = 0;
772
773function rotateClickHandler(deltaBearing)
774{
775	setMode(0);
776
777	var p = viewer.camera.positionCartographic;
778	var b = viewer.camera.heading*180.0/Math.PI;
779	var lat = Cesium.Math.toDegrees(p.latitude);
780	var lon = Cesium.Math.toDegrees(p.longitude);
781	var h0 = p.height;
782	var h1 = p.height - (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
783	if (geogDist(lat, lon, rotatePrevLat, rotatePrevLon) > 1.0 || Math.abs(h0 - rotatePrevH) > 1.0 || Math.abs(b - rotatePrevB) > 1.0)
784	{
785		// Default when looking above horizon...
786		rotateHeight = h0;
787		rotateRadius = h1*10;
788		rotateLat = moveLat(lat, lon, rotateRadius, b);
789		rotateLon = moveLon(lat, lon, rotateRadius, b);
790		var h2 = p.height - (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(rotateLat, rotateLon));
791
792		// If looking below horizon...
793		var ray = new Cesium.Ray(viewer.camera.position, viewer.camera.direction);
794		var cartesian = viewer.scene.globe.pick(ray, viewer.scene);
795		if (cartesian)
796		{
797			var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
798			var lat2 = Cesium.Math.toDegrees(cartographic.latitude);
799			var lon2 = Cesium.Math.toDegrees(cartographic.longitude);
800			var d = geogDist(lat, lon, lat2, lon2);
801			var dMax = h1*10; if (dMax < 100) dMax = 100;
802			if (d > dMax) d = dMax;
803			rotateRadius = d;
804			rotateLat = moveLat(lat, lon, rotateRadius, b);
805			rotateLon = moveLon(lat, lon, rotateRadius, b);
806		}
807		var courseID = currentCourseID();
808		var hole = currentHole();
809		if (rotateRadius > 10.0 && hole == 0)
810		{
811			// Snap to course centre...
812			var minLat = getCourseMinLat(courseID);
813			var minLon = getCourseMinLon(courseID);
814			var maxLat = getCourseMaxLat(courseID);
815			var maxLon = getCourseMaxLon(courseID);
816			var dLat = maxLat - minLat;
817			var dLon = maxLon - minLon;
818			if (dLat > 0 && dLat < 1 && dLon > 0 && dLon < 1)
819			{
820				var centreLat = (minLat + maxLat)/2;
821				var centreLon = (minLon + maxLon)/2;
822				var d2 = geogDist(lat, lon, centreLat, centreLon);
823				var b2 = geogBearing(lat, lon, centreLat, centreLon);
824				while (b2 < b - 180.0) b2 += 360.0;
825				while (b2 > b + 180.0) b2 -= 360.0;
826				var td = 1.0 - Math.abs(d2 - rotateRadius)/10000.0;
827					var tb = 1.0 - Math.abs(b2 - b)/30.0;
828				if (td > 0 && tb > 0)
829				{
830					var t = td*tb;
831					rotateLat = (1 - t)*rotateLat + t*centreLat;
832					rotateLon = (1 - t)*rotateLon + t*centreLon;
833					rotateRadius = geogDist(lat, lon, rotateLat, rotateLon);
834				}
835			}
836		}
837		else if (rotateRadius > 1.0 && hole > 0)
838		{
839			// Choose radius to rotate nicely around pin position...
840			var teeLat = getCourseHoleTeeLat(courseID, hole);
841			var teeLon = getCourseHoleTeeLon(courseID, hole);
842			var pinLat = getCourseHolePinLat(courseID, hole);
843			var pinLon = getCourseHolePinLon(courseID, hole);
844			if ((teeLat != 0 || teeLon != 0) && (pinLat != 0 || pinLon != 0))
845			{
846				var d2 = geogDist(lat, lon, pinLat, pinLon);
847				var b2 = geogBearing(lat, lon, pinLat, pinLon);
848				while (b2 < b - 180.0) b2 += 360.0;
849				while (b2 > b + 180.0) b2 -= 360.0;
850				var db = b - b2;
851				if (Math.abs(db) < 45)
852				{
853					var d = (d2+50+0.7*h1)/(2*Math.cos(db*Math.PI/180.0));
854					if (d > d2) d = d2;
855					rotateLat = moveLat(lat, lon, d, b);
856					rotateLon = moveLon(lat, lon, d, b);
857					rotateRadius = d;
858				}
859			}
860		}
861	}
862	var b2 = (rotateRadius > 1.0 ? geogBearing(lat, lon, rotateLat, rotateLon) : b);
863	b2 += deltaBearing;
864	while (b2 <   0.0) b2 += 360.0;
865	while (b2 > 360.0) b2 -= 360.0;
866	var lat2 = moveLat(rotateLat, rotateLon, rotateRadius, b2+180);
867	var lon2 = moveLon(rotateLat, rotateLon, rotateRadius, b2+180);
868	var h = rotateHeight;
869	var h2 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat2, lon2));
870	if (h < h2 + 2) h = h2 + 2;
871	viewer.camera.setView({
872		destination : Cesium.Cartesian3.fromDegrees(lon2, lat2, h),
873		orientation : {
874			heading : b2 * Math.PI / 180,
875			pitch : viewer.camera.pitch,
876			roll : 0
877		}
878	});
879	rotatePrevLat = lat2;
880	rotatePrevLon = lon2;
881	rotatePrevH = h;
882	rotatePrevB = b2;
883}
884
885function setMode(newMode)
886{
887	if (newMode == mode && mode != 9) return;
888
889	document.getElementById("setTeeButton").style.backgroundColor = "";
890	document.getElementById("setPinButton").style.backgroundColor = "";
891	document.getElementById("setTarget1Button").style.backgroundColor = "";
892	document.getElementById("setTarget2Button").style.backgroundColor = "";
893	document.getElementById("setTarget3Button").style.backgroundColor = "";
894	document.getElementById("distanceButton").style.backgroundColor = "";
895	document.getElementById("distanceButton2").style.backgroundColor = "";
896	document.getElementById("clearDistanceButton").style.backgroundColor = "";
897
898	if (mode == 9)
899	{
900		document.getElementById("symboldivA").style.backgroundColor = "";
901		document.getElementById("symboldivB").style.backgroundColor = "";
902		document.getElementById("symboldivC").style.backgroundColor = "";
903		document.getElementById("symboldivD").style.backgroundColor = "";
904		document.getElementById("symboldivE").style.backgroundColor = "";
905		document.getElementById("symboldivF").style.backgroundColor = "";
906		document.getElementById("symboldiv1").style.backgroundColor = "";
907		document.getElementById("symboldiv2").style.backgroundColor = "";
908		document.getElementById("symboldiv3").style.backgroundColor = "";
909		document.getElementById("symboldiv4").style.backgroundColor = "";
910		document.getElementById("symboldiv5").style.backgroundColor = "";
911		document.getElementById("symboldiv6").style.backgroundColor = "";
912		document.getElementById("symboldivT").style.backgroundColor = "";
913		document.getElementById("symboldivX").style.backgroundColor = "";
914		document.getElementById("symboldivL").style.backgroundColor = "";
915		document.getElementById("symboldivM").style.backgroundColor = "";
916		document.getElementById("symboldivN").style.backgroundColor = "";
917		document.getElementById("symboldivO").style.backgroundColor = "";
918		document.getElementById("symboldivZ").style.backgroundColor = "";
919	}
920
921	mode = newMode;
922
923	var c = "#00FFFF";
924	var c2 = "rgba(0,255,255,0.2)";
925
926	if      (mode == 1) document.getElementById("setTeeButton").style.backgroundColor = c;
927	else if (mode == 2) document.getElementById("setPinButton").style.backgroundColor = c;
928	else if (mode == 3) document.getElementById("setTarget1Button").style.backgroundColor = c;
929	else if (mode == 4) document.getElementById("setTarget2Button").style.backgroundColor = c;
930	else if (mode == 5) document.getElementById("setTarget3Button").style.backgroundColor = c;
931	else if (mode == 6) document.getElementById("distanceButton").style.backgroundColor = c;
932	else if (mode == 7) document.getElementById("distanceButton2").style.backgroundColor = c;
933	else if (mode == 8) document.getElementById("clearDistanceButton").style.backgroundColor = c;
934	else if (mode == 9)
935	{
936		if (activeSymbol == 'A') document.getElementById("symboldivA").style.backgroundColor = c2;
937		if (activeSymbol == 'B') document.getElementById("symboldivB").style.backgroundColor = c2;
938		if (activeSymbol == 'C') document.getElementById("symboldivC").style.backgroundColor = c2;
939		if (activeSymbol == 'D') document.getElementById("symboldivD").style.backgroundColor = c2;
940		if (activeSymbol == 'E') document.getElementById("symboldivE").style.backgroundColor = c2;
941		if (activeSymbol == 'F') document.getElementById("symboldivF").style.backgroundColor = c2;
942		if (activeSymbol == '1') document.getElementById("symboldiv1").style.backgroundColor = c2;
943		if (activeSymbol == '2') document.getElementById("symboldiv2").style.backgroundColor = c2;
944		if (activeSymbol == '3') document.getElementById("symboldiv3").style.backgroundColor = c2;
945		if (activeSymbol == '4') document.getElementById("symboldiv4").style.backgroundColor = c2;
946		if (activeSymbol == '5') document.getElementById("symboldiv5").style.backgroundColor = c2;
947		if (activeSymbol == '6') document.getElementById("symboldiv6").style.backgroundColor = c2;
948		if (activeSymbol == 'T') document.getElementById("symboldivT").style.backgroundColor = c2;
949		if (activeSymbol == 'X') document.getElementById("symboldivX").style.backgroundColor = c2;
950		if (activeSymbol == 'L') document.getElementById("symboldivL").style.backgroundColor = c2;
951		if (activeSymbol == 'M') document.getElementById("symboldivM").style.backgroundColor = c2;
952		if (activeSymbol == 'N') document.getElementById("symboldivN").style.backgroundColor = c2;
953		if (activeSymbol == 'O') document.getElementById("symboldivO").style.backgroundColor = c2;
954		if (activeSymbol == 'Z') document.getElementById("symboldivZ").style.backgroundColor = c2;
955	}
956
957	setDistDiv('');
958	setDistDiv1('');
959	setDistDiv2('');
960	setDistDiv3('');
961}
962
963function initSelectedUnits()
964{
965	document.getElementById('unitselect').selectedIndex = (getDistanceUnit() == "m" ? 1 : 0);
966}
967
968function selectUnitHandler()
969{
970	setMode(0);
971	setDistanceUnit(document.getElementById('unitselect').selectedIndex == 1 ? "m" : "y");
972	refreshView();
973	setWeatherData();
974}
975
976function initTeeCircles()
977{
978	document.getElementById('teecircleselect').selectedIndex = getTeeCircles();
979}
980
981function selectTeeCirclesHandler()
982{
983	setMode(0);
984	setTeeCircles(document.getElementById('teecircleselect').selectedIndex.toString());
985	refreshView();
986}
987
988function teeCirclePlusHandler()
989{
990	setMode(0);
991	var i = getTeeCircles() - 1;
992	if (i < 0) return;
993	setTeeCircles(i.toString());
994	document.getElementById('teecircleselect').selectedIndex = i;
995	refreshView();
996}
997
998function teeCircleMinusHandler()
999{
1000	setMode(0);
1001	var i = getTeeCircles() + 1;
1002	if (i > 13) return;
1003	setTeeCircles(i.toString());
1004	document.getElementById('teecircleselect').selectedIndex = i;
1005	refreshView();
1006}
1007
1008function initCones()
1009{
1010	document.getElementById('conesselect').selectedIndex = 0;
1011}
1012
1013function selectConesHandler()
1014{
1015	setMode(0);
1016	if (coneCount >= 3)
1017		refreshView();
1018
1019	conesHandler();
1020}
1021
1022function conesHandler()
1023{
1024	setMode(0);
1025	if (coneCount >= 3)
1026	{
1027		refreshView();
1028		return;
1029	}
1030
1031	var courseID = currentCourseID();
1032	var hole = currentHole();
1033	if (hole < 1)
1034	{
1035		alert("Please select hole.");
1036		return;
1037	}
1038	var lat2 = getCourseHolePinLat(courseID, hole);
1039	var lon2 = getCourseHolePinLon(courseID, hole);
1040	if (lat2 == 0 && lon2 == 0)
1041	{
1042		alert("Please set pin position.");
1043		return;
1044	}
1045	var lat1 = getCourseHoleTeeLat(courseID, hole);
1046	var lon1 = getCourseHoleTeeLon(courseID, hole);
1047	var par = getCourseHolePar(courseID, hole);
1048	if (par < 3)
1049	{
1050		alert("Please set tee position.")
1051		return;
1052	}
1053	for (var shot = 1; shot <= par - 2; ++shot)
1054	{
1055		lat1 = getCourseHoleTeeLat(courseID, hole);
1056		lon1 = getCourseHoleTeeLon(courseID, hole);
1057		lat2 = getCourseHolePinLat(courseID, hole);
1058		lon2 = getCourseHolePinLon(courseID, hole);
1059		if (shot > 1)
1060		{
1061			lat1 = getCourseHoleTargetLat(courseID, hole, shot-1);
1062			lon1 = getCourseHoleTargetLon(courseID, hole, shot-1);
1063		}
1064		if (shot < par - 2)
1065		{
1066			lat2 = getCourseHoleTargetLat(courseID, hole, shot);
1067			lon2 = getCourseHoleTargetLon(courseID, hole, shot);
1068		}
1069		var d = geogDist(lat1, lon1, lat2, lon2);
1070		if (d < 1)
1071		{
1072			alert("Tee/Targets/Pin too close together.");
1073			return;
1074		}
1075		var bearing = geogBearing(lat1, lon1, lat2, lon2);
1076		var brightness = 0.03 * getCircleBrightness();
1077		var c = getCircleColor();
1078		var color = new Cesium.Color(1,1,0,brightness);
1079		if (c == 1) color = new Cesium.Color(0.5,1.0,0.5,brightness);
1080		if (c == 2) color = new Cesium.Color(0.5,1.0,1.0,brightness);
1081		if (c == 3) color = new Cesium.Color(1.0,1.0,1.0,brightness);
1082		var conesIndex = document.getElementById('conesselect').selectedIndex;
1083		var a = 10;
1084		var b = 5;
1085		var k = 180.0/Math.PI;
1086		if (conesIndex == 0) { a = 6; b = 3; }
1087		if (conesIndex == 1) { a = 8; b = 4; }
1088		if (conesIndex == 2) { a = 10; b = 5; }
1089		if (conesIndex == 3) { a = 0.10*k; b = 0.05*k; }
1090		if (conesIndex == 4) { a = 0.12*k; b = 0.06*k; }
1091		if (conesIndex == 5) { a = 0.14*k; b = 0.07*k; }
1092		if (conesIndex == 6) { a = 0.16*k; b = 0.08*k; }
1093		if (conesIndex == 7) { a = 0.18*k; b = 0.09*k; }
1094		if (conesIndex == 8) { a = 0.20*k; b = 0.10*k; }
1095		for (var i = -a; i <= a; i += b)
1096		{
1097			if (i == 0) i += b;
1098			var lat3 = lat1;
1099			var lon3 = lon1;
1100			var lat4 = moveLat(lat1, lon1, d+10, bearing+i);
1101			var lon4 = moveLon(lat1, lon1, d+10, bearing+i);
1102			if (i == 0)
1103			{
1104				lat3 = moveLat(lat1, lon1, d, bearing);
1105				lon3 = moveLon(lat1, lon1, d, bearing);
1106			}
1107			var coords = [];
1108			var h2 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat2, lon2));
1109			var h3 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat3, lon3));
1110			var h4 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat4, lon4));
1111			var dh = Math.abs(i)/5.0;
1112			if (h4 < h2 - dh) h4 = h2 - dh;
1113			if (h4 > h2 + dh) h4 = h2 + dh;
1114			coords.push(Cesium.Cartesian3.fromDegrees(lon3, lat3, h3));
1115			coords.push(Cesium.Cartesian3.fromDegrees(lon4, lat4, h4));
1116			var line = viewer.entities.add({ polyline : { positions: coords, width: 1.0, material: color } }); 
1117			mapLines2.push(line);
1118		}
1119	}
1120	coneCount += 1;
1121}
1122
1123function initDispersion()
1124{
1125	var i = getDispersionProfileID();
1126	document.getElementById('dispersionselect').selectedIndex = i;
1127	document.getElementById('nrlselect').selectedIndex = getNRL();
1128	if (i == 11)
1129	{
1130		document.getElementById('nrlselectdiv').style.display = 'none';
1131		document.getElementById('customdispersionbuttondiv').style.display = '';
1132		refreshCustomDispersion();
1133	}
1134}
1135
1136function selectDispersionHandler()
1137{
1138	setMode(0);
1139	var i = document.getElementById('dispersionselect').selectedIndex;
1140	setDispersionProfileID(i.toString());
1141	if (greenSlopesCount > 0 && puttHistoryOp.length > 0)
1142	{
1143		if (showPuttDispersion == 0)
1144			showPuttDispersion = 1;
1145		selectGreenSpeedHandler();
1146		return;
1147	}
1148	refreshView();
1149	if (i == 11)
1150	{
1151		document.getElementById('nrlselectdiv').style.display = 'none';
1152		document.getElementById('customdispersionbuttondiv').style.display = '';
1153		refreshCustomDispersion();
1154	}
1155	else
1156	{
1157		document.getElementById('customdispersiondiv').style.display = 'none';
1158		document.getElementById('customdispersionbuttondiv').style.display = 'none';
1159		document.getElementById('nrlselectdiv').style.display = '';
1160	}
1161}
1162
1163function refreshCustomDispersion()
1164{
1165	var s = "<table/>";
1166	s += "<tr>";
1167	s += "<td></td>";
1168	s += "<td align='left'>Left</td>";
1169	s += "<td align='left'>Right</td>";
1170	s += "<td align='left'>Long</td>";
1171	s += "<td align='left'>Short</td>";
1172	s += "<td align='center'>Shape</td>";
1173	s += "</tr>";
1174	s += "<tr>";
1175	s += "<td></td>";
1176	s += "<td><button style='width: 50%;' onclick='increaseSpreadL(-1)'>+</button><button style='width: 50%;' onclick='decreaseSpreadL(-1)'>-</button></td>";
1177	s += "<td><button style='width: 50%;' onclick='increaseSpreadR(-1)'>+</button><button style='width: 50%;' onclick='decreaseSpreadR(-1)'>-</button></td>";
1178	s += "<td><button style='width: 50%;' onclick='increaseSpreadO(-1)'>+</button><button style='width: 50%;' onclick='decreaseSpreadO(-1)'>-</button></td>";
1179	s += "<td><button style='width: 50%;' onclick='increaseSpreadS(-1)'>+</button><button style='width: 50%;' onclick='decreaseSpreadS(-1)'>-</button></td>";
1180	s += "<td>";
1181	s += "<button style='padding: 0px;' onclick='setShape(-1,-2)'>
1181<div style='width: 20px;'>L</div></button>";
1182	s += "<button style='padding: 0px;' onclick='setShape(-1,-1)'><div style='width: 20px;'>-</div></button>";
1183	s += "<button style='padding: 0px;' onclick='setShape(-1, 0)'><div style='width: 20px;'>N</div></button>";
1184	s += "<button style='padding: 0px;' onclick='setShape(-1, 1)'><div style='width: 20px;'>-</div></button>";
1185	s += "<button style='padding: 0px;' onclick='setShape(-1, 2)'><div style='width: 20px;'>R</div></button>";
1186	s += "</td></tr>";
1187	for (var i = 0; i <= 16; ++i)
1188	{
1189		if (true || getClubFlag(i))
1190		{
1191			var id = i.toString();
1192			var name = getClubName(i);
1193			var spreadL = getClubSpreadL(i);
1194			var spreadR = getClubSpreadR(i);
1195			var spreadO = getClubSpreadO(i);
1196			var spreadS = getClubSpreadS(i);
1197			var shape = getClubShape(i);
1198			s += "<tr>";
1199			s += "<td>" + name + "</td>";
1200			s += "<td><input type='textbox' readonly style='width: 50px;' value='" + spreadL.toString() + "'><button onclick='increaseSpreadL("+id+")'>+</button><button onclick='decreaseSpreadL("+id+")'>-</button></td>";
1201			s += "<td><input type='textbox' readonly style='width: 50px;' value='" + spreadR.toString() + "'><button onclick='increaseSpreadR("+id+")'>+</button><button onclick='decreaseSpreadR("+id+")'>-</button></td>";
1202			s += "<td><input type='textbox' readonly style='width: 50px;' value='" + spreadO.toString() + "'><button onclick='increaseSpreadO("+id+")'>+</button><button onclick='decreaseSpreadO("+id+")'>-</button></td>";
1203			s += "<td><input type='textbox' readonly style='width: 50px;' value='" + spreadS.toString() + "'><button onclick='increaseSpreadS("+id+")'>+</button><button onclick='decreaseSpreadS("+id+")'>-</button></td>";
1204			s += "<td>";
1205			s += "<button style='padding: 0px;' onclick='setShape("+id+",-2)'><div style='width: 20px;"; if (shape == -2) s += " background-color: #00FFFF;"; s += "'>L</div></button>";
1206			s += "<button style='padding: 0px;' onclick='setShape("+id+",-1)'><div style='width: 20px;"; if (shape == -1) s += " background-color: #00FFFF;"; s += "'>-</div></button>";
1207			s += "<button style='padding: 0px;' onclick='setShape("+id+", 0)'><div style='width: 20px;"; if (shape ==  0) s += " background-color: #00FFFF;"; s += "'>N</div></button>";
1208			s += "<button style='padding: 0px;' onclick='setShape("+id+", 1)'><div style='width: 20px;"; if (shape ==  1) s += " background-color: #00FFFF;"; s += "'>-</div></button>";
1209			s += "<button style='padding: 0px;' onclick='setShape("+id+", 2)'><div style='width: 20px;"; if (shape ==  2) s += " background-color: #00FFFF;"; s += "'>R</div></button>";
1210			s += "</td></tr>";
1211		}
1212	}
1213	s += "<tr>";
1214	s += "<td></td>";
1215	s += "<td align='center'><button style='width: 100%;' onclick='resetSpreadL()'>Reset</button></td>";
1216	s += "<td align='center'><button style='width: 100%;' onclick='resetSpreadR()'>Reset</button></td>";
1217	s += "<td align='center'><button style='width: 100%;' onclick='resetSpreadO()'>Reset</button></td>";
1218	s += "<td align='center'><button style='width: 100%;' onclick='resetSpreadS()'>Reset</button></td>";
1219	s += "<td align='center'><button style='width: 100%;' onclick='hideCustomDispersion()'>OK</button></td>";
1220	s += "</tr>";
1221	s += "</table>";
1222	document.getElementById('customdispersiondiv').innerHTML = s;
1223}
1224
1225function setShape(club, shape)
1226{
1227	if (club < 0) { for (var i = 0; i <= 16; ++i) setClubShape(i, shape); }
1228	else setClubShape(club, shape);
1229	refreshCustomDispersion();
1230}
1231
1232function increaseSpreadL(club)
1233{
1234	if (club < 0) { for (var i = 0; i <= 16; ++i) setClubSpreadL(i, getClubSpreadL(i)+1); }
1235	else setClubSpreadL(club, getClubSpreadL(club)+1);
1236	refreshCustomDispersion();
1237}
1238
1239function decreaseSpreadL(club)
1240{
1241	if (club < 0) { for (var i = 0; i <= 16; ++i) setClubSpreadL(i, getClubSpreadL(i)-1); }
1242	else setClubSpreadL(club, getClubSpreadL(club)-1);
1243	refreshCustomDispersion();
1244}
1245
1246function increaseSpreadR(club)
1247{
1248	if (club < 0) { for (var i = 0; i <= 16; ++i) setClubSpreadR(i, getClubSpreadR(i)+1); }
1249	else setClubSpreadR(club, getClubSpreadR(club)+1);
1250	refreshCustomDispersion();
1251}
1252
1253function decreaseSpreadR(club)
1254{
1255	if (club < 0) { for (var i = 0; i <= 16; ++i) setClubSpreadR(i, getClubSpreadR(i)-1); }
1256	else setClubSpreadR(club, getClubSpreadR(club)-1);
1257	refreshCustomDispersion();
1258}
1259
1260function increaseSpreadO(club)
1261{
1262	if (club < 0) { for (var i = 0; i <= 16; ++i) setClubSpreadO(i, getClubSpreadO(i)+1); }
1263	else setClubSpreadO(club, getClubSpreadO(club)+1);
1264	refreshCustomDispersion();
1265}
1266
1267function decreaseSpreadO(club)
1268{
1269	if (club < 0) { for (var i = 0; i <= 16; ++i) setClubSpreadO(i, getClubSpreadO(i)-1); }
1270	else setClubSpreadO(club, getClubSpreadO(club)-1);
1271	refreshCustomDispersion();
1272}
1273
1274function increaseSpreadS(club)
1275{
1276	if (club < 0) { for (var i = 0; i <= 16; ++i) setClubSpreadS(i, getClubSpreadS(i)+1); }
1277	else setClubSpreadS(club, getClubSpreadS(club)+1);
1278	refreshCustomDispersion();
1279}
1280
1281function decreaseSpreadS(club)
1282{
1283	if (club < 0) { for (var i = 0; i <= 16; ++i) setClubSpreadS(i, getClubSpreadS(i)-1); }
1284	else setClubSpreadS(club, getClubSpreadS(club)-1);
1285	refreshCustomDispersion();
1286}
1287
1288function resetSpreadL()
1289{
1290	if (!confirm("Reset Left Spread?")) return;
1291	for (i = 0; i <= 16; ++i)
1292		setClubSpreadL(i, getClubSpreadLDefault(i));
1293	refreshCustomDispersion();
1294}
1295
1296function resetSpreadR()
1297{
1298	if (!confirm("Reset Right Spread?")) return;
1299	for (i = 0; i <= 16; ++i)
1300		setClubSpreadR(i, getClubSpreadRDefault(i));
1301	refreshCustomDispersion();
1302}
1303
1304function resetSpreadO()
1305{
1306	if (!confirm("Reset Long Spread?")) return;
1307	for (i = 0; i <= 16; ++i)
1308		setClubSpreadO(i, getClubSpreadODefault(i));
1309	refreshCustomDispersion();
1310}
1311
1312function resetSpreadS()
1313{
1314	if (!confirm("Reset Short Spread?")) return;
1315	for (i = 0; i <= 16; ++i)
1316		setClubSpreadS(i, getClubSpreadSDefault(i));
1317	refreshCustomDispersion();
1318}
1319
1320function hideCustomDispersion()
1321{
1322	document.getElementById('customdispersiondiv').style.display = 'none';
1323	refreshView();
1324}
1325
1326function customDispersionEditHandler()
1327{
1328	var x = document.getElementById('customdispersiondiv');
1329	if (x.style.display == 'none')
1330	{
1331		x.style.display = '';
1332		refreshCustomDispersion();
1333	}
1334	else
1335	{
1336		x.style.display = 'none';
1337		refreshView();
1338	}
1339}
1340
1341function selectNRLHandler()
1342{
1343	setMode(0);
1344	setNRL(document.getElementById('nrlselect').selectedIndex.toString());
1345	refreshView();
1346}
1347
1348function initLineColor()
1349{
1350	document.getElementById('linecolorselect').selectedIndex = getLineColor();
1351}
1352
1353function selectLineColorHandler()
1354{
1355	setMode(0);
1356	setLineColor(document.getElementById('linecolorselect').selectedIndex.toString());
1357	refreshView();
1358}
1359
1360function initLineBrightness()
1361{
1362	document.getElementById('linebrightnessselect').selectedIndex = getLineBrightness();
1363}
1364
1365function selectLineBrightnessHandler()
1366{
1367	setMode(0);
1368	setLineBrightness(document.getElementById('linebrightnessselect').selectedIndex.toString());
1369	refreshView();
1370}
1371
1372function initCircleColor()
1373{
1374	document.getElementById('circlecolorselect').selectedIndex = getCircleColor();
1375}
1376
1377function selectCircleColorHandler()
1378{
1379	setMode(0);
1380	setCircleColor(document.getElementById('circlecolorselect').selectedIndex.toString());
1381	refreshView();
1382}
1383
1384function initCircleBrightness()
1385{
1386	document.getElementById('circlebrightnessselect').selectedIndex = getCircleBrightness();
1387}
1388
1389function selectCircleBrightnessHandler()
1390{
1391	setMode(0);
1392	setCircleBrightness(document.getElementById('circlebrightnessselect').selectedIndex.toString());
1393	refreshView();
1394}
1395
1396function initAutoRotate()
1397{
1398	document.getElementById('autorotateselect').selectedIndex = getAutoRotate();
1399}
1400
1401function selectAutoRotateHandler()
1402{
1403	setMode(0);
1404	setAutoRotate(document.getElementById('autorotateselect').selectedIndex.toString());
1405	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 3);
1406}
1407
1408function compassClickHandler()
1409{
1410	if (mapMode != 1) return;
1411	var i = document.getElementById('autorotateselect').selectedIndex;
1412	document.getElementById('autorotateselect').selectedIndex = (i == 0 ? 1 : 0);
1413	selectAutoRotateHandler();
1414}
1415
1416function populateCourseSelect()
1417{
1418	var x = document.getElementById('courseselect');
1419	
1420	while (x.length > 0)
1421		x.remove(x.length - 1);
1422
1423	x.add(new Option("<New Course>", "0"), null);
1424
1425	var n = getNumCourses();
1426	for (var i = 0; i < n; ++i)
1427	{
1428		var name = getCourseName(i);
1429		var id = getCourseID(i).toString();
1430		var j = 1;
1431		while (j < x.length && name > x.options[j].text)
1432			++j;
1433		if (j < x.length)
1434			x.add(new Option(name, id), j);
1435		else
1436			x.add(new Option(name, id));
1437	}
1438}
1439
1440function currentCourseID()
1441{
1442	return Number(document.getElementById("courseselect").value);
1443}
1444
1445function currentHole()
1446{
1447	return Number(document.getElementById("holeselect").value);
1448}
1449
1450function currentShot()
1451{
1452	return Number(document.getElementById("shotselect").value);
1453}
1454
1455function setSelectedCourse(courseID)
1456{
1457	var x = document.getElementById("courseselect");
1458	for (var i = 0; i < x.length; ++i)
1459	{
1460		if (Number(x.options[i].value) == courseID)
1461			x.selectedIndex = i;
1462	}
1463}
1464
1465function setSelectedHole(hole)
1466{
1467	if (hole < 0)
1468		hole = 18;
1469	if (hole > 18)
1470		hole = 0;
1471	document.getElementById("holeselect").selectedIndex = hole;
1472	setSelectedShot(0);
1473}
1474
1475function setSelectedShot(shot)
1476{
1477	if (currentHole() <= 0)
1478		shot = 0;
1479	else
1480	{
1481		var max = getCourseHolePar(currentCourseID(), currentHole()) - 1;
1482		if (max < 0)
1483			max = 0;
1484		if (shot < 0)
1485			shot = max;
1486		if (shot > max)
1487			shot = 0;
1488	}
1489	document.getElementById("shotselect").selectedIndex = shot;
1490}
1491
1492function selectCourseHandler()
1493{
1494	setMode(0);
1495	hideWeather();
1496	document.getElementById("saveasinput").value = "";
1497	document.getElementById("savebutton").style.backgroundColor = "";
1498	initCourseData(0);
1499	setSelectedHole(0);
1500	document.getElementById("layerselect").selectedIndex = 0;
1501	layerSelectHandler();
1502	document.getElementById("terrainselect").selectedIndex = 0;
1503	terrainSelectHandler();
1504	if (mapMode == 2) toggle3D();
1505	zoomToCourseHole(currentCourseID(), currentHole(), true, true, 5);
1506}
1507
1508function selectHoleHandler()
1509{
1510	setMode(0);
1511	setSelectedShot(0);
1512	zoomToCourseHole(currentCourseID(), currentHole(), true, true, 3);
1513}
1514
1515function selectShotHandler()
1516{
1517	setMode(0);
1518	setSelectedShot(currentShot());
1519	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 2);
1520}
1521
1522function selectNextHoleHandler()
1523{
1524	setMode(0);
1525	setSelectedHole(currentHole() + 1);
1526	setSelectedShot(0);
1527	zoomToCourseHole(currentCourseID(), currentHole(), true, true, 3);
1528}
1529
1530function selectPrevHoleHandler()
1531{
1532	setMode(0);
1533	setSelectedHole(currentHole() - 1);
1534	setSelectedShot(0);
1535	zoomToCourseHole(currentCourseID(), currentHole(), true, true, 3);
1536}
1537
1538function selectNextShotHandler()
1539{
1540	if (currentHole() < 1)
1541	{
1542		selectNextHoleHandler();
1543		return;
1544	}
1545	setMode(0);
1546	setSelectedShot(currentShot() + 1);
1547	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 2);
1548}
1549
1550function selectPrevShotHandler()
1551{
1552	if (currentHole() < 1)
1553	{
1554		selectPrevHoleHandler();
1555		return;
1556	}
1557	setMode(0);
1558	setSelectedShot(currentShot() - 1);
1559	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 2);
1560}
1561
1562function toggle3D()
1563{
1564	setMode(0);
1565	if (mapMode == 1)
1566	{
1567		mapMode = 2;
1568		document.getElementById('toggle3dbutton').innerHTML = "2D/<b>3D</b>";
1569		document.getElementById('2dcontrolsdiv').style.display = "none";
1570		document.getElementById('3dcontrolsdiv').style.display = "";
1571		viewer.scene.globe.maximumScreenSpaceError = 2.0;
1572		var s = document.getElementById('saveasinput').value;
1573		if (s != null && s.indexOf('!!dsm') == 0) { viewer.scene.globe.maximumScreenSpaceError = 1.0; }
1574	}
1575	else
1576	{
1577		mapMode = 1;
1578		document.getElementById('toggle3dbutton').innerHTML = "<b>2D</b>/3D";
1579		document.getElementById('3dcontrolsdiv').style.display = "none";
1580		document.getElementById('2dcontrolsdiv').style.display = "";
1581		viewer.scene.globe.maximumScreenSpaceError = 1.0;
1582	}
1583	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 3);
1584}
1585
1586var heightMultiplier = get3DHeightMultiplier();
1587var offsetMultiplier = get3DOffsetMultiplier();
1588var angleIncrement   = get3DAngleIncrement();
1589
1590function heightPlusHandler()
1591{
1592	heightMultiplier *= 1.1;
1593	set3DHeightMultiplier(heightMultiplier)
1594	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 1);
1595}
1596
1597function heightMinusHandler()
1598{
1599	if (30 * heightMultiplier > 2.0) heightMultiplier /= 1.1;
1600	set3DHeightMultiplier(heightMultiplier)
1601	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 1);
1602}
1603
1604function heightResetHandler()
1605{
1606	heightMultiplier = 1.0;
1607	set3DHeightMultiplier(heightMultiplier)
1608	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 1);
1609}
1610
1611function offsetPlusHandler()
1612{
1613	offsetMultiplier *= 1.1;
1614	set3DOffsetMultiplier(offsetMultiplier)
1615	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 1);
1616}
1617
1618function offsetMinusHandler()
1619{
1620	if (60 * offsetMultiplier > 5) offsetMultiplier /= 1.1;
1621	set3DOffsetMultiplier(offsetMultiplier)
1622	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 1);
1623}
1624
1625function offsetResetHandler()
1626{
1627	offsetMultiplier = 1.0;
1628	set3DOffsetMultiplier(offsetMultiplier)
1629	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 1);
1630}
1631
1632function anglePlusHandler()
1633{
1634	if (angleIncrement < 5) angleIncrement += 1.0;
1635	set3DAngleIncrement(angleIncrement)
1636	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 1);
1637}
1638
1639function angleMinusHandler()
1640{
1641	if (angleIncrement > -45) angleIncrement -= 1.0;
1642	set3DAngleIncrement(angleIncrement)
1643	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 1);
1644}
1645
1646function angleResetHandler()
1647{
1648	angleIncrement = 0.0;
1649	set3DAngleIncrement(angleIncrement)
1650	zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), true, true, 1);
1651}
1652
1653function saveCourseAsHandler()
1654{
1655	setMode(0);
1656	var id1 = currentCourseID();
1657	var par = getCoursePar(id1);
1658	if (par == 0)
1659	{
1660		alert("Please map at least one hole.");
1661		return;
1662	}
1663	var courseName = cleanString(document.getElementById("saveasinput").value);
1664	if (courseName == "")
1665	{
1666		alert("Please enter course name.");
1667		return;
1668	}
1669	if (getCourseIDFromName(courseName) > 0)
1670	{
1671		alert(courseName + " already exists.\nPlease enter unique name.");
1672		return;
1673	}
1674	var id2 = createCourse(courseName);
1675	copyCourseData(id1, id2);
1676	initCourseData(0);
1677	populateCourseSelect();
1678	setSelectedCourse(id2);
1679	document.getElementById("saveasinput").value = "";
1680	document.getElementById("savebutton").style.backgroundColor = "";
1681}
1682
1683function setInfoDiv1(s)
1684{
1685	var x = document.getElementById('infodiv1');
1686	x.innerHTML = s;
1687}
1688
1689function setInfoDiv2(s)
1690{
1691	var x = document.getElementById('infodiv2');
1692	x.innerHTML = s;
1693}
1694
1695function setInfoDiv3(s)
1696{
1697	var x = document.getElementById('infodiv3');
1698	x.innerHTML = s;
1699}
1700
1701function setInfoDiv4(s)
1702{
1703	var x = document.getElementById('infodiv4');
1704	x.innerHTML = s;
1705}
1706
1707function clearInfo()
1708{
1709	setInfoDiv1("&nbsp;");
1710	setInfoDiv2("&nbsp;");
1711	setInfoDiv3("&nbsp;");
1712	setInfoDiv4("&nbsp;");
1713}
1714
1715function setHelpInfo()
1716{
1717	setInfoDiv1("Select course from drop-down list above.");
1718	setInfoDiv2("Or search pre-mapped courses <a href='features/atoz.php'>here</a>.");
1719	setInfoDiv3("Or map new course using buttons below.");
1720	setInfoDiv4("&nbsp;");
1721}
1722
1723function setInfo()
1724{
1725	var courseID = currentCourseID();
1726	var hole = currentHole();
1727	var shot = currentShot();
1728	var info = "&nbsp;";
1729
1730	if (hole == 0)
1731	{
1732
1733		var par = getCoursePar(courseID);
1734		var len = getCourseLength(courseID);
1735		if (par == 0 || len == 0)
1736		{
1737			setHelpInfo();
1738			return;
1739		}
1740		else
1741		{
1742			info = "Par " + par.toString() + ", " + len.toString() + " " + myUnitName();
1743		}
1744	}
1745	else
1746	{
1747		var teeLat = getCourseHoleTeeLat(courseID, hole);
1748		var teeLon = getCourseHoleTeeLon(courseID, hole);
1749		var pinLat = getCourseHolePinLat(courseID, hole);
1750		var pinLon = getCourseHolePinLon(courseID, hole);
1751		if (teeLat == 0 && teeLon == 0)
1752		{
1753			info = "Set Tee Position";
1754		}
1755		else if (pinLat == 0 && pinLon == 0)
1756		{
1757			info = "Set Pin Position";
1758		}
1759		else
1760		{
1761			var par = getCourseHolePar(courseID, hole);
1762			var len = getCourseHoleLength(courseID, hole);
1763			info = "Par " + par.toString() + ", " + len.toString() + " " + myUnitName();
1764			if (par >= 4)
1765			{
1766				info += " (";
1767				for (var i = 1; i <= par - 2; ++i)
1768				{
1769					if (i > 1)
1770						info += " + ";
1771					info += getCourseHoleShotLength(courseID, hole, i).toString();
1772				}
1773				info += ")";
1774			}
1775		}
1776	}
1777
1778	setInfoDiv1(info);
1779}
1780
1781function setDistDiv(s)
1782{
1783	document.getElementById('distanceDiv').innerHTML = s;
1784}
1785
1786function setDistDiv1(s)
1787{
1788	document.getElementById('distanceDiv1').innerHTML = s;
1789}
1790
1791function setDistDiv2(s)
1792{
1793	document.getElementById('distanceDiv2').innerHTML = s;
1794}
1795
1796function setDistDiv3(s)
1797{
1798	document.getElementById('distanceDiv3').innerHTML = s;
1799}
1800
1801function mapLeftClickHandler(click)
1802{
1803	mapClickHandler(click, 1);
1804}
1805
1806function mapRightClickHandler(click)
1807{
1808	mapClickHandler(click, 2);
1809}
1810
1811function mapMouseMoveHandler(move)
1812{
1813	if (mode == 3 || mode == 4 || mode == 5)
1814	{
1815		var mousePos = new Cesium.Cartesian2(move.endPosition.x, move.endPosition.y);
1816		var ray = viewer.camera.getPickRay(mousePos);
1817		var cartesian = viewer.scene.globe.pick(ray, viewer.scene);
1818		if (!cartesian) return;
1819		var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
1820		var lat = Cesium.Math.toDegrees(cartographic.latitude);
1821		var lon = Cesium.Math.toDegrees(cartographic.longitude);
1822		var courseID = currentCourseID();
1823		var hole = currentHole();
1824		var lat1 = getCourseHoleTeeLat(courseID, hole);
1825		var lon1 = getCourseHoleTeeLon(courseID, hole);
1826		var lat2 = getCourseHolePinLat(courseID, hole);
1827		var lon2 = getCourseHolePinLon(courseID, hole);
1828		if (mode == 4)
1829		{
1830			lat1 = getCourseHoleTargetLat(courseID, hole, 1);
1831			lon1 = getCourseHoleTargetLon(courseID, hole, 1);
1832		}
1833		if (mode == 5)
1834		{
1835			lat1 = getCourseHoleTargetLat(courseID, hole, 2);
1836			lon1 = getCourseHoleTargetLon(courseID, hole, 2);
1837		}
1838		if (lat1 != 0 || lon1 != 0)
1839		{
1840			var d = geogDist(lat1, lon1, lat, lon);
1841			var s = Math.round(metresToMyUnits(d)).toString();
1842			if (lat2 != 0 || lon2 != 0)
1843			{
1844				d = geogDist(lat, lon, lat2, lon2);
1845				s += " + " + Math.round(metresToMyUnits(d)).toString();
1846			}
1847			if      (mode == 3) setDistDiv1(s);
1848			else if (mode == 4) setDistDiv2(s);
1849			else if (mode == 5) setDistDiv3(s);
1850		}
1851	}
1852	if (mode == 6 || mode == 7)
1853	{
1854		if (distLat1 == 0 && distLon1 == 0) return;
1855		var mousePos = new Cesium.Cartesian2(move.endPosition.x, move.endPosition.y);
1856		var ray = viewer.camera.getPickRay(mousePos);
1857		var cartesian = viewer.scene.globe.pick(ray, viewer.scene);
1858		if (!cartesian) return;
1859		var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
1860		var lat = Cesium.Math.toDegrees(cartographic.latitude);
1861		var lon = Cesium.Math.toDegrees(cartographic.longitude);
1862		distLat2 = Math.round(lat*999999)/999999;
1863		distLon2 = Math.round(lon*999999)/999999;
1864		var d = geogDist(distLat1, distLon1, distLat2, distLon2);
1865		setDistDiv(formatDist(d));
1866	}
1867}
1868
1869function mapClickHandler(click, button)
1870{
1871	var mousePos = new Cesium.Cartesian2(click.position.x, click.position.y);
1872	var ray = viewer.camera.getPickRay(mousePos);
1873	//var cartesian = viewer.camera.pickEllipsoid(mousePos, viewer.scene.globe.ellipsoid);
1874	var cartesian = viewer.scene.globe.pick(ray, viewer.scene);
1875
1876	if (!cartesian)
1877		return;
1878
1879	var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
1880
1881	var lat = Cesium.Math.toDegrees(cartographic.latitude);
1882	var lon = Cesium.Math.toDegrees(cartographic.longitude);
1883
1884	var courseID = currentCourseID();
1885	var hole = currentHole();
1886
1887	document.getElementById('coordsdiv').innerHTML = "";
1888	var s = document.getElementById('saveasinput').value;
1889	if (s != null && s.indexOf('!!stimp') == 0)
1890	{
1891		var coords = "<table>";
1892		var r = -0.05;
1893		while (r > -0.20)
1894		{
1895			var dt = 0.05;
1896			var s = 0.0;
1897			var v = 1.83;
1898			while (v > 0.001)
1899			{
1900				s += v*dt;
1901				var dv = deltaPuttSpeed(v, 0.0, 0.0, r, dt);
1902				if (dv < 0.0)
1903					v += dv;
1904				else
1905				{
1906					dv = deltaPuttSpeed(v, 0.0, 0.0, r, dt);
1907					v = 0.0;
1908				}
1909			}
1910			var feet = s * 3.0 * 1760.0 / 1609.344;
1911			if (Math.round(feet*1000) % 1000 == 0)
1912			{
1913				coords += "<tr>";
1914				coords += "<td>" + (Math.round(feet*1000)/1000).toString() + "</td>";
1915				coords += "<td>" + (Math.round(r*10000)/10000).toString() + "</td>";
1916				coords += "</tr>";
1917			}
1918			r -= 0.000002;
1919		}
1920		coords += "</table>";
1921		document.getElementById('coordsdiv').innerHTML = coords;
1922		if (mode == 0)
1923			return;
1924	}
1925	else if (s != null && s.indexOf('!!dispersion') == 0)
1926	{
1927		var coords = "<table>";
1928		for (yards = 100; yards <= 300; yards += 10)
1929		{
1930			for (k = 0; k <= 10; ++k)
1931			{
1932			var dispersionMultiplier = 1.0 + k*0.1;
1933			var sumX2 = 0;
1934			var sumY2 = 0;
1935			var sumR2 = 0;
1936			var sumR = 0;
1937			var dist = yards * 1609.344 / 1760;
1938			for (var i = 0; i < 100000; ++i)
1939			{
1940				// Distance Error
1941				// -8% to +6%
1942				var x = (Math.random() + Math.random() + Math.random() + Math.random()) / 4; // Normal distribution 0 .. 1
1943				x = 14*x - 7; // Normal distribution -7 .. +7
1944				if (dist < 90) x *= 1 + (90 - dist)/45; // Distance error gradually increases below 90 metres
1945				x = x - 1; // Normal distribution -8 .. +6
1946				x = x*dist/100; // Normal distribution -8% .. +6%
1947				if (x < 0) x *= dispersionMultiplier;
1948
1949				// Direction Error
1950				// +/- 3/5/7 degrees at 100/200/300 yards
1951				var max = 1 + 2*dist/90; // 3/5/7 at 100/200/300 yards
1952				var y = (Math.random() + Math.random() + Math.random() + Math.random()) / 4; // Normal distribution 0 .. 1
1953				y = 2*max*y - max; // Normal distribution -max .. +max
1954				y *= dispersionMultiplier;
1955				y = (dist + x)*y*Math.PI/180; // Convert degrees to metres
1956
1957				// Wind and bounce
1958				// Up to 5% in random direction
1959				var z = (Math.random() + Math.random() + Math.random() + Math.random()) / 4; // Normal distribution 0 .. 1
1960				z = 10*z - 5; // Normal distribution -5 .. +5
1961				var dist2 = z*dist/100; // Normal distribution -5% .. +5%
1962				var theta = Math.random()*2*Math.PI;
1963				x += dist2*Math.cos(theta);
1964				y += dist2*Math.sin(theta);
1965
1966				sumX2 += x*x;
1967				sumY2 += y*y;
1968				sumR2 += x*x + y*y;
1969				sumR += Math.sqrt(x*x + y*y);
1970			}
1971			// 95% confidence = 2 standard deviations (4 for spread)
1972			// 99.7% confidence = 3 standard deviations (6 for spread)
1973			var spreadX = Math.round(6*100*(Math.sqrt(sumX2/100000))*1760/1609.344)/100; // Long/Short
1974			var spreadY = Math.round(6*100*(Math.sqrt(sumY2/100000))*1760/1609.344)/100; // Left/Right
1975			var spreadR = Math.round(6*100*(Math.sqrt(sumR2/100000))*1760/1609.344)/100; // Total
1976			var proximity = Math.round(100*(sumR/100000)*1760/1609.344)/100;
1977			if (k == 0) coords += "<tr><td>" + yards.toString() + "</td>";
1978			coords += "<td>&nbsp;" + spreadY.toString() + "</td>";
1979			}
1980			coords += "</tr>";
1981		}
1982		coords += "</table>";
1983		document.getElementById('coordsdiv').innerHTML = coords;
1984		if (mode == 0)
1985			return;
1986	}
1987	else if (s != null && s.indexOf('!!tileinfo') == 0)
1988	{
1989		var z = 18;
1990		if (s.length > 10) z = parseInt(s.substring(10));
1991		var x = PV.geog.tileXFromZLon(z,lon);
1992		var y = PV.geog.tileYFromZLat(z,lat);
1993		var minLat = PV.geog.tileMinLatFromZY(z,y);
1994		var minLon = PV.geog.tileMinLonFromZX(z,x);
1995		var maxLat = PV.geog.tileMaxLatFromZY(z,y);
1996		var maxLon = PV.geog.tileMaxLonFromZX(z,x);
1997		var dx = PV.geog.dist(lat,minLon,lat,maxLon);
1998		var dy = PV.geog.dist(minLat,lon,maxLat,lon);
1999		var px = dx/256;
2000		var py = dy/256;
2001		document.getElementById('coordsdiv').innerHTML =
2002			"z="+z.toString()+", y="+y.toString()+", x="+x.toString()+"<br/>"
2003			+"dx="+dx.toFixed(2)+", dy="+dy.toFixed(2)+"<br/>"
2004			+"px="+px.toFixed(2)+", py="+py.toFixed(2)+"<br/>"
2005			+"<a href='https://tile.openstreetmap.org/"+z.toString()+"/"+x.toString()+"/"+y.toString()+".png'>OSM</a>";
2006		if (mode == 0)
2007			return;
2008	}
2009	else if (s != null && s.indexOf('!!coords1') == 0)
2010	{
2011		document.getElementById('coordsdiv').innerHTML =
2012			(Math.round(lat*1000000)/1000000.0).toString() + "," +
2013			(Math.round(lon*1000000)/1000000.0).toString() + "," +
2014			(Math.round(getProVisualizerTerrainHeight(lat,lon)*100)/100.0);
2015		if (mode == 0)
2016			return;
2017	}
2018	else if (s != null && s.indexOf('!!coords2') == 0)
2019	{
2020		var coords = "";
2021		coords += (Math.round(getCourseHoleTeeLat(courseID,hole)*1000000)/1000000.0).toString() + ",";
2022		coords += (Math.round(getCourseHoleTeeLon(courseID,hole)*1000000)/1000000.0).toString() + ",";
2023		coords += (Math.round(getCourseHolePinLat(courseID,hole)*1000000)/1000000.0).toString() + ",";
2024		coords += (Math.round(getCourseHolePinLon(courseID,hole)*1000000)/1000000.0).toString() + ",";
2025		coords += (Math.round(getCourseHoleTargetLat(courseID,hole,1)*1000000)/1000000.0).toString() + ",";
2026		coords += (Math.round(getCourseHoleTargetLon(courseID,hole,1)*1000000)/1000000.0).toString() + ",";
2027		coords += (Math.round(getCourseHoleTargetLat(courseID,hole,2)*1000000)/1000000.0).toString() + ",";
2028		coords += (Math.round(getCourseHoleTargetLon(courseID,hole,2)*1000000)/1000000.0).toString() + ",";
2029		coords += (Math.round(getCourseHoleTargetLat(courseID,hole,3)*1000000)/1000000.0).toString() + ",";
2030		coords += (Math.round(getCourseHoleTargetLon(courseID,hole,3)*1000000)/1000000.0).toString();
2031		document.getElementById('coordsdiv').innerHTML = coords;
2032		if (mode == 0)
2033			return;
2034	}
2035	else if (s != null && s.indexOf('!!coords3') == 0)
2036	{
2037		var minLat = getCourseMinLat(courseID) - 0.001;
2038		var maxLat = getCourseMaxLat(courseID) + 0.001;
2039		var minLon = getCourseMinLon(courseID) - 0.001;
2040		var maxLon = getCourseMaxLon(courseID) + 0.001;
2041		var coords = "<small>";
2042		coords += "function elevationOverride_XXXX(lat, lon)<br/>{<br/>";
2043		coords += "if (lat > "+minLat.toString()+" && lat < "+maxLat.toString()+" && lon > "+minLon.toString()+" && lon < "+maxLon.toString()+")<br/>{<br/>var e;<br/>";
2044		for (var hole = 1; hole <= 18; ++hole)
2045		{
2046			coords += "e = elevHole(lat,lon,";
2047			coords += (Math.round(getCourseHoleTeeLat(courseID,hole)*1000000)/1000000.0).toString() + ",";
2048			coords += (Math.round(getCourseHoleTeeLon(courseID,hole)*1000000)/1000000.0).toString() + ",";
2049			coords += (Math.round(getCourseHolePinLat(courseID,hole)*1000000)/1000000.0).toString() + ",";
2050			coords += (Math.round(getCourseHolePinLon(courseID,hole)*1000000)/1000000.0).toString() + ",";
2051			coords += (Math.round(getCourseHoleTargetLat(courseID,hole,1)*1000000)/1000000.0).toString() + ",";
2052			coords += (Math.round(getCourseHoleTargetLon(courseID,hole,1)*1000000)/1000000.0).toString() + ",";
2053			coords += (Math.round(getCourseHoleTargetLat(courseID,hole,2)*1000000)/1000000.0).toString() + ",";
2054			coords += (Math.round(getCourseHoleTargetLon(courseID,hole,2)*1000000)/1000000.0).toString() + ",";
2055			coords += (Math.round(getCourseHoleTargetLat(courseID,hole,3)*1000000)/1000000.0).toString() + ",";
2056			coords += (Math.round(getCourseHoleTargetLon(courseID,hole,3)*1000000)/1000000.0).toString();
2057			coords += ",,,,,,20,40,40,40,30); if (e > -9999) return e;<br/>";
2058		}
2059		coords += "}<br/>return -1000000;<br/>}<br/></small>";
2060		document.getElementById('coordsdiv').innerHTML = coords;
2061		if (mode == 0)
2062			return;
2063	}
2064	else if (s != null && s.indexOf('!!bbox') == 0)
2065	{
2066		var buffer = 200;
2067		if (s.length > 6) buffer = parseInt(s.substr(6));
2068		var minLat = getCourseMinLat(courseID);
2069		var minLon = getCourseMinLon(courseID);
2070		var maxLat = getCourseMaxLat(courseID);
2071		var maxLon = getCourseMaxLon(courseID);
2072		minLat = Math.round(moveLat(minLat, minLon, buffer, 180)*1000000)/1000000;
2073		minLon = Math.round(moveLon(minLat, minLon, buffer, 270)*1000000)/1000000;
2074		maxLat = Math.round(moveLat(maxLat, maxLon, buffer,   0)*1000000)/1000000;
2075		maxLon = Math.round(moveLon(maxLat, maxLon, buffer,  90)*1000000)/1000000;
2076		var coords = "BBox with ";
2077		coords += buffer.toString() + "m buffer = ";
2078		coords += minLat.toString() + " ";
2079		coords += minLon.toString() + " ";
2080		coords += maxLat.toString() + " ";
2081		coords += maxLon.toString();
2082		document.getElementById('coordsdiv').innerHTML = coords;
2083		if (mode == 0)
2084			return;
2085	}
2086	else if (s != null && s.indexOf('!!tilecounton') == 0)
2087	{
2088		showTileCount = true;
2089	}
2090	else if (s != null && s.indexOf('!!tilecountoff') == 0)
2091	{
2092		showTileCount = false;
2093	}
2094	else if (s != null && s.indexOf('!!tiles') == 0)
2095	{
2096		var courseMinLat = getCourseMinLat(courseID);
2097		var courseMinLon = getCourseMinLon(courseID);
2098		var courseMaxLat = getCourseMaxLat(courseID);
2099		var courseMaxLon = getCourseMaxLon(courseID);
2100		var courseMidLat = (courseMinLat + courseMaxLat)/2;
2101		var courseMidLon = (courseMinLon + courseMaxLon)/2;
2102		var courseDX = 200.0 + geogDist(courseMidLat, courseMinLon, courseMidLat, courseMaxLon);
2103		var courseDY = 200.0 + geogDist(courseMinLat, courseMidLon, courseMaxLat, courseMidLon);
2104		var tileStats = "";
2105		for (var maxPixelSize = 2.0; maxPixelSize > 0.24; maxPixelSize /= 2)
2106		{
2107			var tilePixelsX = 512;
2108			var tilePixelsY = 512;
2109			var tileMinLat = -90;
2110			var tileMaxLat = 90;
2111			var tileMidLat = (tileMinLat + tileMaxLat)/2;
2112			var tileMinLon = -180;
2113			var tileMaxLon = 180;
2114			var tileMidLon = (tileMinLon + tileMaxLon)/2;
2115			var tileDX = geogDist(tileMidLat, tileMinLon, tileMidLat, tileMaxLon);
2116			var tileDY = geogDist(tileMinLat, tileMidLon, tileMaxLat, tileMidLon);
2117			var pixelSizeX = tileDX / tilePixelsX;
2118			var pixelSizeY = tileDY / tilePixelsY;
2119			var level = 0;
2120			while (pixelSizeX > maxPixelSize || pixelSizeY > maxPixelSize)
2121			{
2122				if (courseMidLat >= tileMidLat) tileMinLat = tileMidLat; else tileMaxLat = tileMidLat;
2123				if (courseMidLon >= tileMidLon) tileMinLon = tileMidLon; else tileMaxLon = tileMidLon;
2124				tileMidLat = (tileMinLat + tileMaxLat)/2;
2125				tileMidLon = (tileMinLon + tileMaxLon)/2;
2126				tileDX = geogDist(tileMidLat, tileMinLon, tileMidLat, tileMaxLon);
2127				tileDY = geogDist(tileMinLat, tileMidLon, tileMaxLat, tileMidLon);
2128				pixelSizeX = tileDX / tilePixelsX;
2129				pixelSizeY = tileDY / tilePixelsY;
2130				level += 1;
2131			}
2132			var numTilesX = Math.ceil(courseDX/tileDX);
2133			var numTilesY = Math.ceil(courseDY/tileDY);
2134			tileStats += "maxPixelSize="+maxPixelSize.toString()
2135				+", level="+level.toString()
2136				+", tileDX="+Math.round(tileDX).toString()
2137				+", tileDY="+Math.round(tileDY).toString()
2138				+", pixelSizeX="+(Math.round(1000*pixelSizeX)/1000).toString()
2139				+", pixelSizeY="+(Math.round(1000*pixelSizeY)/1000).toString()
2140				+", numTilesX="+numTilesX.toString()
2141				+", numTilesY="+numTilesY.toString()
2142				+", totalTiles="+(numTilesX*numTilesY).toString()
2143				+"<br/>";
2144		}
2145		document.getElementById('coordsdiv').innerHTML = tileStats;
2146		if (mode == 0)
2147			return;
2148	}
2149	else if (s != null && s.indexOf('!!hdt') == 0)
2150	{
2151		document.getElementById('coordsdiv').innerHTML = "";
2152		if (terrain1 == null || terrain1.heights == null || terrain1.heights.length != terrain1NumLat * terrain1NumLon) { alert("No terrain!"); return; }
2153		var minH = 9999;
2154		var maxH = -9999;
2155		var n = terrain1NumLat * terrain1NumLon;
2156		for (var i = 0; i < n; ++i)
2157		{
2158			var h = terrain1.heights[i];
2159			if (h >= 0 && h < 9999)
2160			{
2161				if (h < minH) minH = h;
2162				if (h > maxH) maxH = h;
2163			}
2164		}
2165		if (maxH < 0) return;
2166		var minLat = Math.round(terrain1MinLat*1000000)/1000000.0;
2167		var maxLat = Math.round(terrain1MaxLat*1000000)/1000000.0;
2168		var minLon = Math.round(terrain1MinLon*1000000)/1000000.0;
2169		var maxLon = Math.round(terrain1MaxLon*1000000)/1000000.0;
2170		var avgLat = (minLat + maxLat)/2;
2171		var dLat = geogDist(minLat, minLon, maxLat, minLon);
2172		var dLon = geogDist(avgLat, minLon, avgLat, maxLon);
2173		var res = 1;
2174		if (s.indexOf('!!hdt2') == 0) res = 2;
2175		if (s.indexOf('!!hdt3') == 0) res = 3;
2176		if (s.indexOf('!!hdt4') == 0) res = 4;
2177		if (s.indexOf('!!hdt5') == 0) res = 5;
2178		var numLat = 2 + Math.round(dLat / res);
2179		var numLon = 2 + Math.round(dLon / res);
2180		numLon = Math.round(numLon / Math.cos(avgLat*Math.PI/180.0));
2181		var h0 = Math.round(minH - 50);
2182		if (h0 < 0) h0 = 0;
2183		var urlPrefix = 'https://elevation.nationalmap.gov/arcgis/rest/services/3DEPElevation/ImageServer/';
2184		var source = 1;
2185		if (minLon > -15)
2186		{
2187			urlPrefix = 'https://environment.data.gov.uk/image/rest/services/SURVEY/LIDAR_Composite_1m_DTM_2022_TSR/ImageServer/';
2188			source = 2;
2189			var k = (numLat * numLon)/16000000.0;
2190			if (k > 1.0) { numLat /= k; numLon /= k; }
2191		}
2192		var h00 = h0;
2193		while (h0 < maxH + 50)
2194		{
2195			h1 = h0 + 50;
2196			var colorMap = "%5B";
2197			for (var h = h0; h < h1; ++h)
2198			{
2199				var dh = h - h00;
2200				var r = 5 + 10*(Math.floor(dh)%25);
2201				var g = 5 + 10*(Math.floor(dh/25)%25);
2202				var b = 5 + 10*(Math.floor(dh/625)%25);
2203				if (h > h0) colorMap += ",";
2204				colorMap += "%5B" + h.toString() + "," + r.toString() + "," + g.toString() + "," + b.toString() + "%5D";
2205			}
2206			colorMap += "%5D";
2207			var url = urlPrefix + 'exportImage?f=image&bandIds=&renderingRule={"rasterFunction"%3A"Colormap",%22rasterFunctionArguments"%3A{"Colormap"%3A'+colorMap+'},"variableName"%3A"DEM"}&bbox='+minLon.toString()+'%2C'+minLat.toString()+'%2C'+maxLon.toString()+'%2C'+maxLat.toString()+'&imageSR=4326&bboxSR=4326&size='+numLon.toString()+'%2C'+numLat.toString()+'&format=png';
2208			window.open(url, "_blank");
2209			h0 = h1;
2210		}
2211		document.getElementById('coordsdiv').innerHTML = "h00 = " + h00.toString() + " ";
2212		document.getElementById('coordsdiv').innerHTML += "terrainList.push(";
2213		document.getElementById('coordsdiv').innerHTML += "{name:''";
2214		document.getElementById('coordsdiv').innerHTML += ",source:" + source.toString();
2215		document.getElementById('coordsdiv').innerHTML += ",minLat:" + minLat.toString();
2216		document.getElementById('coordsdiv').innerHTML += ",minLon:" + minLon.toString();
2217		document.getElementById('coordsdiv').innerHTML += ",maxLat:" + maxLat.toString();
2218		document.getElementById('coordsdiv').innerHTML += ",maxLon:" + maxLon.toString();
2219		document.getElementById('coordsdiv').innerHTML += ",numLat:" + numLat.toString();
2220		document.getElementById('coordsdiv').innerHTML += ",numLon:" + numLon.toString();
2221		document.getElementById('coordsdiv').innerHTML += "});";
2222		if (mode == 0)
2223			return;
2224	}
2225
2226	var repeatDispersionCount = 0;
2227	if (dispersionMode == 1)
2228		repeatDispersionCount = mapMarkers2.length;
2229
2230	dispersionMode = 0;
2231
2232	if (mode == 0)
2233	{
2234		if (hole > 0)
2235		{
2236			var lat2 = getCourseHolePinLat(courseID, hole);
2237			var lon2 = getCourseHolePinLon(courseID, hole);
2238			if (geogDist(lat, lon, lat2, lon2) < 0.5) { lat = lat2; lon = lon2; }
2239			if ((greenSlopesCount > 0 || greenContoursCount > 0) && geogDist(lat, lon, lat2, lon2) < myUnitsToMetres(50))
2240			{
2241				var opToRemove = (button == 1 ? 1 : 2);
2242				if (removePuttHistory(opToRemove, lat, lon, 0.5) > 0) selectGreenSlopesHandler();
2243				else if (button == 1) addPuttLine(lat, lon, true, true, (s != null && s.indexOf('!!puttballs') == 0));
2244				else setPuttTarget(lat, lon);
2245				return;
2246			}
2247			if (geogDist(lat, lon, lat2, lon2) < 10) { setPinHandler(); dispersionMode = 1; return; }
2248			lat2 = getCourseHoleTeeLat(courseID, hole);
2249			lon2 = getCourseHoleTeeLon(courseID, hole);
2250			if (geogDist(lat, lon, lat2, lon2) < 10) { setTeeHandler(); dispersionMode = 1; return; }
2251			lat2 = getCourseHoleTargetLat(courseID, hole, 1);
2252			lon2 = getCourseHoleTargetLon(courseID, hole, 1);
2253			if (geogDist(lat, lon, lat2, lon2) < 10) { setTargetHandler(1); dispersionMode = 1; return; }
2254			lat2 = getCourseHoleTargetLat(courseID, hole, 2);
2255			lon2 = getCourseHoleTargetLon(courseID, hole, 2);
2256			if (geogDist(lat, lon, lat2, lon2) < 10) { setTargetHandler(2); dispersionMode = 1; return; }
2257			lat2 = getCourseHoleTargetLat(courseID, hole, 3);
2258			lon2 = getCourseHoleTargetLon(courseID, hole, 3);
2259			if (geogDist(lat, lon, lat2, lon2) < 10) { setTargetHandler(3); dispersionMode = 1; return; }
2260		}
2261		return;
2262	}
2263
2264	if (mode == 6)
2265	{
2266		if (distLat1 == 0 && distLon1 == 0)
2267		{
2268			distLat1 = Math.round(lat*999999)/999999;
2269			distLon1 = Math.round(lon*999999)/999999;
2270			setDistDiv("Click End");
2271		}
2272		else
2273		{
2274			distLat2 = Math.round(lat*999999)/999999;
2275			distLon2 = Math.round(lon*999999)/999999;
2276			var d = geogDist(distLat1, distLon1, distLat2, distLon2);
2277			if (d < 5) return;
2278			addDist(distLat1, distLon1, distLat2, distLon2);
2279			if (button == 2) setMode(0);
2280			setDistDiv(formatDist(d));
2281			var i = getCourseHoleMeasures(courseID, hole);
2282			setCourseHoleMeasure(courseID, hole, i, encodeMeasure(distLat1, distLon1, distLat2, distLon2));
2283			distLat1 = 0;
2284			distLon1 = 0;
2285		}
2286		return;
2287	}
2288
2289	if (mode == 7)
2290	{
2291		distLat2 = Math.round(lat*999999)/999999;
2292		distLon2 = Math.round(lon*999999)/999999;
2293		var d = geogDist(distLat1, distLon1, distLat2, distLon2);
2294		if (d < 5) return;
2295		addDist(distLat1, distLon1, distLat2, distLon2);
2296		if (button == 2) setMode(0);
2297		setDistDiv(formatDist(d));
2298		var i = getCourseHoleMeasures(courseID, hole);
2299		setCourseHoleMeasure(courseID, hole, i, encodeMeasure(distLat1, distLon1, distLat2, distLon2));
2300		return;
2301	}
2302
2303	if (mode == 8)
2304	{
2305		var n = getCourseHoleMeasures(courseID, hole);
2306		var best = -1;
2307		var bestDist = 2;
2308		for (var i = 0; i < n; ++i)
2309		{
2310			var x = decodeMeasure(getCourseHoleMeasure(courseID, hole, i));
2311			var d = geogDist(lat, lon, x.lat1, x.lon1) + geogDist(lat, lon, x.lat2, x.lon2) - geogDist(x.lat1, x.lon1, x.lat2, x.lon2);
2312			if (d < bestDist) { best = i; bestDist = d; }
2313		}
2314		if (best >= 0)
2315		{
2316			for (var j = best; j < n - 1; ++j)
2317				setCourseHoleMeasure(courseID, hole, j, getCourseHoleMeasure(courseID, hole, j + 1));
2318			deleteCourseHoleMeasure(courseID, hole, n - 1);
2319			refreshView();
2320			--n;
2321			if (n == 0) setMode(0);
2322			else setDistDiv("Click Map");
2323			if (mapMode == 2 && n > 0)
2324			{
2325				for (var k = 0; k < n; ++k)
2326				{
2327					var x = decodeMeasure(getCourseHoleMeasure(courseID, hole, k));
2328					addDist(x.lat1, x.lon1, x.lat2, x.lon2);
2329				}
2330			}
2331			return;
2332		}
2333		setMode(0);
2334		return;
2335	}
2336
2337	if (mode == 9)
2338	{
2339		if (activeSymbol == 'Z')
2340		{
2341			var n = getCourseHoleSymbols(courseID, hole);
2342			var best = -1;
2343			var bestDist = 10;
2344			for (var i = 0; i < n; ++i)
2345			{
2346				var x = decodeSymbol(getCourseHoleSymbol(courseID, hole, i));
2347				var d = geogDist(lat, lon, x.lat, x.lon);
2348				if (d < bestDist) { best = i; bestDist = d; }
2349			}
2350			if (best >= 0)
2351			{
2352				for (var j = best; j < n - 1; ++j)
2353					setCourseHoleSymbol(courseID, hole, j, getCourseHoleSymbol(courseID, hole, j + 1));
2354				deleteCourseHoleSymbol(courseID, hole, n - 1);
2355				refreshView();
2356				--n;
2357				if (n == 0) setMode(0);
2358				return;
2359			}
2360			setMode(0);
2361			return;
2362		}
2363		else
2364		{
2365			var n = getCourseHoleSymbols(courseID, hole);
2366			if (n >= 50)
2367			{
2368				alert("Max 50 symbols per hole.");
2369				setMode(0);
2370				return;
2371			}
2372			var theta = 0 - getCourseHoleBearing(courseID, hole);
2373			addSymbol(lat, lon, activeSymbol, theta);
2374			setCourseHoleSymbol(courseID, hole, n, encodeSymbol(lat, lon, activeSymbol));
2375			if (activeSymbol != 'T' && activeSymbol != 'X') setMode(0);
2376			return;
2377		}
2378	}
2379
2380	if (hole == 0)
2381	{
2382		setMode(0);
2383		return;
2384	}
2385
2386	if (mode == 1)
2387	{
2388		setCourseHoleTeeLat(courseID, hole, lat);
2389		setCourseHoleTeeLon(courseID, hole, lon);
2390	}
2391	else if (mode == 2)
2392	{
2393		setCourseHolePinLat(courseID, hole, lat);
2394		setCourseHolePinLon(courseID, hole, lon);
2395	}
2396	else if (mode >= 3 && mode <= 5)
2397	{
2398		setCourseHoleTargetLat(courseID, hole, mode - 2, lat);
2399		setCourseHoleTargetLon(courseID, hole, mode - 2, lon);
2400	}
2401
2402	if (courseID == 0)
2403		document.getElementById("savebutton").style.backgroundColor = "#FF8888";
2404
2405	if (button == 1)
2406		setMode(0);
2407
2408	normalizeCourseHoleTargets(courseID, hole);
2409	refreshView();
2410
2411	while (mapMarkers2.length < repeatDispersionCount)
2412		dispersionHandler();
2413}
2414
2415function newHandler()
2416{
2417	setSelectedCourse(0);
2418	selectCourseHandler();
2419}
2420
2421function searchHandler()
2422{
2423	var searchstring = document.getElementById("saveasinput").value;
2424	if (searchstring == null) searchstring = "";
2425	searchstring = cleanString(searchstring);
2426	if (searchstring == "")
2427	{
2428		alert("Please enter course address, town or postcode.");
2429		return;
2430	}
2431	setMode(0);
2432	initCourseData(0);
2433	setSelectedCourse(0);
2434	setSelectedHole(1);
2435	refreshView();
2436	document.getElementById("savebutton").style.backgroundColor = "";
2437	doXHR("findonmap", "s=" + searchstring);
2438	if (window.XMLHttpRequest)
2439	{
2440		var req = 'https://nominatim.openstreetmap.org/search?q=' + searchstring + '&format=json&polygon=0&addressdetails=0';
2441		var xhr = new XMLHttpRequest();
2442		xhr.onreadystatechange = function()
2443		{
2444			if (xhr.readyState == 4 && xhr.status == 200)
2445				searchResultHandler(xhr.responseText);
2446		}
2447		xhr.open("GET", req, true);
2448		xhr.send();
2449	}
2450}
2451
2452function searchResultHandler(responseText)
2453{
2454	var results = JSON.parse(responseText);
2455	if (results == null || results.length < 1)
2456	{
2457		alert('Address not found. Please try again.');
2458		return;
2459	}
2460
2461	var bb = results[0].boundingbox;
2462	if (bb && bb.length == 4)
2463	{
2464		var lat1 = Number(bb[0]);
2465		var lat2 = Number(bb[1]);
2466		var lon1 = Number(bb[2]);
2467		var lon2 = Number(bb[3]);
2468		zoomToBounds(lat1,lat2,lon1,lon2);
2469	}
2470	else
2471	{
2472		alert("Search result has no bounding box. Please try again.");
2473	}
2474}
2475
2476function zoomToBounds(minLat, maxLat, minLon, maxLon)
2477{
2478	var lat = (minLat + maxLat) / 2;
2479	var lon = (minLon + maxLon) / 2;
2480	var height = geogDist(minLat, minLon, maxLat, maxLon);
2481	if (height < 1000) height = 1000;
2482	if (height >
2482 1000000) height = 1000000;
2483	var heading = 0;
2484	var angle = -90;
2485	var durationSeconds = 3;
2486	flyToHeightAboveGround(lat, lon, lat, lon, height, heading, angle, durationSeconds);
2487}
2488
2489function setTeeHandler()
2490{
2491	if (currentHole() == 0)
2492	{
2493		alert("Please select hole.");
2494	}
2495	else if (mode == 1)
2496	{
2497		setMode(0);
2498	}
2499	else
2500	{
2501		setMode(1);
2502	}
2503}
2504
2505function setPinHandler()
2506{
2507	if (currentHole() == 0)
2508	{
2509		alert("Please select hole.");
2510	}
2511	else if (mode == 2)
2512	{
2513		setMode(0);
2514	}
2515	else
2516	{
2517		setMode(2);
2518	}
2519}
2520
2521function setTargetHandler(i)
2522{
2523	if (currentHole() == 0)
2524	{
2525		alert("Please select hole.");
2526	}
2527	else if (mode == 2 + i)
2528	{
2529		setMode(0);
2530	}
2531	else
2532	{
2533		setMode(2 + i);
2534	}
2535}
2536
2537function zoomTeeHandler()
2538{
2539	setMode(0);
2540	if (currentHole() == 0) { alert('Please select hole.'); return; }
2541	var lat = getCourseHoleTeeLat(currentCourseID(), currentHole());
2542	var lon = getCourseHoleTeeLon(currentCourseID(), currentHole());
2543	if (lat == 0 && lon == 0) return;
2544	var h = 90 + (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
2545	if ((mapMode == 1 && Math.abs(viewer.camera.positionCartographic.height - h) < 0.1) || (mapMode == 2 && viewer.camera.pitch < - Math.PI / 3))
2546	{
2547		zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), false, false, 2)
2548		return;
2549	}
2550	var height = 90;
2551	var heading = viewer.camera.heading * 180 / Math.PI;
2552	var angle = -90;
2553	var durationSeconds = 2;
2554	flyToHeightAboveGround(lat, lon, lat, lon, height, heading, angle, durationSeconds);
2555}
2556
2557function zoomPinHandler()
2558{
2559	setMode(0);
2560	if (currentHole() == 0) { alert('Please select hole.'); return; }
2561	var lat = getCourseHolePinLat(currentCourseID(), currentHole());
2562	var lon = getCourseHolePinLon(currentCourseID(), currentHole());
2563	if (lat == 0 && lon == 0) return;
2564	var h = 60 + (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
2565	if ((mapMode == 1 && Math.abs(viewer.camera.positionCartographic.height - h) < 0.1) || (mapMode == 2 && viewer.camera.pitch < - Math.PI / 3))
2566	{
2567		zoomToCourseHoleShot(currentCourseID(), currentHole(), currentShot(), false, false, 2)
2568		return;
2569	}
2570	var height = 60;
2571	var heading = viewer.camera.heading * 180 / Math.PI;
2572	var angle = -90;
2573	var durationSeconds = 2;
2574	flyToHeightAboveGround(lat, lon, lat, lon, height, heading, angle, durationSeconds);
2575}
2576
2577function clearTeeHandler()
2578{
2579	setMode(0);
2580	if (currentHole() == 0) return;
2581	setCourseHoleTeeLat(currentCourseID(), currentHole(), 0);
2582	setCourseHoleTeeLon(currentCourseID(), currentHole(), 0);
2583	refreshView();
2584}
2585
2586function clearPinHandler()
2587{
2588	setMode(0);
2589	if (currentHole() == 0) return;
2590	setCourseHolePinLat(currentCourseID(), currentHole(), 0);
2591	setCourseHolePinLon(currentCourseID(), currentHole(), 0);
2592	refreshView();
2593}
2594
2595function clearTargetHandler(i)
2596{
2597	setMode(0);
2598	if (currentHole() == 0) return;
2599	setCourseHoleTargetLat(currentCourseID(), currentHole(), i, 0);
2600	setCourseHoleTargetLon(currentCourseID(), currentHole(), i, 0);
2601	normalizeCourseHoleTargets(currentCourseID(), currentHole());
2602	refreshView();
2603}
2604
2605function setTargetMaxHandler(i)
2606{
2607	setMode(0);
2608	if (currentHole() == 0) return;
2609	setCourseHoleTargetMax(currentCourseID(), currentHole(), i);
2610	normalizeCourseHoleTargets(currentCourseID(), currentHole());
2611	refreshView();
2612}
2613
2614function setTargetDeltaHandler(i, delta)
2615{
2616	setMode(0);
2617	if (currentHole() == 0) return;
2618	setCourseHoleTargetDelta(currentCourseID(), currentHole(), i, delta);
2619	normalizeCourseHoleTargets(currentCourseID(), currentHole());
2620	refreshView();
2621}
2622
2623function setTargetPlusHandler(i)
2624{
2625	setMode(0);
2626	if (currentHole() == 0) return;
2627	setCourseHoleTargetDelta(currentCourseID(), currentHole(), i, 5);
2628	normalizeCourseHoleTargets(currentCourseID(), currentHole());
2629	refreshView();
2630}
2631
2632function setTargetMinusHandler(i)
2633{
2634	setMode(0);
2635	if (currentHole() == 0) return;
2636	setCourseHoleTargetDelta(currentCourseID(), currentHole(), i, -5);
2637	normalizeCourseHoleTargets(currentCourseID(), currentHole());
2638	refreshView();
2639}
2640
2641function teeGridHandler()
2642{
2643	if (teeGridCount >= 1)
2644	{
2645		refreshView();
2646		return;
2647	}
2648
2649	var courseID = currentCourseID();
2650	var hole = currentHole();
2651	if (hole < 1)
2652	{
2653		alert("Please select hole.");
2654		return;
2655	}
2656	var lat2 = getCourseHolePinLat(courseID, hole);
2657	var lon2 = getCourseHolePinLon(courseID, hole);
2658	if (lat2 == 0 && lon2 == 0)
2659	{
2660		alert("Please set pin position.");
2661		return;
2662	}
2663	var par = getCourseHolePar(courseID, hole);
2664	if (par < 3)
2665	{
2666		alert("Please set tee position.")
2667		return;
2668	}
2669	var lat1 = getCourseHoleTeeLat(courseID, hole);
2670	var lon1 = getCourseHoleTeeLon(courseID, hole);
2671	if (par >= 4)
2672	{
2673		lat2 = getCourseHoleTargetLat(courseID, hole, 1);
2674		lon2 = getCourseHoleTargetLon(courseID, hole, 1);
2675	}
2676	var d = geogDist(lat1, lon1, lat2, lon2);
2677	if (d < 1)
2678	{
2679		alert("Tee/Targets/Pin too close together.");
2680		return;
2681	}
2682	var bearingY = geogBearing(lat1, lon1, lat2, lon2);
2683	var dLatY = moveLat(lat2, lon2, myUnitsToMetres(1), bearingY) - lat2;
2684	var dLonY = moveLon(lat2, lon2, myUnitsToMetres(1), bearingY) - lon2;
2685	var bearingX = bearingY + 90;
2686	var dLatX = moveLat(lat2, lon2, myUnitsToMetres(1), bearingX) - lat2;
2687	var dLonX = moveLon(lat2, lon2, myUnitsToMetres(1), bearingX) - lon2;
2688	var circleBrightness = document.getElementById('circlebrightnessselect').selectedIndex;
2689	var gridBrightness1 = 0.040 * circleBrightness;
2690	var gridBrightness2 = 0.015 * circleBrightness;
2691	var gridColor1 = new Cesium.Color(1,1,1,gridBrightness1);
2692	var gridColor2 = new Cesium.Color(1,1,1,gridBrightness2);
2693	for (var y = -50; y <= 100; y += 10)
2694	{
2695		var coords = [];
2696		for (var x = -50; x <= 50; x += 2)
2697		{
2698			var lat = lat1 + y*dLatY +x*dLatX;
2699			var lon = lon1 + y*dLonY +x*dLonX;
2700			var h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
2701			coords.push(Cesium.Cartesian3.fromDegrees(lon, lat, h));
2702		}
2703		var line = viewer.entities.add({ polyline : { positions: coords, width: 1.0, material: (y % 50 == 0 ? gridColor1 : gridColor2) } });
2704		mapLines2.push(line);
2705	}
2706	teeGridCount += 1;
2707}
2708
2709function greenGridHandler()
2710{
2711	setMode(0);
2712	var courseID = currentCourseID();
2713	var hole = currentHole();
2714	if (hole < 1)
2715	{
2716		alert("Please select hole.");
2717		return;
2718	}
2719	var lat2 = getCourseHolePinLat(courseID, hole);
2720	var lon2 = getCourseHolePinLon(courseID, hole);
2721	if (lat2 == 0 && lon2 == 0)
2722	{
2723		alert("Please set pin position.");
2724		return;
2725	}
2726	var par = getCourseHolePar(courseID, hole);
2727	if (par < 3)
2728	{
2729		alert("Please set tee position.")
2730		return;
2731	}
2732	if (greenGridCount == 6)
2733	{
2734		refreshView();
2735		return;
2736	}
2737	else if (greenGridCount == 3)
2738	{
2739		refreshView();
2740		if (par < 5)
2741			return;
2742		greenGridCount = 3;
2743	}
2744
2745	var lat1 = 0;
2746	var lon1 = 0;
2747	if (par == 3)
2748	{
2749		lat1 = getCourseHoleTeeLat(courseID, hole);
2750		lon1 = getCourseHoleTeeLon(courseID, hole);
2751	}
2752	else if (par == 4 || (par == 5 && greenGridCount >= 3))
2753	{
2754		lat1 = getCourseHoleTargetLat(courseID, hole, 1);
2755		lon1 = getCourseHoleTargetLon(courseID, hole, 1);
2756	}
2757	else if (par == 5 || (par == 6 && greenGridCount >= 3))
2758	{
2759		lat1 = getCourseHoleTargetLat(courseID, hole, 2);
2760		lon1 = getCourseHoleTargetLon(courseID, hole, 2);
2761	}
2762	else if (par == 6)
2763	{
2764		lat1 = getCourseHoleTargetLat(courseID, hole, 3);
2765		lon1 = getCourseHoleTargetLon(courseID, hole, 3);
2766	}
2767	var d = geogDist(lat1, lon1, lat2, lon2);
2768	if (d < 1)
2769	{
2770		alert("Tee/Targets/Pin too close together.");
2771		return;
2772	}
2773	var bearingY = geogBearing(lat1, lon1, lat2, lon2);
2774	var dLatY = moveLat(lat2, lon2, myUnitsToMetres(1), bearingY) - lat2;
2775	var dLonY = moveLon(lat2, lon2, myUnitsToMetres(1), bearingY) - lon2;
2776	var bearingX = bearingY + 90;
2777	var dLatX = moveLat(lat2, lon2, myUnitsToMetres(1), bearingX) - lat2;
2778	var dLonX = moveLon(lat2, lon2, myUnitsToMetres(1), bearingX) - lon2;
2779	var gridBrightness1 = (greenGridCount % 3 == 0 ? 0.02 : 0.01) * getCircleBrightness();
2780	var gridBrightness2 = 0.01 * getCircleBrightness();
2781	var gridColor1 = new Cesium.Color(1,1,1,gridBrightness1);
2782	var gridColor2 = new Cesium.Color(1,1,1,gridBrightness2);
2783	var step = 10;
2784	if (greenGridCount % 3 == 1) step = 5;
2785	if (greenGridCount % 3 == 2) step = 1;
2786	for (var y = -30; y <= 30; y += step)
2787	{
2788		var coords = [];
2789		for (var x = -30; x <= 30; x += 1)
2790		{
2791			var lat = lat2 + y*dLatY +x*dLatX;
2792			var lon = lon2 + y*dLonY +x*dLonX;
2793			var h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
2794			coords.push(Cesium.Cartesian3.fromDegrees(lon, lat, h));
2795		}
2796		var line = viewer.entities.add({ polyline : { positions: coords, width: 1.0, material: (y % 10 == 0 ? gridColor1 : gridColor2) } });
2797		mapLines2.push(line);
2798	}
2799	for (var x = -30; x <= 30; x += step)
2800	{
2801		var coords = [];
2802		for (var y = -30; y <= 30; y += 1)
2803		{
2804			var lat = lat2 + y*dLatY +x*dLatX;
2805			var lon = lon2 + y*dLonY +x*dLonX;
2806			var h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
2807			coords.push(Cesium.Cartesian3.fromDegrees(lon, lat, h));
2808		}
2809		var line = viewer.entities.add({ polyline : { positions: coords, width: 1.0, material: (x % 10 == 0 ? gridColor1 : gridColor2) } });
2810		mapLines2.push(line);
2811	}
2812	if (greenGridCount >= 3)
2813	{
2814		var coords = [];
2815		var lat3 = lat2 - 30*dLatY;
2816		var lon3 = lon2 - 30*dLonY;
2817		var h1 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat1, lon1));
2818		var h3 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat3, lon3));
2819		coords.push(Cesium.Cartesian3.fromDegrees(lon1, lat1, h1));
2820		coords.push(Cesium.Cartesian3.fromDegrees(lon3, lat3, h3));
2821		var line = viewer.entities.add({ polyline : { positions: coords, width: 1.0, material: (greenGridCount == 3 ? gridColor1 : gridColor2) } });
2822		mapLines2.push(line);
2823	}
2824	greenGridCount += 1;
2825}
2826
2827function selectGreenSpeedHandler()
2828{
2829	if (greenSlopesCount == 0) { greenSlopesHandler(); return; }
2830	var n1 = greenGridCount;
2831	var n2 = greenSlopesCount;
2832	var n3 = greenContoursCount;
2833	if (n2 < 1) n2 = 1;
2834	refreshView();
2835	var i = 0;
2836	for (i = 0; i < n1; ++i) greenGridHandler();
2837	for (i = 0; i < n2; ++i) greenSlopesHandler(false);
2838	for (i = 0; i < n3; ++i) greenContoursHandler();
2839	if (n2 > 0) replayPuttHistory();
2840}
2841
2842function selectGreenSlopesHandler()
2843{
2844	if (greenSlopesCount == 0) { greenSlopesHandler(); return; }
2845	var n1 = greenGridCount;
2846	var n2 = greenSlopesCount;
2847	var n3 = greenContoursCount;
2848	if (n2 < 1) n2 = 1;
2849	refreshView();
2850	var i = 0;
2851	for (i = 0; i < n1; ++i) greenGridHandler();
2852	for (i = 0; i < n2; ++i) greenSlopesHandler(false);
2853	for (i = 0; i < n3; ++i) greenContoursHandler();
2854	if (n2 > 0) replayPuttHistory();
2855}
2856
2857function selectGreenContoursHandler()
2858{
2859	if (greenContoursCount == 0) { greenContoursHandler(); return; }
2860	var n1 = greenGridCount;
2861	var n2 = greenSlopesCount;
2862	var n3 = greenContoursCount;
2863	if (n3 < 1) n3 = 1;
2864	refreshView();
2865	var i = 0;
2866	for (i = 0; i < n1; ++i) greenGridHandler();
2867	for (i = 0; i < n2; ++i) greenSlopesHandler(false);
2868	for (i = 0; i < n3; ++i) greenContoursHandler();
2869	if (n2 > 0) replayPuttHistory();
2870}
2871
2872function greenSlopesHandler(clearHistory = true)
2873{
2874	setMode(0);
2875	if (clearHistory) clearPuttHistory();
2876	var courseID = currentCourseID();
2877	var hole = currentHole();
2878	if (hole < 1)
2879	{
2880		alert("Please select hole.");
2881		return;
2882	}
2883	var lat2 = getCourseHolePinLat(courseID, hole);
2884	var lon2 = getCourseHolePinLon(courseID, hole);
2885	if (lat2 == 0 && lon2 == 0)
2886	{
2887		alert("Please set pin position.");
2888		return;
2889	}
2890	var par = getCourseHolePar(courseID, hole);
2891	if (par < 3)
2892	{
2893		alert("Please set tee position.")
2894		return;
2895	}
2896	if (greenSlopesCount != 0)
2897	{
2898		refreshView();
2899		return;
2900	}
2901	var lat1 = 0;
2902	var lon1 = 0;
2903	if (par == 3)
2904	{
2905		lat1 = getCourseHoleTeeLat(courseID, hole);
2906		lon1 = getCourseHoleTeeLon(courseID, hole);
2907	}
2908	else if (par == 4 || (par == 5 && greenGridCount > 3))
2909	{
2910		lat1 = getCourseHoleTargetLat(courseID, hole, 1);
2911		lon1 = getCourseHoleTargetLon(courseID, hole, 1);
2912	}
2913	else if (par == 5 || (par == 6 && greenGridCount > 3))
2914	{
2915		lat1 = getCourseHoleTargetLat(courseID, hole, 2);
2916		lon1 = getCourseHoleTargetLon(courseID, hole, 2);
2917	}
2918	else if (par == 6)
2919	{
2920		lat1 = getCourseHoleTargetLat(courseID, hole, 3);
2921		lon1 = getCourseHoleTargetLon(courseID, hole, 3);
2922	}
2923	var d = geogDist(lat1, lon1, lat2, lon2);
2924	if (d < 1)
2925	{
2926		alert("Tee/Targets/Pin too close together.");
2927		return;
2928	}
2929	var bearingY = geogBearing(lat1, lon1, lat2, lon2);
2930	var dLatY = moveLat(lat2, lon2, myUnitsToMetres(1), bearingY) - lat2;
2931	var dLonY = moveLon(lat2, lon2, myUnitsToMetres(1), bearingY) - lon2;
2932	var bearingX = bearingY + 90;
2933	var dLatX = moveLat(lat2, lon2, myUnitsToMetres(1), bearingX) - lat2;
2934	var dLonX = moveLon(lat2, lon2, myUnitsToMetres(1), bearingX) - lon2;
2935	var gridBrightness = 0.05*document.getElementById("greenslopebrightnessselect").value;
2936	var gridColor = [];
2937	gridColor[0] = new Cesium.Color(1.00, 1.00, 1.00, gridBrightness);//White
2938	gridColor[1] = new Cesium.Color(1.00, 1.00, 0.80, gridBrightness);
2939	gridColor[2] = new Cesium.Color(1.00, 1.00, 0.60, gridBrightness);
2940	gridColor[3] = new Cesium.Color(1.00, 1.00, 0.40, gridBrightness);
2941	gridColor[4] = new Cesium.Color(1.00, 1.00, 0.20, gridBrightness);
2942	gridColor[5] = new Cesium.Color(1.00, 1.00, 0.00, gridBrightness);//Yellow
2943	gridColor[6] = new Cesium.Color(0.95, 0.80, 0.00, gridBrightness);
2944	gridColor[7] = new Cesium.Color(0.90, 0.60, 0.00, gridBrightness);
2945	gridColor[8] = new Cesium.Color(0.85, 0.40, 0.00, gridBrightness);
2946	gridColor[9] = new Cesium.Color(0.80, 0.20, 0.00, gridBrightness);
2947	gridColor[10] = new Cesium.Color(0.75, 0.00, 0.00, gridBrightness);//Red
2948	gridColor[11] = new Cesium.Color(0.60, 0.00, 0.00, gridBrightness);
2949	gridColor[12] = new Cesium.Color(0.45, 0.00, 0.00, gridBrightness);
2950	gridColor[13] = new Cesium.Color(0.30, 0.00, 0.00, gridBrightness);
2951	gridColor[14] = new Cesium.Color(0.15, 0.00, 0.00, gridBrightness);
2952	gridColor[15] = new Cesium.Color(0.00, 0.00, 0.00, gridBrightness);//Black
2953	var colorFactor = 1000.0 / getRedSlope();
2954	var sizeIndex = document.getElementById("greenslopesizeselect").selectedIndex;
2955	var addNumbers = false;
2956	if (sizeIndex >= 4) { addNumbers = true; sizeIndex -= 4; }
2957	var lengthFactor = 1.0 + 0.5*sizeIndex;
2958	for (var x = -30; x <= 30; x += 1)
2959	{
2960		for (var y = -30; y <= 30; y += 1)
2961		{
2962			var lat3 = lat2 + y*dLatY + x*dLatX;
2963			var lon3 = lon2 + y*dLonY + x*dLonX;
2964			var hg = getProVisualizerTerrainHeightAndGradient(lat3, lon3, 0.5, bearingX, bearingY);
2965			var gx = hg.g1;
2966			var gy = hg.g2;
2967			var absgx = Math.abs(gx);
2968			var absgy = Math.abs(gy);
2969			var absgsum = absgx + absgy;
2970			if (absgsum > 0.001)
2971			{
2972				var k = 1.0;
2973				if (absgx > absgy) { var yx = absgy/absgx; k = Math.sqrt(1 + yx*yx); }
2974				else               { var xy = absgx/absgy; k = Math.sqrt(1 + xy*xy); }
2975				var g = (absgx + absgy)/k;
2976				var colorIndex = Math.floor(g*colorFactor);
2977				if (colorIndex > 15) colorIndex = 15;
2978				if (addNumbers && ((x+30) % 2 == 1) && ((y+30) % 2 == 1))
2979				{
2980					var lat4 = lat3 - (gx*dLatX + gy*dLatY);
2981					var lon4 = lon3 - (gx*dLonX + gy*dLonY);
2982					var lat5 = lat3 + (gx*dLatX + gy*dLatY);
2983					var lon5 = lon3 + (gx*dLonX + gy*dLonY);
2984					var theta = 180.0 - geogBearing(lat4, lon4, lat5, lon5);
2985					var k2 = (3.0 + sizeIndex)/100.0;
2986					var text = Math.round(g*100).toString();
2987					addText(text, lat3, lon3, theta, k2, k2, gridColor[colorIndex], hg.h, gx, bearingX, gy, bearingY);
2988				}
2989				else
2990				{
2991					var k2 = 2.0*lengthFactor*Math.sqrt(10*g)/(10*g);
2992					var g1 = k2*(gx/k);
2993					var g2 = k2*(gy/k);
2994					var lat4 = lat3 - (g1*dLatX + g2*dLatY);
2995					var lon4 = lon3 - (g1*dLonX + g2*dLonY);
2996					var lat5 = lat3 + (g1*dLatX + g2*dLatY);
2997					var lon5 = lon3 + (g1*dLonX + g2*dLonY);
2998					var d45 = geogDist(lat4, lon4, lat5, lon5);
2999					var b45 = geogBearing(lat4, lon4, lat5, lon5);
3000					var lat6 = moveLat(lat4, lon4, 0.2*d45, b45-45.0);
3001					var lon6 = moveLon(lat4, lon4, 0.2*d45, b45-45.0);
3002					var lat7 = moveLat(lat4, lon4, 0.2*d45, b45+45.0);
3003					var lon7 = moveLon(lat4, lon4, 0.2*d45, b45+45.0);
3004					var h4 = getProVisualizerTerrainHeight(lat4, lon4);
3005					var h5 = getProVisualizerTerrainHeight(lat5, lon5);
3006					var h6 = getProVisualizerTerrainHeight(lat6, lon6);
3007					var h7 = getProVisualizerTerrainHeight(lat7, lon7);
3008					var coords = [];
3009					coords.push(Cesium.Cartesian3.fromDegrees(lon5, lat5, h5));
3010					coords.push(Cesium.Cartesian3.fromDegrees(lon4, lat4, h4));
3011					coords.push(Cesium.Cartesian3.fromDegrees(lon6, lat6, h6));
3012					coords.push(Cesium.Cartesian3.fromDegrees(lon7, lat7, h7));
3013					coords.push(Cesium.Cartesian3.fromDegrees(lon4, lat4, h4));
3014					var line = viewer.entities.add({ polyline : { positions: coords, width: 1.0, material: gridColor[colorIndex] } });
3015					mapLines2.push(line);
3016				}
3017			}
3018		}
3019	}
3020	greenSlopesCount += 1;
3021	greenSlopesBearing = bearingY;
3022}
3023
3024function addGradient(lat, lon)
3025{
3026	setMode(0);
3027	var courseID = currentCourseID();
3028	var hole = currentHole();
3029	if (hole < 1) { return; }
3030	var lat2 = getCourseHolePinLat(courseID, hole);
3031	var lon2 = getCourseHolePinLon(courseID, hole);
3032	if (lat2 == 0 && lon2 == 0) { return; }
3033	var par = getCourseHolePar(courseID, hole);
3034	if (par < 3) { return; }
3035	var lat1 = 0;
3036	var lon1 = 0;
3037	if (par == 3)
3038	{
3039		lat1 = getCourseHoleTeeLat(courseID, hole);
3040		lon1 = getCourseHoleTeeLon(courseID, hole);
3041	}
3042	else if (par == 4 || (par == 5 && greenGridCount > 3))
3043	{
3044		lat1 = getCourseHoleTargetLat(courseID, hole, 1);
3045		lon1 = getCourseHoleTargetLon(courseID, hole, 1);
3046	}
3047	else if (par == 5 || (par == 6 && greenGridCount > 3))
3048	{
3049		lat1 = getCourseHoleTargetLat(courseID, hole, 2);
3050		lon1 = getCourseHoleTargetLon(courseID, hole, 2);
3051	}
3052	else if (par == 6)
3053	{
3054		lat1 = getCourseHoleTargetLat(courseID, hole, 3);
3055		lon1 = getCourseHoleTargetLon(courseID, hole, 3);
3056	}
3057	var d = geogDist(lat1, lon1, lat2, lon2);
3058	if (d < 1) { return; }
3059	var bearingY = geogBearing(lat1, lon1, lat2, lon2);
3060	var bearingX = bearingY + 90;
3061	var hg = getProVisualizerTerrainHeightAndGradient(lat, lon, 0.5, bearingX, bearingY);
3062	var gx = hg.g1;
3063	var gy = hg.g2;
3064	var absgx = Math.abs(gx);
3065	var absgy = Math.abs(gy);
3066	var absgsum = absgx + absgy;
3067	if (absgsum > 0.001)
3068	{
3069		var k = 1.0;
3070		if (absgx > absgy) { var yx = absgy/absgx; k = Math.sqrt(1 + yx*yx); }
3071		else               { var xy = absgx/absgy; k = Math.sqrt(1 + xy*xy); }
3072		var g = (absgx + absgy)/k;
3073		var dLatY = moveLat(lat, lon, myUnitsToMetres(1), bearingY) - lat;
3074		var dLonY = moveLon(lat, lon, myUnitsToMetres(1), bearingY) - lon;
3075		var dLatX = moveLat(lat, lon, myUnitsToMetres(1), bearingX) - lat;
3076		var dLonX = moveLon(lat, lon, myUnitsToMetres(1), bearingX) - lon;
3077		var lat4 = lat - (gx*dLatX + gy*dLatY);
3078		var lon4 = lon - (gx*dLonX + gy*dLonY);
3079		var lat5 = lat + (gx*dLatX + gy*dLatY);
3080		var lon5 = lon + (gx*dLonX + gy*dLonY);
3081		var text = Math.round(g*100).toString();
3082		var theta = 180.0 - geogBearing(lat4, lon4, lat5, lon5);
3083		var hgrad1 = gx;
3084		var htheta1 = bearingX;
3085		var hgrad2 = gy;
3086		var htheta2 = bearingY;
3087		var gridBrightness = 0.07 * getCircleBrightness();
3088		var gridColor = [];
3089		gridColor[0] = new Cesium.Color(1.00, 1.00, 1.00, gridBrightness);//White
3090		gridColor[1] = new Cesium.Color(1.00, 1.00, 0.80, gridBrightness);
3091		gridColor[2] = new Cesium.Color(1.00, 1.00, 0.60, gridBrightness);
3092		gridColor[3] = new Cesium.Color(1.00, 1.00, 0.40, gridBrightness);
3093		gridColor[4] = new Cesium.Color(1.00, 1.00, 0.20, gridBrightness);
3094		gridColor[5] = new Cesium.Color(1.00, 1.00, 0.00, gridBrightness);//Yellow
3095		gridColor[6] = new Cesium.Color(0.95, 0.80, 0.00, gridBrightness);
3096		gridColor[7] = new Cesium.Color(0.90, 0.60, 0.00, gridBrightness);
3097		gridColor[8] = new Cesium.Color(0.85, 0.40, 0.00, gridBrightness);
3098		gridColor[9] = new Cesium.Color(0.80, 0.20, 0.00, gridBrightness);
3099		gridColor[10] = new Cesium.Color(0.75, 0.00, 0.00, gridBrightness);//Red
3100		gridColor[11] = new Cesium.Color(0.60, 0.00, 0.00, gridBrightness);
3101		gridColor[12] = new Cesium.Color(0.45, 0.00, 0.00, gridBrightness);
3102		gridColor[13] = new Cesium.Color(0.30, 0.00, 0.00, gridBrightness);
3103		gridColor[14] = new Cesium.Color(0.15, 0.00, 0.00, gridBrightness);
3104		gridColor[15] = new Cesium.Color(0.00, 0.00, 0.00, gridBrightness);//Black
3105		var colorFactor = 1000.0 / getRedSlope();
3106		var colorIndex = Math.floor(g*colorFactor);
3107		if (colorIndex > 15) colorIndex = 15;
3108		var sizeIndex = document.getElementById("greenslopesizeselect").selectedIndex;
3109		if (sizeIndex >= 4) { sizeIndex -= 4; }
3110		var k2 = (3.0 + sizeIndex)/100.0;
3111		addText(text, lat, lon, theta, k2, k2, gridColor[colorIndex], hg.h, hgrad1, htheta1, hgrad2, htheta2);
3112	}
3113}
3114
3115function deltaPuttSpeed(speed, gradientF, gradientL, rollingGradient, dt)
3116{
3117	var maxSpeed = 30;
3118	if (maxSpeed < speed) maxSpeed = speed;
3119	var rollingGradientAtSpeed = rollingGradient/(1.0 - 0.99*speed/maxSpeed) + rollingGradient*0.1*speed;
3120	var dgF = gradientF - rollingGradientAtSpeed;
3121	var deltaSpeedF = -4.0*dt*dgF - 1.0*speed*dt*dgF - 2.0*speed*dt*gradientF;
3122	var deltaSpeedL = -4.0*dt*gradientL*(speed/(speed+0.02))*(1.0 + 0.3*speed);
3123	var vF = speed + deltaSpeedF;
3124	if (vF < 0.0001) return -speed;
3125	var vL = deltaSpeedL;
3126	var vv = vF*vF + vL*vL;
3127	var newSpeed = (vv < 0.0001 ? 0.0 : Math.sqrt(vv));
3128	return newSpeed - speed;
3129}
3130
3131function deltaPuttBearing(speed, gradientF, gradientL, rollingGradient, dt)
3132{
3133	var maxSpeed = 30;
3134	if (maxSpeed < speed) maxSpeed = speed;
3135	var rollingGradientAtSpeed = rollingGradient/(1.0 - 0.99*speed/maxSpeed) + rollingGradient*0.1*speed;
3136	var dgF = gradientF - rollingGradientAtSpeed;
3137	var deltaSpeedF = -4.0*dt*dgF - 1.0*speed*dt*dgF - 2.0*speed*dt*gradientF;
3138	var deltaSpeedL = -4.0*dt*gradientL*(speed/(speed+0.02))*(1.0 + 0.3*speed);
3139	var vF = speed + deltaSpeedF;
3140	if (vF < 0.0001) return 0.0;
3141	var vL = deltaSpeedL;
3142	var db = Math.atan2(-vL, vF)*180.0/Math.PI;
3143	return db;
3144}
3145
3146var puttTargetLat = 0;
3147var puttTargetLon = 0;
3148var puttHistoryOp = [];
3149var puttHistoryLat = [];
3150var puttHistoryLon = [];
3151
3152function clearPuttHistory()
3153{
3154	puttTargetLat = 0;
3155	puttTargetLon = 0;
3156	puttHistoryOp = [];
3157	puttHistoryLat = [];
3158	puttHistoryLon = [];
3159}
3160
3161function appendPuttHistory(op, lat, lon)
3162{
3163	var n = puttHistoryOp.length;
3164	puttHistoryOp[n] = op;
3165	puttHistoryLat[n] = lat;
3166	puttHistoryLon[n] = lon;
3167}
3168
3169function removePuttHistory(opToRemove, lat, lon, d)
3170{
3171	var numRemoved = 0;
3172	var n = puttHistoryOp.length;
3173	var i = 0;
3174	while (i < n)
3175	{
3176		if (puttHistoryOp[i] == opToRemove && geogDist(lat, lon, puttHistoryLat[i], puttHistoryLon[i]) < d)
3177		{
3178			var k = 1;
3179			if (opToRemove == 1 ) { while (i+k < n && puttHistoryOp[i+k] == 2) ++k; }
3180			for (var j = i+k; j < n; ++j)
3181			{
3182				puttHistoryOp[j-k] = puttHistoryOp[j];
3183				puttHistoryLat[j-k] = puttHistoryLat[j];
3184				puttHistoryLon[j-k] = puttHistoryLon[j];
3185			} 
3186			numRemoved += k;
3187			n -= k;
3188		}
3189		else
3190		{
3191			i += 1;
3192		}
3193	}
3194	puttHistoryOp.length -= numRemoved;
3195	puttHistoryLat.length -= numRemoved;
3196	puttHistoryLon.length -= numRemoved;
3197	return numRemoved;
3198}
3199
3200function replayPuttHistory()
3201{
3202	var copyOp = [];
3203	var copyLat = [];
3204	var copyLon = [];
3205	var n = puttHistoryOp.length;
3206	for (var i = 0; i < n; ++i)
3207	{
3208		copyOp[i] = puttHistoryOp[i];
3209		copyLat[i] = puttHistoryLat[i];
3210		copyLon[i] = puttHistoryLon[i];
3211	} 
3212	clearPuttHistory();
3213	var n = copyOp.length;
3214	for (var i = 0; i < n; ++i)
3215	{
3216		if (copyOp[i] == 1) setPuttTarget(copyLat[i], copyLon[i]);
3217		else if (copyOp[i] == 2) addPuttLine(copyLat[i], copyLon[i]);
3218	}
3219}
3220
3221function setPuttTarget(lat, lon)
3222{
3223	var snapLat = 0;
3224	var snapLon = 0;
3225	var snapDist = 0.5;
3226	var snapIndex = -1;
3227	var n = puttHistoryOp.length;
3228	for (var i = 0; i < n; ++i)
3229	{
3230		if (puttHistoryOp[i] == 1)
3231		{
3232			var d = geogDist(lat, lon, puttHistoryLat[i], puttHistoryLon[i]);
3233			if (d < snapDist)
3234			{
3235				snapLat = puttHistoryLat[i];
3236				snapLon = puttHistoryLon[i];
3237				snapDist = d;
3238				snapIndex = i;
3239			}
3240		}
3241	}
3242	if (snapIndex >= 0)
3243	{
3244		lat = snapLat;
3245		lon = snapLon;
3246	}
3247	appendPuttHistory(1, lat, lon);
3248	puttTargetLat = lat;
3249	puttTargetLon = lon;
3250	var repeatCount = 0;
3251	var j = n - 1;
3252	while (j >= 0 && puttHistoryOp[j] == 1 && puttHistoryLat[j] == puttTargetLat && puttHistoryLon[j] == puttTargetLon)
3253	{
3254		repeatCount += 1;
3255		--j;
3256	}
3257	if (repeatCount == 1)
3258	{
3259		var tenFeet = 10.0 * 1609.344 / (3.0 * 1760.0);
3260		for (var i = 0; i < 8; ++i)
3261		{
3262			var bearing = greenSlopesBearing + i*45;
3263			var lat2 = moveLat(lat, lon, tenFeet, bearing);
3264			var lon2 = moveLon(lat, lon, tenFeet, bearing);
3265			addPuttLine(lat2, lon2, false, true);
3266		}
3267	}
3268	else if (repeatCount == 2)
3269	{
3270		var tenFeet = 10.0 * 1609.344 / (3.0 * 1760.0);
3271		for (var i = 0; i < 8; ++i)
3272		{
3273			var bearing = greenSlopesBearing + i*45 + 22.5;
3274			var lat2 = moveLat(lat, lon, tenFeet, bearing);
3275			var lon2 = moveLon(lat, lon, tenFeet, bearing);
3276			addPuttLine(lat2, lon2, false, true);
3277		}
3278	}
3279	else
3280	{
3281		addHoleMarker(lat, lon);
3282	}
3283}
3284
3285function getRollingGradient()
3286{
3287	var greenSpeed = 8 + document.getElementById("greenspeedselect").selectedIndex;
3288	var rollingGradient = -0.0707;
3289	switch (greenSpeed)
3290	{
3291	case 8:  rollingGradient = -0.1162; break;
3292	case 9:  rollingGradient = -0.1031; break;
3293	case 10: rollingGradient = -0.0927; break;
3294	case 11: rollingGradient = -0.0842; break;
3295	case 12: rollingGradient = -0.0771; break;
3296	case 13: rollingGradient = -0.0711; break;
3297	case 14: rollingGradient = -0.0660; break;
3298	case 15: rollingGradient = -0.0616; break;
3299	default: alert("Unexpected green speed.");
3300	}
3301	return rollingGradient;
3302}
3303
3304function getRedSlope()
3305{
3306	var i = document.getElementById("greenslopecolorselect").selectedIndex;
3307	if (i > 0) return 4+i;
3308	var rollingGradient = getRollingGradient();
3309	return -100.0*rollingGradient;
3310}
3311
3312function addPuttLine(lat, lon, addToHistory = true, animate = true, addBallMarkers = false)
3313{
3314	var courseID = currentCourseID();
3315	var hole = currentHole();
3316	if (hole < 1) { alert("Please select hole."); return; }
3317	if (puttTargetLat == 0 && puttTargetLon == 0)
3318	{
3319		puttTargetLat = getCourseHolePinLat(courseID, hole);
3320		puttTargetLon = getCourseHolePinLon(courseID, hole);
3321	}
3322	if (puttTargetLat == 0 && puttTargetLon == 0) { alert("Please set pin position."); return; }
3323	var d = geogDist(lat, lon, puttTargetLat, puttTargetLon);
3324	if (d < 0.3) { return; }
3325	if (d > 50) { alert("Max 50 metres."); return; }
3326	if (addToHistory) appendPuttHistory(2, lat, lon);
3327	var rollingGradient = getRollingGradient();
3328	var brightness = 0.07 * getCircleBrightness();
3329	var color1 = new Cesium.Color(1.00, 1.00, 1.00, brightness);
3330	var color2 = new Cesium.Color(0.00, 1.00, 1.00, brightness*0.5);
3331	var color3 = new Cesium.Color(0.00, 1.00, 1.00, brightness);
3332	var dt = 0.05;
3333	var iMax = 10;
3334	var puttingSpeed = document.getElementById("puttingspeedselect").value;
3335	var attempt = 0;
3336	var maxAttempts = (puttingSpeed <= 6 ? 10 : 3);
3337	var finished = false;
3338	while (!finished && attempt < maxAttempts)
3339	{
3340	attempt += 1;
3341	var bearing0 = geogBearing(lat, lon, puttTargetLat, puttTargetLon);
3342	var distMax = d + 10.0;
3343	var tol = d * 10.0 * Math.PI/180.0; // 10 degrees
3344	var bearingDelta = 2.0; // 2 degrees
3345	for (var i = 0; i <= iMax; ++i)
3346	{
3347		tol *= 0.5;
3348		bearingDelta *= 0.5;
3349		finished = false;
3350		var n = 0;
3351		while (n < 360 && !finished)
3352		{
3353			var lat2 = puttTargetLat;
3354			var lon2 = puttTargetLon;
3355			var dist = 0.0;
3356			var bearingF = bearing0;
3357			if (n % 2 == 0) bearingF += bearingDelta*n/2;
3358			else bearingF -= bearingDelta*(n+1)/2;
3359			var bearing1 = bearingF;
3360			var bearingL = bearingF - 90;
3361			var hg = getProVisualizerTerrainHeightAndGradient(lat2, lon2, 0.5, bearingF, bearingL);
3362			var h2 = hg.h;
3363			var speed = 0.05;
3364			if (puttingSpeed < 1)
3365			{
3366				speed = 0.25+0.05*attempt;
3367				var minRolloutDist = 0.30;
3368				var actualRolloutDist = puttRollDist(puttTargetLat, puttTargetLon, speed, bearing1, rollingGradient);
3369				if (actualRolloutDist < minRolloutDist)
3370				{
3371					var n2 = 0;
3372					while (n2 < 10 && Math.abs(actualRolloutDist - minRolloutDist) >= 0.005)
3373					{
3374						n2 += 1;
3375						if (actualRolloutDist < 0.01) speed += 0.1;
3376						else speed *= Math.sqrt(minRolloutDist / actualRolloutDist);
3377						actualRolloutDist = puttRollDist(puttTargetLat, puttTargetLon, speed, bearing1, rollingGradient);
3378					}
3379					if (speed > 0.60) speed = 0.60;
3380					if (speed < 0.30) speed = 0.30;
3381				}
3382				if (attempt > 1) speed += 0.0005*(attempt-1);
3383			}
3384			else if (puttingSpeed <= 6)
3385			{
3386				speed = 0.1*(puttingSpeed-0);
3387				if (attempt > 1) speed += 0.0005*(attempt-1);
3388			}
3389			else
3390			{
3391				var desiredRolloutDist = 0.1*(puttingSpeed-6);
3392				var actualRolloutDist = puttRollDist(puttTargetLat, puttTargetLon, speed, bearing1, rollingGradient);
3393				var n2 = 0;
3394				while (n2 < 10 && Math.abs(actualRolloutDist - desiredRolloutDist) >= 0.005)
3395				{
3396					n2 += 1;
3397					if (actualRolloutDist < 0.01) speed += 0.1;
3398					else speed *= Math.sqrt(desiredRolloutDist / actualRolloutDist);
3399					actualRolloutDist = puttRollDist(puttTargetLat, puttTargetLon, speed, bearing1, rollingGradient);
3400				}
3401				if (speed > 1.00) speed = 1.00;
3402				if (speed < 0.01) speed = 0.01;
3403				if (attempt > 1) speed += 0.001*(attempt-1);
3404			}
3405			var speedAtHole = speed;
3406			var speedAtHoleEntry = speed;
3407			var speedAtPutter = speed;
3408			var speed0 = speed;
3409			var timeAtPutter = 0.0;
3410			var holeRadius = 0.054;
3411			var coords1 = [];
3412			var lats = [];
3413			var lons = [];
3414			var hs = [];
3415			var dt0 = dt;
3416			if (i == iMax) { coords1.push(Cesium.Cartesian3.fromDegrees(lon2, lat2, h2)); lats.push(lat2); lons.push(lon2); hs.push(h2); }
3417			while (dist < distMax && speed > 0.0001 && !finished)
3418			{
3419				var ds = speed*dt;
3420				var lat3 = moveLat(lat2, lon2, ds, bearingF + 180.0);
3421				var lon3 = moveLon(lat2, lon2, ds, bearingF + 180.0);
3422				var hg = getProVisualizerTerrainHeightAndGradient(lat3, lon3, 0.5, bearingF, bearingL);
3423				var gradientF = hg.g1;
3424				var gradientL = hg.g2;
3425				var deltaSpeed   = deltaPuttSpeed  (speed, gradientF, gradientL, rollingGradient, dt);
3426				var deltaBearing = deltaPuttBearing(speed, gradientF, gradientL, rollingGradient, dt);
3427				var d2 = geogDist(lat, lon, lat2, lon2);
3428				var d3 = geogDist(lat, lon, lat3, lon3);
3429				timeAtPutter += dt;
3430				if (d2 + d3 < ds + tol)
3431				{
3432					finished = true;
3433					var t = d2/ds;
3434					if (i == iMax)
3435					{
3436						var dLat = lat3 - lat2;
3437						var dLon = lon3 - lon2;
3438						lat3 = lat2 + t*dLat;
3439						lon3 = lon2 + t*dLon;
3440					}
3441					speed0 = speed;
3442					speedAtPutter = speed - t*deltaSpeed;
3443					timeAtPutter -= (1.0 - t)*dt;
3444					dt0 = t*dt;
3445				}
3446				if (dist < holeRadius)
3447				{
3448					var t = (holeRadius - dist)/ds;
3449					speedAtHoleEntry = speed - t*deltaSpeed;
3450					//deltaBearing *= 0.75;
3451				}
3452				lat2 = lat3;
3453				lon2 = lon3;
3454				dist += ds;
3455				speed -= deltaSpeed;
3456				bearingF -= deltaBearing;
3457				bearingL = bearingF - 90;
3458				h2 = hg.h;
3459				if (i == iMax) { coords1.push(Cesium.Cartesian3.fromDegrees(lon2, lat2, h2)); lats.push(lat2); lons.push(lon2); hs.push(h2); }
3460			}
3461			if (finished)
3462			{
3463				if (i < iMax)
3464				{
3465					bearing0 = bearing1;
3466				}
3467				else
3468				{
3469					var lat0 = lat2;
3470					var lon0 = lon2;
3471					if (animate)
3472					{
3473						var simStartTime = Date.now() + 1000;
3474						var line = viewer.entities.add({
3475							polyline : { 	positions: new Cesium.CallbackProperty(function(time, result) {
3476										var elapsed = Date.now() - simStartTime;
3477										var maxIndexToShow = Math.round(elapsed / (1000*dt));
3478										if (maxIndexToShow < 1) return [];
3479										if (maxIndexToShow >= coords1.length - 1) return coords1;
3480										return coords1.slice(coords1.length - 1 - maxIndexToShow, coords1.length);
3481									}, false),
3482									width: 1.0,
3483									material: color1 } });
3484						fastRender(1.0 + dt*lats.length);
3485					}
3486					else
3487					{
3488						var line = viewer.entities.add({ polyline : { positions: coords1, width: 1.0, material: color1 } });
3489						mapLines2.push(line);
3490					}
3491					if (addBallMarkers)
3492					{
3493						for (var j = 1; j <= lats.length; j += 20)
3494							addBallMarker(lats[lats.length - j], lons[lons.length - j]);
3495					}
3496					var coords2 = [];
3497					coords2.push(Cesium.Cartesian3.fromDegrees(lon2, lat2, h2));
3498					if (speed < 0.01) speed = 0.01;
3499					var d1 = geogDist(lat2, lon2, puttTargetLat, puttTargetLon);
3500					var d2 = 0.0;
3501					while (speed > 0.001)
3502					{
3503						d2 += speed*dt;
3504						var lat3 = moveLat(lat2, lon2, speed*dt, bearingF);
3505						var lon3 = moveLon(lat2, lon2, speed*dt, bearingF);
3506						var deltaSpeed = deltaPuttSpeed(speed, 0.0, 0.0, rollingGradient, dt);
3507						lat2 = lat3;
3508						lon2 = lon3;
3509						speed += deltaSpeed;
3510						if (d2 <= d1)
3511						{
3512							h2 = getProVisualizerTerrainHeight(lat2, lon2);
3513							coords2.push(Cesium.Cartesian3.fromDegrees(lon2, lat2, h2));
3514						}
3515					}
3516					if (d2 <= d1)
3517					{
3518						var lat3 = moveLat(lat2, lon2, 0.05, bearingF+90.0);
3519						var lon3 = moveLon(lat2, lon2, 0.05, bearingF+90.0);
3520						var h3 = getProVisualizerTerrainHeight(lat3, lon3);
3521						coords2.push(Cesium.Cartesian3.fromDegrees(lon3, lat3, h3));
3522						var lat3 = moveLat(lat2, lon2, 0.05, bearingF-90.0);
3523						var lon3 = moveLon(lat2, lon2, 0.05, bearingF-90.0);
3524						var h3 = getProVisualizerTerrainHeight(lat3, lon3);
3525						coords2.push(Cesium.Cartesian3.fromDegrees(lon3, lat3, h3));
3526						coords2.push(Cesium.Cartesian3.fromDegrees(lon2, lat2, h2));
3527					}
3528					var line = viewer.entities.add({ polyline : { positions: coords2, width: 1.0, material: color2 } });
3529					mapLines2.push(line);
3530					var k = 0.02;
3531					var theta = (bearingF - geogBearing(lat0, lon0, puttTargetLat, puttTargetLon))*Math.PI/180.0;
3532					while (theta < -Math.PI) theta += 2*Math.PI;
3533					while (theta >  Math.PI) theta -= 2*Math.PI;
3534					var d3 = Math.abs(d1*theta);
3535					var text1 = Math.round(100*d1).toString();
3536					var text2 = Math.round(100*d2).toString();
3537					var text3 = Math.round(100*d3).toString();
3538					if (getDistanceUnit() == "y")
3539					{
3540						var inches = Math.round(metresToYards(d1)*36); var feet = Math.floor(inches/12); inches -= 12*feet;
3541						text1 = feet.toString() + "'" + inches.toString() + '"';
3542						var inches = Math.round(metresToYards(d2)*36); var feet = Math.floor(inches/12); inches -= 12*feet;
3543						text2 = feet.toString() + "'" + inches.toString() + '"';
3544						var inches = Math.round(metresToYards(d3)*36); var feet = Math.floor(inches/12); inches -= 12*feet;
3545						text3 = feet.toString() + "'" + inches.toString() + '"';
3546					}
3547					var textLat = moveLat(lat0, lon0, -0.2, bearingF);
3548					var textLon = moveLon(lat0, lon0, -0.2, bearingF);
3549					var hg = getProVisualizerTerrainHeightAndGradient(textLat, textLon, 0.5, bearingF+90, bearingF);
3550					addText(text1, textLat, textLon, -bearingF, k, k, color1, hg.h, hg.g1, bearingF+90, hg.g2, bearingF);
3551					var textLat = moveLat(lat0, lon0, -0.4, bearingF);
3552					var textLon = moveLon(lat0, lon0, -0.4, bearingF);
3553					var hg = getProVisualizerTerrainHeightAndGradient(textLat, textLon, 0.5, bearingF+90, bearingF);
3554					addText(text2, textLat, textLon, -bearingF, k, k, color3, hg.h, hg.g1, bearingF+90, hg.g2, bearingF);
3555					var textLat = moveLat(lat0, lon0, -0.6, bearingF);
3556					var textLon = moveLon(lat0, lon0, -0.6, bearingF);
3557					var hg = getProVisualizerTerrainHeightAndGradient(textLat, textLon, 0.5, bearingF+90, bearingF);
3558					addText(text3, textLat, textLon, -bearingF, k, k, color3, hg.h, hg.g1, bearingF+90, hg.g2, bearingF);
3559					var text4 = Math.round(100*speedAtHole).toString() + "cm/s";
3560					var textLat = moveLat(lat0, lon0, -0.8, bearingF);
3561					var textLon = moveLon(lat0, lon0, -0.8, bearingF);
3562					var hg = getProVisualizerTerrainHeightAndGradient(textLat, textLon, 0.5, bearingF+90, bearingF);
3563					addText(text4, textLat, textLon, -bearingF, 0.8*k, 0.8*k, color3, hg.h, hg.g1, bearingF+90, hg.g2, bearingF);
3564					var distPastHole = puttRollDist(puttTargetLat, puttTargetLon, speedAtHole, bearing1, rollingGradient);
3565					var text5 = "+" + (distPastHole < 9.99 ? Math.round(100*distPastHole).toString() + "cm" : "999cm");
3566					var textLat = moveLat(lat0, lon0, -1.0, bearingF);
3567					var textLon = moveLon(lat0, lon0, -1.0, bearingF);
3568					var hg = getProVisualizerTerrainHeightAndGradient(textLat, textLon, 0.5, bearingF+90, bearingF);
3569					addText(text5, textLat, textLon, -bearingF, 0.8*k, 0.8*k, color3, hg.h, hg.g1, bearingF+90, hg.g2, bearingF);
3570					var s = document.getElementById('saveasinput').value;
3571					if (showPuttDispersion > 0)
3572					{
3573						var percentHoled = addPuttDispersion(puttTargetLat, puttTargetLon, lat0, lon0, speed0, bearingF, rollingGradient, dt0, dt, (addToHistory ? viewer : null));
3574						var text6 = Math.floor(percentHoled).toString()+"%";
3575						var textLat = moveLat(lat0, lon0, -1.2, bearingF);
3576						var textLon = moveLon(lat0, lon0, -1.2, bearingF);
3577						var hg = getProVisualizerTerrainHeightAndGradient(textLat, textLon, 0.5, bearingF+90, bearingF);
3578						addText(text6, textLat, textLon, -bearingF, 0.8*k, 0.8*k, color3, hg.h, hg.g1, bearingF+90, hg.g2, bearingF);
3579					}
3580					else if (s != null && s.indexOf('!!puttstats') == 0)
3581					{
3582						var text6 = Math.round(100*speedAtHoleEntry).toString() + "cm/s";
3583						var textLat = moveLat(lat0, lon0, -1.2, bearingF);
3584						var textLon = moveLon(lat0, lon0, -1.2, bearingF);
3585						var hg = getProVisualizerTerrainHeightAndGradient(textLat, textLon, 0.5, bearingF+90, bearingF);
3586						addText(text6, textLat, textLon, -bearingF, 0.8*k, 0.8*k, color3, hg.h, hg.g1, bearingF+90, hg.g2, bearingF);
3587						var text7 = Math.round(100*speedAtPutter).toString() + "cm/s";
3588						var textLat = moveLat(lat0, lon0, -1.4, bearingF);
3589						var textLon = moveLon(lat0, lon0, -1.4, bearingF);
3590						var hg = getProVisualizerTerrainHeightAndGradient(textLat, textLon, 0.5, bearingF+90, bearingF);
3591						addText(text7, textLat, textLon, -bearingF, 0.8*k, 0.8*k, color3, hg.h, hg.g1, bearingF+90, hg.g2, bearingF);
3592						var seconds2dp = (Math.floor(100*timeAtPutter)/100).toString();
3593						if (seconds2dp.indexOf(".") < 0) seconds2dp += ".";
3594						while (seconds2dp.indexOf(".") > seconds2dp.length - 3) seconds2dp += "0";
3595						var text8 = seconds2dp.toString()+"s";
3596						var textLat = moveLat(lat0, lon0, -1.6, bearingF);
3597						var textLon = moveLon(lat0, lon0, -1.6, bearingF);
3598						var hg = getProVisualizerTerrainHeightAndGradient(textLat, textLon, 0.5, bearingF+90, bearingF);
3599						addText(text8, textLat, textLon, -bearingF, 0.8*k, 0.8*k, color3, hg.h, hg.g1, bearingF+90, hg.g2, bearingF);
3600					}
3601				}
3602			}
3603			n += 1;
3604		}
3605	}
3606	}
3607	if (!finished)
3608	{
3609		var color = new Cesium.Color(1.00, 1.00, 1.00, brightness);
3610		var bearing = geogBearing(lat, lon, puttTargetLat, puttTargetLon);
3611		var lat1 = moveLat(lat, lon, 0.15, bearing+ 45); var lon1 = moveLon(lat, lon, 0.15, bearing+ 45); var h1 = getProVisualizerTerrainHeight(lat1, lon1);
3612		var lat2 = moveLat(lat, lon, 0.15, bearing- 45); var lon2 = moveLon(lat, lon, 0.15, bearing- 45); var h2 = getProVisualizerTerrainHeight(lat2, lon2);
3613		var lat3 = moveLat(lat, lon, 0.15, bearing+135); var lon3 = moveLon(lat, lon, 0.15, bearing+135); var h3 = getProVisualizerTerrainHeight(lat3, lon3);
3614		var lat4 = moveLat(lat, lon, 0.15, bearing-135); var lon4 = moveLon(lat, lon, 0.15, bearing-135); var h4 = getProVisualizerTerrainHeight(lat4, lon4);
3615		var coords1 = [];
3616		coords1.push(Cesium.Cartesian3.fromDegrees(lon1, lat1, h1));
3617		coords1.push(Cesium.Cartesian3.fromDegrees(lon4, lat4, h4));
3618		var line1 = viewer.entities.add({ polyline : { positions: coords1, width: 1.0, material: color } });
3619		mapLines2.push(line1);
3620		var coords2 = [];
3621		coords2.push(Cesium.Cartesian3.fromDegrees(lon2, lat2, h2));
3622		coords2.push(Cesium.Cartesian3.fromDegrees(lon3, lat3, h3));
3623		var line2 = viewer.entities.add({ polyline : { positions: coords2, width: 1.0, material: color } });
3624		mapLines2.push(line2);
3625	}
3626}
3627
3628function puttRollDist(lat, lon, speed, bearing, rollingGradient, flat = false)
3629{
3630	var d = 0.0;
3631	var dt = 0.05;
3632	var lat2 = lat;
3633	var lon2 = lon;
3634	var speed2 = speed;
3635	var bearing2 = bearing;
3636	while (speed2 > 0.0001)
3637	{
3638		var ds = speed2*dt;
3639		d += ds;
3640		var hg2 = getProVisualizerTerrainHeightAndGradient(lat2, lon2, 0.5, bearing2, bearing2-90);
3641		if (flat) { hg2.g1 = 0; hg2.g2 = 0; }
3642		var deltaSpeed   = deltaPuttSpeed  (speed2, hg2.g1, hg2.g2, rollingGradient, dt);
3643		var deltaBearing = deltaPuttBearing(speed2, hg2.g1, hg2.g2, rollingGradient, dt);
3644		var lat3 = moveLat(lat2, lon2, ds, bearing2);
3645		var lon3 = moveLon(lat2, lon2, ds, bearing2);
3646		lat2 = lat3;
3647		lon2 = lon3;
3648		speed2 += deltaSpeed;
3649		bearing2 += deltaBearing;
3650	}
3651	return d;
3652}
3653
3654function addPuttDispersion(targetLat, targetLon, startLat, startLon, startSpeed, startBearing, rollingGradient, dt0, dt1, viewer)
3655{
3656	var targetDist = PV.geog.dist(startLat, startLon, targetLat, targetLon);
3657	if (targetDist < 0.1) return 100;
3658	var targetBearing = PV.geog.bearing(startLat, startLon, targetLat, targetLon);
3659	while (targetBearing < startBearing - 180) targetBearingF += 360;
3660	while (targetBearing > startBearing + 180) targetBearingF -= 360;
3661	var maxSpeedErrorFraction = 0.04*(targetDist+0.5)/targetDist;
3662	var maxBearingError = 1 + 0.2*Math.abs(targetBearing - startBearing);
3663	var playsLikeDist = PV.sim.puttRollDistFlat(startSpeed, rollingGradient);
3664	var distRatio = targetDist / playsLikeDist;
3665	if (distRatio > 1)
3666	{
3667		if (distRatio > 10) distRatio = 10;
3668		maxSpeedErrorFraction *= distRatio;
3669		maxBearingError *= distRatio;
3670	}
3671	if (maxSpeedErrorFraction > 0.5) maxSpeedErrorFraction = 0.5;
3672	if (maxBearingError > 10) maxBearingError = 10;
3673	var rollingGradientAtStimp12 = PV.sim.getRollingGradientAtStimp(12);
3674	var bearingErrorFactor = Math.abs(rollingGradientAtStimp12/rollingGradient);
3675	if (bearingErrorFactor > 1.0) maxBearingError *= (0.5 + 0.5*bearingErrorFactor*bearingErrorFactor);
3676	var dispersionProfileID = getDispersionProfileID();
3677	if (dispersionProfileID <= 10)
3678	{
3679		var k = 1.0 + 0.1*dispersionProfileID;
3680		maxSpeedErrorFraction *= k;
3681		maxBearingError *= k;
3682	}
3683	var holeRadius = PV.sim.holeRadius();
3684	var holeRadiusDLat = PV.geog.distToDeltaLat(holeRadius, targetLat, targetLon);
3685	var holeRadiusDLon = PV.geog.distToDeltaLon(holeRadius, targetLat, targetLon);
3686	var maxHoleSpeed = PV.sim.maxHoleSpeed();
3687	var opacity = 0.05*targetDist; if (opacity > 0.2) opacity = 0.2;
3688	var missColor = new Cesium.Color(1.00, 1.00, 1.00, opacity);
3689	var holeColor = new Cesium.Color(0.50, 1.00, 0.50, 2*opacity);
3690	var holedPercent = 0;
3691	for (var i = 1; i <= showPuttDispersion; ++i)
3692	{
3693		var numHoled = 0;
3694		var numPutts = 0;
3695	for (var speedErrorIndex = -20; speedErrorIndex <= 20; ++speedErrorIndex)
3696	{
3697	for (var bearingErrorIndex = -20; bearingErrorIndex <= 20; ++bearingErrorIndex)
3698	{
3699	if (i == 2 || speedErrorIndex*speedErrorIndex + bearingErrorIndex*bearingErrorIndex < 400)
3700	{
3701		numPutts += 1;
3702		var coords = [];
3703		var lat = startLat;
3704		var lon = startLon;
3705		var speedError = startSpeed*maxSpeedErrorFraction*speedErrorIndex*(Math.abs(speedErrorIndex)+20)/800;
3706		var bearingError = maxBearingError*bearingErrorIndex/20;
3707		if (i == 2)
3708		{
3709			speedError = startSpeed*maxSpeedErrorFraction*speedErrorIndex*Math.abs(speedErrorIndex)/400;
3710			bearingError = maxBearingError*bearingErrorIndex*Math.abs(bearingErrorIndex)/400;
3711		}
3712		var speed = startSpeed + speedError;
3713		var bearing = startBearing + bearingError;
3714		var holed = false;
3715		var dt = dt0;
3716		var d = 0.0;
3717		var dMax = targetDist + 10.0;
3718		while (speed > 0.0001 && d < dMax)
3719		{
3720			var ds = speed*dt;
3721			d += ds;
3722			var lat2 = moveLat(lat, lon, ds, bearing);
3723			var lon2 = moveLon(lat, lon, ds, bearing);
3724			if (!holed && speed < maxHoleSpeed)
3725			{
3726				var effectiveHoleRadius = PV.sim.effectiveHoleRadius(speed);
3727				var k = effectiveHoleRadius / holeRadius;
3728				var holeMinLat = targetLat - k*holeRadiusDLat;
3729				var holeMinLon = targetLon - k*holeRadiusDLon;
3730				var holeMaxLat = targetLat + k*holeRadiusDLat;
3731				var holeMaxLon = targetLon + k*holeRadiusDLon;
3732				var minLat = lat; var maxLat = lat2; var minLon = lon; var maxLon = lon2;
3733				if (minLat > maxLat) { minLat = lat2; maxLat = lat; }
3734				if (minLon > maxLon) { minLon = lon2; maxLon = lon; }
3735				if (minLat < holeMaxLat && maxLat > holeMinLat && minLon < holeMaxLon && maxLon > holeMinLon)
3736				{
3737					var m = 1 + Math.round(100*ds);
3738					var dLat = lat2 - lat;
3739					var dLon = lon2 - lon;
3740					for (var j = 0; j <= m; ++j)
3741					{
3742						var t = j/m
3743						var lat3 = lat + t*dLat;
3744						var lon3 = lon + t*dLon;
3745						var d3 = PV.geog.dist(targetLat, targetLon, lat3, lon3);
3746						if (d3 < effectiveHoleRadius)
3747						{
3748							holed = true;
3749							break;
3750						}
3751					}
3752				}
3753			}
3754			lat = lat2;
3755			lon = lon2;
3756			dt = dt1;
3757			var hg = getProVisualizerTerrainHeightAndGradient(lat, lon, 0.5, bearing, bearing-90);
3758			var deltaSpeed   = deltaPuttSpeed  (speed, hg.g1, hg.g2, rollingGradient, dt);
3759			var deltaBearing = deltaPuttBearing(speed, hg.g1, hg.g2, rollingGradient, dt);
3760			if (speed + deltaSpeed > 0.001)
3761			{
3762				hg = getProVisualizerTerrainHeightAndGradient(lat, lon, 0.5, bearing+deltaBearing, bearing+deltaBearing-90);
3763				deltaSpeed   = deltaPuttSpeed  (speed+deltaSpeed, hg.g1, hg.g2, rollingGradient, dt);
3764				deltaBearing = deltaPuttBearing(speed+deltaSpeed, hg.g1, hg.g2, rollingGradient, dt);
3765				if (speed + deltaSpeed > 0.001)
3766				{
3767					hg = getProVisualizerTerrainHeightAndGradient(lat, lon, 0.5, bearing+deltaBearing, bearing+deltaBearing-90);
3768					deltaSpeed   = deltaPuttSpeed  (speed+deltaSpeed, hg.g1, hg.g2, rollingGradient, dt);
3769					deltaBearing = deltaPuttBearing(speed+deltaSpeed, hg.g1, hg.g2, rollingGradient, dt);
3770				}
3771			}
3772			speed += deltaSpeed;
3773			bearing += deltaBearing;
3774			coords.push(Cesium.Cartesian3.fromDegrees(lon, lat, hg.h));
3775		}
3776		if (holed) numHoled += 1;
3777		if (viewer != null && d < dMax && i == showPuttDispersion)
3778		{
3779			var coords2 = [];
3780			var j0 = coords.length - 10; if (j0 < 0) j0 = 0;
3781			for (var j = j0; j < coords.length; ++j) coords2.push(coords[j]);
3782			var line = viewer.entities.add({ polyline : { positions: coords2, width: 1.0, material: (holed ? holeColor : missColor) } });
3783			mapLines2.push(line);
3784		}
3785	}
3786	}
3787	}
3788		if (i == 1)
3789			holedPercent =  Math.floor(100*numHoled/numPutts);
3790	}
3791	return holedPercent;
3792}
3793
3794function greenContoursHandler()
3795{
3796	setMode(0);
3797	var courseID = currentCourseID();
3798	var hole = currentHole();
3799	if (hole < 1)
3800	{
3801		alert("Please select hole.");
3802		return;
3803	}
3804	var lat2 = getCourseHolePinLat(courseID, hole);
3805	var lon2 = getCourseHolePinLon(courseID, hole);
3806	if (lat2 == 0 && lon2 == 0)
3807	{
3808		alert("Please set pin position.");
3809		return;
3810	}
3811	var par = getCourseHolePar(courseID, hole);
3812	if (par < 3)
3813	{
3814		alert("Please set tee position.")
3815		return;
3816	}
3817	if (greenContoursCount != 0)
3818	{
3819		refreshView();
3820		return;
3821	}
3822	var lat1 = 0;
3823	var lon1 = 0;
3824	if (par == 3)
3825	{
3826		lat1 = getCourseHoleTeeLat(courseID, hole);
3827		lon1 = getCourseHoleTeeLon(courseID, hole);
3828	}
3829	else if (par == 4 || (par == 5 && greenGridCount > 3))
3830	{
3831		lat1 = getCourseHoleTargetLat(courseID, hole, 1);
3832		lon1 = getCourseHoleTargetLon(courseID, hole, 1);
3833	}
3834	else if (par == 5 || (par == 6 && greenGridCount > 3))
3835	{
3836		lat1 = getCourseHoleTargetLat(courseID, hole, 2);
3837		lon1 = getCourseHoleTargetLon(courseID, hole, 2);
3838	}
3839	else if (par == 6)
3840	{
3841		lat1 = getCourseHoleTargetLat(courseID, hole, 3);
3842		lon1 = getCourseHoleTargetLon(courseID, hole, 3);
3843	}
3844	var d = geogDist(lat1, lon1, lat2, lon2);
3845	if (d < 1)
3846	{
3847		alert("Tee/Targets/Pin too close together.");
3848		return;
3849	}
3850	var bearingY = geogBearing(lat1, lon1, lat2, lon2);
3851	var dLatY = moveLat(lat2, lon2, myUnitsToMetres(1), bearingY) - lat2;
3852	var dLonY = moveLon(lat2, lon2, myUnitsToMetres(1), bearingY) - lon2;
3853	var bearingX = bearingY + 90;
3854	var dLatX = moveLat(lat2, lon2, myUnitsToMetres(1), bearingX) - lat2;
3855	var dLonX = moveLon(lat2, lon2, myUnitsToMetres(1), bearingX) - lon2;
3856	var gridBrightness = 0.05*document.getElementById("greencontourbrightnessselect").value;
3857	var gridColor = [];
3858	for (var j = 1; j <= 10; ++j) { for (var i = 0; i <= 255; ++i) gridColor[j*256 + i] = new Cesium.Color(i/255.0, 1.0, (255-i)/255.0, gridBrightness*(0.3+0.07*j)); }
3859	var h2 = getProVisualizerTerrainHeight(lat2, lon2);
3860	var con1 = 10*(1 + document.getElementById("greencontourselect").selectedIndex);
3861	var con2 = 2*con1;
3862	var minG = 0.01*document.getElementById("greencontourminselect").value;
3863	for (var x = -300; x <= 300; x += 1)
3864	{
3865		for (var y = -300; y <= 300; y += 1)
3866		{
3867			var lat3 = lat2 + 0.1*y*dLatY + 0.1*x*dLatX;
3868			var lon3 = lon2 + 0.1*y*dLonY + 0.1*x*dLonX;
3869			var h3 = getProVisualizerTerrainHeight(lat3, lon3);
3870			var dh = h3 - h2;
3871			var cm = con1 + Math.round(200*dh);
3872			if (cm % con2 == 0)
3873			{
3874				var hg = getProVisualizerTerrainHeightAndGradient(lat3, lon3, 0.5, bearingX, bearingY);
3875				var gx = hg.g1;
3876				var gy = hg.g2;
3877				var absgx = Math.abs(gx);
3878				var absgy = Math.abs(gy);
3879				var absgsum = absgx + absgy;
3880				if (absgsum > 0.001)
3881				{
3882					var k = 1.0;
3883					if (absgx > absgy) { var yx = absgy/absgx; k = Math.sqrt(1 + yx*yx); }
3884					else               { var xy = absgx/absgy; k = Math.sqrt(1 + xy*xy); }
3885					var g = (absgx + absgy)/k;
3886					var j = Math.floor(g*200);
3887					if (j > 10) j = 10;
3888					if (j > 0 && g > minG)
3889					{
3890						var k = 0.05/g; //0.2*(g + 0.2*(1.0-g))/g;
3891						var g1 = k * hg.g1;
3892						var g2 = k * hg.g2;
3893						var lat4 = lat3 - (g1*dLatX + g2*dLatY);
3894						var lon4 = lon3 - (g1*dLonX + g2*dLonY);
3895						var lat5 = lat3 + (g1*dLatX + g2*dLatY);
3896						var lon5 = lon3 + (g1*dLonX + g2*dLonY);
3897						var h4 = getProVisualizerTerrainHeight(lat4, lon4);
3898						var h5 = getProVisualizerTerrainHeight(lat5, lon5);
3899						var coords = [];
3900						coords.push(Cesium.Cartesian3.fromDegrees(lon4, lat4, h4));
3901						coords.push(Cesium.Cartesian3.fromDegrees(lon5, lat5, h5));
3902						var colorIndex = 128 + Math.round(cm/2);
3903						if (colorIndex < 0) colorIndex = 0;
3904						if (colorIndex > 255) colorIndex = 255;
3905						colorIndex += j*256;
3906						var line = viewer.entities.add({ polyline : { positions: coords, width: 1.0, material: gridColor[colorIndex] } });
3907						mapLines2.push(line);
3908					}
3909				}
3910			}
3911		}
3912	}
3913	greenContoursCount += 1;
3914	greenSlopesBearing = bearingY;
3915}
3916
3917var col1 = new Cesium.Color(1,1,1,0.3);
3918var col2 = new Cesium.Color(1,1,1,0.5);
3919var colW = new Cesium.Color(1.0,1.0,1.0,1.0);
3920var colY = new Cesium.Color(1.0,1.0,0.0,1.0);
3921var colG = new Cesium.Color(0.2,1.0,0.2,1.0);
3922var colB = new Cesium.Color(0.0,1.0,1.0,1.0);
3923var colR = new Cesium.Color(1.0,0.2,0.2,1.0);
3924function addDist(lat1, lon1, lat2, lon2)
3925{
3926	distCount += 1;
3927	var d = geogDist(lat1, lon1, lat2, lon2);
3928	var d2 = Math.round(metresToMyUnits(d));
3929	var t = 0.5;
3930	if (mapMode == 1 && d > 150)
3931	{
3932		var hole = currentHole();
3933		if (hole > 0)
3934		{
3935			var courseID = currentCourseID();
3936			var teeLat = getCourseHoleTeeLat(courseID, hole);
3937			var teeLon = getCourseHoleTeeLon(courseID, hole);
3938			if (teeLat != 0 || teeLon != 0)
3939			{
3940				var d3 = geogDist(teeLat, teeLon, lat1, lon1);
3941				var d4 = geogDist(teeLat, teeLon, lat2, lon2);
3942				if (d3 < 100 && d4 > d3 + 100) t = (d-75)/d;
3943			}
3944		}
3945	}
3946	var text = d2.toString();
3947	var k = 0.5 + d/1000.0;
3948	var coords1 = [];
3949	var h1 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat1, lon1));
3950	var h2 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat2, lon2));
3951	var lon3 = lon1 + ((t*d - (3*text.length+1)*k)/d)*(lon2-lon1);
3952	var lat3 = lat1 + ((t*d - (3*text.length+1)*k)/d)*(lat2-lat1);
3953	var h3 = h1 + (h2-h1)*geogDist(lat1,lon1,lat3,lon3)/d;
3954	coords1.push(Cesium.Cartesian3.fromDegrees(lon1, lat1, h1));
3955	coords1.push(Cesium.Cartesian3.fromDegrees(lon3, lat3, h3));
3956	var line1 = viewer.entities.add({ polyline : { positions: coords1, width: 1.0, material: col1 } });
3957	mapLines2.push(line1);
3958	var coords2 = [];
3959	var lon4 = lon1 + ((t*d + (3*text.length+1)*k)/d)*(lon2-lon1);
3960	var lat4 = lat1 + ((t*d + (3*text.length+1)*k)/d)*(lat2-lat1);
3961	var h4 = h1 + (h2-h1)*geogDist(lat1,lon1,lat4,lon4)/d;
3962	coords2.push(Cesium.Cartesian3.fromDegrees(lon4, lat4, h4));
3963	coords2.push(Cesium.Cartesian3.fromDegrees(lon2, lat2, h2));
3964	var line2 = viewer.entities.add({ polyline : { positions: coords2, width: 1.0, material: col1 } });
3965	mapLines2.push(line2);
3966	var lat = lat1 + t*(lat2-lat1);
3967	var lon = lon1 + t*(lon2-lon1);
3968	var h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
3969	var theta = 90 - geogBearing(lat1, lon1, lat2, lon2);
3970	var kx = k;
3971	var ky = k;
3972	if (mapMode == 2 && d2 >= 100)
3973	{
3974		theta -= 90;
3975		kx *= 0.5;
3976		ky *= 2.0;
3977	}
3978	var hh = 0.5*(h1+h2);
3979	var hgrad1 = (h2 - h1)/geogDist(lat1, lon1, lat2, lon2);
3980	var htheta1 = geogBearing(lat1, lon1, lat2, lon2);
3981	var hgrad2 = 0.0;
3982	var htheta2 = htheta1 + 90.0;
3983	if (hh < h + 2.0)
3984	{
3985		var lat3 = moveLat(lat, lon, -1.0, htheta2);
3986		var lon3 = moveLon(lat, lon, -1.0, htheta2);
3987		var h3 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat3, lon3));
3988		var lat4 = moveLat(lat, lon, 1.0, htheta2);
3989		var lon4 = moveLon(lat, lon, 1.0, htheta2);
3990		var h4 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat4, lon4));
3991		hgrad2 = (h4 - h3)/2.0;
3992		if (hh > h) hgrad2 *= 1.0 - (hh - h)/2.0;
3993	}
3994	addText(text, lat, lon, theta, kx, ky, col2, hh, hgrad1, htheta1, hgrad2, htheta2);
3995}
3996
3997function addSymbol(lat, lon, symbol, theta)
3998{
3999	var h = 0;
4000	var a = 0;
4001	var b = 0;
4002	var c = 0;
4003	var d = 90.0;
4004	if (terrainMode == 2)
4005	{
4006		h = getProVisualizerTerrainHeight(lat, lon);
4007		var lat1 = moveLat(lat, lon, -1.0, b);
4008		var lon1 = moveLon(lat, lon, -1.0, b);
4009		var h1 = getProVisualizerTerrainHeight(lat1, lon1);
4010		var lat2 = moveLat(lat, lon, 1.0, b);
4011		var lon2 = moveLon(lat, lon, 1.0, b);
4012		var h2 = getProVisualizerTerrainHeight(lat2, lon2);
4013		a = (h2 - h1)/2.0;
4014		var lat3 = moveLat(lat, lon, -1.0, d);
4015		var lon3 = moveLon(lat, lon, -1.0, d);
4016		var h3 = getProVisualizerTerrainHeight(lat3, lon3);
4017		var lat4 = moveLat(lat, lon, 1.0, d);
4018		var lon4 = moveLon(lat, lon, 1.0, d);
4019		var h4 = getProVisualizerTerrainHeight(lat4, lon4);
4020		c = (h4 - h3)/2.0;
4021	}
4022	if (symbol == 'A') addText(symbol, lat, lon, theta, 1, 1, col2,h,a,b,c,d);
4023	if (symbol == 'B') addText(symbol, lat, lon, theta, 1, 1, col2,h,a,b,c,d);
4024	if (symbol == 'C') addText(symbol, lat, lon, theta, 1, 1, col2,h,a,b,c,d);
4025	if (symbol == 'D') addText(symbol, lat, lon, theta, 1, 1, col2,h,a,b,c,d);
4026	if (symbol == 'E') addText(symbol, lat, lon, theta, 1, 1, col2,h,a,b,c,d);
4027	if (symbol == 'F') addText(symbol, lat, lon, theta, 1, 1, col2,h,a,b,c,d);
4028	if (symbol == '1') addText(symbol, lat, lon, theta, 0.4, 0.4, col2,h,a,b,c,d);
4029	if (symbol == '2') addText(symbol, lat, lon, theta, 0.4, 0.4, col2,h,a,b,c,d);
4030	if (symbol == '3') addText(symbol, lat, lon, theta, 0.4, 0.4, col2,h,a,b,c,d);
4031	if (symbol == '4') addText(symbol, lat, lon, theta, 0.4, 0.4, col2,h,a,b,c,d);
4032	if (symbol == '5') addText(symbol, lat, lon, theta, 0.4, 0.4, col2,h,a,b,c,d);
4033	if (symbol == '6') addText(symbol, lat, lon, theta, 0.4, 0.4, col2,h,a,b,c,d);
4034	if (symbol == 'T') addText(symbol, lat, lon, theta, 1, 1, colG,h,a,b,c,d);
4035	if (symbol == 'X') addText(symbol, lat, lon, theta, 1, 1, colR,h,a,b,c,d);
4036
4037	if (symbol == 'L') addText(symbol, lat, lon, theta, 0.10, 0.10, colW,h,a,b,c,d);
4038	if (symbol == 'L') addText(symbol, lat, lon, theta, 0.08, 0.08, colW,h,a,b,c,d);
4039	if (symbol == 'L') addText(symbol, lat, lon, theta, 0.06, 0.06, c
4039olW,h,a,b,c,d);
4040	if (symbol == 'L') addText(symbol, lat, lon, theta, 0.04, 0.04, colW,h,a,b,c,d);
4041	if (symbol == 'L') addText(symbol, lat, lon, theta, 0.02, 0.02, colW,h,a,b,c,d);
4042
4043	if (symbol == 'M') addText(symbol, lat, lon, theta, 0.10, 0.10, colY,h,a,b,c,d);
4044	if (symbol == 'M') addText(symbol, lat, lon, theta, 0.08, 0.08, colY,h,a,b,c,d);
4045	if (symbol == 'M') addText(symbol, lat, lon, theta, 0.06, 0.06, colY,h,a,b,c,d);
4046	if (symbol == 'M') addText(symbol, lat, lon, theta, 0.04, 0.04, colY,h,a,b,c,d);
4047	if (symbol == 'M') addText(symbol, lat, lon, theta, 0.02, 0.02, colY,h,a,b,c,d);
4048
4049	if (symbol == 'N') addText(symbol, lat, lon, theta, 0.10, 0.10, colG,h,a,b,c,d);
4050	if (symbol == 'N') addText(symbol, lat, lon, theta, 0.08, 0.08, colG,h,a,b,c,d);
4051	if (symbol == 'N') addText(symbol, lat, lon, theta, 0.06, 0.06, colG,h,a,b,c,d);
4052	if (symbol == 'N') addText(symbol, lat, lon, theta, 0.04, 0.04, colG,h,a,b,c,d);
4053	if (symbol == 'N') addText(symbol, lat, lon, theta, 0.02, 0.02, colG,h,a,b,c,d);
4054
4055	if (symbol == 'O') addText(symbol, lat, lon, theta, 0.10, 0.10, colB,h,a,b,c,d);
4056	if (symbol == 'O') addText(symbol, lat, lon, theta, 0.08, 0.08, colB,h,a,b,c,d);
4057	if (symbol == 'O') addText(symbol, lat, lon, theta, 0.06, 0.06, colB,h,a,b,c,d);
4058	if (symbol == 'O') addText(symbol, lat, lon, theta, 0.04, 0.04, colB,h,a,b,c,d);
4059	if (symbol == 'O') addText(symbol, lat, lon, theta, 0.02, 0.02, colB,h,a,b,c,d);
4060
4061}
4062
4063function adjustHeight(lat, lon, h, lat2, lon2, hgrad1, htheta1, hgrad2, htheta2)
4064{
4065	var h2 = h;
4066	var d2 = geogDist(lat, lon, lat2, lon2);
4067	var theta2 = geogBearing(lat, lon, lat2, lon2);
4068	while (theta2 > htheta1 + 180.0) theta2 -= 360.0;
4069	while (theta2 < htheta1 - 180.0) theta2 += 360.0;
4070	h2 += d2*hgrad1*Math.cos((theta2 - htheta1)*Math.PI/180.0);
4071	while (theta2 > htheta2 + 180.0) theta2 -= 360.0;
4072	while (theta2 < htheta2 - 180.0) theta2 += 360.0;
4073	h2 += d2*hgrad2*Math.cos((theta2 - htheta2)*Math.PI/180.0);
4074	return h2;
4075}
4076
4077function addText(text, lat, lon, theta, kx, ky, col, h = 0.0, hgrad1 = 0.0, htheta1 = 0.0, hgrad2 = 0.0, htheta2 = 0.0)
4078{
4079	theta *= Math.PI/180.0;
4080	var deltaLat = distToDeltaLat(1, lat, lon);
4081	var deltaLon = distToDeltaLon(1, lat, lon);
4082	var cosTheta = Math.cos(theta);
4083	var sinTheta = Math.sin(theta);
4084	var dLatX = deltaLat*sinTheta*kx;
4085	var dLatY = deltaLat*cosTheta*ky;
4086	var dLonX = deltaLon*cosTheta*kx;
4087	var dLonY = -deltaLon*sinTheta*ky;
4088	var totalWidth = 0;
4089	for (var i = 0; i < text.length; ++i)
4090	{
4091		var c = text.charAt(i);
4092		if      (c == "'") totalWidth += 2;
4093		else if (c == '"') totalWidth += 3;
4094		else if (c == '/') totalWidth += 4;
4095		else if (c == '.') totalWidth += 2;
4096		else totalWidth += 6;
4097	}
4098	widthSoFar = 0;
4099	for (var i = 0; i < text.length; ++i)
4100	{
4101		var c = text.charAt(i);
4102		var xyCoords1 = [];
4103		var xyCoords2 = [];
4104		var xyCoords3 = [];
4105		if (c == '1')
4106		{
4107			xyCoords1.push(1); xyCoords1.push(0);
4108			xyCoords1.push(4); xyCoords1.push(0);
4109			xyCoords2.push(3); xyCoords2.push(0);
4110			xyCoords2.push(3); xyCoords2.push(6);
4111			xyCoords2.push(1); xyCoords2.push(4);
4112		}
4113		else if (c == '2')
4114		{
4115			xyCoords1.push(4); xyCoords1.push(1);
4116			xyCoords1.push(4); xyCoords1.push(0);
4117			xyCoords1.push(0); xyCoords1.push(0);
4118			xyCoords1.push(4); xyCoords1.push(4);
4119			xyCoords1.push(4); xyCoords1.push(5);
4120			xyCoords1.push(3); xyCoords1.push(6);
4121			xyCoords1.push(1); xyCoords1.push(6);
4122			xyCoords1.push(0); xyCoords1.push(5);
4123		}
4124		else if (c == '3')
4125		{
4126			xyCoords1.push(0); xyCoords1.push(1);
4127			xyCoords1.push(1); xyCoords1.push(0);
4128			xyCoords1.push(3); xyCoords1.push(0);
4129			xyCoords1.push(4); xyCoords1.push(1);
4130			xyCoords1.push(4); xyCoords1.push(2);
4131			xyCoords1.push(3); xyCoords1.push(3);
4132			xyCoords1.push(1); xyCoords1.push(3);
4133			xyCoords2.push(3); xyCoords2.push(3);
4134			xyCoords2.push(4); xyCoords2.push(4);
4135			xyCoords2.push(4); xyCoords2.push(5);
4136			xyCoords2.push(3); xyCoords2.push(6);
4137			xyCoords2.push(1); xyCoords2.push(6);
4138			xyCoords2.push(0); xyCoords2.push(5);
4139		}
4140		else if (c == '4')
4141		{
4142			xyCoords1.push(3); xyCoords1.push(0);
4143			xyCoords1.push(3); xyCoords1.push(6);
4144			xyCoords1.push(0); xyCoords1.push(2);
4145			xyCoords1.push(4); xyCoords1.push(2);
4146		}
4147		else if (c == '5')
4148		{
4149			xyCoords1.push(4); xyCoords1.push(6);
4150			xyCoords1.push(0); xyCoords1.push(6);
4151			xyCoords1.push(0); xyCoords1.push(4);
4152			xyCoords1.push(3); xyCoords1.push(4);
4153			xyCoords1.push(4); xyCoords1.push(3);
4154			xyCoords1.push(4); xyCoords1.push(1);
4155			xyCoords1.push(3); xyCoords1.push(0);
4156			xyCoords1.push(0); xyCoords1.push(0);
4157			xyCoords1.push(0); xyCoords1.push(1);
4158		}
4159		else if (c == '6')
4160		{
4161			xyCoords1.push(4); xyCoords1.push(6);
4162			xyCoords1.push(1); xyCoords1.push(6);
4163			xyCoords1.push(0); xyCoords1.push(5);
4164			xyCoords1.push(0); xyCoords1.push(1);
4165			xyCoords1.push(1); xyCoords1.push(0);
4166			xyCoords1.push(3); xyCoords1.push(0);
4167			xyCoords1.push(4); xyCoords1.push(1);
4168			xyCoords1.push(4); xyCoords1.push(3);
4169			xyCoords1.push(3); xyCoords1.push(4);
4170			xyCoords1.push(1); xyCoords1.push(4);
4171			xyCoords1.push(0); xyCoords1.push(3);
4172		}
4173		else if (c == '7')
4174		{
4175			xyCoords1.push(2); xyCoords1.push(0);
4176			xyCoords1.push(2); xyCoords1.push(2);
4177			xyCoords1.push(4); xyCoords1.push(6);
4178			xyCoords1.push(0); xyCoords1.push(6);
4179			xyCoords1.push(0); xyCoords1.push(5);
4180		}
4181		else if (c == '8')
4182		{
4183			xyCoords1.push(1); xyCoords1.push(3);
4184			xyCoords1.push(3); xyCoords1.push(3);
4185			xyCoords1.push(4); xyCoords1.push(4);
4186			xyCoords1.push(4); xyCoords1.push(5);
4187			xyCoords1.push(3); xyCoords1.push(6);
4188			xyCoords1.push(1); xyCoords1.push(6);
4189			xyCoords1.push(0); xyCoords1.push(5);
4190			xyCoords1.push(0); xyCoords1.push(4);
4191			xyCoords1.push(1); xyCoords1.push(3);
4192			xyCoords1.push(0); xyCoords1.push(2);
4193			xyCoords1.push(0); xyCoords1.push(1);
4194			xyCoords1.push(1); xyCoords1.push(0);
4195			xyCoords1.push(3); xyCoords1.push(0);
4196			xyCoords1.push(4); xyCoords1.push(1);
4197			xyCoords1.push(4); xyCoords1.push(2);
4198			xyCoords1.push(3); xyCoords1.push(3);
4199		}
4200		else if (c == '9')
4201		{
4202			xyCoords1.push(0); xyCoords1.push(0);
4203			xyCoords1.push(3); xyCoords1.push(0);
4204			xyCoords1.push(4); xyCoords1.push(1);
4205			xyCoords1.push(4); xyCoords1.push(5);
4206			xyCoords1.push(3); xyCoords1.push(6);
4207			xyCoords1.push(1); xyCoords1.push(6);
4208			xyCoords1.push(0); xyCoords1.push(5);
4209			xyCoords1.push(0); xyCoords1.push(3);
4210			xyCoords1.push(1); xyCoords1.push(2);
4211			xyCoords1.push(3); xyCoords1.push(2);
4212			xyCoords1.push(4); xyCoords1.push(3);
4213		}
4214		else if (c == '0')
4215		{
4216			xyCoords1.push(1); xyCoords1.push(0);
4217			xyCoords1.push(3); xyCoords1.push(0);
4218			xyCoords1.push(4); xyCoords1.push(1);
4219			xyCoords1.push(4); xyCoords1.push(5);
4220			xyCoords1.push(3); xyCoords1.push(6);
4221			xyCoords1.push(1); xyCoords1.push(6);
4222			xyCoords1.push(0); xyCoords1.push(5);
4223			xyCoords1.push(0); xyCoords1.push(1);
4224			xyCoords1.push(1); xyCoords1.push(0);
4225		}
4226		else if (c == 'A')
4227		{
4228			xyCoords1.push(-0.5); xyCoords1.push(0);
4229			xyCoords1.push(2); xyCoords1.push(6);
4230			xyCoords1.push(4.5); xyCoords1.push(0);
4231			xyCoords2.push(0.5); xyCoords2.push(2.5);
4232			xyCoords2.push(3.5); xyCoords2.push(2.5);
4233		}
4234		else if (c == 'B')
4235		{
4236			xyCoords1.push(0); xyCoords1.push(0);
4237			xyCoords1.push(0); xyCoords1.push(6);
4238			xyCoords1.push(3); xyCoords1.push(6);
4239			xyCoords1.push(4); xyCoords1.push(5);
4240			xyCoords1.push(4); xyCoords1.push(4);
4241			xyCoords1.push(3); xyCoords1.push(3);
4242			xyCoords1.push(4); xyCoords1.push(2);
4243			xyCoords1.push(4); xyCoords1.push(1);
4244			xyCoords1.push(3); xyCoords1.push(0);
4245			xyCoords1.push(0); xyCoords1.push(0);
4246			xyCoords2.push(0); xyCoords2.push(3);
4247			xyCoords2.push(3); xyCoords2.push(3);
4248		}
4249		else if (c == 'C')
4250		{
4251			xyCoords1.push(4); xyCoords1.push(1);
4252			xyCoords1.push(3); xyCoords1.push(0);
4253			xyCoords1.push(1); xyCoords1.push(0);
4254			xyCoords1.push(0); xyCoords1.push(1);
4255			xyCoords1.push(0); xyCoords1.push(5);
4256			xyCoords1.push(1); xyCoords1.push(6);
4257			xyCoords1.push(3); xyCoords1.push(6);
4258			xyCoords1.push(4); xyCoords1.push(5);
4259		}
4260		else if (c == 'c')
4261		{
4262			xyCoords1.push(4); xyCoords1.push(1);
4263			xyCoords1.push(3); xyCoords1.push(0);
4264			xyCoords1.push(1); xyCoords1.push(0);
4265			xyCoords1.push(0); xyCoords1.push(1);
4266			xyCoords1.push(0); xyCoords1.push(3);
4267			xyCoords1.push(1); xyCoords1.push(4);
4268			xyCoords1.push(3); xyCoords1.push(4);
4269			xyCoords1.push(4); xyCoords1.push(3);
4270		}
4271		else if (c == 'D')
4272		{
4273			xyCoords1.push(0); xyCoords1.push(0);
4274			xyCoords1.push(0); xyCoords1.push(6);
4275			xyCoords1.push(3); xyCoords1.push(6);
4276			xyCoords1.push(4); xyCoords1.push(5);
4277			xyCoords1.push(4); xyCoords1.push(1);
4278			xyCoords1.push(3); xyCoords1.push(0);
4279			xyCoords1.push(0); xyCoords1.push(0);
4280		}
4281		else if (c == 'E')
4282		{
4283			xyCoords1.push(4); xyCoords1.push(0);
4284			xyCoords1.push(0); xyCoords1.push(0);
4285			xyCoords1.push(0); xyCoords1.push(6);
4286			xyCoords1.push(4); xyCoords1.push(6);
4287			xyCoords2.push(0); xyCoords2.push(3);
4288			xyCoords2.push(4); xyCoords2.push(3);
4289		}
4290		else if (c == 'F')
4291		{
4292			xyCoords1.push(0); xyCoords1.push(0);
4293			xyCoords1.push(0); xyCoords1.push(6);
4294			xyCoords1.push(4); xyCoords1.push(6);
4295			xyCoords2.push(0); xyCoords2.push(3);
4296			xyCoords2.push(3.5); xyCoords2.push(3);
4297		}
4298		else if (c == 'm')
4299		{
4300			xyCoords1.push(0); xyCoords1.push(0);
4301			xyCoords1.push(0); xyCoords1.push(4);
4302			xyCoords1.push(2); xyCoords1.push(4);
4303			xyCoords1.push(2); xyCoords1.push(0);
4304			xyCoords2.push(2); xyCoords2.push(4);
4305			xyCoords2.push(4); xyCoords2.push(4);
4306			xyCoords2.push(4); xyCoords2.push(0);
4307		}
4308		else if (c == 's')
4309		{
4310			xyCoords1.push(0); xyCoords1.push(0);
4311			xyCoords1.push(4); xyCoords1.push(0);
4312			xyCoords1.push(4); xyCoords1.push(2);
4313			xyCoords1.push(0); xyCoords1.push(2);
4314			xyCoords1.push(0); xyCoords1.push(4);
4315			xyCoords1.push(4); xyCoords1.push(4);
4316		}
4317		else if (c == 'T')
4318		{
4319			xyCoords1.push(0); xyCoords1.push(2.5);
4320			xyCoords1.push(1.2); xyCoords1.push(1);
4321			xyCoords1.push(4); xyCoords1.push(5);
4322		}
4323		else if (c == 'X')
4324		{
4325			xyCoords1.push(0); xyCoords1.push(1);
4326			xyCoords1.push(4); xyCoords1.push(5);
4327			xyCoords2.push(4); xyCoords2.push(1);
4328			xyCoords2.push(0); xyCoords2.push(5);
4329		}
4330		else if (c == '/')
4331		{
4332			xyCoords1.push(0); xyCoords1.push(0);
4333			xyCoords1.push(3); xyCoords1.push(6);
4334		}
4335		else if (c == '%')
4336		{
4337			xyCoords1.push(0); xyCoords1.push(0);
4338			xyCoords1.push(4); xyCoords1.push(6);
4339			xyCoords2.push(0); xyCoords2.push(6);
4340			xyCoords2.push(0); xyCoords2.push(4);
4341			xyCoords2.push(2); xyCoords2.push(4);
4342			xyCoords2.push(2); xyCoords2.push(6);
4343			xyCoords2.push(0); xyCoords2.push(6);
4344			xyCoords3.push(2); xyCoords3.push(2);
4345			xyCoords3.push(2); xyCoords3.push(0);
4346			xyCoords3.push(4); xyCoords3.push(0);
4347			xyCoords3.push(4); xyCoords3.push(2);
4348			xyCoords3.push(2); xyCoords3.push(2);
4349		}
4350		else if (c == '+')
4351		{
4352			xyCoords1.push(0); xyCoords1.push(3);
4353			xyCoords1.push(4); xyCoords1.push(3);
4354			xyCoords2.push(2); xyCoords2.push(1);
4355			xyCoords2.push(2); xyCoords2.push(5);
4356		}
4357		else if (c == '"')
4358		{
4359			xyCoords1.push(0); xyCoords1.push(5);
4360			xyCoords1.push(0); xyCoords1.push(6);
4361			xyCoords2.push(2); xyCoords2.push(5);
4362			xyCoords2.push(2); xyCoords2.push(6);
4363		}
4364		else if (c == "'")
4365		{
4366			xyCoords1.push(0); xyCoords1.push(5);
4367			xyCoords1.push(0); xyCoords1.push(6);
4368		}
4369		else if (c == '.')
4370		{
4371			xyCoords1.push(0); xyCoords1.push(0);
4372			xyCoords1.push(0); xyCoords1.push(1);
4373		}
4374		else
4375		{
4376			xyCoords1.push(1); xyCoords1.push(1);
4377			xyCoords1.push(3); xyCoords1.push(1);
4378			xyCoords1.push(4); xyCoords1.push(2);
4379			xyCoords1.push(4); xyCoords1.push(4);
4380			xyCoords1.push(3); xyCoords1.push(5);
4381			xyCoords1.push(1); xyCoords1.push(5);
4382			xyCoords1.push(0); xyCoords1.push(4);
4383			xyCoords1.push(0); xyCoords1.push(2);
4384			xyCoords1.push(1); xyCoords1.push(1);
4385		}
4386		if (xyCoords1.length > 0)
4387		{
4388			var coords = [];
4389			for (var j = 0; j < xyCoords1.length; j += 2)
4390			{
4391				var x = xyCoords1[j] - totalWidth/2 + widthSoFar + 1;
4392				var y = xyCoords1[j+1] - 3;
4393				var lat2 = lat + x*dLatX + y*dLatY;
4394				var lon2 = lon + x*dLonX + y*dLonY;
4395				var h2 = adjustHeight(lat, lon, h, lat2, lon2, hgrad1, htheta1, hgrad2, htheta2);
4396				coords.push(Cesium.Cartesian3.fromDegrees(lon2, lat2, h2));
4397			}
4398			var line = viewer.entities.add({ polyline : { positions: coords, width: 1.0, material: col } });
4399			mapLines2.push(line);
4400		}
4401		if (xyCoords2.length > 0)
4402		{
4403			var coords = [];
4404			for (var j = 0; j < xyCoords2.length; j += 2)
4405			{
4406				var x = xyCoords2[j] - totalWidth/2 + widthSoFar + 1;
4407				var y = xyCoords2[j+1] - 3;
4408				var lat2 = lat + x*dLatX + y*dLatY;
4409				var lon2 = lon + x*dLonX + y*dLonY;
4410				var h2 = adjustHeight(lat, lon, h, lat2, lon2, hgrad1, htheta1, hgrad2, htheta2);
4411				coords.push(Cesium.Cartesian3.fromDegrees(lon2, lat2, h2));
4412			}
4413			var line = viewer.entities.add({ polyline : { positions: coords, width: 1.0, material: col } });
4414			mapLines2.push(line);
4415		}
4416		if (xyCoords3.length > 0)
4417		{
4418			var coords = [];
4419			for (var j = 0; j < xyCoords3.length; j += 2)
4420			{
4421				var x = xyCoords3[j] - totalWidth/2 + widthSoFar + 1;
4422				var y = xyCoords3[j+1] - 3;
4423				var lat2 = lat + x*dLatX + y*dLatY;
4424				var lon2 = lon + x*dLonX + y*dLonY;
4425				var h2 = adjustHeight(lat, lon, h, lat2, lon2, hgrad1, htheta1, hgrad2, htheta2);
4426				coords.push(Cesium.Cartesian3.fromDegrees(lon2, lat2, h2));
4427			}
4428			var line = viewer.entities.add({ polyline : { positions: coords, width: 1.0, material: col } });
4429			mapLines2.push(line);
4430		}
4431		if      (c == "'") widthSoFar += 2;
4432		else if (c == '"') widthSoFar += 3;
4433		else if (c == '/') widthSoFar += 4;
4434		else if (c == '.') widthSoFar += 2;
4435		else widthSoFar += 6;
4436	}
4437}
4438
4439function distanceHandler()
4440{
4441	if (mode == 6)
4442	{
4443		setMode(0);
4444	}
4445	else
4446	{
4447		distLat1 = 0;
4448		distLon1 = 0;
4449		distLat2 = 0;
4450		distLon2 = 0;
4451		if (mapMode == 2 && distCount == 0)
4452		{
4453			var courseID = currentCourseID();
4454			var hole = currentHole();
4455			var n = getCourseHoleMeasures(courseID, hole);
4456			if (n > 0)
4457			{
4458				setMode(0);
4459				for (var i = 0; i < n; ++i)
4460				{
4461					var x = decodeMeasure(getCourseHoleMeasure(courseID, hole, i));
4462					addDist(x.lat1, x.lon1, x.lat2, x.lon2);
4463				}
4464				return;
4465			}
4466		}
4467		setMode(6);
4468		setDistDiv("Click Start");
4469	}
4470}
4471
4472function distanceHandler2()
4473{
4474	if (mode == 7)
4475	{
4476		setMode(0);
4477	}
4478	else
4479	{
4480		var courseID = currentCourseID();
4481		var hole = currentHole();
4482		if (hole < 1)
4483		{
4484			alert("Please select hole.");
4485			setMode(0);
4486			return;
4487		}
4488		distLat1 = getCourseHoleTeeLat(courseID, hole);
4489		distLon1 = getCourseHoleTeeLon(courseID, hole);
4490		if (distLat1 == 0 && distLon1 == 0)
4491		{
4492			alert("Please set tee position.");
4493			setMode(0);
4494			return;
4495		}
4496		distLat2 = 0;
4497		distLon2 = 0;
4498		if (mapMode == 2 && distCount == 0)
4499		{
4500			var n = getCourseHoleMeasures(courseID, hole);
4501			if (n > 0)
4502			{
4503				setMode(0);
4504				for (var i = 0; i < n; ++i)
4505				{
4506					var x = decodeMeasure(getCourseHoleMeasure(courseID, hole, i));
4507					addDist(x.lat1, x.lon1, x.lat2, x.lon2);
4508				}
4509				return;
4510			}
4511		}
4512		setMode(7);
4513		setDistDiv("Click Map");
4514	}
4515}
4516
4517function clearDistanceHandler()
4518{
4519	if (mode == 8 || distCount == 0)
4520	{
4521		setMode(0);
4522	}
4523	else
4524	{
4525		setMode(8);
4526		setDistDiv("Click Map");
4527	}
4528}
4529
4530function clearDispersionHandler()
4531{
4532	setMode(0);
4533	if (greenSlopesCount > 0 && puttHistoryOp.length > 0)
4534	{
4535		if (showPuttDispersion != 0)
4536		{
4537			showPuttDispersion = 0;
4538			selectGreenSpeedHandler();
4539		}
4540		return;
4541	}
4542	removeDispersionFeatures();
4543}
4544
4545function clearSpreadHandler()
4546{
4547	setMode(0);
4548	if (greenSlopesCount > 0 && puttHistoryOp.length > 0)
4549	{
4550		if (showPuttDispersion != 0)
4551		{
4552			showPuttDispersion = 0;
4553			selectGreenSpeedHandler();
4554		}
4555		return;
4556	}
4557	removeDispersionFeatures();
4558}
4559
4560function zoomToCourseHole(courseID, hole, clear, refresh, durationSeconds)
4561{
4562	if (clear)
4563		removeAllFeatures();
4564
4565	var x = document.getElementById('mapdiv');
4566	var mw = x.clientWidth;
4567	var mh = x.clientHeight;
4568	if (mw < 100) mw = 100;
4569	if (mh < 100) mh = 100;
4570
4571	var t = mw/mh - 1;
4572	var height = (50 - t*10) * heightMultiplier;
4573	var offset = (45 + (t > 0 ? t*35 : t*10)) * offsetMultiplier;
4574	var angle = (-25 + t*10) + angleIncrement;
4575
4576	if (height < 2.0) height = 2.0;
4577	if (offset < 5.0) offset = 5.0;
4578	if (angle > -5.0) angle = -5.0;
4579	if (angle < -90.0) angle = -90.0;
4580
4581	var lat = 0;
4582	var lon = 0;
4583	var hlat = 0;
4584	var hlon = 0;
4585	var heading = 0;
4586
4587	if (hole == 0)
4588	{
4589		var lat1 = getCourseMinLat(courseID);
4590		var lon1 = getCourseMinLon(courseID);
4591		var lat2 = getCourseMaxLat(courseID);
4592		var lon2 = getCourseMaxLon(courseID);
4593		if (lat1 != 0 || lon1 != 0)
4594		{
4595			if (lat2 <= lat1) lat2 = lat1 + 0.0001;
4596			while (lon2 < lon1 - 180) lon2 += 360;
4597			while (lon2 > lon1 + 180) lon2 -= 360;
4598			lat = (lat1 + lat2)/2;
4599			lon = (lon1 + lon2)/2;
4600			hlat = lat;
4601			hlon = lon;
4602			var d1 = geogDist(lat1, lon1, lat2, lon2);
4603			var d2 = geogDist(lat, lon, lat + 1, lon);
4604			if (mapMode == 2)
4605			{
4606				lat -= 3000 * (d1/4000) / d2;
4607				height = 1000 * (d1/4000);
4608				angle = -22;
4609			}
4610			else
4611			{
4612				height = 1.0*d1;
4613				heading = 0;
4614				angle = -90;
4615			}
4616		}
4617	}
4618	else if (hole > 0)
4619	{
4620		var par = getCourseHolePar(courseID, hole);
4621		if (par >= 3)
4622		{
4623			if (mapMode == 1)
4624			{
4625				var lat1 = getCourseHoleTeeLat(courseID, hole);
4626				var lon1 = getCourseHoleTeeLon(courseID, hole);
4627				var lat2 = getCourseHolePinLat(courseID, hole);
4628				var lon2 = getCourseHolePinLon(courseID, hole);
4629				heading = geogBearing(lat1, lon1, lat2, lon2);
4630				d = geogDist(lat1, lon1, lat2, lon2);
4631				var t = 0.5;
4632				if (d > 1) t = 0.5 * (d + 20) / d;
4633				lat = lat1 + t * (lat2 - lat1);
4634				lon = lon1 + t * (lon2 - lon1);
4635				hlat = lat;
4636				hlon = lon;
4637				height = d + 70;
4638				angle = -90;
4639			}
4640			else
4641			{
4642				lat = getCourseHoleShotLat(courseID, hole, 1, offset, offset + 30);
4643				lon = getCourseHoleShotLon(courseID, hole, 1, offset, offset + 30);
4644				hlat = getCourseHoleTeeLat(courseID, hole);
4645				hlon = getCourseHoleTeeLon(courseID, hole);
4646				heading = getCourseHoleShotBearing(courseID, hole, 1);
4647			}
4648		}
4649		else
4650		{
4651			var lat1 = getCourseHoleMinLat(courseID, hole);
4652			var lon1 = getCourseHoleMinLon(courseID, hole);
4653			var lat2 = getCourseHoleMaxLat(courseID, hole);
4654			var lon2 = getCourseHoleMaxLon(courseID, hole);
4655			if (lat1 != 0 || lon1 != 0)
4656			{
4657				if (lat2 <= lat1) lat2 = lat1 + 0.0001;
4658				while (lon2 < lon1 - 180) lon2 += 360;
4659				while (lon2 > lon1 + 180) lon2 -= 360;
4660				lat = (lat1 + lat2)/2;
4661				lon = (lon1 + lon2)/2;
4662				hlat = lat;
4663				hlon = lon;
4664				var d1 = geogDist(lat1, lon1, lat2, lon2);
4665				height = 1.0*d1;
4666				heading = 0;
4667				angle = -90;
4668				if (height < 200)
4669					height = 200;
4670			}
4671		}
4672	}
4673
4674	if (mapMode == 1)
4675	{
4676		var x = document.getElementById('mapdiv');
4677		var w = window.innerWidth;
4678		var h = window.innerHeight;
4679		w = x.clientWidth;
4680		h = x.clientHeight;
4681		if (w < 100) w = 100;
4682		if (h < 100) h = 100;
4683		height *= 1.05 * Math.sqrt(w / h);
4684		if (getAutoRotate() != 0)
4685			heading = 0;
4686	}
4687
4688	if (lat != 0 || lon != 0)
4689	{
4690		flyToHeightAboveGround(lat, lon, hlat, hlon, height, heading, angle, durationSeconds)
4691	}
4692
4693	if (refresh)
4694		refreshView(); 
4695}
4696
4697function zoomToCourseHoleShot(courseID, hole, shot, clear, refresh, durationSeconds)
4698{
4699	if (hole == 0 || shot == 0)
4700	{
4701		zoomToCourseHole(courseID, hole, clear, refresh, durationSeconds);
4702		return;
4703	}
4704	if (clear)
4705		removeAllFeatures();
4706
4707	var x = document.getElementById('mapdiv');
4708	var mw = x.clientWidth;
4709	var mh = x.clientHeight;
4710	if (mw < 100) mw = 100;
4711	if (mh < 100) mh = 100;
4712
4713	var t = mw/mh - 1;
4714	var height = (50 - t*10) * heightMultiplier;
4715	var offset = ((shot <= 1 ? 45 : 50) + (t > 0 ? t*35 : t*10)) * offsetMultiplier;
4716	var angle = (-25 + t*10) + angleIncrement;
4717
4718	if (shot == 1) offset -= 5.0;
4719	if (height < 2.0) height = 2.0;
4720	if (offset < 5.0) offset = 5.0;
4721	if (angle > -5.0) angle = -5.0;
4722	if (angle < -90.0) angle = -90.0;
4723
4724	height *= 0.50;
4725	offset *= 0.65;
4726	angle *= 0.90;
4727
4728	var lat = 0;
4729	var lon = 0;
4730	var hlat = 0;
4731	var hlon = 0;
4732	var heading = 0;
4733
4734	var par = getCourseHolePar(courseID, hole);
4735
4736	if (par >= 3)
4737	{
4738		if (mapMode == 1)
4739		{
4740			var lat1 = getCourseHoleTeeLat(courseID, hole);
4741			var lon1 = getCourseHoleTeeLon(courseID, hole);
4742			var lat2 = getCourseHolePinLat(courseID, hole);
4743			var lon2 = getCourseHolePinLon(courseID, hole);
4744			heading = geogBearing(lat1, lon1, lat2, lon2);
4745			if (shot > 0)
4746			{
4747				if (shot == 1 && par > 3)
4748				{
4749					lat2 = getCourseHoleTargetLat(courseID, hole, shot);
4750					lon2 = getCourseHoleTargetLon(courseID, hole, shot);
4751				}
4752				else if (shot >= 2 && shot <= par - 2)
4753				{
4754					lat1 = getCourseHoleTargetLat(courseID, hole, shot - 1);
4755					lon1 = getCourseHoleTargetLon(courseID, hole, shot - 1);
4756				}
4757				else if (shot > par - 2)
4758				{
4759					lat1 = getCourseHolePinLat(courseID, hole);
4760					lon1 = getCourseHolePinLon(courseID, hole);
4761				}
4762			}
4763			var d = geogDist(lat1, lon1, lat2, lon2);
4764			var t = 0.5;
4765			if (shot <= 1 && d > 1) t = 0.5 * (d + 20) / d;
4766			lat = lat1 + t * (lat2 - lat1);
4767			lon = lon1 + t * (lon2 - lon1);
4768			hlat = lat;
4769			hlon = lon;
4770			height = d + 70;
4771			angle = -90;
4772		}
4773		else
4774		{
4775			lat = getCourseHoleShotLat(courseID, hole, shot, offset, offset + 30);
4776			lon = getCourseHoleShotLon(courseID, hole, shot, offset, offset + 30);
4777			heading = getCourseHoleShotBearing(courseID, hole, shot);
4778			if (shot <= 1)
4779			{
4780				hlat = getCourseHoleTeeLat(courseID, hole);
4781				hlon = getCourseHoleTeeLon(courseID, hole);
4782			}
4783			else if (shot <= par - 2)
4784			{
4785				hlat = getCourseHoleTargetLat(courseID, hole, shot - 1);
4786				hlon = getCourseHoleTargetLon(courseID, hole, shot - 1);
4787			}
4788			else
4789			{
4790				hlat = getCourseHolePinLat(courseID, hole);
4791				hlon = getCourseHolePinLon(courseID, hole);
4792			}
4793		}
4794	}
4795
4796	if (mapMode == 1)
4797	{
4798		var x = document.getElementById('mapdiv');
4799		var w = window.innerWidth;
4800		var h = window.innerHeight;
4801		w = x.clientWidth;
4802		h = x.clientHeight;
4803		if (w < 100) w = 100;
4804		if (h < 100) h = 100;
4805		height *= 1.05 * Math.sqrt(w / h);
4806		if (getAutoRotate() != 0)
4807			heading = 0;
4808	}
4809
4810	if (lat != 0 || lon != 0)
4811	{
4812		flyToHeightAboveGround(lat, lon, hlat, hlon, height, heading, angle, durationSeconds)
4813	}
4814
4815	if (refresh)
4816		refreshView();
4817}
4818
4819function addHoleMarker(lat, lon)
4820{
4821	var h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
4822	var marker = viewer.entities.add({
4823		position : Cesium.Cartesian3.fromDegrees(lon, lat, h + 0.005),
4824		cylinder : {
4825			length: 0.01,
4826			topRadius: 0.05,
4827			bottomRadius: 0.05,
4828			material: Cesium.Color.WHITE,
4829			outline: true,
4830			outlineColor: Cesium.Color.WHITE
4831		}
4832	}); 
4833	mapMarkers2.push(marker);
4834}
4835
4836function addBallMarker(lat, lon)
4837{
4838	var h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
4839	var marker = viewer.entities.add({
4840		position : Cesium.Cartesian3.fromDegrees(lon, lat, h + 0.005),
4841		cylinder : {
4842			length: 0.04,
4843			topRadius: 0.02,
4844			bottomRadius: 0.02,
4845			material: Cesium.Color.WHITE,
4846			outline: true,
4847			outlineColor: Cesium.Color.WHITE
4848		}
4849	}); 
4850	mapMarkers2.push(marker);
4851}
4852
4853function addDispersionMarker(lat, lon)
4854{
4855	var h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
4856	var marker = viewer.entities.add({
4857		position : Cesium.Cartesian3.fromDegrees(lon, lat, h + 0.05),
4858		cylinder : {
4859			length: 0.1,
4860			topRadius: 0.05,
4861			bottomRadius: 0.05,
4862			material: Cesium.Color.WHITE,
4863			outline: true,
4864			outlineColor: Cesium.Color.WHITE
4865		}
4866	}); 
4867	mapMarkers2.push(marker);
4868}
4869
4870function addMarker(lat, lon, addYellowCircles, addWhiteCircles, addTargetCircles, numTargetCircles, bearing)
4871{
4872	var h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
4873	var marker = viewer.entities.add({
4874		position : Cesium.Cartesian3.fromDegrees(lon, lat, h + 0.9),
4875		cylinder : {
4876			length: 1.8,
4877			topRadius: 0.1,
4878			bottomRadius: 0.1,
4879			material: Cesium.Color.WHITE,
4880			outline: true,
4881			outlineColor: Cesium.Color.WHITE
4882		}
4883	}); 
4884	mapMarkers.push(marker);
4885	if (mapMode == 1)
4886	{
4887		var x = myUnitsToMetres(1);
4888		var c = getCircleColor();
4889		if (addYellowCircles)
4890		{
4891			var y = getCircleBrightness() / 10.0;
4892			var y0 = (y > 0 ? y*0.75+0.25 : 0.0);
4893			var c0 = new Cesium.Color(1, 1, 0, y0);
4894			var c1 = new Cesium.Color(1, 1, 0, y*0.5);
4895			var c2 = new Cesium.Color(1, 1, 0, y*0.2);
4896			var c3 = new Cesium.Color(1, 1, 0, y*0.15);
4897			var c4 = new Cesium.Color(1, 1, 0, y*0.12);
4898			if (c == 1)
4899			{
4900				c0 = new Cesium.Color(0.5, 1, 0.5, y0);
4901				c1 = new Cesium.Color(0.5, 1, 0.5, y*0.5);
4902				c2 = new Cesium.Color(0.5, 1, 0.5, y*0.2);
4903				c3 = new Cesium.Color(0.5, 1, 0.5, y*0.15);
4904				c4 = new Cesium.Color(0.5, 1, 0.5, y*0.12);
4905			}
4906			if (c == 2)
4907			{
4908				c0 = new Cesium.Color(0.5, 1, 1, y0);
4909				c1 = new Cesium.Color(0.5, 1, 1, y*0.5);
4910				c2 = new Cesium.Color(0.5, 1, 1, y*0.2);
4911				c3 = new Cesium.Color(0.5, 1, 1, y*0.15);
4912				c4 = new Cesium.Color(0.5, 1, 1, y*0.12);
4913			}
4914			if (c == 3)
4915			{
4916				c0 = new Cesium.Color(1, 1, 1, y0);
4917				c1 = new Cesium.Color(1, 1, 1, y*0.5);
4918				c2 = new Cesium.Color(1, 1, 1, y*0.2);
4919				c3 = new Cesium.Color(1, 1, 1, y*0.15);
4920				c4 = new Cesium.Color(1, 1, 1, y*0.12);
4921			}
4922			var i = getTeeCircles();
4923			if (i <= 6)
4924			{
4925				addCircle(lat, lon, x*200, c1);
4926				addCircle(lat, lon, x*(250-5*i), c2);
4927				addCircle(lat, lon, x*(300-10*i), c1);
4928			}
4929			else if (i <= 10)
4930			{
4931				addCircle(lat, lon, x*(260-10*i), c1);
4932				addCircle(lat, lon, x*(280-10*i), c2);
4933				addCircle(lat, lon, x*(300-10*i), c1);
4934			}
4935			else
4936			{
4937				var j = 0;
4938				var n = 0;
4939				var m = 3 + i - 11;
4940				while (n < m && j <= 16)
4941				{
4942					if (getClubFlag(j))
4943					{
4944						n += 1;
4945						var d = getClubTotal(j);
4946						addCircle(lat, lon, x*d, (n == 1 ? c1 : (n == 2 ? c2 : (n == 3 ? c3 : c4))));
4947						if (bearing > -9999)
4948						{
4949							var lat2 = moveLat(lat, lon, x*d - 3, bearing + 10);
4950							var lon2 = moveLon(lat, lon, x*d - 3, bearing + 10);
4951							var hg2 = getProVisualizerTerrainHeightAndGradient(lat2, lon2, 2.0, 0.0, 90.0);
4952							addText(Math.round(d).toString(), lat2, lon2, -bearing - 10, 0.4, 0.4, c0, hg2.h, hg2.g1, 0.0, hg2.g2, 90.0);
4953							var lat3 = moveLat(lat, lon, x*d - 3, bearing - 10);
4954							var lon3 = moveLon(lat, lon, x*d - 3, bearing - 10);
4955							var hg3 = getProVisualizerTerrainHeightAndGradient(lat3, lon3, 2.0, 0.0, 90.0);
4956							addText(Math.round(d).toString(), lat3, lon3, -bearing + 10, 0.4, 0.4, c0, hg3.h, hg3.g1, 0.0, hg3.g2, 90.0);
4957						}
4958					}
4959					j += 1;
4960				}
4961			}
4962		}
4963		if (addWhiteCircles)
4964		{
4965			var y = getCircleBrightness() / 15.0;
4966			var c1 = new Cesium.Color(1, 1, 1, y*0.5);
4967			var c2 = new Cesium.Color(1, 1, 1, y*0.2);
4968			var c3 = new Cesium.Color(1, 1, 1, y*0.1);
4969			addCircle(lat, lon, x*10, c1);
4970			addCircle(lat, lon, x*50, c2);
4971			addCircle(lat, lon, x*100, c1);
4972			addCircle(lat, lon, x*150, c2);
4973			addCircle(lat, lon, x*200, c1);
4974			addCircle(lat, lon, x*250, c2);
4975
4976			if (currentShot() >= 0)
4977			{
4978				addCircle(lat, lon, x*20, c3);
4979				addCircle(lat, lon, x*30, c3);
4980				addCircle(lat, lon, x*40, c3);
4981
4982				addCircle(lat, lon, x*60, c3);
4983				addCircle(lat, lon, x*70, c3);
4984				addCircle(lat, lon, x*80, c3);
4985				addCircle(lat, lon, x*90, c3);
4986
4987				addCircle(lat, lon, x*110, c3);
4988				addCircle(lat, lon, x*120, c3);
4989				addCircle(lat, lon, x*130, c3);
4990				addCircle(lat, lon, x*140, c3);
4991
4992				addCircle(lat, lon, x*160, c3);
4993				addCircle(lat, lon, x*170, c3);
4994				addCircle(lat, lon, x*180, c3);
4995				addCircle(lat, lon, x*190, c3);
4996
4997				addCircle(lat, lon, x*210, c3);
4998				addCircle(lat, lon, x*220, c3);
4999				addCircle(lat, lon, x*230, c3);
5000				addCircle(lat, lon, x*240, c3);
5001			}
5002		}
5003		if (addTargetCircles)
5004		{
5005			var y = getCircleBrightness() / 15.0;
5006			var c1 = new Cesium.Color(1, 1, 1, y*0.25);
5007			var c2 = new Cesium.Color(1, 1, 1, y*0.15);
5008			var c3 = new Cesium.Color(1, 1, 1, y*0.10);
5009			if (numTargetCircles >= 1) addCircle(lat, lon, x*10, c1);
5010			if (numTargetCircles >= 2) addCircle(lat, lon, x*20, c2);
5011			if (numTargetCircles >= 3) addCircle(lat, lon, x*30, c3);
5012		}
5013	}
5014}
5015
5016function addCircle(lat, lon, radius, color)
5017{
5018	var rLat = moveLat(lat, lon, radius, 0) - lat;
5019	var rLon = moveLon(lat, lon, radius, 90) - lon;
5020	var coords = [];
5021	var theta = 0.0;
5022	var n = 60 + Math.round(radius);
5023	var dTheta = 2*Math.PI/n;
5024	for (var i = 0; i <= n; ++i)
5025	{
5026		if (i == n) theta = 0.0;
5027		var lat2 = lat + rLat*Math.cos(theta);
5028		var lon2 = lon + rLon*Math.sin(theta);
5029		var h2 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat2, lon2));
5030		coords.push(Cesium.Cartesian3.fromDegrees(lon2, lat2, h2));
5031		theta += dTheta;
5032	}
5033	for (var i = 0; i < 2; ++i)
5034	{
5035		var circle = viewer.entities.add({ polyline : { positions: coords, width: 1, material: color } });
5036		mapCircles.push(circle);
5037	}
5038}
5039
5040function addCenterLines()
5041{
5042	var courseID = currentCourseID();
5043	for (var i = 1; i <= 18; ++i)
5044		addCourseHoleCenterLine(courseID, i, 1, 1, 0, 1, true);
5045}
5046
5047function addCenterLine()
5048{
5049	var courseID = currentCourseID();
5050	var hole = currentHole();
5051	var a = getLineBrightness() * 0.1;
5052	var c = getLineColor();
5053	var r = 0;
5054	var g = 0;
5055	var b = 0;
5056	if      (c == 1) { r = 1.0; g = 1.0; b = 1.0; }
5057	else if (c == 2) { r = 1.0; g = 1.0; b = 0.0; }
5058	else if (c == 3) { r = 0.5; g = 1.0; b = 0.5; }
5059	else if (c == 4) { r = 0.5; g = 1.0; b = 1.0; }
5060	addCourseHoleCenterLine(courseID, hole, r, g, b, a, false);
5061}
5062
5063function addCourseHoleCenterLine(courseID, hole, r, g, b, a, isOverview)
5064{
5065	if (hole < 1)
5066		return;
5067
5068	var par = getCourseHolePar(courseID, hole);
5069
5070	if (par < 3)
5071		return;
5072
5073	var positions = [];
5074	var i = 0;
5075
5076	var lat = getCourseHoleTeeLat(courseID, hole);
5077	var lon = getCourseHoleTeeLon(courseID, hole);
5078	var h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
5079	positions[i++] = Cesium.Cartesian3.fromDegrees(lon, lat, h);
5080
5081	if (par >= 4)
5082	{
5083		lat = getCourseHoleTargetLat(courseID, hole, 1);
5084		lon = getCourseHoleTargetLon(courseID, hole, 1);
5085		h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
5086		positions[i++] = Cesium.Cartesian3.fromDegrees(lon, lat, h);
5087	}
5088	if (par >= 5)
5089	{
5090		lat = getCourseHoleTargetLat(courseID, hole, 2);
5091		lon = getCourseHoleTargetLon(courseID, hole, 2);
5092		h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
5093		positions[i++] = Cesium.Cartesian3.fromDegrees(lon, lat, h);
5094	}
5095	if (par >= 6)
5096	{
5097		lat = getCourseHoleTargetLat(courseID, hole, 3);
5098		lon = getCourseHoleTargetLon(courseID, hole, 3);
5099		h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
5100		positions[i++] = Cesium.Cartesian3.fromDegrees(lon, lat, h);
5101	}
5102
5103	lat = getCourseHolePinLat(courseID, hole);
5104	lon = getCourseHolePinLon(courseID, hole);
5105	h = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat, lon));
5106	positions[i++] = Cesium.Cartesian3.fromDegrees(lon, lat, h);
5107
5108	if (isOverview)
5109	{
5110		var x = (positions[0].x + positions[1].x)/2;
5111		var y = (positions[0].y + positions[1].y)/2;
5112		var z = (positions[0].z + positions[1].z)/2;
5113		viewer.entities.add({
5114			position: new Cesium.Cartesian3(x,y,z),
5115			billboard : { image: '/images/' + hole.toString() + 'y.png', eyeOffset: new Cesium.Cartesian3(0,0,-50) }
5116		});
5117
5118		var line = viewer.entities.add({
5119			polyline : {
5120				positions: positions,
5121				width: 7,
5122				material: new Cesium.PolylineArrowMaterialProperty(new Cesium.Color(r, g, b, a))
5123			}
5124		}); 
5125
5126		mapLines.push(line);
5127	}
5128	else
5129	{
5130		var line = viewer.entities.add({
5131			polyline : {
5132				positions: positions,
5133				width: 1.0,
5134				material: new Cesium.Color(r, g, b, a)
5135			}
5136		}); 
5137
5138		mapLines.push(line);
5139	}
5140}
5141
5142function removeAllFeatures()
5143{
5144	viewer.entities.removeAll();
5145	mapMarkers = [];
5146	mapMarkers2 = [];
5147	mapCircles = [];
5148	mapLines = [];
5149	mapLines2 = [];
5150	teeGridCount = 0;
5151	greenGridCount = 0;
5152	greenSlopesCount = 0;
5153	greenSlopesBearing = 0;
5154	greenContoursCount = 0;
5155	distCount = 0;
5156	coneCount = 0;
5157}
5158
5159function removeDispersionFeatures()
5160{
5161	refreshView();
5162}
5163
5164function clearNotes()
5165{
5166	var x = document.getElementById('notesdiv');
5167	x.innerHTML = '';
5168}
5169
5170function setNotes(courseID, hole)
5171{
5172	var s = getCourseHoleNotes(courseID, hole);
5173	s = s.replace(/z1z/g, "&");
5174	s = s.replace(/z2z/g, "'");
5175	s = s.replace(/z3z/g, '"');
5176	s = s.replace(/z4z/g, '|');
5177	var x = document.getElementById('notesdiv');
5178	x.innerHTML = s;
5179}
5180
5181function saveNotes()
5182{
5183	var courseID = currentCourseID();
5184	var hole = currentHole();
5185	var x = document.getElementById('notesdiv');
5186	var s = x.innerHTML;
5187	s = s.replace(/&/g, "z1z");
5188	s = s.replace(/'/g, "z2z");
5189	s = s.replace(/"/g, "z3z");
5190	s = s.replace(/\|/g, "z4z");
5191	setCourseHoleNotes(courseID, hole, s);
5192}
5193
5194function refreshView()
5195{
5196	var courseID = currentCourseID();
5197	var hole = currentHole();
5198	var shot = currentShot();
5199	var par = getCourseHolePar(courseID, hole);
5200
5201	removeAllFeatures();
5202	setDistDiv('');
5203	clearInfo();
5204	clearNotes();
5205	setInfo();
5206	setNotes(courseID, hole);
5207
5208	if (hole == 0)
5209	{
5210		addCenterLines();
5211	}
5212	else
5213	{
5214		var teeLat = getCourseHoleTeeLat(courseID, hole);
5215		var teeLon = getCourseHoleTeeLon(courseID, hole);
5216
5217		var pinLat = getCourseHolePinLat(courseID, hole);
5218		var pinLon = getCourseHolePinLon(courseID, hole);
5219
5220		var target1Lat = getCourseHoleTargetLat(courseID, hole, 1);
5221		var target1Lon = getCourseHoleTargetLon(courseID, hole, 1);
5222
5223		var target2Lat = getCourseHoleTargetLat(courseID, hole, 2);
5224		var target2Lon = getCourseHoleTargetLon(courseID, hole, 2);
5225
5226		var target3Lat = getCourseHoleTargetLat(courseID, hole, 3);
5227		var target3Lon = getCourseHoleTargetLon(courseID, hole, 3);
5228
5229		if (mapMode == 1)
5230			addCenterLine();
5231
5232		if (teeLat != 0 || teeLon != 0)
5233		{
5234			var bearing = -9999;
5235			if (target1Lat != 0 || target1Lon != 0) bearing = geogBearing(teeLat, teeLon, target1Lat, target1Lon);
5236			else if (pinLat != 0 || pinLon != 0) bearing = geogBearing(teeLat, teeLon, pinLat, pinLon);
5237			addMarker(teeLat, teeLon, true, false, false, 0, bearing);
5238		}
5239
5240		if (pinLat != 0 || pinLon != 0)
5241			addMarker(pinLat, pinLon, false, true, false, 0, 0);
5242
5243		if (target1Lat != 0 || target1Lon != 0)
5244			addMarker(target1Lat, target1Lon, false, false, shot >= 1, 3, 0);
5245
5246		if (target2Lat != 0 || target2Lon != 0)
5247			addMarker(target2Lat, target2Lon, false, false, shot >= 1, 1, 0);
5248
5249		if (target3Lat != 0 || target3Lon != 0)
5250			addMarker(target3Lat, target3Lon, false, false, shot >= 1, 1, 0);
5251	}
5252	if (mapMode == 1)
5253	{
5254		var n = getCourseHoleMeasures(courseID, hole);
5255		var i;
5256		for (i = 0; i < n; ++i)
5257		{
5258			var x = decodeMeasure(getCourseHoleMeasure(courseID, hole, i));
5259			addDist(x.lat1, x.lon1, x.lat2, x.lon2);
5260		}
5261	}
5262	var n = getCourseHoleSymbols(courseID, hole);
5263	if (n > 0)
5264	{
5265		var theta = 0 - getCourseHoleBearing(courseID, hole);
5266		for (i = 0; i < n; ++i)
5267		{
5268			var x = decodeSymbol(getCourseHoleSymbol(courseID, hole, i));
5269			addSymbol(x.lat, x.lon, x.symbol, theta);
5270		}
5271	}
5272	viewTimestamp = Date.now();
5273}
5274
5275function formatElevDiff(diff)
5276{
5277	if (diff < 0)
5278		return "&minus; " + Math.round(-diff).toString();
5279
5280	return "&plus; " + Math.round(diff).toString();
5281}
5282
5283function playsLikeDist(distMetres, elev1Metres, elev2Metres)
5284{
5285	// dist + height gain - 3% for each 1000m above sea level
5286	return (distMetres + elev2Metres - elev1Metres) * (1.0 - 0.00003 * elev1Metres);
5287}
5288
5289var elevationSource = 0;
5290var elevationSourceRef = "";
5291
5292function elevationRef()
5293{
5294	if (elevationSource == 1)
5295		return "(<a href='https://apps.nationalmap.gov/epqs/'>EPQS</a>)";
5296	else if (elevationSource == 2)
5297		return "(<a href='https://www.usgs.gov/centers/eros/science/usgs-eros-archive-digital-elevation-shuttle-radar-topography-mission-srtm-1-arc?qt-science_center_objects=0#qt-science_center_objects'>SRTM</a>)";
5298	else if (elevationSource == 3)
5299		return "(<a href='https://asterweb.jpl.nasa.gov/gdem.asp'>GDEM</a>)";
5300	else if (elevationSource == 99)
5301		return "(Mixed)";
5302	else if (elevationSource == 100)
5303		return elevationSourceRef;
5304	return "";
5305}
5306
5307function refreshElevation()
5308{
5309	if (elevationTimestamp == viewTimestamp)
5310	{
5311		window.setTimeout(refreshElevation, 1000);
5312		return;
5313	}
5314
5315	var courseID = currentCourseID();
5316	var hole = currentHole();
5317	bestClubID[1] = -1;
5318	bestClubID[2] = -1;
5319	bestClubID[3] = -1;
5320	bestClubID[4] = -1;
5321
5322	if (hole == 0)
5323	{
5324		var par = getCoursePar(courseID);
5325		var len = getCourseLength(courseID);
5326		if (par == 0 || len == 0)
5327		{
5328			setHelpInfo();
5329		}
5330		else
5331		{
5332			var frontNineDesc = "";
5333			var frontNinePar = 0;
5334			var frontNineLen = 0;
5335			var backNineDesc = "";
5336			var backNinePar = 0;
5337			var backNineLen = 0;
5338			for (var hole = 1; hole <= 18; ++hole)
5339			{
5340				var par = getCourseHolePar(courseID, hole);
5341				var len = getCourseHoleLength(courseID, hole);
5342				var desc = "";
5343				if (par >= 3) desc += par.toString();
5344				else desc += "&minus;";
5345				desc += "&nbsp;";
5346				if (hole <= 9)
5347				{
5348					frontNineDesc += desc;
5349					frontNinePar += par;
5350					frontNineLen += len;
5351				}
5352				else
5353				{
5354					backNineDesc += desc;
5355					backNinePar += par;
5356					backNineLen += len;
5357				}
5358			}
5359			if (frontNinePar > 0)
5360			{
5361				frontNineDesc += "= " + frontNinePar.toString() + " = " + Math.round(frontNineLen).toString();
5362				setInfoDiv2(frontNineDesc);
5363			}
5364			else
5365			{
5366				setInfoDiv2("&nbsp;");
5367			}
5368			if (backNinePar > 0)
5369			{
5370				backNineDesc += "= " + backNinePar.toString() + " = " + Math.round(backNineLen).toString();
5371				setInfoDiv3(backNineDesc);
5372			}
5373			else
5374			{
5375				setInfoDiv3("&nbsp;");
5376			}
5377			setInfoDiv4("&nbsp;");
5378		}
5379		elevationTimestamp = viewTimestamp;
5380		window.setTimeout(refreshElevation, 1000);
5381		return;
5382	}
5383
5384	var par = getCourseHolePar(courseID, hole);
5385
5386	if (par < 3)
5387	{
5388		elevationTimestamp = viewTimestamp;
5389		window.setTimeout(refreshElevation, 1000);
5390		return;
5391	}
5392
5393	if (par == 3)
5394	{
5395		var lat1 = getCourseHoleTeeLat(courseID, hole);
5396		var lon1 = getCourseHoleTeeLon(courseID, hole);
5397		var elev1 = lookupElevation(lat1, lon1);
5398
5399		var lat2 = getCourseHolePinLat(courseID, hole);
5400		var lon2 = getCourseHolePinLon(courseID, hole);
5401		var elev2 = lookupElevation(lat2, lon2);
5402
5403		if (elev1 == -1000000 || elev2 == -1000000)
5404		{
5405			var dist1 = geogDist(lat1, lon1, lat2, lon2);
5406			setInfoDiv2("Elevation: Not available");
5407			setInfoDiv3("Plays Like: Not available");
5408			var club1 = getBestClubForDist(dist1); var name1 = getClubShortName(club1); var perc1 = getClubDistPercentage(club1, dist1);
5409			setInfoDiv4(""
5410				+ name1 + " " + perc1.toString() + "%");
5411			elevationTimestamp = viewTimestamp;
5412			bestClubID[1] = club1;
5413		}
5414		else if (elev1 == -999999 || elev2 == -999999)
5415		{
5416			setInfoDiv2("&nbsp;");//"Elevation: Fetching...");
5417			setInfoDiv3("&nbsp;");//Plays Like:");
5418		}
5419		else
5420		{
5421			if (elev1 < elev2 - 6) elev1 += 3; else if (elev1 < elev2) elev1 += 0.5 * (elev2 - elev1);
5422			if (elev2 < elev1 - 4) elev2 += 2; else if (elev2 < elev1) elev2 += 0.5 * (elev1 - elev2);
5423			if (elev2 > elev1 + 4) elev2 -= 2; else if (elev2 > elev1) elev2 += 0.5 * (elev1 - elev2);
5424			var dist1 = playsLikeDist(geogDist(lat1, lon1, lat2, lon2), elev1, elev2);
5425			setInfoDiv2("Elevation: "
5426				+ Math.round(elev1).toString() + " "
5427				+ formatElevDiff(elev2-elev1) + " metres "
5428				+ elevationRef());
5429			setInfoDiv3("Plays Like: "
5430				+ Math.round(metresToMyUnits(dist1)).toString() + " " + myUnitName());
5431			var club1 = getBestClubForDist(dist1); var name1 = getClubShortName(club1); var perc1 = getClubDistPercentage(club1, dist1);
5432			setInfoDiv4(""
5433				+ name1 + " " + perc1.toString() + "%");
5434			elevationTimestamp = viewTimestamp;
5435			bestClubID[1] = club1;
5436		}
5437	}
5438	else if (par == 4)
5439	{
5440		var lat1 = getCourseHoleTeeLat(courseID, hole);
5441		var lon1 = getCourseHoleTeeLon(courseID, hole);
5442		var elev1 = lookupElevation(lat1, lon1);
5443
5444		var lat2 = getCourseHoleTargetLat(courseID, hole, 1);
5445		var lon2 = getCourseHoleTargetLon(courseID, hole, 1);
5446		var elev2 = lookupElevation(lat2, lon2);
5447
5448		var lat3 = getCourseHolePinLat(courseID, hole);
5449		var lon3 = getCourseHolePinLon(courseID, hole);
5450		var elev3 = lookupElevation(lat3, lon3);
5451
5452		if (elev1 == -1000000 || elev2 == -1000000 || elev3 == -1000000)
5453		{
5454			var dist1 = geogDist(lat1, lon1, lat2, lon2);
5455			var dist2 = geogDist(lat2, lon2, lat3, lon3);
5456			setInfoDiv2("Elevation: Not available");
5457			setInfoDiv3("Plays Like: Not available");
5458			var club1 = getBestClubForDist(dist1); var name1 = getClubShortName(club1); var perc1 = getClubDistPercentage(club1, dist1);
5459			var club2 = getBestClubForDist(dist2); var name2 = getClubShortName(club2); var perc2 = getClubDistPercentage(club2, dist2);
5460			setInfoDiv4(""
5461				+ name1 + " " + perc1.toString() + "% + "
5462				+ name2 + " " + perc2.toString() + "%");
5463			elevationTimestamp = viewTimestamp;
5464			bestClubID[1] = club1;
5465			bestClubID[2] = club2;
5466		}
5467		else if (elev1 == -999999 || elev2 == -999999 || elev3 == -999999)
5468		{
5469			setInfoDiv2("&nbsp;");//"Elevation: Fetching...");
5470			setInfoDiv3("&nbsp;");//Plays Like:");
5471		}
5472		else
5473		{
5474			if (elev1 < elev2 - 6) elev1 += 3; else if (elev1 < elev2) elev1 += 0.5 * (elev2 - elev1);
5475			if (elev2 < elev1 - 4) elev2 += 2; else if (elev2 < elev1) elev2 += 0.5 * (elev1 - elev2);
5476			if (elev2 > elev1 + 4) elev2 -= 2; else if (elev2 > elev1) elev2 += 0.5 * (elev1 - elev2);
5477			if (elev3 < elev1 - 4) elev3 += 2; else if (elev3 < elev1) elev3 += 0.5 * (elev1 - elev3);
5478			if (elev3 > elev1 + 4) elev3 -= 2; else if (elev3 > elev1) elev3 += 0.5 * (elev1 - elev3);
5479			var dist1 = playsLikeDist(geogDist(lat1, lon1, lat2, lon2), elev1, elev2);
5480			var dist2 = playsLikeDist(geogDist(lat2, lon2, lat3, lon3), elev2, elev3);
5481			setInfoDiv2("Elevation: "
5482				+ Math.round(elev1).toString() + " "
5483				+ formatElevDiff(elev2-elev1) + " "
5484				+ formatElevDiff(elev3-elev2) + " metres "
5485				+ elevationRef());
5486			setInfoDiv3("Plays Like: "
5487				+ Math.round(metresToMyUnits(dist1 + dist2)).toString() + " " + myUnitName() + " ("
5488				+ Math.round(metresToMyUnits(dist1)).toString() + " + "
5489				+ Math.round(metresToMyUnits(dist2)).toString() + ")");
5490			var club1 = getBestClubForDist(dist1); var name1 = getClubShortName(club1); var perc1 = getClubDistPercentage(club1, dist1);
5491			var club2 = getBestClubForDist(dist2); var name2 = getClubShortName(club2); var perc2 = getClubDistPercentage(club2, dist2);
5492			setInfoDiv4(""
5493				+ name1 + " " + perc1.toString() + "% + "
5494				+ name2 + " " + perc2.toString() + "%");
5495			elevationTimestamp = viewTimestamp;
5496			bestClubID[1] = club1;
5497			bestClubID[2] = club2;
5498		}
5499	}
5500	else if (par == 5)
5501	{
5502		var lat1 = getCourseHoleTeeLat(courseID, hole);
5503		var lon1 = getCourseHoleTeeLon(courseID, hole);
5504		var elev1 = lookupElevation(lat1, lon1);
5505
5506		var lat2 = getCourseHoleTargetLat(courseID, hole, 1);
5507		var lon2 = getCourseHoleTargetLon(courseID, hole, 1);
5508		var elev2 = lookupElevation(lat2, lon2);
5509
5510		var lat3 = getCourseHoleTargetLat(courseID, hole, 2);
5511		var lon3 = getCourseHoleTargetLon(courseID, hole, 2);
5512		var elev3 = lookupElevation(lat3, lon3);
5513
5514		var lat4 = getCourseHolePinLat(courseID, hole);
5515		var lon4 = getCourseHolePinLon(courseID, hole);
5516		var elev4 = lookupElevation(lat4, lon4);
5517
5518		if (elev1 == -1000000 || elev2 == -1000000 || elev3 == -1000000 || elev4 == -1000000)
5519		{
5520			var dist1 = geogDist(lat1, lon1, lat2, lon2);
5521			var dist2 = geogDist(lat2, lon2, lat3, lon3);
5522			var dist3 = geogDist(lat3, lon3, lat4, lon4);
5523			setInfoDiv2("Elevation: Not available");
5524			setInfoDiv3("Plays Like: Not available");
5525			var club1 = getBestClubForDist(dist1); var name1 = getClubShortName(club1); var perc1 = getClubDistPercentage(club1, dist1);
5526			var club2 = getBestClubForDist(dist2); var name2 = getClubShortName(club2); var perc2 = getClubDistPercentage(club2, dist2);
5527			var club3 = getBestClubForDist(dist3); var name3 = getClubShortName(club3); var perc3 = getClubDistPercentage(club3, dist3);
5528			setInfoDiv4(""
5529				+ name1 + " " + perc1.toString() + "% + "
5530				+ name2 + " " + perc2.toString() + "% + "
5531				+ name3 + " " + perc3.toString() + "%");
5532			elevationTimestamp = viewTimestamp;
5533			bestClubID[1] = club1;
5534			bestClubID[2] = club2;
5535			bestClubID[3] = club3;
5536		}
5537		else if (elev1 == -999999 || elev2 == -999999 || elev3 == -999999 || elev4 == -999999)
5538		{
5539			setInfoDiv2("&nbsp;");//"Elevation: Fetching...");
5540			setInfoDiv3("&nbsp;");//Plays Like:");
5541		}
5542		else
5543		{
5544			if (elev1 < elev2 - 6) elev1 += 3; else if (elev1 < elev2) elev1 += 0.5 * (elev2 - elev1);
5545			if (elev2 < elev1 - 4) elev2 += 2; else if (elev2 < elev1) elev2 += 0.5 * (elev1 - elev2);
5546			if (elev2 > elev1 + 4) elev2 -= 2; else if (elev2 > elev1) elev2 += 0.5 * (elev1 - elev2);
5547			if (elev3 < elev1 - 4) elev3 += 2;
5547 else if (elev3 < elev1) elev3 += 0.5 * (elev1 - elev3);
5548			if (elev3 > elev1 + 4) elev3 -= 2; else if (elev3 > elev1) elev3 += 0.5 * (elev1 - elev3);
5549			if (elev4 < elev1 - 4) elev4 += 2; else if (elev4 < elev1) elev4 += 0.5 * (elev1 - elev4);
5550			if (elev4 > elev1 + 4) elev4 -= 2; else if (elev4 > elev1) elev4 += 0.5 * (elev1 - elev4);
5551			var dist1 = playsLikeDist(geogDist(lat1, lon1, lat2, lon2), elev1, elev2);
5552			var dist2 = playsLikeDist(geogDist(lat2, lon2, lat3, lon3), elev2, elev3);
5553			var dist3 = playsLikeDist(geogDist(lat3, lon3, lat4, lon4), elev3, elev4);
5554			setInfoDiv2("Elevation: "
5555				+ Math.round(elev1).toString() + " "
5556				+ formatElevDiff(elev2-elev1) + " "
5557				+ formatElevDiff(elev3-elev2) + " "
5558				+ formatElevDiff(elev4-elev3) + " metres "
5559				+ elevationRef());
5560			setInfoDiv3("Plays Like: "
5561				+ Math.round(metresToMyUnits(dist1 + dist2 + dist3)).toString() + " " + myUnitName() + " ("
5562				+ Math.round(metresToMyUnits(dist1)).toString() + " + "
5563				+ Math.round(metresToMyUnits(dist2)).toString() + " + "
5564				+ Math.round(metresToMyUnits(dist3)).toString() + ")");
5565			var club1 = getBestClubForDist(dist1); var name1 = getClubShortName(club1); var perc1 = getClubDistPercentage(club1, dist1);
5566			var club2 = getBestClubForDist(dist2); var name2 = getClubShortName(club2); var perc2 = getClubDistPercentage(club2, dist2);
5567			var club3 = getBestClubForDist(dist3); var name3 = getClubShortName(club3); var perc3 = getClubDistPercentage(club3, dist3);
5568			setInfoDiv4(""
5569				+ name1 + " " + perc1.toString() + "% + "
5570				+ name2 + " " + perc2.toString() + "% + "
5571				+ name3 + " " + perc3.toString() + "%");
5572			elevationTimestamp = viewTimestamp;
5573			bestClubID[1] = club1;
5574			bestClubID[2] = club2;
5575			bestClubID[3] = club3;
5576		}
5577	}
5578	else if (par == 6)
5579	{
5580		var lat1 = getCourseHoleTeeLat(courseID, hole);
5581		var lon1 = getCourseHoleTeeLon(courseID, hole);
5582		var elev1 = lookupElevation(lat1, lon1);
5583
5584		var lat2 = getCourseHoleTargetLat(courseID, hole, 1);
5585		var lon2 = getCourseHoleTargetLon(courseID, hole, 1);
5586		var elev2 = lookupElevation(lat2, lon2);
5587
5588		var lat3 = getCourseHoleTargetLat(courseID, hole, 2);
5589		var lon3 = getCourseHoleTargetLon(courseID, hole, 2);
5590		var elev3 = lookupElevation(lat3, lon3);
5591
5592		var lat4 = getCourseHoleTargetLat(courseID, hole, 3);
5593		var lon4 = getCourseHoleTargetLon(courseID, hole, 3);
5594		var elev4 = lookupElevation(lat4, lon4);
5595
5596		var lat5 = getCourseHolePinLat(courseID, hole);
5597		var lon5 = getCourseHolePinLon(courseID, hole);
5598		var elev5 = lookupElevation(lat5, lon5);
5599
5600		if (elev1 == -1000000 || elev2 == -1000000 || elev3 == -1000000 || elev4 == -1000000 || elev5 == -1000000)
5601		{
5602			var dist1 = geogDist(lat1, lon1, lat2, lon2);
5603			var dist2 = geogDist(lat2, lon2, lat3, lon3);
5604			var dist3 = geogDist(lat3, lon3, lat4, lon4);
5605			var dist4 = geogDist(lat4, lon4, lat5, lon5);
5606			setInfoDiv2("Elevation: Not available");
5607			setInfoDiv3("Plays Like: Not available");
5608			var club1 = getBestClubForDist(dist1); var name1 = getClubShortName(club1); var perc1 = getClubDistPercentage(club1, dist1);
5609			var club2 = getBestClubForDist(dist2); var name2 = getClubShortName(club2); var perc2 = getClubDistPercentage(club2, dist2);
5610			var club3 = getBestClubForDist(dist3); var name3 = getClubShortName(club3); var perc3 = getClubDistPercentage(club3, dist3);
5611			var club4 = getBestClubForDist(dist4); var name4 = getClubShortName(club4); var perc4 = getClubDistPercentage(club4, dist4);
5612			setInfoDiv4(""
5613				+ name1 + " " + perc1.toString() + "% + "
5614				+ name2 + " " + perc2.toString() + "% + "
5615				+ name3 + " " + perc3.toString() + "% + "
5616				+ name4 + " " + perc4.toString() + "%");
5617			elevationTimestamp = viewTimestamp;
5618			bestClubID[1] = club1;
5619			bestClubID[2] = club2;
5620			bestClubID[3] = club3;
5621			bestClubID[4] = club4;
5622		}
5623		else if (elev1 == -999999 || elev2 == -999999 || elev3 == -999999 || elev4 == -999999 || elev5 == -999999)
5624		{
5625			setInfoDiv2("&nbsp;");//"Elevation: Fetching...");
5626			setInfoDiv3("&nbsp;");//Plays Like:");
5627		}
5628		else
5629		{
5630			if (elev1 < elev2 - 6) elev1 += 3; else if (elev1 < elev2) elev1 += 0.5 * (elev2 - elev1);
5631			if (elev2 < elev1 - 4) elev2 += 2;
5631 else if (elev2 < elev1) elev2 += 0.5 * (elev1 - elev2);
5632			if (elev2 > elev1 + 4) elev2 -= 2; else if (elev2 > elev1) elev2 += 0.5 * (elev1 - elev2);
5633			if (elev3 < elev1 - 4) elev3 += 2; else if (elev3 < elev1) elev3 += 0.5 * (elev1 - elev3);
5634			if (elev3 > elev1 + 4) elev3 -= 2; else if (elev3 > elev1) elev3 += 0.5 * (elev1 - elev3);
5635			if (elev4 < elev1 - 4) elev4 += 2; else if (elev4 < elev1) elev4 += 0.5 * (elev1 - elev4);
5636			if (elev4 > elev1 + 4) elev4 -= 2; else if (elev4 > elev1) elev4 += 0.5 * (elev1 - elev4);
5637			if (elev5 < elev1 - 4) elev5 += 2; else if (elev5 < elev1) elev5 += 0.5 * (elev1 - elev5);
5638			if (elev5 > elev1 + 4) elev5 -= 2; else if (elev5 > elev1) elev5 += 0.5 * (elev1 - elev5);
5639			var dist1 = playsLikeDist(geogDist(lat1, lon1, lat2, lon2), elev1, elev2);
5640			var dist2 = playsLikeDist(geogDist(lat2, lon2, lat3, lon3), elev2, elev3);
5641			var dist3 = playsLikeDist(geogDist(lat3, lon3, lat4, lon4), elev3, elev4);
5642			var dist4 = playsLikeDist(geogDist(lat4, lon4, lat5, lon5), elev4, elev5);
5643			setInfoDiv2("Elevation: "
5644				+ Math.round(elev1).toString() + " "
5645				+ formatElevDiff(elev2-elev1) + " "
5646				+ formatElevDiff(elev3-elev2) + " "
5647				+ formatElevDiff(elev4-elev3) + " "
5648				+ formatElevDiff(elev5-elev4) + " metres "
5649				+ elevationRef());
5650			setInfoDiv3("Plays Like: "
5651				+ Math.round(metresToMyUnits(dist1 + dist2 + dist3 + dist4)).toString() + " " + myUnitName() + " ("
5652				+ Math.round(metresToMyUnits(dist1)).toString() + " + "
5653				+ Math.round(metresToMyUnits(dist2)).toString() + " + "
5654				+ Math.round(metresToMyUnits(dist3)).toString() + " + "
5655				+ Math.round(metresToMyUnits(dist4)).toString() + ")");
5656			var club1 = getBestClubForDist(dist1); var name1 = getClubShortName(club1); var perc1 = getClubDistPercentage(club1, dist1);
5657			var club2 = getBestClubForDist(dist2); var name2 = getClubShortName(club2); var perc2 = getClubDistPercentage(club2, dist2);
5658			var club3 = getBestClubForDist(dist3); var name3 = getClubShortName(club3); var perc3 = getClubDistPercentage(club3, dist3);
5659			var club4 = getBestClubForDist(dist4); var name4 = getClubShortName(club4); var perc4 = getClubDistPercentage(club4, dist4);
5660			setInfoDiv4(""
5661				+ name1 + " " + perc1.toString() + "% + "
5662				+ name2 + " " + perc2.toString() + "% + "
5663				+ name3 + " " + perc3.toString() + "% + "
5664				+ name4 + " " + perc4.toString() + "%");
5665			elevationTimestamp = viewTimestamp;
5666			bestClubID[1] = club1;
5667			bestClubID[2] = club2;
5668			bestClubID[3] = club3;
5669			bestClubID[4] = club4;
5670		}
5671	}
5672	window.setTimeout(refreshElevation, 1000);
5673}
5674
5675function lookupElevation(lat, lon)
5676{
5677	elevationSource = 0;
5678	var e = -9999;
5679	if (terrainMode == 2 && terrain1SourceID >= 1) // Lidar
5680	{
5681		e = getProVisualizerTerrain1Height(lat, lon);
5682		if (e > -9999)
5683		{
5684			elevationSource = 100;
5685			elevationSourceRef = "(<a href="+terrainURLs[terrain1SourceID]+">Lidar</a>)";
5686			return e;
5687		}
5688	}
5689	e = elevationOverride(lat, lon);
5690	if (e > -9999)
5691	{
5692		elevationSource = 99;
5693		return e;
5694	}
5695	var dLat = distToDeltaLat(3, lat, lon);
5696	var dLon = distToDeltaLon(3, lat, lon);
5697	var lat1 = lat - dLat;
5698	var lat2 = lat + dLat;
5699	var lon1 = lon - dLon;
5700	var lon2 = lon + dLon;
5701	var lat3 = lat - dLat*1000;
5702	var lat4 = lat + dLat*1000;
5703	var lon3 = lon - dLon*1000;
5704	var lon4 = lon + dLon*1000;
5705	var elev = null;
5706	var minReqTimestamp = Date.now() - 10000;
5707	var i = 0;
5708	while (elev == null && i < elevation.length)
5709	{
5710		var eLat = elevationLat[i];
5711		var eLon = elevationLon[i];
5712		var e = elevation[i];
5713		if (e == -1000000)
5714		{
5715			if (eLat >= lat3 && eLat <= lat4 && eLon >= lon3 && eLon <= lon4)
5716				elev = e;
5717		}
5718		else
5719		{
5720			if (eLat >= lat1 && eLat <= lat2 && eLon >= lon1 && eLon <= lon2)
5721			{
5722				elev = e;
5723				if (elev == -999999 && elevationReqTimestamp[i] < minReqTimestamp)
5724				{
5725					elevation[i] = -1000000;
5726					elev = -1000000;
5727				}
5728			}
5729		}
5730		i += 1;
5731	}
5732	if (elev == null)
5733	{
5734		fetchElevation(lat, lon);
5735		return -999999; // fetching
5736	}
5737	else if (elev == -999999)
5738	{
5739		return elev; // fetching
5740	}
5741	else if (elev < -1000 || elev > 5000)
5742	{
5743		return lookupElevation2(lat, lon);
5744	}
5745	elevationSource = 1;
5746	return elev;
5747}
5748
5749function lookupElevation2(lat, lon)
5750{
5751	var dLat = distToDeltaLat(3, lat, lon);
5752	var dLon = distToDeltaLon(3, lat, lon);
5753	var lat1 = lat - dLat;
5754	var lat2 = lat + dLat;
5755	var lon1 = lon - dLon;
5756	var lon2 = lon + dLon;
5757	var lat3 = lat - dLat*1000;
5758	var lat4 = lat + dLat*1000;
5759	var lon3 = lon - dLon*1000;
5760	var lon4 = lon + dLon*1000;
5761	var elev = null;
5762	var i = 0;
5763	while (elev == null && i < elevation2.length)
5764	{
5765		var eLat = elevation2Lat[i];
5766		var eLon = elevation2Lon[i];
5767		var e = elevation2[i];
5768		if (e == -1000000)
5769		{
5770			if (eLat >= lat3 && eLat <= lat4 && eLon >= lon3 && eLon <= lon4)
5771				elev = e;
5772		}
5773		else
5774		{
5775			if (eLat >= lat1 && eLat <= lat2 && eLon >= lon1 && eLon <= lon2)
5776				elev = e;
5777		}
5778		i += 1;
5779	}
5780	if (elev == null)
5781	{
5782		fetchElevation2(lat, lon);
5783		return -999999; // fetching
5784	}
5785	else if (elev == -999999)
5786	{
5787		return elev; // fetching
5788	}
5789	else if (elev < -1000 || elev > 5000)
5790	{
5791		return -1000000; // not available
5792	}
5793	elevationSource = 2;
5794	if (lat > 60 || lat < -56) elevationSource = 3;
5795	return elev;
5796}
5797
5798function fetchElevation(lat, lon)
5799{
5800	var i = elevation.length;
5801	elevationReqTimestamp[i] = Date.now();
5802	elevationLat[i] = lat;
5803	elevationLon[i] = lon;
5804	elevation[i] = -999999; // fetching
5805	if (lon > -20)
5806	{
5807		elevation[i] = -1000000;
5808	}
5809	else if (window.XMLHttpRequest)
5810	{
5811		var req = "https://epqs.nationalmap.gov/v1/json?x=" + lon.toString() + "&y=" + lat.toString() + "&units=Meters&wkid=4326&includeDate=False";
5812		var xhr = new XMLHttpRequest();
5813		xhr.onreadystatechange = function()
5814		{
5815			if (xhr.readyState == 4 && xhr.status == 200)
5816			{
5817				if (xhr.responseText == "")
5818				{
5819					elevation[i] = -1000000;
5820				}
5821				else
5822				{
5823					var obj = JSON.parse(xhr.responseText);
5824					elevation[i] = Math.round(parseFloat(obj.value));
5825				}
5826			}
5827		}
5828		xhr.open("GET", req, true);
5829		xhr.send();
5830	}
5831}
5832
5833function fetchElevation2(lat, lon)
5834{
5835	var i = elevation2.length;
5836	elevation2Lat[i] = lat;
5837	elevation2Lon[i] = lon;
5838	elevation2[i] = -999999; // fetching
5839	if (window.XMLHttpRequest)
5840	{
5841		var req = "/elevation.php?coords=";
5842		req += Math.round(lat * 111111).toString() + ",";
5843		req += Math.round(lon * 111111).toString();
5844		var xhr = new XMLHttpRequest();
5845		xhr.onreadystatechange = function()
5846		{
5847			if (xhr.readyState == 4 && xhr.status == 200)
5848			{
5849				if (xhr.responseText.length > 5)
5850				{
5851					eval(xhr.responseText);
5852					if (elevHeightsMetres.length == 1)
5853						elevation2[i] = elevHeightsMetres[0];
5854					else if (elevHeightsFeet.length == 1)
5855						elevation2[i] = elevHeightsFeet[0] * (1609.344 / (3*1760));
5856					else
5857						elevation2[i] = -1000000;
5858				}
5859				else
5860				{
5861					elevation2[i] = -1000000;
5862				}
5863			}
5864		}
5865		xhr.open("GET", req, true);
5866		xhr.send();
5867	}
5868}
5869
5870function spreadHandler()
5871{
5872	dispersionHandler(true);
5873}
5874
5875var showPuttDispersion = 0; // 0 = off, 1 = dispersion, 2 = spread
5876
5877function dispersionHandler(showBox = false)
5878{
5879	setMode(0);
5880	if (greenSlopesCount > 0 && puttHistoryOp.length > 0)
5881	{
5882		if (showBox && showPuttDispersion != 2)
5883		{
5884			showPuttDispersion = 2;
5885			selectGreenSpeedHandler();
5886		}
5887		if (!showBox && showPuttDispersion != 1)
5888		{
5889			showPuttDispersion = 1;
5890			selectGreenSpeedHandler();
5891		}
5892		return;
5893	}
5894	var courseID = currentCourseID();
5895	var hole = currentHole();
5896	var dispersionProfileID = getDispersionProfileID();
5897	if (hole < 1)
5898	{
5899		alert("Please select hole.");
5900		return;
5901	}
5902	var teeLat = getCourseHoleTeeLat(courseID, hole);
5903	var teeLon = getCourseHoleTeeLon(courseID, hole);
5904	if (teeLat == 0 && teeLon == 0)
5905	{
5906		alert("Please set tee position.");
5907		return;
5908	}
5909	var pinLat = getCourseHolePinLat(courseID, hole);
5910	var pinLon = getCourseHolePinLon(courseID, hole);
5911	if (pinLat == 0 && pinLon == 0)
5912	{
5913		alert("Please set pin position.");
5914		return;
5915	}
5916	var par = getCourseHolePar(courseID, hole);
5917	if (par < 3)
5918	{
5919		alert("Please set tee and pin position.");
5920		return;
5921	}
5922
5923	var target1Lat = getCourseHoleTargetLat(courseID, hole, 1);
5924	var target1Lon = getCourseHoleTargetLon(courseID, hole, 1);
5925
5926	var target2Lat = getCourseHoleTargetLat(courseID, hole, 2);
5927	var target2Lon = getCourseHoleTargetLon(courseID, hole, 2);
5928
5929	var target3Lat = getCourseHoleTargetLat(courseID, hole, 3);
5930	var target3Lon = getCourseHoleTargetLon(courseID, hole, 3);
5931
5932	if (dispersionProfileID <= 10)
5933	{
5934	// Pro+X%
5935	var nrl = getNRL();
5936	var dispersionMultiplier = 1.0 + 0.1 * dispersionProfileID;
5937	var dispersionMultiplier2 = 1.0 + 0.05 * dispersionProfileID;
5938	for (var i = 0; i < 100; ++i)
5939	{
5940		var lat1 = teeLat;
5941		var lon1 = teeLon;
5942
5943		for (var shot = 1; shot <= par - 2; ++shot)
5944		{
5945			var lat0 = teeLat;
5946			var lon0 = teeLon;
5947			if (shot == 2)
5948			{
5949				lat0 = target1Lat;
5950				lon0 = target1Lon;
5951			}
5952			else if (shot == 3)
5953			{
5954				lat0 = target2Lat;
5955				lon0 = target2Lon;
5956			}
5957			else if (shot == 4)
5958			{
5959				lat0 = target3Lat;
5960				lon0 = target3Lon;
5961			}
5962			var lat2 = 0;
5963			var lon2 = 0;
5964			if (shot == par - 2)
5965			{
5966				lat2 = pinLat;
5967				lon2 = pinLon;
5968			}
5969			else if (shot == 1)
5970			{
5971				lat2 = target1Lat;
5972				lon2 = target1Lon;
5973			}
5974			else if (shot == 2)
5975			{
5976				lat2 = target2Lat;
5977				lon2 = target2Lon;
5978			}
5979			else if (shot == 3)
5980			{
5981				lat2 = target3Lat;
5982				lon2 = target3Lon;
5983			}
5984			var nominalDist = geogDist(lat0, lon0, lat2, lon2);
5985			var dist = geogDist(lat1, lon1, lat2, lon2);
5986			var bearing = geogBearing(lat1, lon1, lat2, lon2);
5987
5988			// Distance Error
5989			// -8% to +6%
5990			var x = (Math.random() + Math.random() + Math.random() + Math.random()) / 4; // Normal distribution 0 .. 1
5991			x = 14*x - 7; // Normal distribution -7 .. +7
5992			if (dist < 90) x *= 1 + (90 - dist)/45; // Distance error gradually increases below 90 metres
5993			x = x - 1; // Normal distribution -8 .. +6
5994			x = x*dist/100; // Normal distribution -8% .. +6%
5995			if (x < 0) x *= dispersionMultiplier;
5996			else x *= dispersionMultiplier2;
5997
5998			// Direction Error
5999			// +/- 3/5/7 degrees at 100/200/300 yards
6000			var max = 1 + 2*dist/90; // 3/5/7 at 100/200/300 yards
6001			var y = (Math.random() + Math.random() + Math.random() + Math.random()) / 4; // Normal distribution 0 .. 1
6002			y = 2*max*y - max; // Normal distribution -max .. +max
6003			y *= dispersionMultiplier;
6004			if (nrl == 1)
6005			{
6006				if (y > 0) x -= 0.3*dist*y*Math.PI/180;
6007				else       x -= 0.1*dist*y*Math.PI/180;
6008			}
6009			if (nrl == 2)
6010			{
6011				if (y > 0) x += 0.1*dist*y*Math.PI/180;
6012				else       x += 0.3*dist*y*Math.PI/180;
6013			}
6014
6015			var dist2 = dist + x;
6016			var bearing2 = bearing + y;
6017			var lat3 = moveLat(lat1, lon1, dist2, bearing2);
6018			var lon3 = moveLon(lat1, lon1, dist2, bearing2);
6019
6020			// Wind and bounce
6021			// Up to 5% in random direction
6022			var z = (Math.random() + Math.random() + Math.random() + Math.random()) / 4; // Normal distribution 0 .. 1
6023			z = 10*z - 5; // Normal distribution -5 .. +5
6024			var dist3 = z*dist/100; // Normal distribution -5% .. +5%
6025			var bearing3 = 360 * Math.random();
6026			var lat4 = moveLat(lat3, lon3, dist3, bearing3);
6027			var lon4 = moveLon(lat3, lon3, dist3, bearing3);
6028
6029			if (!showBox && nominalDist > 10.0)
6030				addDispersionMarker(lat4, lon4);
6031
6032			lat1 = lat4;
6033			lon1 = lon4;
6034		}
6035	}
6036	}
6037	else
6038	{
6039	// Custom
6040	var lat1 = teeLat;
6041	var lon1 = teeLon;
6042
6043	for (var shot = 1; shot <= par - 2; ++shot)
6044	{
6045		var lat0 = teeLat;
6046		var lon0 = teeLon;
6047		if (shot == 2)
6048		{
6049			lat0 = target1Lat;
6050			lon0 = target1Lon;
6051		}
6052		else if (shot == 3)
6053		{
6054			lat0 = target2Lat;
6055			lon0 = target2Lon;
6056		}
6057		else if (shot == 4)
6058		{
6059			lat0 = target3Lat;
6060			lon0 = target3Lon;
6061		}
6062		var lat2 = 0;
6063		var lon2 = 0;
6064		if (shot == par - 2)
6065		{
6066			lat2 = pinLat;
6067			lon2 = pinLon;
6068		}
6069		else if (shot == 1)
6070		{
6071			lat2 = target1Lat;
6072			lon2 = target1Lon;
6073		}
6074		else if (shot == 2)
6075		{
6076			lat2 = target2Lat;
6077			lon2 = target2Lon;
6078		}
6079		else if (shot == 3)
6080		{
6081			lat2 = target3Lat;
6082			lon2 = target3Lon;
6083		}
6084		var nominalDist = geogDist(lat0, lon0, lat2, lon2);
6085		var dist = geogDist(lat1, lon1, lat2, lon2);
6086		var bearing = geogBearing(lat1, lon1, lat2, lon2);
6087		var clubID = getBestClubForDist(dist);
6088		var clubPercent = getClubDistPercentage(clubID, dist, false);
6089		var clubSpreadL = myUnitsToMetres(getClubSpreadL(clubID)) * (clubPercent/100.0);
6090		var clubSpreadR = myUnitsToMetres(getClubSpreadR(clubID)) * (clubPercent/100.0);
6091		var clubSpreadO = myUnitsToMetres(getClubSpreadO(clubID)) * (clubPercent/100.0);
6092		var clubSpreadS = myUnitsToMetres(getClubSpreadS(clubID)) * (clubPercent/100.0);
6093		var clubSpreadOL = clubSpreadO;
6094		var clubSpreadOR = clubSpreadO;
6095		var clubSpreadSL = clubSpreadS;
6096		var clubSpreadSR = clubSpreadS;
6097		var clubShape = getClubShape(clubID);
6098		if (clubShape ==   2) { clubSpreadOL += 0.20*clubSpreadL; clubSpreadOR -= 0.20*clubSpreadR;
6099					clubSpreadSL -= 0.20*clubSpreadL; clubSpreadSR += 0.20*clubSpreadR; }
6100		if (clubShape ==   1) { clubSpreadOL += 0.10*clubSpreadL; clubSpreadOR -= 0.10*clubSpreadR;
6101					clubSpreadSL -= 0.10*clubSpreadL; clubSpreadSR += 0.10*clubSpreadR; }
6102		if (clubShape ==  -1) { clubSpreadOL -= 0.10*clubSpreadL; clubSpreadOR += 0.10*clubSpreadR;
6103					clubSpreadSL += 0.10*clubSpreadL; clubSpreadSR -= 0.10*clubSpreadR; }
6104		if (clubShape ==  -2) { clubSpreadOL -= 0.20*clubSpreadL; clubSpreadOR += 0.20*clubSpreadR;
6105					clubSpreadSL += 0.20*clubSpreadL; clubSpreadSR -= 0.20*clubSpreadR; }
6106		if (dist < 90)
6107		{
6108			// Distance error gradually increases below 90 metres
6109			clubSpreadO *= 1 + (90 - dist)/45;
6110			clubSpreadS *= 1 + (90 - dist)/45;
6111		}
6112		for (var i = 0; i < 100; ++i)
6113		{
6114			// Distance Error
6115			var x = (Math.random() + Math.random() + Math.random()) / 3; // Normal distribution 0 .. 1
6116			x = 2*x - 1; // Normal distribution -1 .. +1
6117			if (x < 0) x *= clubSpreadS;
6118			if (x > 0) x *= clubSpreadO;
6119			x *= 1.3;
6120
6121			// Direction Error
6122			var y = (Math.random() + Math.random() + Math.random()) / 3; // Normal distribution 0 .. 1
6123			y = 2*y - 1; // Normal distribution -1 .. +1
6124			if (y < 0) y *= clubSpreadL;
6125			if (y > 0) y *= clubSpreadR;
6126			y *= 1.3;
6127			if (clubShape ==  2) { if (y > 0) x -= 0.30*y; else x -= 0.10*y; }
6128			if (clubShape ==  1) { if (y > 0) x -= 0.15*y; else x -= 0.05*y; }
6129			if (clubShape == -1) { if (y < 0) x += 0.15*y; else x += 0.05*y; }
6130			if (clubShape == -2) { if (y < 0) x += 0.30*y; else x += 0.10*y; }
6131
6132			var dist2 = dist + x;
6133			var bearing2 = bearing + (y/dist)*(180.0/Math.PI);
6134			var lat3 = moveLat(lat1, lon1, dist2, bearing2);
6135			var lon3 = moveLon(lat1, lon1, dist2, bearing2);
6136
6137			// Wind and bounce
6138			// Up to 2m in a random direction
6139			var z = (Math.random() + Math.random() + Math.random()) / 3; // Normal distribution 0 .. 1
6140			var dist3 = 4*z - 2; // Normal distribution -2 .. +2
6141			var bearing3 = 360 * Math.random();
6142			var lat4 = moveLat(lat3, lon3, dist3, bearing3);
6143			var lon4 = moveLon(lat3, lon3, dist3, bearing3);
6144
6145			if (!showBox && nominalDist > 10.0)
6146				addDispersionMarker(lat4, lon4);
6147		}
6148		lat1 = lat2;
6149		lon1 = lon2;
6150	}
6151	}
6152	if (showBox)
6153	{
6154		// Draw dispersion boxes...
6155		for (var shot = 1; shot <= par - 2; ++shot)
6156		{
6157			var lat1 = teeLat;
6158			var lon1 = teeLon;
6159			if (shot == 2)
6160			{
6161				lat1 = target1Lat;
6162				lon1 = target1Lon;
6163			}
6164			else if (shot == 3)
6165			{
6166				lat1 = target2Lat;
6167				lon1 = target2Lon;
6168			}
6169			else if (shot == 4)
6170			{
6171				lat1 = target3Lat;
6172				lon1 = target3Lon;
6173			}
6174			var lat2 = 0;
6175			var lon2 = 0;
6176			if (shot == par - 2)
6177			{
6178				lat2 = pinLat;
6179				lon2 = pinLon;
6180			}
6181			else if (shot == 1)
6182			{
6183				lat2 = target1Lat;
6184				lon2 = target1Lon;
6185			}
6186			else if (shot == 2)
6187			{
6188				lat2 = target2Lat;
6189				lon2 = target2Lon;
6190			}
6191			else if (shot == 3)
6192			{
6193				lat2 = target3Lat;
6194				lon2 = target3Lon;
6195			}
6196			var dist = geogDist(lat1, lon1, lat2, lon2);
6197			if (dist > 10.0)
6198			{
6199				var bearing = geogBearing(lat1, lon1, lat2, lon2);
6200				var clubSpreadL = 0.10*dist;
6201				var clubSpreadR = 0.10*dist;
6202				var clubSpreadO = 0.06*dist;
6203				var clubSpreadS = 0.08*dist;
6204				var clubShape = 0;
6205				if (dispersionProfileID <= 10)
6206				{
6207					// Pro+X%
6208					var k  = 0.8;
6209					var tO = (dist > 275.0 ? 0.0 : (1.0 - dist/275.0));
6210					var kL = 1.0 + 0.08 * dispersionProfileID;
6211					var kR = 1.0 + 0.08 * dispersionProfileID;
6212					var kO = 1.0 + 0.08 * dispersionProfileID * tO;
6213					var kS = 1.0 + 0.08 * dispersionProfileID;
6214					clubSpreadLRDegrees = 1 + 2*dist/90;
6215					if (clubSpreadLRDegrees < 3) clubSpreadLRDegrees = 3;
6216					clubSpreadLRRadians = clubSpreadLRDegrees*(Math.PI/180.0);
6217					clubSpreadL = k*kL*clubSpreadLRRadians*dist;
6218					clubSpreadR = k*kR*clubSpreadLRRadians*dist;
6219					clubSpreadO = k*kO*0.06*dist;
6220					clubSpreadS = k*kS*0.09*dist;
6221					if (dist < 90)
6222					{
6223						// Error gradually increases below 90 metres
6224						clubSpreadL *= 1 + (90 - dist)/90;
6225						clubSpreadR *= 1 + (90 - dist)/90;
6226						clubSpreadO *= 1 + (90 - dist)/90;
6227						clubSpreadS *= 1 + (90 - dist)/90;
6228					}
6229					var nrl = getNRL();
6230					if (nrl == 1) clubShape = +2;
6231					if (nrl == 2) clubShape = -2;
6232				}
6233				else
6234				{
6235					// Custom
6236					var clubID = getBestClubForDist(dist);
6237					var clubPercent = getClubDistPercentage(clubID, dist, false);
6238					clubSpreadL = myUnitsToMetres(getClubSpreadL(clubID)) * (clubPercent/100.0);
6239					clubSpreadR = myUnitsToMetres(getClubSpreadR(clubID)) * (clubPercent/100.0);
6240					clubSpreadO = myUnitsToMetres(getClubSpreadO(clubID)) * (clubPercent/100.0);
6241					clubSpreadS = myUnitsToMetres(getClubSpreadS(clubID)) * (clubPercent/100.0);
6242					if (dist < 90)
6243					{
6244						// Distance error gradually increases below 90 metres
6245						clubSpreadO *= 1 + (90 - dist)/90;
6246						clubSpreadS *= 1 + (90 - dist)/90;
6247					}
6248					clubShape = getClubShape(clubID);
6249				}
6250				var clubSpreadOL = clubSpreadO;
6251				var clubSpreadOR = clubSpreadO;
6252				var clubSpreadSL = clubSpreadS;
6253				var clubSpreadSR = clubSpreadS;
6254				if (clubShape ==   2) { clubSpreadOL += 0.20*clubSpreadL; clubSpreadOR -= 0.20*clubSpreadR;
6255							clubSpreadSL -= 0.20*clubSpreadL; clubSpreadSR += 0.20*clubSpreadR; }
6256				if (clubShape ==   1) { clubSpreadOL += 0.10*clubSpreadL; clubSpreadOR -= 0.10*clubSpreadR;
6257							clubSpreadSL -= 0.10*clubSpreadL; clubSpreadSR += 0.10*clubSpreadR; }
6258				if (clubShape ==  -1) { clubSpreadOL -= 0.10*clubSpreadL; clubSpreadOR += 0.10*clubSpreadR;
6259							clubSpreadSL += 0.10*clubSpreadL; clubSpreadSR -= 0.10*clubSpreadR; }
6260				if (clubShape ==  -2) { clubSpreadOL -= 0.20*clubSpreadL; clubSpreadOR += 0.20*clubSpreadR;
6261							clubSpreadSL += 0.20*clubSpreadL; clubSpreadSR -= 0.20*clubSpreadR; }
6262				// Draw box around dispersion...
6263				var brightness = 0.03 * getCircleBrightness();
6264				var c = getCircleColor();
6265				var color = new Cesium.Color(1,1,0,brightness);
6266				if (c == 1) color = new Cesium.Color(0.5,1.0,0.5,brightness);
6267				if (c == 2) color = new Cesium.Color(0.5,1.0,1.0,brightness);
6268				if (c == 3) color = new Cesium.Color(1.0,1.0,1.0,brightness);
6269				var bearingLO = bearing - (clubSpreadL/dist)*(180.0/Math.PI);
6270				var bearingRO = bearing + (clubSpreadR/dist)*(180.0/Math.PI);
6271				var bearingLS = bearing - (clubSpreadL/dist)*(180.0/Math.PI);
6272				var bearingRS = bearing + (clubSpreadR/dist)*(180.0/Math.PI);
6273				var coords = [];
6274				var bb = 5000;
6275				var n = 1 + Math.round(clubSpreadL);
6276				for (var i = 0; i <= n; ++i)
6277				{
6278					var d = dist+clubSpreadO+(i/n)*(clubSpreadOL-clubSpreadO);
6279					var b = bearing+(i/n)*(bearingLO-bearing);
6280					d -= d*(b-bearing)*(b-bearing)/bb;
6281					var lat3 = moveLat(lat1, lon1, d, b);
6282					var lon3 = moveLon(lat1, lon1, d, b);
6283					var h3 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat3, lon3));
6284					coords.push(Cesium.Cartesian3.fromDegrees(lon3, lat3, h3));
6285				}
6286				var n = 1 + Math.round(clubSpreadO+clubSpreadS);
6287				for (var i = 1; i <= n; ++i)
6288				{
6289					var d = dist+clubSpreadOL-(i/n)*(clubSpreadSL+clubSpreadOL);
6290					var b = bearingLO+(i/n)*(bearingLS-bearingLO);
6291					d -= d*(b-bearing)*(b-bearing)/bb;
6292					var lat3 = moveLat(lat1, lon1, d, b);
6293					var lon3 = moveLon(lat1, lon1, d, b);
6294					var h3 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat3, lon3));
6295					coords.push(Cesium.Cartesian3.fromDegrees(lon3, lat3, h3));
6296				}
6297				var n = 1 + Math.round(clubSpreadL);
6298				for (var i = 1; i <= n; ++i)
6299				{
6300					var d = dist-clubSpreadSL-(i/n)*(clubSpreadS-clubSpreadSL);
6301					var b = bearingLS+(i/n)*(bearing-bearingLS);
6302					d -= d*(b-bearing)*(b-bearing)/bb;
6303					var lat3 = moveLat(lat1, lon1, d, b);
6304					var lon3 = moveLon(lat1, lon1, d, b);
6305					var h3 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat3, lon3));
6306					coords.push(Cesium.Cartesian3.fromDegrees(lon3, lat3, h3));
6307				}
6308				var n = 1 + Math.round(clubSpreadR);
6309				for (var i = 1; i <= n; ++i)
6310				{
6311					var d = dist-clubSpreadS-(i/n)*(clubSpreadSR-clubSpreadS);
6312					var b = bearing+(i/n)*(bearingRS-bearing);
6313					d -= d*(b-bearing)*(b-bearing)/bb;
6314					var lat3 = moveLat(lat1, lon1, d, b);
6315					var lon3 = moveLon(lat1, lon1, d, b);
6316					var h3 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat3, lon3));
6317					coords.push(Cesium.Cartesian3.fromDegrees(lon3, lat3, h3));
6318				}
6319				var n = 1 + Math.round(clubSpreadO+clubSpreadS);
6320				for (var i = 1; i <= n; ++i)
6321				{
6322					var d = dist-clubSpreadSR+(i/n)*(clubSpreadSR+clubSpreadOR);
6323					var b = bearingRS+(i/n)*(bearingRO-bearingRS);
6324					d -= d*(b-bearing)*(b-bearing)/bb;
6325					var lat3 = moveLat(lat1, lon1, d, b);
6326					var lon3 = moveLon(lat1, lon1, d, b);
6327					var h3 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat3, lon3));
6328					coords.push(Cesium.Cartesian3.fromDegrees(lon3, lat3, h3));
6329				}
6330				var n = 1 + Math.round(clubSpreadR);
6331				for (var i = 1; i <= n; ++i)
6332				{
6333					var d = dist+clubSpreadOR+(i/n)*(clubSpreadO-clubSpreadOR);
6334					var b = bearingRO+(i/n)*(bearing-bearingRO);
6335					d -= d*(b-bearing)*(b-bearing)/bb;
6336					var lat3 = moveLat(lat1, lon1, d, b);
6337					var lon3 = moveLon(lat1, lon1, d, b);
6338					var h3 = (terrainMode == 1 ? 0 : getProVisualizerTerrainHeight(lat3, lon3));
6339					coords.push(Cesium.Cartesian3.fromDegrees(lon3, lat3, h3));
6340				}
6341				var line = viewer.entities.add({ polyline : { positions: coords, width: 1.0, material: color } }); 
6342				mapLines2.push(line);
6343			}
6344		}
6345	}
6346}
6347
6348function shotHelp()
6349{
6350	setMode(0);
6351	alert("Use the Next Hole/Shot and Prev Hole/Shot buttons to navigate around the course, and use the 2D/3D button to switch between 2D and 3D mode. Distances are based on the tee, pin and target positions shown, and may not agree exactly with the official scorecard. Elevation is based on USGS National Map Elevation Point Query Service in the United States and the SRTM 1 Arc-Second Global Dataset and ASTER GDEM V3 Dataset elsewhere, as indicated. Click on the link for more information regarding coverage and accuracy. Plays like is based on horizontal distance plus/minus 1 yard for each yard of elevation gain/loss, minus 3% for each 1000 metres above sea level. This is only approximate as it depends on the exact trajectory and spin of each shot, but hopefully it gives a reasonably accurate estimate in most cases. Clubbing is based on My Profile."); 
6352}
6353
6354function dispersionHelp()
6355{
6356	setMode(0);
6357	alert("Dispersion is based on PGA Tour statistics and may not accurately reflect your individual dispersion profile. Specifically, it is based on a distance error of -8% to +6% and a direction error of 3 degrees left/right at 100 yards, rising to 5 degrees at 200 yards and 7 degrees at 300 yards. A random error of up to 5% is also added to simulate unpredictable wind and bounce. Distance, direction, wind and bounce are all modelled using a fourth order approximation to a normal distribution, i.e. by taking the average of 4 random numbers in the given range. This is only approximate, and every player is different, but we believe it gives a fairly good representation of pro-level dispersion patterns in good weather conditions. Select Pro+X% to increase the dispersion by the specified factor, and N/R/L to switch between neutral, right-handed and left-handed patterns. Use the Custom option if you want to specify the exact width, depth and shape of each individual club.");
6358}
6359
6360function spreadHelp()
6361{
6362	setMode(0);
6363	alert("Spread puts a box around your chosen dispersion pattern (Pro+X% or Custom). If you have selected Pro+X%, the box will be based on our Pro+X% dispersion model. If you have selected Custom, the box will be exactly as specified by the Left/Right/Long/Short dispersion settings in My Profile. Note that in reality dispersion doesn't have a precise boundary, so there will be a small percentage of shots that stray outside the box. If you use the Custom option, you should set Left/Right/Long/Short to be your estimated spread at your preferred confidence level, e.g. 90%, 95%, etc.");
6364}
6365
6366function slopesHelp()
6367{
6368	setMode(0);
6369	alert("Slopes and contours are based on lidar survey data. We believe the data is fairly accurate in most cases. However, please note that the data can often be several years old, and might not accurately represent the current state of the course. Therefore, it is essential that you verify the accuracy of the data before using it. When the slopes are visible, you can right click anywhere on the green to set the hole location, and then left click anywhere on the green to simulate a putt to the specified hole location. Green speed is the stimp rating. Putt speed is the speed the ball is rolling when it reaches the hole. Double or triple right click anywhere on the green to simulate 10-foot putts from all directions. Left click a hole location to remove it. Right click a putt start point to remove it. Note that putting simulations are not saved, so please capture a screenshot if you wish to save the image.");
6370}
6371
6372function moveLat(lat, lon, dist, bearing)
6373{
6374	var radians = Math.PI * (90 - bearing) / 180;
6375	var dLat = Math.sin(radians) * dist * 360 / 40000000;
6376	return lat + dLat;
6377}
6378
6379function moveLon(lat, lon, dist, bearing)
6380{
6381	var radians = Math.PI * (90 - bearing) / 180;
6382	var k = 1 / Math.cos(Math.PI * lat / 180);
6383	var dLon = Math.cos(radians) * dist * k * 360 / 40000000;
6384	return lon + dLon;
6385}
6386
6387function GETeeHandler()
6388{
6389	setMode(0);
6390	var courseID = currentCourseID();
6391	var hole = currentHole();
6392	if (hole < 1)
6393	{
6394		alert("Please select hole.");
6395		return;
6396	}
6397	var lat1 = getCourseHoleTeeLat(courseID, hole);
6398	var lon1 = getCourseHoleTeeLon(courseID, hole);
6399	if (lat1 == 0 && lon1 == 0)
6400	{
6401		alert("Please set tee position.");
6402		return;
6403	}
6404	var lat2 = getCourseHoleTargetLat(courseID, hole, 1);
6405	var lon2 = getCourseHoleTargetLon(courseID, hole, 1);
6406	if (lat2 == 0 && lon2 == 0)
6407	{
6408		lat2 = getCourseHolePinLat(courseID, hole);
6409		lon2 = getCourseHolePinLon(courseID, hole);
6410	}
6411	if (lat2 == 0 && lon2 == 0)
6412	{
6413		alert("Please set pin position.");
6414		return;
6415	}
6416	var i = document.getElementById("geheightselect").selectedIndex;
6417	var bearing = geogBearing(lat1, lon1, lat2, lon2);
6418	var height = 24;
6419	var angle = 70;
6420	var range = 90;
6421	var desc = "Tee High";
6422	if (i == 1)
6423	{
6424		height = 18;
6425		angle = 75;
6426		range = 70;
6427		desc = "Tee Mid"
6428	}
6429	else if (i == 2)
6430	{
6431		height = 12;
6432		angle = 80;
6433		range = 50;
6434		desc = "Tee Low"
6435	}
6436	else if (i == 3)
6437	{
6438		height = 0;
6439		angle = 0;
6440		range = 200;
6441		desc = "Tee 2D"
6442	}
6443	GEViewLink(lat1, lon1, height, bearing, angle, range, GEViewName(courseID, hole, desc));
6444}
6445
6446function GET1Handler()
6447{
6448	setMode(0);
6449	var courseID = currentCourseID();
6450	var hole = currentHole();
6451	if (hole < 1)
6452	{
6453		alert("Please select hole.");
6454		return;
6455	}
6456	var lat1 = getCourseHoleTargetLat(courseID, hole, 1);
6457	var lon1 = getCourseHoleTargetLon(courseID, hole, 1);
6458	if (lat1 == 0 && lon1 == 0)
6459	{
6460		alert("Please set target 1.");
6461		return;
6462	}
6463	var lat2 = getCourseHoleTargetLat(courseID, hole, 2);
6464	var lon2 = getCourseHoleTargetLon(courseID, hole, 2);
6465	if (lat2 == 0 && lon2 == 0)
6466	{
6467		lat2 = getCourseHolePinLat(courseID, hole);
6468		lon2 = getCourseHolePinLon(courseID, hole);
6469	}
6470	if (lat2 == 0 && lon2 == 0)
6471	{
6472		alert("Please set pin position.");
6473		return;
6474	}
6475	var i = document.getElementById("geheightselect").selectedIndex;
6476	var bearing = geogBearing(lat1, lon1, lat2, lon2);
6477	var height = 15;
6478	var angle = 75;
6479	var range = 70;
6480	var desc = "Target 1 High";
6481	if (i == 1)
6482	{
6483		height = 10;
6484		angle = 80;
6485		range = 50;
6486		desc = "Target 1 Mid";
6487	}
6488	else if (i == 2)
6489	{
6490		height = 5;
6491		angle = 85;
6492		range = 30;
6493		desc = "Target 1 Low";
6494	}
6495	else if (i == 3)
6496	{
6497		height = 0;
6498		angle = 0;
6499		range = 400;
6500		desc = "Target 1 2D"
6501	}
6502	GEViewLink(lat1, lon1, height, bearing, angle, range, GEViewName(courseID, hole, desc));
6503}
6504
6505function GET2Handler()
6506{
6507	setMode(0);
6508	var courseID = currentCourseID();
6509	var hole = currentHole();
6510	if (hole < 1)
6511	{
6512		alert("Please select hole.");
6513		return;
6514	}
6515	var lat1 = getCourseHoleTargetLat(courseID, hole, 2);
6516	var lon1 = getCourseHoleTargetLon(courseID, hole, 2);
6517	if (lat1 == 0 && lon1 == 0)
6518	{
6519		alert("Please set target 2.");
6520		return;
6521	}
6522	var lat2 = getCourseHoleTargetLat(courseID, hole, 3);
6523	var lon2 = getCourseHoleTargetLon(courseID, hole, 3);
6524	if (lat2 == 0 && lon2 == 0)
6525	{
6526		lat2 = getCourseHolePinLat(courseID, hole);
6527		lon2 = getCourseHolePinLon(courseID, hole);
6528	}
6529	if (lat2 == 0 && lon2 == 0)
6530	{
6531		alert("Please set pin position.");
6532		return;
6533	}
6534	var i = document.getElementById("geheightselect").selectedIndex;
6535	var bearing = geogBearing(lat1, lon1, lat2, lon2);
6536	var height = 15;
6537	var angle = 75;
6538	var range = 70;
6539	var desc = "Target 2 High";
6540	if (i == 1)
6541	{
6542		height = 10;
6543		angle = 80;
6544		range = 50;
6545		desc = "Target 2 Mid";
6546	}
6547	else if (i == 2)
6548	{
6549		height = 5;
6550		angle = 85;
6551		range = 30;
6552		desc = "Target 2 Low";
6553	}
6554	else if (i == 3)
6555	{
6556		height = 0;
6557		angle = 0;
6558		range = 300;
6559		desc = "Target 2 2D"
6560	}
6561	GEViewLink(lat1, lon1, height, bearing, angle, range, GEViewName(courseID, hole, desc));
6562}
6563
6564function GET3Handler()
6565{
6566	setMode(0);
6567	var courseID = currentCourseID();
6568	var hole = currentHole();
6569	if (hole < 1)
6570	{
6571		alert("Please select hole.");
6572		return;
6573	}
6574	var lat1 = getCourseHoleTargetLat(courseID, hole, 3);
6575	var lon1 = getCourseHoleTargetLon(courseID, hole, 3);
6576	if (lat1 == 0 && lon1 == 0)
6577	{
6578		alert("Please set target 3.");
6579		return;
6580	}
6581	lat2 = getCourseHolePinLat(courseID, hole);
6582	lon2 = getCourseHolePinLon(courseID, hole);
6583	if (lat2 == 0 && lon2 == 0)
6584	{
6585		alert("Please set pin position.");
6586		return;
6587	}
6588	var i = document.getElementById("geheightselect").selectedIndex;
6589	var bearing = geogBearing(lat1, lon1, lat2, lon2);
6590	var height = 15;
6591	var angle = 75;
6592	var range = 70;
6593	var desc = "Target 3 High";
6594	if (i == 1)
6595	{
6596		height = 10;
6597		angle = 80;
6598		range = 50;
6599		desc = "Target 3 Mid";
6600	}
6601	else if (i == 2)
6602	{
6603		height = 5;
6604		angle = 85;
6605		range = 30;
6606		desc = "Target 3 Low";
6607	}
6608	else if (i == 3)
6609	{
6610		height = 0;
6611		angle = 0;
6612		range = 300;
6613		desc = "Target 3 2D"
6614	}
6615	GEViewLink(lat1, lon1, height, bearing, angle, range, GEViewName(courseID, hole, desc));
6616}
6617
6618function GEGreenHandler()
6619{
6620	setMode(0);
6621	var courseID = currentCourseID();
6622	var hole = currentHole();
6623	if (hole < 1)
6624	{
6625		alert("Please select hole.");
6626		return;
6627	}
6628	var lat2 = getCourseHolePinLat(courseID, hole);
6629	var lon2 = getCourseHolePinLon(courseID, hole);
6630	if (lat2 == 0 && lon2 == 0)
6631	{
6632		alert("Please set pin position.");
6633		return;
6634	}
6635	var lat1 = getCourseHoleTargetLat(courseID, hole, 3);
6636	var lon1 = getCourseHoleTargetLon(courseID, hole, 3);
6637	if (lat1 == 0 && lon1 == 0)
6638	{
6639		lat1 = getCourseHoleTargetLat(courseID, hole, 2);
6640		lon1 = getCourseHoleTargetLon(courseID, hole, 2);
6641	}
6642	if (lat1 == 0 && lon1 == 0)
6643	{
6644		lat1 = getCourseHoleTargetLat(courseID, hole, 1);
6645		lon1 = getCourseHoleTargetLon(courseID, hole, 1);
6646	}
6647	if (lat1 == 0 && lon1 == 0)
6648	{
6649		lat1 = getCourseHoleTeeLat(courseID, hole);
6650		lon1 = getCourseHoleTeeLon(courseID, hole);
6651	}
6652	if (lat1 == 0 && lon1 == 0)
6653	{
6654		alert("Please set tee position.");
6655		return;
6656	}
6657	var i = document.getElementById("geheightselect").selectedIndex;
6658	var bearing = geogBearing(lat1, lon1, lat2, lon2);
6659	var height = 0;
6660	var angle = 75;
6661	var range = 70;
6662	var desc = "Green High";
6663	if (i == 1)
6664	{
6665		height = 0;
6666		angle = 80;
6667		range = 50;
6668		desc = "Green Mid";
6669	}
6670	else if (i == 2)
6671	{
6672		height = 0;
6673		angle = 85;
6674		range = 30;
6675		desc = "Green Low";
6676	}
6677	else if (i == 3)
6678	{
6679		height = 0;
6680		angle = 0;
6681		range = 200;
6682		desc = "Green 2D"
6683	}
6684	GEViewLink(lat2, lon2, height, bearing, angle, range, GEViewName(courseID, hole, desc));
6685}
6686
6687function GEViewName(courseID, hole, view)
6688{
6689	var courseName = getCourseNameFromID(courseID);
6690	if (courseName == null || courseName == "")
6691		courseName = cleanString(document.getElementById("saveasinput").value);
6692	if (courseName == null || courseName == "")
6693		courseName = "NewCourse";
6694	var h = hole.toString();
6695	if (h.length < 2) h = "0" + h;
6696	return courseName + " " + h + " " + view;
6697}
6698
6699function GEViewLink(lat, lon, height, bearing, angle, range, name)
6700{
6701	window.location.href = "flyto.php?"
6702				+"lat="+lat.toString()
6703				+"&lon="+lon.toString()
6704				+"&h="+height.toString()
6705				+"&b="+bearing.toString()
6706				+"&a="+angle.toString()
6707				+"&r="+range.toString()
6708				+"&n="+encodeURIComponent(name);
6709}
6710
6711function GEHelp()
6712{
6713	alert("Use these buttons to quickly navigate to the required location in Google Earth. To get the most accurate available elevation and terrain, make sure you have 3D Buildings and Terrain selected in the Google Earth Layers, and adjust Tools > Options > Navigation > Fly-To Speed so that Google Earth moves at the required speed. These buttons work by generating small KML files with the required view parameters. If you associate KML files with Google Earth, then Google Earth will automatically open the KML file and fly to the required location when you click the button. The KML files also work with Esri ArcGIS Earth, which can be a good option in some regions where the Google Earth imagery/terrain isn't very good.");
6714}
6715
6716var weather = []; // Array of forecasts
6717var weatherIndex = -1; // Which forecast to show
6718var weatherIndex2 = -1; // Which time offset to show
6719var weatherRequests = 0;
6720var weatherWindSpeed = 0;
6721var weatherWindBearing = 0;
6722
6723function toggleWeather()
6724{
6725	setMode(0);
6726	if (weatherIndex < 0) showWeather(true);
6727	else hideWeather();
6728}
6729
6730function startWeatherHandler()
6731{
6732	weatherIndex2 = 0;
6733	setWeatherData();
6734}
6735
6736function nextWeatherHandler()
6737{
6738	weatherIndex2 += 1;
6739	setWeatherData();
6740}
6741
6742function prevWeatherHandler()
6743{
6744	weatherIndex2 -= 1;
6745	setWeatherData();
6746}
6747
6748function setWeatherData()
6749{
6750	if (weatherIndex < 0) return;
6751	var w = weather[weatherIndex];
6752	while (weatherIndex2 < 0) weatherIndex2 += 1;
6753	while (weatherIndex2 >= w.list.length) weatherIndex2 -= 1;
6754	var w2 = w.list[weatherIndex2];
6755	weatherTempC = Math.round(w2.main.temp);
6756	weatherWindSpeed = Math.round(w2.wind.speed*3600/(getDistanceUnit() == "m" ? 1000.0 : 1609.344));
6757	weatherWindBearing = w2.wind.deg + 180;
6758	while (weatherWindBearing < 0) weatherWindBearing += 360;
6759	while (weatherWindBearing > 360) weatherWindBearing -= 360;
6760	var div1 = document.getElementById('weatherdiv1');
6761	var div3 = document.getElementById('weatherdiv3');
6762	var div4 = document.getElementById('weatherdiv4');
6763	var div5 = document.getElementById('weatherdiv5');
6764	var div6 = document.getElementById('weatherdiv6');
6765	div1.innerHTML = "<img src='https://openweathermap.org/img/w/"+w2.weather[0].icon+".png'/>";
6766	div3.innerHTML = weatherWindSpeed.toString() + (getDistanceUnit() == "m" ? " KPH" : " MPH");
6767	div4.innerHTML = weatherTempC.toString()+"&deg;C";;
6768	div5.innerHTML = w2.localDayTimeStr;
6769	div6.innerHTML = w.timezoneStr;
6770}
6771
6772function hideWeather()
6773{
6774	weatherIndex = -1;
6775	document.getElementById('weatherdiv1').style.display = 'none';
6776	document.getElementById('weatherdiv2').style.display = 'none';
6777	document.getElementById('weatherdiv3').style.display = 'none';
6778	document.getElementById('weatherdiv4').style.display = 'none';
6779	document.getElementById('weatherdiv5').style.display = 'none';
6780	document.getElementById('weatherdiv6').style.display = 'none';
6781	document.getElementById('weatherdiv7').style.display = 'none';
6782	document.getElementById('weatherdiv8').style.display = 'none';
6783}
6784
6785function showWeather(allowGet)
6786{
6787	weatherIndex = -1;
6788	var courseID = currentCourseID();
6789	var hole = 0;
6790	do
6791	{
6792		hole += 1;
6793		lat = getCourseHoleTeeLat(courseID, 1);
6794		lon = getCourseHoleTeeLon(courseID, 1);
6795	}
6796	while (lat == 0 && lon == 0 && hole < 18);
6797	if (lat == 0 && lon == 0)
6798	{
6799		alert("Please set 1st tee position.");
6800		return;
6801	}
6802	var minTime = Date.now() - 3600*1000;
6803	var minLat = moveLat(lat, lon, 5000, 180);
6804	var minLon = moveLon(lat, lon, 5000, 270);
6805	var maxLat = moveLat(lat, lon, 5000,   0);
6806	var maxLon = moveLon(lat, lon, 5000,  90);
6807	var i = 0;
6808	var found = false;
6809	while (!found && i < weather.length)
6810	{
6811		if (weather[i].reqTime > minTime && weather[i].reqLat > minLat && weather[i].reqLat < maxLat && weather[i].reqLon > minLon && weather[i].reqLon < maxLon) found = true;
6812		else ++i;
6813	}
6814	if (found)
6815	{
6816		weatherIndex = i;
6817		document.getElementById('weatherdiv1').innerHTML = '';
6818		document.getElementById('weatherdiv3').innerHTML = '';
6819		document.getElementById('weatherdiv4').innerHTML = '';
6820		document.getElementById('weatherdiv5').innerHTML = '';
6821		document.getElementById('weatherdiv6').innerHTML = '';
6822		document.getElementById('weatherdiv1').style.display = '';
6823		document.getElementById('weatherdiv2').style.display = '';
6824		document.getElementById('weatherdiv3').style.display = '';
6825		document.getElementById('weatherdiv4').style.display = '';
6826		document.getElementById('weatherdiv5').style.display = '';
6827		document.getElementById('weatherdiv6').style.display = '';
6828		document.getElementById('weatherdiv7').style.display = '';
6829		document.getElementById('weatherdiv8').style.display = '';
6830		startWeatherHandler();
6831		return;
6832	}
6833	if (!allowGet)
6834	{
6835		alert("Weather not available.");
6836		return;
6837	}
6838	getWeather(lat, lon);
6839}
6840
6841function getWeather(lat, lon)
6842{
6843	if (weatherRequests >= 10)
6844	{
6845		alert('Request limit exceeded.');
6846		return;
6847	}
6848	if (lat < -75 || lat > 75)
6849	{
6850		alert('Weather not available for this location.');
6851		return;
6852	}
6853	while (lon < -180.0) lon += 360.0;
6854	while (lon >  180.0) lon -= 360.0;
6855	weatherRequests += 1;
6856	var req = "https://api.openweathermap.org/data/2.5/forecast?lat=" + lat.toString() + "&lon=" + lon.toString() + "&appid=58e7b34079d0f22aa5fd07d1cab41d19&units=metric";
6857	if (window.XMLHttpRequest)
6858	{
6859		var xhr = new XMLHttpRequest();
6860		xhr.onreadystatechange = function()
6861		{
6862			if (xhr.readyState == 4 && xhr.status == 200)
6863			{
6864				var w = JSON.parse(xhr.responseText);
6865				if (w == null || w.cod == null || w.cod != "200" || w.list == null || w.list.length < 10
6866					|| w.list[0].wind == null || w.list[0].wind.speed == null || w.list[0].wind.deg == null
6867					|| w.list[0].main == null || w.list[0].main.temp == null)
6868				{
6869					alert("Weather not available for this location.");
6870					hideWeather();
6871					return;
6872				}
6873				w.reqLat = lat;
6874				w.reqLon = lon;
6875				w.reqTime = Date.now();
6876				w.utcOffsetHours = Math.round((12 * lon) / 180);
6877				w.timezoneStr = (w.utcOffsetHours >= 0 ? "UTC+" + w.utcOffsetHours.toString() : "UTC-" + (-(w.utcOffsetHours)).toString())+"h";
6878				for (var i = 0; i < w.list.length; ++i)
6879				{
6880					var localTime = w.list[i].dt + w.utcOffsetHours * 3600;
6881					var hour = Math.floor(localTime / 3600) % 24;
6882					var day = Math.floor(localTime / 86400) % 7;
6883					var s1 = (hour < 10 ? "0" : "") + hour.toString() + "00";
6884					var s2 = "";
6885					if      (day == 0) s2 = "Thu";
6886					else if (day == 1) s2 = "Fri";
6887					else if (day == 2) s2 = "Sat";
6888					else if (day == 3) s2 = "Sun";
6889					else if (day == 4) s2 = "Mon";
6890					else if (day == 5) s2 = "Tue";
6891					else if (day == 6) s2 = "Wed";
6892					w.list[i].localDayTimeStr = s2 + " " + s1;
6893				}
6894				weather.push(w);
6895				showWeather(false);
6896			}
6897		}
6898		xhr.open("GET", req, true);
6899		xhr.send();
6900	}
6901}
6902
6903function showSymbolsPanel()
6904{
6905	setMode(0);
6906	var x = document.getElementById('symbolsdiv');
6907	var y = x.style.display;
6908	if (y == '') x.style.display = 'none';
6909	else x.style.display = '';
6910}
6911
6912var activeSymbol = '';
6913
6914function symbolClickHandler(symbol)
6915{
6916	if (mode == 9 && symbol == activeSymbol)
6917	{
6918		activeSymbol = '';
6919		setMode(0);
6920		return;
6921	}
6922	if (symbol == 'Z' || symbol == 'ZZ')
6923	{
6924		var courseID = currentCourseID();
6925		var hole = currentHole();
6926		var n = getCourseHoleSymbols(courseID, hole);
6927		if (n < 1)
6928		{
6929			activeSymbol = '';
6930			setMode(0);
6931			return;
6932		}
6933		if (symbol == 'ZZ')
6934		{
6935			activeSymbol = '';
6936			setMode(0);
6937			if (!confirm("Clear all symbols?"))
6938				return;
6939			for (var i = n - 1; i >= 0; --i)
6940				deleteCourseHoleSymbol(courseID, hole, i);
6941			refreshView();
6942			return;
6943		}
6944	}
6945	else if (symbol != 'T' && symbol != 'X')
6946	{
6947		var courseID = currentCourseID();
6948		var hole = currentHole();
6949		var n = getCourseHoleSymbols(courseID, hole);
6950		var i = 0;
6951		while (i < n && decodeSymbol(getCourseHoleSymbol(courseID, hole, i)).symbol != symbol)
6952			i += 1;
6953		while (i < n - 1)
6954		{
6955			setCourseHoleSymbol(courseID, hole, i, getCourseHoleSymbol(courseID, hole, i+1));
6956			i += 1;
6957		}
6958		if (i < n)
6959		{
6960			deleteCourseHoleSymbol(courseID, hole, i);
6961			refreshView();
6962		}
6963	}
6964	activeSymbol = symbol;
6965	setMode(9);
6966}
6967

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.