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. }