Source code of a graphical tool for drawing and computing distances over Google maps.
Run Tool | index.html | main.css | formatters.js | geoCircle.js | geoCode.js | geo.js | index.js | mapControls.js | tableManager.js | util.js | wayPoint.js | wayPointsManager.js
// Copyright 2006-2008 (c) Paul Demers <paul@acscdg.com>
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA., or visit one
// of the links here:
// http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
// http://www.acscdg.com/LICENSE.txt
//////////////////////////////////////////////////////////////////
//
// map drawing and distance tools.
// Web site with this code running: http://www.acscdg.com/
//
// Geo Circle object. A geo circle is a circle of equal distance around
// a point. It might represent range, or an area withing a certain distance.
// For example, a search radius, or the range of a rocket.
//
// Dependency on other modules:
// geo.js
// formatters.js
// Google maps (for geo point object).
//
//// Renders a GeoCircle object as an HTML table row element.
function geoCircleToElement(distanceUnits)
{
var trElement = document.createElement("tr");
// Allow different formats for odd and even rows, for example to shade them
// differently for legibility. The classes are defined in a CSS file.
var isEven = (Math.round(this.circleNumber / 2) * 2) == this.circleNumber;
if (isEven)
trElement.className = "ptsroweven";
else
trElement.className = "ptsrowodd";
var tdElement;
var distanceMultiplier = getDistanceMultiplier(distanceUnits);
tdElement = document.createElement("td");
tdElement.appendChild(document.createTextNode(this.circleNumber.toString()));
tdElement.className = "numcol";
trElement.appendChild(tdElement); // 0
tdElement = document.createElement("td");
tdElement.appendChild(document.createTextNode(" ")); // Unused Point # column
tdElement.className = "numcol";
trElement.appendChild(tdElement); // 1
tdElement = document.createElement("td");
tdElement.appendChild(document.createTextNode(myLatToString(this.centerPoint)));
tdElement.className = "llcol";
trElement.appendChild(tdElement); // 2
tdElement = document.createElement("td");
tdElement.appendChild(document.createTextNode(myLngToString(this.centerPoint)));
tdElement.className = "llcol";
trElement.appendChild(tdElement); // 3
var radiusString = formatFloat(this.radiusNM * distanceMultiplier);
tdElement = document.createElement("td");
tdElement.appendChild(document.createTextNode(radiusString)); // radius
tdElement.className = "distcol";
trElement.appendChild(tdElement); // 4
tdElement = document.createElement("td");
tdElement.appendChild(document.createTextNode(" ")); // Unused azimuth column.
tdElement.className = "distcol";
trElement.appendChild(tdElement); // 5
var circumferenceString;
if (this.circumferenceNM == null)
circumferenceString = " ";
else
circumferenceString = formatFloat(this.circumferenceNM * distanceMultiplier);
tdElement = document.createElement("td");
tdElement.appendChild(document.createTextNode(circumferenceString)); // circumference
tdElement.className = "distcol";
trElement.appendChild(tdElement); // 6
return trElement;
}
//
//// Replace the radius and circumference TD elements in a TR element
//// created by geoCircleToElement.
function geoCircleUpdateElement(distanceUnits)
{
var distanceMultiplier = getDistanceMultiplier(distanceUnits);
// child 0 is course number.
// child 1 is unused point number
// child 2 is center lat
// child 3 is center lng
var radiusString = formatFloat(this.radiusNM * distanceMultiplier);
var tdElement = this.tableRowElement.childNodes[4]; // radius
tdElement.replaceChild(document.createTextNode(formatFloat(radiusString)), tdElement.childNodes[0]);
// child 5 is unused azimuth column.
var circumferenceString = formatFloat(this.circumferenceNM * distanceMultiplier);
tdElement = this.tableRowElement.childNodes[6]; // circumference
tdElement.replaceChild(document.createTextNode(formatFloat(circumferenceString)), tdElement.childNodes[0]);
return this.tableRowElement;
}
//
//// Removes the circle line (and inner circle line), but leaves the center marker.
function geoCircleRemoveLines(map)
{
if (this.circleLine != null)
map.removeOverlay(this.circleLine);
if (this.innerCircleLine != null)
map.removeOverlay(this.innerCircleLine);
}
//
//// Removes all the overlays for a GeoCircle object.
function geoCircleRemoveOverlays(map)
{
this.removeLines(map);
if (this.centerMarker != null)
map.removeOverlay(this.centerMarker);
}
//
/// Update radius, circumference, and circle line by recomputing from new endPoint.
function geoCircleUpdate(map, edgePoint, finalP, distanceUnits)
{
this.removeLines(map);
this.updateEdge(edgePoint, finalP);
map.addOverlay(this.circleLine);
if (this.innerCircleLine != null)
map.addOverlay(this.innerCircleLine);
this.updateElement(distanceUnits);
}
//
//// Compute the points around the GeoCircle from two points.
//// This method has two modes. The first mode is not final, which is
//// very fast. There are less points around the circle, and the inner helper
//// lines are not drawn.
//// The inner line is a set of lines that point to the center of the circle.
//// Circles with a large radius (more than 8,000 NM or so) curve around so much
//// it is hard to know where the center vs. the antipodal center is.
//// Very very large radius create circles around the points outside of
//// radius (that is, the circle is the points greater than the radius).
//// For these circles, the inner points line is essential.
function geoCircleComputeFromTwoPoints(edgePoint, finalP)
{
// Compute distance between points.
var bestCourse = new BestCourse(this.centerPoint, edgePoint);
var radiusVector = bestCourse.getVector();
if (!finalP && (this.radiusNM == radiusVector.distanceNM))
return; // No difference from last time.
this.radiusNM = radiusVector.distanceNM;
// Compute necessary number of steps
// TODO: Change number of degrees based on radiusNM.
var angleDelta = null; // must be a factor of 360
if (finalP)
angleDelta = 3;
else
angleDelta = 12;
// Start at 0 degrees, walk around in steps, compute lat/lng of distance from center
var outerPointsList = new Array();
for (var nextAngle = 0; nextAngle <= 360; nextAngle += angleDelta)
{
var nextAngleR = nextAngle / radiansToDegrees;
// TODO: Pass EarthVector. Set radiusR just once.
var nextPoint = directSolution(this.centerPoint, nextAngleR, this.radiusNM);
outerPointsList.push(nextPoint);
}
// the circle will close because both 0 and 360 are points.
// Create a polyline from all the points.
this.circleLine = new GPolyline(outerPointsList, vectorColor, vectorWeight, vectorOpacity, _optsNotClickable);
//
// The inner points is drawn on the final drawing of the circle only, because it is so slow.
if (finalP)
{
var innerPointsList = new Array();
var innerRadiusNM = this.radiusNM * 0.95;
this.circumferenceNM = 0.0;
var lastPoint = outerPointsList[0]; // handle first point as special case, because of circumference math.
innerPointsList.push(lastPoint); // Because 0 is even.
for (var p = 1; p < outerPointsList.length; p++)
{
// Compute circumference:
var thisPoint = outerPointsList[p];
var innerbestCourse = new BestCourse(lastPoint, thisPoint);
var innerVector = innerbestCourse.getVector();
this.circumferenceNM += innerVector.distanceNM;
// Build the inner points:
var isEven = (Math.round(p / 2) * 2) == p;
if (isEven)
{
innerPointsList.push(thisPoint);
}
else
{
var nextAngleR = (p * angleDelta) / radiansToDegrees;
var innerPoint = directSolution(this.centerPoint, nextAngleR, innerRadiusNM);
innerPointsList.push(innerPoint);
}
lastPoint = thisPoint;
}
this.innerCircleLine = new GPolyline(innerPointsList, vectorColor, 1, .5, _optsNotClickable);
}
else
{
this.innerCircleLine = null;
}
// this.outerPointsList = outerPointsList; // multi circles need the points.
}
//
//// Constructor for the GeoCircle object.
function GeoCircle(map, circleNumber, centerPoint)
{
this.toElement = geoCircleToElement;
this.updateElement = geoCircleUpdateElement;
this.update = geoCircleUpdate;
this.removeOverlays = geoCircleRemoveOverlays;
this.removeLines = geoCircleRemoveLines;
this.updateEdge = geoCircleComputeFromTwoPoints;
this.circleNumber = circleNumber;
this.centerPoint = centerPoint;
this.radiusNM = 0;
this.circumferenceNM = null;
var circleTitle = "Circle # " + circleNumber.toString();
this.centerMarker = new GMarker(centerPoint, {title: circleTitle, icon: _wayPointIcon, clickable: true });
map.addOverlay(this.centerMarker);
this.tableRowElement = this.toElement(0); // Pass distance units of 0, since radius is 0 anyway.
}