usng-gmaps
Version:
USNG for Google Maps V3
1,459 lines (1,174 loc) • 46.1 kB
JavaScript
import { MARCONI } from './marconi.library';
import { MARCONIMAP } from './marconiMap.library';
const google = window.google;
/* eslint-disable */
export const USNG = (function() {
var latLongWGS84 = new MARCONIMAP.SpatialReference(MARCONIMAP.COORDSYS_WORLD, MARCONIMAP.DATUM_HORIZ_WGS84, MARCONIMAP.UNITS_DEGREES);
var usngRef = MARCONIMAP.spatialRefGivenCode(MARCONIMAP.SPATIALREF_USNG);
var pt = new MARCONIMAP.GeoPoint();
return {
LLtoUSNG : function ( latitude, longitude, precision ) {
try {
pt.x = longitude;
pt.y = latitude;
pt.convert(latLongWGS84, usngRef);
return pt.x;
}
catch(ex) {
throw("LLtoUSNG(): Error converting lat-long " + latitude + ", " + longitude + " to USNG, err is " + ex);
}
},
USNGtoLL : function (gridRef) {
try {
pt.x = gridRef;
pt.convert(usngRef, latLongWGS84);
return new google.maps.LatLng(pt.y, pt.x);
}
catch(ex) {
throw("USNGtoLL(): Error converting grid value " + gridRef + " to lat-long, err is " + ex);
}
},
LLtoUTM : function ( latitude, longitude, utmcoords, zoneNumber) {
try {
zoneNumber = (zoneNumber ? zoneNumber : MARCONIMAP.getUTMZoneFromLatLong(latitude, longitude));
var utmRef = MARCONIMAP.UTMSpatialRef(zoneNumber);
pt.x = longitude;
pt.y = latitude;
pt.convert(latLongWGS84, utmRef);
utmcoords[0] = pt.x;
utmcoords[1] = pt.y;
utmcoords[2] = zoneNumber;
}
catch(ex) {
throw("LLtoUTM(): Error converting lat-long " + latitude + ", " + longitude + " zone " + zoneNumber + " to UTM, err is " + ex);
}
},
UTMtoLL_GeoPoint : function ( x, y, zoneNumber) {
try {
var utmRef = MARCONIMAP.UTMSpatialRef(zoneNumber);
pt.x = x;
pt.y = y;
pt.convert(utmRef,latLongWGS84);
return pt;
}
catch(ex) {
throw("UTMtoLL_GeoPoint(): Error converting utm " + x + ", " + y + " zone " + zoneNumber + " to lat-long, err is " + ex);
}
},
UTMtoLL : function ( x, y, zoneNumber) {
try {
var utmRef = MARCONIMAP.UTMSpatialRef(zoneNumber);
pt.x = x;
pt.y = y;
pt.convert(utmRef,latLongWGS84);
return new google.maps.LatLng(pt.y, pt.x);
}
catch(ex) {
throw("UTMtoLL(): Error converting utm " + x + ", " + y + " zone " + zoneNumber + " to lat-long, err is " + ex);
}
} };
}());
export function USNGGraticule(map, gridStyle) {
var that=this;
//alert("usng grid constructor");
if(!map ) {
throw "Must supply map to the constructor";
}
this._map = map;
this.gridStyle = gridStyle;
this.setMap(map); // will trigger a draw()
// Google API should call draw() on its own at various times but often fails to
// so put our own listeners in.
this.resizeListener = google.maps.event.addDomListener(window, "resize", function() {
that.draw();});
this.dragListener = google.maps.event.addListener(map, 'dragend',
function() { that.draw(); })
}
USNGGraticule.prototype = new google.maps.OverlayView();
USNGGraticule.prototype.onAdd= function() {};
USNGGraticule.prototype.onRemove = function(leaveHandlersAlone) {
try {
if( this.zoneLines ) {
this.zoneLines.remove();
this.zoneLines = null;
}
if( this.grid100k) {
this.grid100k.remove();
this.grid100k = null;
}
if( this.grid1k) {
this.grid1k.remove();
this.grid1k = null;
}
if( this.grid100m ) {
this.grid100m.remove();
this.grid100m = null;
}
if( leaveHandlersAlone!==true ) {
google.maps.event.removeListener(this.resizeListener);
google.maps.event.removeListener(this.dragListener);
}
}
catch(e) {
MARCONI.log("Error " + e + " removing USNG graticule");
}
}
USNGGraticule.prototype.draw = function() {
try {
this.onRemove(true);
this.view = new USNGViewport(this._map);
var zoomLevel = this._map.getZoom(); // zero is whole world, higher numbers (to about 20 are move detailed)
if( zoomLevel < 6 ) { // zoomed way out
this.zoneLines = new USNGZonelines(this._map, this.view, this,
this.gridStyle.majorLineColor, this.gridStyle.majorLineWeight, this.gridStyle.majorLineOpacity);
}
else { // close enough to draw the 100km lines
this.grid100k = new Grid100klines(this._map, this.view, this,
this.gridStyle.semiMajorLineColor,
this.gridStyle.semiMajorLineWeight,
this.gridStyle.semiMajorLineOpacity);
if(zoomLevel > 10 ) { // draw 1k lines also if close enough
this.grid1k = new Grid1klines(this._map, this.view, this,
this.gridStyle.minorLineColor,
this.gridStyle.minorLineWeight,
this.gridStyle.minorLineOpacity);
if( zoomLevel > 13 ) { // draw 100m lines if very close
this.grid100m = new Grid100mlines(this._map, this.view, this,
this.gridStyle.fineLineColor,
this.gridStyle.fineLineWeight,
this.gridStyle.fineLineOpacity);
}
}
}
}
catch(ex) {
MARCONI.warn("Error " + ex + " drawing USNG graticule");
}
};
USNGGraticule.prototype.gridValueFromPt = function(latLongPt) {
return USNG.LLtoUSNG(latLongPt.lat(), latLongPt.lng());
};
/////////////////////// begin class USNGViewport ///////////////////////////////////////
//
// class that keeps track of the viewport context
// unlike most of the other classes in this module, does *not* implement a Google Maps custom overlay
// stores the corner coordinates of the viewport, and coordinate information
// that defines the top-level MGRS/USNG zones within the viewport
// USNGViewport is a geographic rectangle (bounded by parallels and meridians). zone lines and
// grid lines are computed within and clipped to this rectangle
function USNGViewport(mygmap) { // mygmap is an instance of GMap, created by calling function
// arrays that hold the key coordinates...corners of viewport and UTM zone boundary intersections
this.lat_coords = [];
this.lng_coords = [];
// array that holds instances of the class usng_georectangle, for this viewport
this.georectangle = [];
// call to Google Maps to get the boundaries of the map
this.bounds = mygmap.getBounds();
// geographic coordinates of edges of viewport
this.slat = this.bounds.getSouthWest().lat();
this.wlng = this.bounds.getSouthWest().lng();
this.nlat = this.bounds.getNorthEast().lat();
this.elng = this.bounds.getNorthEast().lng();
// UTM is undefined beyond 84N or 80S, so this application defines viewport at those limits
// even though USNG can go all the way to the poles
if (this.nlat > 84) {
this.nlat=84;
}
// first zone intersection inside the southwest corner of the map window
// longitude coordinate is straight-forward...
var x1 = (Math.floor((this.wlng/6)+1)*6.0)
// but latitude coordinate has three cases
var y1 = (this.slat >= -80 ? Math.floor((this.slat/8)+1)*8.0 : -80);
// compute lines of UTM zones -- geographic lines at 6x8 deg intervals
// latitudes first
if (this.slat < -80) {
this.lat_coords[0] = -80;
} // special case of southern limit
else {
this.lat_coords[0] = this.slat;
} // normal case
for( var lat=y1, j=1; lat < this.nlat; lat += 8, j++) {
if (lat <= 72) {
this.lat_coords[j] = lat;
}
else if (lat <= 80) {
this.lat_coords[j] = 84;
}
else {
j--;
}
}
this.lat_coords[j] = this.nlat;
// compute the longitude coordinates that belong to this viewport
var lng;
this.lng_coords[0] = this.wlng;
if (this.wlng < this.elng) { // normal case
for (lng=x1, j=1; lng < this.elng; lng+=6, j++) {
this.lng_coords[j] = lng;
}
}
else { // special case of window that includes the international dateline
for (lng=x1, j=1; lng <= 180; lng+=6, j++) {
this.lng_coords[j] = lng;
}
for (lng=-180; lng < this.elng; lng+=6, j++) {
this.lng_coords[j] = lng;
}
}
this.lng_coords[j++] = this.elng;
// store corners and center point for each geographic rectangle in the viewport
// each rectangle may be a full UTM cell, but more commonly will have one or more
// edges bounded by the extent of the viewport
// these geographic rectangles are stored in instances of the class 'usng_georectangle'
var k = 0;
for (var i=0; i < this.lat_coords.length-1 ; i++) {
for (j = 0; j < this.lng_coords.length-1 ; j++) {
if( this.lat_coords[i] >= 72 && this.lng_coords[j] == 6 ) { } // do nothing
else if (this.lat_coords[i] >= 72 && this.lng_coords[j] == 18 ) { } // do nothing
else if (this.lat_coords[i] >= 72 && this.lng_coords[j] == 30 ) { } // do nothing
else {
this.georectangle[k] = new usng_georectangle();
this.georectangle[k].assignCorners(this.lat_coords[i], this.lat_coords[i+1], this.lng_coords[j], this.lng_coords[j+1])
if (this.lat_coords[i] != this.lat_coords[i+1]) { // ignore special case of -80 deg latitude
this.georectangle[k].assignCenter()
}
k++;
}
}
}
} // end of function USNGViewport()
// return array of latitude coordinates corresponding to lat lines
USNGViewport.prototype.lats = function() {
return this.lat_coords;
}
// return array of longitude coordinates corresponding to lng lines
USNGViewport.prototype.lngs = function() {
return this.lng_coords;
}
// return an array or georectangles associated with this viewprot
USNGViewport.prototype.geoextents = function() {
return this.georectangle;
}
////////////////////// end class USNGViewport /////////////////////////////////
///////////////////// class to draw UTM zone lines/////////////////////////
// zones are defined by lines of latitude and longitude, normally 6 deg wide by 8 deg high
// northern-most zone is 12 deg high, from 72N to 84N
function USNGZonelines(map, viewport, parent) {
try {
this._map = map;
this.view = viewport;
this.parent=parent;
this.lat_line = [];
this.lng_line = [];
var latlines = this.view.lats();
var lnglines = this.view.lngs();
this.gzd_rectangles = this.view.geoextents();
this.marker = [];
var temp = [];
var i;
// creates polylines corresponding to zone lines using arrays of lat and lng points for the viewport
for( i = 1 ; i < latlines.length ; i++) {
temp=[];
for (var j = 0 ; j < lnglines.length; j++) {
temp.push(new google.maps.LatLng(latlines[i],lnglines[j]));
}
this.lat_line.push(new google.maps.Polyline({
path: temp,
strokeColor: this.parent.gridStyle.majorLineColor,
strokeWeight: this.parent.gridStyle.majorLineWeight,
strokeOpacity: this.parent.gridStyle.majorLineOpacity, map: this._map
}));
}
for( i = 1 ; i < lnglines.length ; i++ ) {
// need to reset array for every line of longitude!
temp = [];
// deal with norway special case at longitude 6
if( lnglines[i] == 6 ) {
for( j = 0 ; j < latlines.length ; j++ ) {
if (latlines[j]==56) {
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]));
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]-3));
}
else if( latlines[j]<56 || (latlines[j]>64 && latlines[j]<72)) {
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]));
}
else if (latlines[j]>56 && latlines[j]<64) {
temp.push(new google.maps.LatLng(latlines[j],lnglines[i]-3));
}
else if (latlines[j]==64) {
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]-3));
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]));
}
// Svlabard special case
else if (latlines[j]==72) {
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]));
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]+3));
}
else if (latlines[j]<72) {
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]));
}
else if (latlines[j]>72) {
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]+3));
}
else {
temp.push(new google.maps.LatLng(latlines[j],lnglines[i]-3));
}
}
}
// additional Svlabard cases
// lines at 12,18 and 36 stop at latitude 72
else if (lnglines[i] == 12 || lnglines[i] == 18 || lnglines[i] == 36) {
for (j = 0; j < latlines.length; j++) {
if (latlines[j]<=72) {
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]));
}
}
}
else if (lnglines[i] == 24) {
for (j=0; j < latlines.length ; j++) {
if (latlines[j] == 72) {
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]));
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]-3));
}
else if ( latlines[j] < 72) {
temp.push(new google.maps.LatLng(latlines[j],lnglines[i]));
}
else if ( latlines[j] > 72) {
temp.push(new google.maps.LatLng(latlines[j],lnglines[i]-3));
}
}
}
else if (lnglines[i] == 30) {
for ( j = 0 ; j < latlines.length ; j++) {
if( latlines[j] == 72 ) {
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]));
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]+3));
}
else if ( latlines[j] < 72 ) {
temp.push(new google.maps.LatLng( latlines[j], lnglines[i]));
}
else if ( latlines[j] > 72 ) {
temp.push(new google.maps.LatLng( latlines[j], lnglines[i]+3));
}
}
}
// normal case, not in Norway or Svalbard
else {
for( j = 0 ; j < latlines.length; j++) {
temp.push(new google.maps.LatLng(latlines[j], lnglines[i]));
}
}
this.lng_line.push(new google.maps.Polyline({path:temp,
strokeColor: this.parent.gridStyle.majorLineColor,
strokeWeight: this.parent.gridStyle.majorLineWeight,
strokeOpacity: this.parent.gridStyle.majorLineOpacity,
map: this._map}));
} // for each latitude line
//Only draw the zone marker at certain scales
if (this._map.getZoom() < 10 ) {
this.zonemarkerdraw();
}
}
catch(ex) {
throw("Error drawing USNG zone boundaries: " + ex);
}
} // constructor
USNGZonelines.prototype.remove = function() {
try {
var i;
if( this.lat_line ) {
for(i=0; i< this.lat_line.length; i++) {
this.lat_line[i].setMap(null);
}
this.lat_line = null;
}
if( this.lng_line ) {
for(i=0; i< this.lng_line.length; i++) {
this.lng_line[i].setMap(null);
}
this.lng_line = null;
}
// remove center-point label markers
if (this.marker) {
for (i=0; i<this.marker.length; i++) {
this.marker[i].parentNode.removeChild(this.marker[i]);
}
this.marker=null;
}
}
catch(ex) {
alert("Error removing zone lines: " + ex);
}
}
// zone label markers
USNGZonelines.prototype.zonemarkerdraw = function() {
function makeLabel(parent, latLong, labelText, className) {
try {
var pixelPoint = parent.getProjection().fromLatLngToDivPixel(latLong);
var d = document.createElement("div");
var x = pixelPoint.x;
var y = pixelPoint.y;
var height=20;
var width=50;
d.style.position = "absolute";
d.style.width = "" + width + "px";
d.style.height = "" + height + "px";
d.innerHTML = labelText;
if( className ) {
d.className = className;
}
else {
d.style.color = "#000000";
d.style.fontFamily='Arial';
d.style.fontSize='small';
d.style.backgroundColor = "white";
d.style.opacity=0.5;
}
d.style.textAlign = "center";
d.style.verticalAlign = "middle";
d.style.left = (x-width*.5).toString() + "px";
d.style.top = (y-height*.5).toString() + "px";
if( parent && parent.getPanes && parent.getPanes().overlayLayer ) {
parent.getPanes().overlayLayer.appendChild(d);
}
else {
MARCONI.warn("parent is " + parent + " drawing label");
}
return d;
}
catch(ex) {
throw "Error making zone label " + labelText + ": " + ex;
}
}
for (var i = 0 ; i <this.gzd_rectangles.length; i++ ) {
var lat = this.gzd_rectangles[i].getCenter().lat();
var lng = this.gzd_rectangles[i].getCenter().lng();
// labeled marker
var z = USNG.LLtoUSNG(lat,lng,1);
z = z.substring(0,3);
this.marker.push(makeLabel(this.parent, this.gzd_rectangles[i].getCenter(), z, this.parent.gridStyle.majorLabelClass));
}
}
/////////////////end of class that draws zone lines///////////////////////////////
///////////////////// class to draw 100,000-meter grid lines/////////////////////////
function Grid100klines(map, viewport, parent) {
this._map = map;
this.view = viewport;
this.parent = parent;
this.Gridcell_100k = [];
// zone lines are also the boundaries of 100k lines...separate instance of this class for 100k lines
// The problem is this also adds a zone label at inappropriate scales, so need to change the function
this.zonelines = new USNGZonelines(this._map, this.view, parent);
this.zones = this.view.geoextents();
for (var i=0; i < this.zones.length; i++) {
var newCell = new Gridcell(this._map, this.parent, this.zones[i],100000);
this.Gridcell_100k.push(newCell);
newCell.drawOneCell();
}
}
Grid100klines.prototype.remove = function() {
try {
if( this.zonelines ) {
this.zonelines.remove();
}
if( this.Gridcell_100k ) {
for (var i=0; i < this.Gridcell_100k.length; i++) {
this.Gridcell_100k[i].remove();
}
this.Gridcell_100k = null;
}
}
catch(ex) {
alert("Error " + ex + " trying to remove 100k gridlines");
}
}
/////////////end class Grid100klines ////////////////////////////////////////
///////////////////// class to draw 1,000-meter grid lines/////////////////////////
function Grid1klines(map, viewport, parent) {
this._map = map;
this.view = viewport;
this.parent = parent;
this.Gridcell_1k = [];
this.zones = this.view.geoextents();
for (var i = 0 ; i < this.zones.length ; i++ ) {
this.Gridcell_1k[i] = new Gridcell(this._map, this.parent, this.zones[i], 1000);
this.Gridcell_1k[i].drawOneCell();
}
}
Grid1klines.prototype.remove = function() {
// remove 1k grid lines
for (var i=0; i<this.zones.length; i++) {
this.Gridcell_1k[i].remove();
}
this.Gridcell_1k = null;
}
/////////////end class Grid1klines ////////////////////////////////////////
///////////////////// class to draw 100-meter grid lines/////////////////////////
function Grid100mlines(map, viewport, parent) {
this._map = map;
this.view = viewport;
this.parent = parent;
this.Gridcell_100m = [];
this.zones = this.view.geoextents();
for (var i=0; i<this.zones.length; i++) {
this.Gridcell_100m[i] = new Gridcell(this._map, this.parent, this.zones[i], 100);
this.Gridcell_100m[i].drawOneCell();
}
}
Grid100mlines.prototype.remove = function() {
// remove 100-m grid lines
for (var i = 0 ; i < this.zones.length ; i++) {
this.Gridcell_100m[i].remove();
}
}
/////////////end class Grid100mlines ////////////////////////////////////////
///////////////////////// class usng_georectangle//////////////////////////
function usng_georectangle() {
this.nlat = 0;
this.slat = 0;
this.wlng=0;
this.elng=0;
this.centerlat=0;
this.centerlng=0;
}
usng_georectangle.prototype.assignCorners = function(slat,nlat,wlng,elng) {
this.nlat = nlat;
this.slat = slat;
// special case: Norway
if (slat==56 && wlng==0) {
this.wlng = wlng;
this.elng = elng-3;
}
else if (slat==56 && wlng==6) {
this.wlng = wlng-3;
this.elng = elng;
}
// special case: Svlabard
else if (slat==72 && wlng==0) {
this.wlng = wlng;
this.elng = elng+3;
}
else if (slat==72 && wlng==12) {
this.wlng = wlng-3;
this.elng = elng+3;
}
else if (slat==72 && wlng==36) {
this.wlng = wlng-3;
this.elng = elng;
}
else {
this.wlng = wlng;
this.elng = elng;
}
}
usng_georectangle.prototype.assignCenter = function() {
this.centerlat = (this.nlat+this.slat)/2;
this.centerlng = (this.wlng+this.elng)/2;
}
usng_georectangle.prototype.getCenter = function() {
return(new google.maps.LatLng(this.centerlat,this.centerlng));
}
usng_georectangle.prototype.getNW = function() {
return(new google.maps.LatLng(this.nlat,this.wlng));
}
usng_georectangle.prototype.getSW = function() {
return(new google.maps.LatLng(this.slat,this.wlng));
}
usng_georectangle.prototype.getSE = function() {
return(new google.maps.LatLng(this.slat,this.elng));
}
usng_georectangle.prototype.getNE = function() {
return(new google.maps.LatLng(this.nlat,this.elng));
}
//////////////////////////////////////////////////////////////////////////////
///////////////////// class to calculate and draw grid lines ///////////////////////
// constructor
function Gridcell(map, parent, zones,interval) {
if(!map) {
throw "map argument not supplied to Gridcell constructor";
}
if(!parent) {
// TODO -- check some properties of parent to make sure it's real
throw "parent USNG grid not supplied to Gridcell constructor";
}
this._map = map;
this.parent = parent; // provides access to gridStyle for example
this.slat = zones.slat;
this.wlng = zones.wlng;
this.nlat = zones.nlat;
this.elng = zones.elng;
this.interval = interval;
this.gridlines = [];
this.label_100k = [];
this.label_1k = [];
this.label_100m = [];
}
// instance of one utm cell
Gridcell.prototype.drawOneCell = function() {
try {
var utmcoords = [];
var zone = MARCONIMAP.getUTMZoneFromLatLong((this.slat+this.nlat)/2,(this.wlng+this.elng)/2);
var i,j,k,m,n,p,q;
USNG.LLtoUTM(this.slat,this.wlng,utmcoords,zone);
var sw_utm_e = (Math.floor(utmcoords[0]/this.interval)*this.interval)-this.interval;
var sw_utm_n = (Math.floor(utmcoords[1]/this.interval)*this.interval)-this.interval;
USNG.LLtoUTM(this.nlat,this.elng,utmcoords,zone);
var ne_utm_e = (Math.floor(utmcoords[0]/this.interval+1)*this.interval) + 10 * this.interval;
var ne_utm_n = (Math.floor(utmcoords[1]/this.interval+1)*this.interval) + 10 * this.interval;
if( sw_utm_n > ne_utm_n || sw_utm_e > ne_utm_e) {
throw("Error, northeast of cell less than southwest");
}
var geocoords = null;
var temp = null;
var gr100kCoord = null;
var northings = [];
var eastings = [];
// set density of points on grid lines as space in meters between points
// case 1: zoomed out a long way; not very dense
var precision;
if (this._map.getZoom() < 12 ) {
precision = 10000;
}
// case 2: zoomed in a long way
else if (this._map.getZoom() > 15) {
precision = 100;
}
// case 3: in between, zoom levels 12-15
else {
precision = 1000;
}
precision *= 10; // experiment here with a speedup multiplier
if( precision > this.interval * 5) {
precision = this.interval * 5;
}
// ensure at least two vertices for each segment
if( precision > ne_utm_n - sw_utm_n ) {
precision = ne_utm_n - sw_utm_n;
}
if( precision > ne_utm_e - sw_utm_e ) {
precision = ne_utm_e - sw_utm_e;
}
var skipFactor=1;
if( this.interval==1000 && this._map.getZoom() == 11) {
skipFactor=2;
}
// for each e-w line that covers the cell, with overedge
northings[0] = this.slat;
if( !northings[0]) {
throw "Southern latitude is " + northings[0];
}
k=1;
for (i=sw_utm_n, j=0 ; i < ne_utm_n ; i += this.interval * skipFactor, j++) {
// collect coords to be used to place markers
// '2*this.interval' is a fudge factor that approximately offsets grid line convergence
geocoords = USNG.UTMtoLL_GeoPoint(sw_utm_e+(2*this.interval), i, zone);
if ((geocoords.y > this.slat) && (geocoords.y < this.nlat)) {
northings[k++] = geocoords.y;
}
// calculate line segments of one e-w line
temp=[];
for( m = sw_utm_e ; m <= ne_utm_e ; m += precision ) {
temp.push(USNG.UTMtoLL(m, i, zone));
}
gr100kCoord = [];
//+var to store result of checkClip+
var check = false;
// clipping routine...eliminate overedge lines
// case of final point in the array is not covered
//+revised to return either 'false' or an array of google maps lat/lng pairs+
for( p = 0 ; p < temp.length-1 ; p++ ) {
check = this.checkClip(temp, p);
if((check.constructor == Array)) {
gr100kCoord.push.apply(gr100kCoord, check);
}
}
if (this.interval == 100000) {
this.gridlines.push(new google.maps.Polyline( {
path: gr100kCoord,
strokeColor: this.parent.gridStyle.semiMajorLineColor,
strokeWeight: this.parent.gridStyle.semiMajorLineWeight,
strokeOpacity: this.parent.gridStyle.semiMajorLineOpacity,
map: this._map}));
}
else if (this.interval == 1000) {
this.gridlines.push(new google.maps.Polyline( {
path: gr100kCoord,
strokeColor: this.parent.gridStyle.minorLineColor,
strokeWeight: this.parent.gridStyle.minorLineWeight,
strokeOpacity: this.parent.gridStyle.minorLineOpacity,
map: this._map}));
}
else if (this.interval == 100) {
this.gridlines.push(new google.maps.Polyline( {
path: gr100kCoord,
strokeColor: this.parent.gridStyle.fineLineColor,
strokeWeight: this.parent.gridStyle.fineLineWeight,
strokeOpacity: this.parent.gridStyle.fineLineOpacity,
map: this._map}));
}
}
northings[k++] = this.nlat;
eastings[0] = this.wlng;
k=1;
// for each n-s line that covers the cell, with overedge
for (i=sw_utm_e; i<ne_utm_e; i+=this.interval * skipFactor,j++) {
// collect coords to be used to place markers
// '2*this.interval' is a fudge factor that approximately offsets grid line convergence
geocoords = USNG.UTMtoLL_GeoPoint(i, sw_utm_n+(2*this.interval), zone);
if (geocoords.x > this.wlng && geocoords.x < this.elng) {
eastings[k++] = geocoords.x;
}
temp=[];
for (m=sw_utm_n,n=0; m<=ne_utm_n; m+=precision,n++) {
temp.push(USNG.UTMtoLL(i, m, zone));
}
// clipping routine...eliminate overedge lines
gr100kCoord = [];
//+var to store result of checkClip+
var check = false;
// clipping routine...eliminate overedge lines
// case of final point in the array is not covered
//+revised to return false or an array of google maps lat/lng pairs+
for (p=0 ; p < temp.length-1; p++) {
//+test to see about skipping clip+
//+if (1) {+
check = this.checkClip(temp, p);
if((check.constructor == Array)) {
gr100kCoord.push.apply(gr100kCoord, check);
}
}
if (this.interval == 100000) {
this.gridlines.push(new google.maps.Polyline({
path: gr100kCoord,
strokeColor: this.parent.gridStyle.semiMajorLineColor,
strokeWeight: this.parent.gridStyle.semiMajorLineWeight,
strokeOpacity: this.parent.gridStyle.semiMajorLineOpacity,
map: this._map}));
}
else if (this.interval == 1000) {
this.gridlines.push(new google.maps.Polyline( {
path: gr100kCoord,
strokeColor: this.parent.gridStyle.minorLineColor,
strokeWeight: this.parent.gridStyle.minorLineWeight,
strokeOpacity: this.parent.gridStyle.minorLineOpacity,
map: this._map}));
}
else if (this.interval == 100) {
this.gridlines.push(new google.maps.Polyline( {
path: gr100kCoord,
strokeColor: this.parent.gridStyle.fineLineColor,
strokeWeight: this.parent.gridStyle.fineLineWeight,
strokeOpacity: this.parent.gridStyle.fineLineOpacity,
map: this._map}));
}
}
eastings[k] = this.elng;
if (this.interval == 100000) {
this.place100kLabels(eastings,northings);
}
else if (this.interval == 1000) {
this.place1kLabels(eastings,northings);
}
else if (this.interval == 100) {
this.place100mLabels(eastings,northings);
}
}
catch(oneCellErr) {
throw("Error drawing a cell: " + oneCellErr);
}
} // end drawOneCell
Gridcell.prototype.remove = function() {
try {
if( this.gridlines ) {
for (var i=0; i < this.gridlines.length ; i++) {
this.gridlines[i].setMap(null);
}
this.gridlines=[];
}
if( this.label_100k ) {
for (i=0; this.label_100k[i]; i++) {
this.label_100k[i].parentNode.removeChild(this.label_100k[i]);
}
this.label_100k = [];
}
if( this.label_1k ) {
for (i=0; this.label_1k[i]; i++) {
this.label_1k[i].parentNode.removeChild(this.label_1k[i]);
}
this.label_1k=[];
}
if( this.label_100m ) {
for (i=0; this.label_100m[i]; i++) {
this.label_100m[i].parentNode.removeChild(this.label_100m[i]);
}
this.label_100m = [];
}
}
catch(ex) {
alert("Error removing old USNG graticule: " + ex);
}
}
Gridcell.prototype.makeLabel = function (parentGrid, latLong, labelText, horizontalAlignment, verticalAlignment, className) {
var pixelPoint = this.parent.getProjection().fromLatLngToDivPixel(latLong);
var d = document.createElement("div");
var x = pixelPoint.x;
var y = pixelPoint.y;
var height = 30;
var width = 50;
d.style.position = "absolute";
d.style.width = "" + width + "px";
d.style.height = "" + height + "px";
if( className ) {
d.className = className;
}
else {
d.style.color = "#000000";
d.style.fontFamily='Arial';
d.style.fontSize='small';
}
d.innerHTML = labelText;
d.style.textAlign = horizontalAlignment || "center";
d.style.verticalAlign = verticalAlignment || "middle";
d.style.left = ((horizontalAlignment || "center") == "center" ? (x-0.5*width) : x).toString() + "px";
d.style.top = ((verticalAlignment || "middle") == "middle" ? (y-0.5*height) : y).toString() + "px";
parentGrid.getPanes().overlayLayer.appendChild(d);
return d;
}
Gridcell.prototype.place100kLabels = function(east,north) {
try {
var zone;
var labelText;
var latitude;
var longitude;
/*Removing so this label always shows
if (this._map.getZoom() > 15) {
return; // don't display label when zoomed way in
}
*/
for (var i=0 ; east[i+1] ; i++ ) {
for (var j=0; north[j+1]; j++) {
// labeled marker
zone = MARCONIMAP.getUTMZoneFromLatLong((north[j]+north[j+1])/2,(east[i]+east[i+1])/2 );
// lat and long of center of area
latitude = (north[j]+north[j+1])/2;
longitude = (east[i] + east[i+1])/2;
labelText = USNG.LLtoUSNG(latitude, longitude);
//console.log("Initial label text in this 100klabels function is: "+labelText);
// if zoomed way out use a different label
// MD: Use this section to adjust when zoomed way in, to remove the zone portion
if (this._map.getZoom() < 10 || this._map.getZoom() > 13) {
if (zone > 9) {
labelText = labelText.substring(4,6)
}
else {
labelText = labelText.substring(3,5)
}
}
else {
if (zone > 9) {
labelText = labelText.substring(0,3) + labelText.substring(4,6)
}
else {
labelText = labelText.substring(0,2) + labelText.substring(3,5)
}
}
//original vertical alignment was "middle". Changed to "bottom" to attempt a small offset
this.label_100k.push(this.makeLabel(
this.parent, new google.maps.LatLng(latitude,longitude), labelText, "center", "bottom",
this.parent.gridStyle.semiMajorLabelClass));
//console.log("Placing 100k label: "+labelText);
}
}
}
catch(markerError) {
throw("Error placing 100k markers: " + markerError);
}
}
Gridcell.prototype.place1kLabels = function(east,north) {
try {
var latitude;
var longitude;
// at high zooms, don't label the 1k line since it'll get a 100m label'
if (this._map.getZoom() > 15) {
return;
}
// place labels on N-S grid lines (that is, ladder labels lined up in an E-W row)
// label x-axis
for (var i=1; east[i+1] ; i++) {
if( !east[i] || !east[i+1] ) {
//alert("at i=" + i + ", east is " + east[i] + " and " + east[i+1]);
}
for (var j=1; j<2 && j+1 < north.length ; j++) {
if( !north[j] || !north[j+1] ) {
//alert("at j=" + j + ", northing is " + north[j] + " and " + north[j+1]);
}
// labeled marker
latitude = (north[j]+north[j+1])/2;
longitude = east[i];
if(!latitude) {
MARCONI.warn("x-axis latitude is " + latitude + " when j=" + j);
}
if(!longitude) {
MARCONI.warn("longitude is " + longitude);
}
var gridRef = USNG.LLtoUSNG(latitude, longitude);
var parts = gridRef.split(" ");
var x = parseFloat(parts[2].substr(0,2));
var z = parseFloat(parts[2].substr(2,3));
if( z > 500 ) {
x++;
z=0;
}
var labelText = "" + x +"k";
var marker = this.makeLabel(this.parent, new google.maps.LatLng(latitude,longitude), labelText, "left", "top",
this.parent.gridStyle.minorLabelClass);
this.label_1k.push(marker);
}
}
// place labels on y-axis
for (i=1; i<2; i++) {
for (j=1; north[j+1]; j++) {
// labeled marker
latitude = north[j];
longitude = (east[i]+east[i+1])/2;
if(!latitude) {
MARCONI.warn("y-axis latitude is " + latitude);
}
if(!longitude) {
MARCONI.warn("y-axis longitude is " + longitude);
}
gridRef = USNG.LLtoUSNG(latitude,longitude);
parts = gridRef.split(" ");
var y = parseFloat(parts[3].substr(0,2));
z = parseFloat(parts[3].substr(2,3));
if( z > 500 ) {
y++;
z=0;
}
labelText = "" + y +"k";
marker = this.makeLabel(this.parent, new google.maps.LatLng(latitude,longitude), labelText, "center", "top",
this.parent.gridStyle.minorLabelClass);
this.label_1k.push(marker);
}
}
}
catch(ex) {
throw("Error placeing 1k markers: " + ex);
}
} // end place1kLabels()
Gridcell.prototype.place100mLabels = function(east,north) {
try {
// only label lines when zoomed way in
if( this._map.getZoom() < 14) {
return;
}
//++both arrays must have two or more elements++
if( east.length < 2 || north.length < 2 ) {
return;
}
var skipFactor = (this._map.getZoom() > 15 ? 1 : 2);
//++get lengths of respective lat and long arrays++
var northlen = north.length;
var eastlen = east.length;
// place "x-axis" labels
for (var i = 1; east[i+1] ; i+= 1) {
var count = 1;
for (var j=1; j< 2; j++) {
//++will always be at least two elements (code above on line 1254 checks for that)++
//++array is zero-based, so the first array element is skipped.++
//++for special case where array only has two elements,++
//++lat value for north[j+1] is undefined and 'NaN' gets passed to LLtoUSNG here, resulting in error++
//++added a test for special case of two element array and hard-code distance halfway between two northings++
if (northlen==2){
var gridRefLat = ((north[0]+north[1])/2);
}else{
var gridRefLat = (north[j]+north[j+1])/2;
}
var gridRef = USNG.LLtoUSNG(gridRefLat, east[i]);
var parts = gridRef.split(" ");
var x = parseFloat(parts[2].substr(0,3));
var z = parseFloat(parts[2].substr(3,2));
if( z > 50 ) {
x++;
z=0;
}
if( !(x % skipFactor) ) {
var insigDigits = (skipFactor == 1 || !(x%10) ? "<sup>00</sup>" : "");
//++passing in gridRefLat variable for label++
this.label_100m.push(this.makeLabel(this.parent, new google.maps.LatLng(gridRefLat,(east[i])),
//((north[j]+north[j+1])/2,(east[i])),
MARCONI.fixedFormatNumber(x, 1, 0, true) + insigDigits, "left", "top",
this.parent.gridStyle.fineLabelClass));
}
}
}
// place "y-axis" labels, don't worry about skip factor since there's plenty of room comparatively
for (i=1; i<2; i++) {
for (j=1; north[j+1]; j++) {
//++can get a long value of 'NaN' here too, if zoomed in close enough, and viewport is sized right++
//++again, with special case where easting array only has two elements,++
//++lon value for east[i+1] is undefined,'NaN' gets passed to LLtoUSNG, and results in error++
//++add test for special case of two element array and hard-code distance halfway between two eastings++
if (eastlen==2){
var gridRefLon = ((east[0]+east[1])/2);
}else{
var gridRefLon = (east[i]+east[i+1])/2;
}
gridRef = USNG.LLtoUSNG(north[j],gridRefLon,4);
parts = gridRef.split(" ");
var y = parseFloat(parts[3].substr(0,3));
z = parseFloat(parts[3].substr(3,2));
// if due to roundoff we got something like 99 for z, make it a perfect zero
if( z > 50) {
y++;
z=0;
}
this.label_100m.push(this.makeLabel(
this.parent,
//++use gridRefLon value for creating label++
new google.maps.LatLng((north[j]),gridRefLon),
MARCONI.fixedFormatNumber(y,1,0,true) + "<sup>00</sup>", "center", "top",
this.parent.gridStyle.fineLabelClass));
}
}
}
catch(ex) {
MARCONI.log("Error placing 100-meter markers: " + ex);
throw("Error placing 100-meter markers: " + ex);
}
} // end place100mLabels()
/**++Revised to ensure display of grid lines (see https://code.google.com/p/usng-gmap-v3/issues/detail?id=4). In non-trivial case, now adds both endpoints of a grid line crossing the viewport and having endpoints falling outside of viewport to the array used for rendering polylines ++**/
Gridcell.prototype.checkClip = function(cp, p) {
/// implementation of Cohen-Sutherland clipping algorithm to clip grid lines at boundarie
// of utm zones and the viewport edges
var that=this; // so private funcs can see this via that
var temp;
var pair = []; //+empty array for coordinate pair+
var t;
var u1=cp[p].lng();
var v1=cp[p].lat();
var u2=cp[p+1].lng();
var v2=cp[p+1].lat();
//+flag if nontrivial case+
var nontrivial = false;
//+flag if coordinates have been swapped for clipping+
var swap = false;
//+if point falls outside of viewport, determine if point lies above or below viewport, and if point lies to the left or right of viewport+
function outcode(lat,lng) {
var code = 0;
if (lat < that.slat) {
code |= 4;
}
if (lat > that.nlat) {
code |= 8;
}
if (lng < that.wlng) {
code |= 1;}
if (lng > that.elng) {
code |= 2;
}
return code;
}
//+bitwise code returned after determining where first point lies relative to viewport+
var code1 = outcode(v1, u1);
//+bitwise code returned after determining where second point lies relative to viewport+
var code2 = outcode(v2, u2);
//+determine if coordinate pairs are trivial accept or trivial reject case, or non-trivial case+
//+if non-trivial case, clip segment+
if (nontrivialcheck(v1,u1,v2,u2)) {
clip(v1,u1,v2,u2);
}
function nontrivialcheck(v1,u1,v2,u2){
//+if both points fall outside of the viewport on the same side (e.g. above, below, to left, or to right)+
if ((code1 & code2) !== 0) { // line segment outside window...don't draw it
return null;
}
//+if both points fall within viewport (bitwise OR) and,+
//+if trivial case add first endpoint to array for rendering grid line;+
//+if non-trivial case add both endpoints to array for rendering grid line+
if ((code1 | code2) === 0) { // line segment completely inside window...draw it
pair[0] = new google.maps.LatLng(v1,u1);
if (nontrivial) {
pair[1] = new google.maps.LatLng(v2,u2);
}
nontrivial = false;
return nontrivial;
}
//+otherwise, non-trivial case, clip+
nontrivial = true;
return nontrivial;
}
//+if endpoint falls within viewport, return true+
function inside(lat,lng) {
if (lat < that.slat || lat > that.nlat) {
return 0;
}
if (lng < that.wlng || lng > that.elng) {
return 0;
}
return 1;
}
function clip(v1,u1,v2,u2) {
//+check coordinates of first endpoint to determine if point falls within viewport+
if (inside(v1,u1)) { // coordinates must be altered
// swap coordinates
temp = u1;
u1 = u2;
u2 = temp;
temp = v1;
v1 = v2;
v2 = temp;
temp = code1;
code1 = code2;
code2 = temp;
//+flag that swap of coordinates occured and should be swapped again+
swap = true;
}
//+now clip endpoint to boundary of viewport based on point's position relative to the viewport.+
//generalize code param
if (code1 & 8) { // clip along northern edge of polygon
t = (that.nlat - v1)/(v2-v1);
u1 += t*(u2-u1);
v1 = that.nlat;
}
else if (code1 & 4) { // clip along southern edge
t = (that.slat - v1)/(v2-v1);
u1 += t*(u2-u1);
v1 = that.slat;
}
else if (code1 & 1) { // clip along west edge
t = (that.wlng - u1)/(u2-u1);
v1 += t*(v2-v1);
u1 = that.wlng;
}
else if (code1 & 2) { // clip along east edge
t = (that.elng - u1)/(u2-u1);
v1 += t*(v2-v1);
u1 = that.elng;
}
if (swap){
//+swap coords again+
temp = u1;
u1 = u2;
u2 = temp;
temp = v1;
v1 = v2;
v2 = temp;
//+reset flag+
swap = false;
}
//+get respective endpoints' bitwise codes after clip+
code1 = outcode(v1, u1);
code2 = outcode(v2, u2);
//+now check endpoint to determine if it is now on the viewport boundary+
//+if endpoint is on viewport boundary, but opposite endpoint is outside of viewport, will need to clip again+
if (nontrivialcheck(v1,u1,v2,u2)) {
clip(v1,u1,v2,u2);
}
}
if (pair.length == 0){
return false;
}else{
return pair;
}
}