6

I am looking for a utility to calculate MGRS grid lines, or at least an algorithm. I would like to draw MGRS grid lines on a google or leaflet map.

Seems like most of what I find are shapefiles/vector data. Problem is this won't work if I don't have a connection. I would like to be able to draw the line dynamically.

Any ideas?

1
  • MGRS is based on UTM (WGS84). Please edit your question to specify the level of resolution required, since MGRS was developed for placement of military assets and artillery bombardment, and therfore has a range of potential scales.
    – Vince
    Nov 4, 2016 at 23:29

4 Answers 4

6

There's a rough start to drawing MGRS grids in the leaflet-grids plugin.

If you just need the algorithms, mgrs.js should cover that.

2
  • awesome. Where did you find the algorithm? Nov 4, 2016 at 17:33
  • see updated answer Nov 4, 2016 at 18:08
0

How to draw mgrs grid lines on leaflet, googlemaps, apple maps, etc?

a leaflet sample: https://www.simplegrg.com/?grid=18SVF0944087193

there are 3 steps to do so

  1. Conversion LatLng to/from MGRS
  2. Draw lines
  3. Add labels

Step 1 :

http://www.fleyo.com/ge/help_usng.html

usng.js file on github

https://github.com/codice/usng.js

Step 2, 3, you need to do it use leaflet javascript api

Step 2: The grid lines are Leaflet polylines.

Step 3: The labels at the bottom edge and left edge of the screen are Leaflet divIcons.

0

If you cannot or do not want to use the leaflet plugin, the following javascript code will draw blue MGRS grid lines on a HTML canvas context which it is assumed already contains a map rendered using slippy map tiles, for example OpenStreetMap. These tiles come in different zoom sets from zoom 0 displaying the whole world in one tile, through to zoom 19 displaying the whole world in 2**19 tiles. SlippyMapTiles

The code will display one of the following grid sizes depending on the current zoom level : 100m, 1km, 10km, 100km or the MGRS zone. Grid lines are annotated at the top, bottom, left and right of the viewable area using 12pt san-serif font. The colour and the font size can easily be changed, see definitions at the top of the code block. The grid extends to double the viewing area in all directions to allow for user panning, this can also be easily changed.

The code in my answer makes the following assumptions.

A javascript function must be called to draw the MGRS grid and requires the four following parameters passed to it:

  1. Map, an existing HTML canvas object.
  2. Context, a canvas context that has already been created for Map.
  3. CentreX, half the width of the canvas in pixels.
  4. CentreY, half the height of the canvas in pixels.

In addition the following assumptions are also required, most of which (in my case) were required to render the map tiles in the first place and hence simplify the code presented below to draw the grid lines.

A. The lat,long centre of the currently viewing area for which the grid lines are required has been set in Map.CentreLat and Map.CentreLong.

B. If the canvas has already been scaled for display, this value must have been set in Map.ScaleFactor, if it has not scaled then Map.ScaleFactor must have been set to unity.

C. The full MGRS location string, e.g 30U WD 72583 66413 must have been pre-calculated and stored in Map.CentreMGRS using the function MGRSString, included in the code. This function is not invoked unless the map is panned, except at certain zoom levels where it can be invoked a maximum of two further times (per screen rendering). The function MGRSString is included in the utilities section below.

D. Map.Power has already been set for efficiency to Math.pow (2,zoom); where zoom is the current map zoom level (see drawing maps using slippy tiles)

E. Map.LongitudePerTile has already been set for efficiency to 360 / MapObj.Power;

F. Map.YOrigin has already been set to MercatorY (MapObj.CentreLat, Map.Power). MercatorY is defined in the code.

The primary function PlanMGRSGrid is attached below. This function takes the parameters listed 1-4 above and makes use of the data described in A-F above to decide what grid lines to render.

The decision is based on the value of the number of pixels per kilometre, which is directly related to the zoom level but is more useful when rendering.

As the number of pixels per kilometer increases, first the 100Km grid squares are rendered, then 10Km, then 1km and finally 100m. The code could relatively easily be extended to draw 10m but a need for this would be rare. See also special case below for zoom levels 0-5

The most tricky aspect of rendering MGRS tiles is coping with zone boundaries (the second most tricky aspect is that the orientation of the grid lines changes across each zone).

In order to cope with this, PlanMGRSGrid determines what zones are involved in the rendering and invokes a function DrawMGRSGridLines for each zone involved.

The zone in which the centre of the map is displayed first and then either the zone to the west of this (if one is needed) and/or the zone to the east of this.

For the zoom levels (0-5), this answer only draws the MGRS zones. These are presented as lines of longitude every six degrees and lines of latitude every 8 degrees and annotates them with the MGRS grid zone numbers e.g 30U. To do this a separate function DrawMGRSGridZones is invoked.

Code for PlanMGRSGrid is shown below. You may not like my code formatting (I started coding with Algol 60) but you can pretty print it to suit your coding style.

function PlanMGRSGrid (Map,Context,CentreX,CentreY)
{
if (Map.CentreMGRS.length < 17) return ; // off grid too far north or south

Context.strokeStyle = Context.fillStyle = "#0000ff" ;
Context.font = "12 px sans-serif" ;

var lng = MGRSZoneOrigin (Map.CentreLong);
var eastDiffLong = lng + 3 - Map.CentreLong ;
var westDiffLong = lng - 3 - Map.CentreLong ;
var easternLimit = eastDiffLong * Map.ScaleFactor * 256 / Map.LongitudePerTile ; // longitude change to end of zone (+ve)
var westernLimit = westDiffLong * Map.ScaleFactor * 256 / Map.LongitudePerTile ; // longitude change to end of zone (-ve)

var easternLimit2 = easternLimit + 6 * Map.ScaleFactor * 256 / Map.LongitudePerTile ; // longitude change to end of next zone
var westernLimit2 = westernLimit - 6 * Map.ScaleFactor * 256 / Map.LongitudePerTile ; // longitude change to end of next zone

switch (Map.CentreMGRS.length)
    {
    case 17 : var easting = Map.CentreMGRS.substr (6,5); var northing = Map.CentreMGRS.substr (12,5); var eastingLetter = Map.CentreMGRS.substr (3,1); var northingLetter = Map.CentreMGRS.substr (4,1); break ;
    case 18 :     easting = Map.CentreMGRS.substr (7,5);     northing = Map.CentreMGRS.substr (13,5);     eastingLetter = Map.CentreMGRS.substr (4,1);      northingLetter = Map.CentreMGRS.substr (5,1); break ;
    };

var ppkm = UnscaledPixelsPerKM (Map);

var offMapFactor = 2 ; // 2 times the distance from centre to an edge
var gridTop    = - offMapFactor * CentreY / Map.ScaleFactor ;
var gridBottom =   offMapFactor * CentreY / Map.ScaleFactor ;

var gridLeft   = (( - CentreX * offMapFactor > westernLimit)? - CentreX * offMapFactor : westernLimit) / Map.ScaleFactor ;
var gridRight  = ((   CentreX * offMapFactor < easternLimit)?   CentreX * offMapFactor : easternLimit) / Map.ScaleFactor ;

var eastingsLabelTop    = (- CentreY + 12) / Map.ScaleFactor ;
var eastingsLabelBottom = (  CentreY -  5) / Map.ScaleFactor ;
var northingsLabelLeft  = (- CentreX > westernLimit)?  (- CentreX + 5 ) / Map.ScaleFactor : undefined ;
var northingsLabelRight = (  CentreX < easternLimit)?  (  CentreX - 20) / Map.ScaleFactor : undefined ;

if (ppkm < 0.5) // draw lat long zones only
    {
    DrawMGRSGridZones (Map, Context, Map.CentreLat, Map.CentreLong, 256 / Map.LongitudePerTile, gridTop, (- CentreX * offMapFactor) / Map.ScaleFactor, gridBottom, (CentreX * offMapFactor) / Map.ScaleFactor, eastingsLabelTop, eastingsLabelBottom, (- CentreX + 5) / Map.ScaleFactor, (CentreX - 20) / Map.ScaleFactor); // note gridLeft, gridRight, northingsLabelLeft and northingsLabelRight cannot be used in this case
    return ;
    };

DrawMGRSGridLines (Map, Context, ppkm, eastingLetter, easting, northingLetter, northing, gridTop, gridLeft, gridBottom, gridRight, Map.MGRSConvergence, Map.MGRSConvRate, eastingsLabelTop, eastingsLabelBottom, northingsLabelLeft, northingsLabelRight, 0);

if (westernLimit > - CentreX) // display Additional Grid to West, but only if it will be visible with no panning
    {
    // set westing limits for additional grid
    gridRight = westernLimit / Map.ScaleFactor ;
    gridLeft  = ((-CentreX * offMapFactor > westernLimit2)? - CentreX * offMapFactor : westernLimit2) / Map.ScaleFactor ;
    // set new northing label locations
    northingsLabelLeft  = (- CentreX > westernLimit2)?  (- CentreX + 5 ) / Map.ScaleFactor : undefined ;
    northingsLabelRight = undefined ;
    // choose new origin at the left edge of the screen and compute MGRS grid at it
    var newLongPos = - CentreX ;
    var newLong    = Map.CentreLong + newLongPos * Map.LongitudePerTile / 256 ;
    if (newLong < Map.CentreLong + westDiffLong - 6) 
        {
        // special case - new origin is further west than the western edge of next zone - so choose another origin half way to left edge of screen
        newLongPos = - CentreX / 2 ;
        newLong    = Map.CentreLong + newLongPos * Map.LongitudePerTile / 256 ;
        };
    var newMGRS = MGRSString (Map.CentreLat, newLong);
    // set new easting and new northing to this new origin
    switch (newMGRS.length)
        {
        case 17 : easting = newMGRS.substr (6,5); northing = newMGRS.substr (12,5); eastingLetter = newMGRS.substr (3,1); northingLetter = newMGRS.substr (4,1); break ;
        case 18 : easting = newMGRS.substr (7,5); northing = newMGRS.substr (13,5); eastingLetter = newMGRS.substr (4,1); northingLetter = newMGRS.substr (5,1); break ;
        };
    // call DrawMGRSGridLines with a pixel offset based on the distance from the old origin to the new origin
    //console.log ("Next Zone Westwards ", ppkm, eastingLetter, easting, northingLetter, northing, gridTop, gridLeft, gridBottom, gridRight, MGRSConvergence (Map.CentreLat, newLong), Map.MGRSConvRate, eastingsLabelTop, eastingsLabelBottom, northingsLabelLeft, northingsLabelRight, newLongPos);
    DrawMGRSGridLines (Map, Context, ppkm, eastingLetter, easting, northingLetter, northing, gridTop, gridLeft, gridBottom, gridRight, MGRSConvergence (Map.CentreLat, newLong), Map.MGRSConvRate, eastingsLabelTop, eastingsLabelBottom, northingsLabelLeft, northingsLabelRight, newLongPos);
    };

if (easternLimit < CentreX) // display Additional Grid to East, but only if it will be visible with no panning
    {
    // set easting limits for additional grid
    gridLeft  = easternLimit / Map.ScaleFactor ;
    gridRight = ((CentreX * offMapFactor < easternLimit2)? CentreX * offMapFactor : easternLimit2) / Map.ScaleFactor ;
    // set new northing label locations
    northingsLabelLeft  = undefined ;
    northingsLabelRight = (  CentreX < easternLimit2)?  (  CentreX - 20) / Map.ScaleFactor : undefined ;
    // choose new origin at the right edge of the visible screen and compute MGRS grid at it
    newLongPos = CentreX ;
    newLong = Map.CentreLong + newLongPos * Map.LongitudePerTile / 256 ;
    if (newLong > Map.CentreLong + eastDiffLong + 6) 
        {
        // special case - new origin is further east than the eastern edge of next zone - so choose another origin half way to right edge of screen
        newLongPos = CentreX / 2 ;
        newLong    = Map.CentreLong + newLongPos * Map.LongitudePerTile / 256 ;
        };
    newMGRS = MGRSString (Map.CentreLat, newLong);
    // set new easting and new northing to this new origin
    switch (newMGRS.length)
        {
        case 17 : easting = newMGRS.substr (6,5); northing = newMGRS.substr (12,5); eastingLetter = newMGRS.substr (3,1); northingLetter = newMGRS.substr (4,1); break ;
        case 18 : easting = newMGRS.substr (7,5); northing = newMGRS.substr (13,5); eastingLetter = newMGRS.substr (4,1); northingLetter = newMGRS.substr (5,1); break ;
        };
    // draw additional grid with a pixel offset based on the distance from the old origin to the new origin
    //console.log ("Next Zone Eastwards ", ppkm, eastingLetter, easting, northingLetter, northing, gridTop, gridLeft, gridBottom, gridRight, MGRSConvergence (Map.CentreLat, newLong), Map.MGRSConvRate, eastingsLabelTop, eastingsLabelBottom, northingsLabelLeft, northingsLabelRight, newLongPos);
    DrawMGRSGridLines (Map, Context, ppkm, eastingLetter, easting, northingLetter, northing, gridTop, gridLeft, gridBottom, gridRight, MGRSConvergence (Map.CentreLat, newLong), Map.MGRSConvRate, eastingsLabelTop, eastingsLabelBottom, northingsLabelLeft, northingsLabelRight, newLongPos);
    };

};// end of PlanMGRSGrid function

The code for the special case DrawMGRSGridZones is shown below

function DrawMGRSGridZones (Map,Context,Lat,Long,PPDegree,GridTop,GridLeft,GridBottom,GridRight,EastingsLabelTop,EastingsLabelBottom,NorthingsLabelLeft,NorthingsLabelRight)
{
console.log (GridTop,GridLeft,GridBottom,GridRight);
var eastLabelOffset  = 3 / Map.ScaleFactor ;
var northLabelOffset = 5 / Map.ScaleFactor ;
var east , eastOrig , eastAlign , eastZone  ;
var north, northOrig, northAlign, northZone ;
var gridX, gridY ;
    //----------------
    function yPos (lat)
    {
    return 256 * (MercatorY (lat, Map.Power) - Map.YOrigin); 
    };// end of local function
    //----------------
    function xPos (lng)
    {
    return 256 * (lng - Map.CentreLong) / Map.LongitudePerTile ;
    };// end of local function
    //----------------

// first plot lines of Longitude every six degrees from approximately near the centre across to the right of the area and then those from just left of the same place across to the left of the area
eastOrig  = east = 6 * Math.floor (Long/6) ; // round to the nearest 6 degrees
eastAlign = Long - east ;
gridX = xPos (east); 
while (gridX < GridRight) {plot6DegreesLongitude (east); east += 6 ; gridX = xPos (east)}; // allow east > 180
east = eastOrig - 6 ; // allow east < -180
gridX = xPos (east); 
while (gridX > GridLeft ) {plot6DegreesLongitude (east); east -= 6 ; gridX = xPos (east)}; // allow east < -180

// then plot lines of Latitude every eight degrees from approximately near the centre up to the top of the area and then those from just below the same place down to the bottom of the area
//console.log ("Orig Lat " + Lat);
northOrig  = north = 8 * Math.floor (Lat/8) ; // round to the nearest 8 degrees
//console.log ("Rounded Lat " + north);
northAlign = Lat - north ;
gridY = yPos (north); 
while (gridY > GridTop   ) {plot8DegreesLatitude (north); north += 8 ; gridY = yPos (north)};
north = northOrig - 8 ;
gridY = yPos (north); 
while (gridY < GridBottom) {plot8DegreesLatitude (north); north -= 8 ; gridY = yPos (north)};

//----------------
function plot6DegreesLongitude (lng)
    {
    if (lng >   180) lng = lng - 360 ; // earth wrap around E to W
    if (lng <= -180) lng = lng + 360 ; // earth wrap around W to E
    eastZone = 1 + Math.floor ((lng+180)/6); // positive number
    if (eastZone > 60) eastZone -= 60 ;
    Context.beginPath (); 
    Context.lineWidth = 0.5 ; 
    Context.moveTo (gridX , GridTop);
    Context.lineTo (gridX , GridBottom);
    Context.stroke ();
    Context.fillText (eastZone, gridX + eastLabelOffset, EastingsLabelTop   ); // label is to the east (right) of the line
    Context.fillText (eastZone, gridX + eastLabelOffset, EastingsLabelBottom);
};// end of local function
//------------------------
function plot8DegreesLatitude (lat)
    {
    //console.log ("Plot Latitude " + lat);
    northZone = 'CDEFGHJKLMNPQRSTUVWXX'.charAt (Math.floor (lat/8 + 10)); // X is a double height band, hence XX
    Context.beginPath (); 
    Context.lineWidth = 0.5 ; 
    Context.moveTo (GridLeft , gridY);
    Context.lineTo (GridRight, gridY);
    Context.stroke ();
    Context.fillText (northZone, NorthingsLabelLeft , gridY - northLabelOffset); // label is to the north of (above) the line
    Context.fillText (northZone, NorthingsLabelRight, gridY - northLabelOffset);
    };// end of local function
//------------------------

};// end of DrawMGRSGridZones function

The following utility functions are required

function MGRSZoneOrigin (Long) // -180 <= Long < 180
{
return 6 * Math.floor ((Long + 180)/6) - 177 ;

};//end of MGRSZoneOrigin
//--------------------------------------------------------------
function MGRSConvergence (Lat, Long) // difference in radians between True North and MGRS Grid North (zero on Equator, up to +/- 3 degrees at 80 degrees North/South)
{
var posInZone = Long - MGRSZoneOrigin (Long);
var tanPIZ = Math.tan (posInZone * Math.PI/180);
var sinLat = Math.sin (Lat       * Math.PI/180);

return Math.atan (tanPIZ * sinLat);

};//end of MGRSConvergence
//--------------------------------------------------------------------------------------
function MGRSConvergenceRateOfChange (Lat) // rads per KM
{
var zoneConvDiff = 0.104815 * Math.sin (Lat * Math.PI/180); // 0.104815 = 2 * tan (3 degrees)
var zoneKM       = 111.1657 * 6 * Math.cos (Lat * Math.PI/180); 

return zoneConvDiff / zoneKM ;
};//end of MGRSConvergenceRateOfChange
//--------------------------------------------------------------------------------------
function MercatorY (Lat,Power) // returns the mercator Y value for a Latitude for a particular map defined by the number of total number of tiles 2,4,8,16,32,64,128... for map levels 1,2,3,4,5,6,7.... 
{
var pi = Math.PI;
var latRad = Lat * pi/180 ;
var tanLat = Math.tan (latRad);
var secLat = 1/Math.cos (latRad);
return Power * (1 - Math.log (tanLat + secLat) / pi)/2 ;

};// end of MercatorY function
//--------------------------------------------------------------------------------------
function PreviousMGRSEastingsLetter (Letter)
{
var letters = "ABCDEFGHJKLMNPQRSTUVWXYZ" ;
var pos = letters.indexOf (Letter);
if (pos == 0) pos = letters.length ; // should never be requested
return letters [pos-1];

};//end of PreviousMGRSEastingsLetter
//--------------------------------------------------------------------------------------
function NextMGRSEastingsLetter (Letter)
{
var letters = "ABCDEFGHJKLMNPQRSTUVWXYZ" ;
var pos = letters.indexOf (Letter);
if (pos == letters.length-1) pos = 0 ; // should never be requested
return letters [pos+1];

};//end of NextMGRSEastingsLetter
//--------------------------------------------------------------------------------------
function PreviousMGRSNorthingsLetter (Letter)
{
var letters = "ABCDEFGHJKLMNPQRSTUV";
var pos = letters.indexOf (Letter);
if (pos == 0) pos = letters.length ; 
return letters [pos-1];

};//end of PreviousMGRSNorthingsLetter
//--------------------------------------------------------------------------------------
function NextMGRSNorthingsLetter (Letter)
{
var letters = "ABCDEFGHJKLMNPQRSTUV";
var pos = letters.indexOf (Letter);
if (pos == letters.length-1) pos = -1 ; 
return letters [pos+1];

};//end of NextMGRSNorthingsLetter
//--------------------------------------------------------------------------------------
function MGRSString (Lat, Long)
{ 
if (Lat < -80) return 'Too far South' ; if (Lat > 84) return 'Too far North' ;
var c = 1 + Math.floor ((Long+180)/6);
var e = c*6 - 183 ;
var k = Lat*Math.PI/180;
var l = Long*Math.PI/180;
var m = e*Math.PI/180;
var n = Math.cos (k);
var o = 0.006739496819936062*Math.pow (n,2);
var p = 40680631590769/(6356752.314*Math.sqrt(1 + o));
var q = Math.tan (k);
var r = q*q;
var t = l - m;
var u = 1.0 - r + o;
var v = 5.0 - r + 9*o + 4.0*(o*o);
var w = 5.0 - 18.0*r + (r*r) + 14.0*o - 58.0*r*o;
var x = 61.0 - 58.0*r + (r*r) + 270.0*o - 330.0*r*o;
var y = 61.0 - 479.0*r + 179.0*(r*r) - (r*r*r);
var z = 1385.0 - 3111.0*r + 543.0*(r*r) - (r*r*r);
var aa = p*n*t + (p/6.0*Math.pow (n,3)*u*Math.pow (t,3)) + (p/120.0*Math.pow (n,5)*w*Math.pow (t,5)) + (p/5040.0*Math.pow (n,7)*y*Math.pow (t,7));
var ab = 6367449.14570093*(k - (0.00251882794504*Math.sin (2*k)) + (0.00000264354112*Math.sin (4*k)) - (0.00000000345262*Math.sin (6*k)) + (0.000000000004892*Math.sin (8*k))) + (q/2.0*p*Math.pow (n,2)*Math.pow (t,2)) + (q/24.0*p*Math.pow (n,4)*v*Math.pow (t,4)) + (q/720.0*p*Math.pow (n,6)*x*Math.pow (t,6)) + (q/40320.0*p*Math.pow (n,8)*z*Math.pow (t,8));
aa = aa*0.9996 + 500000.0;
ab = ab*0.9996; if (ab < 0.0) ab += 10000000.0;
var ad = 'CDEFGHJKLMNPQRSTUVWXX'.charAt (Math.floor (Lat/8 + 10));
var ae = Math.floor (aa/100000);
var af = ['ABCDEFGH','JKLMNPQR','STUVWXYZ'][(c-1)%3].charAt (ae-1);
var ag = Math.floor (ab/100000)%20;
var ah = ['ABCDEFGHJKLMNPQRSTUV','FGHJKLMNPQRSTUVABCDE'][(c-1)%2].charAt (ag);
function pad (val) {if (val < 10) {val = '0000' + val} else if (val < 100) {val = '000' + val} else if (val < 1000) {val = '00' + val} else if (val < 10000) {val = '0' + val};return val};
aa = Math.floor (aa%100000); aa = pad (aa); 
ab = Math.floor (ab%100000); ab = pad (ab);
var mgrs = c + ad + ' ' + af + ah + ' ' + aa + ' ' + ab;
return mgrs ;
};//end of MGRSString function

The code for DrawMGRSGridLines is presented as a separate answer as it will not fit.

0

This is the code for DrawMGRSGridZones that will not fit in my earlier answer. I could have formatted it to do, by removing comments and blank lines but I decided not to do this as it would reduce it's value to anyone needing to change it.

The function below contains section for 100m, 1km, 10km and 100km MGRS grid rendering which could probably be condensed but I did not have time to do so and it would (probably) make it harder to maintain if I had.

Each section starts by using elements of the MGRS location string for the centre point of the displayed map (or if OriginOffset is non zero a point directly to the east or west of this) and then renders the MGRS grid within the top, left, bottom and right limits passed to it from PlanMGRSGrid.

In each case the MGRS location at the centre point is used to establish that at the left hand limit and then the drawing of the eastings are made one by one till the right hand limit is reached. Note that the orientation of these eastings changes each time, not noticeably in the 100m case but definitely noticeable in the 100km case.

Then in each case the MGRS location at the centre point is used to establish that at the bottom limit and then the drawing of the northings are made one by one till the top limit is reached. Note that the northings are drawn as segmented straight lines rather than curves, and the orientation of these segments changes from left to right to present a curve, again not noticeably in the 100m case but definitely noticeable in the 100km case.

The code is attached below. See my earlier answer for the calling function and any utility functions used.

function DrawMGRSGridLines (Map,Context,PPKM,EastingLetter,Easting,NorthingLetter,Northing,GridTop,GridLeft,GridBottom,GridRight,BaseConvergence,RateOfChangeOfConvergence,EastingsLabelTop, EastingsLabelBottom, NorthingsLabelLeft, NorthingsLabelRight, OriginOffset)  // PPKM = Pixels per KM
{
var eastLabelOffset  = 3 / Map.ScaleFactor ;
var northLabelOffset = 5 / Map.ScaleFactor ;

if (PPKM > 200) // draw 100m grid 
    {
    var pp100m = PPKM / 10 ; // pixels per 100m
    var eastAlign  = (100 - Easting .substring (3)) / 100 ; // fraction of 100m eastwards  to next grid line
    var northAlign =        Northing.substring (3)  / 100 ; // fraction of 100m northwards to next grid line
    var eastings100M  = parseInt (Easting .substr (0,3)) + 1 ;
    var northings100M = parseInt (Northing.substr (0,3));
    var convergence = BaseConvergence ;
    var gridX = OriginOffset + eastAlign * pp100m ;
    while (gridX - pp100m > GridLeft ) {gridX -= pp100m ; eastings100M-- ; convergence -= RateOfChangeOfConvergence / 10 ; if (eastings100M < 0) eastings100M = 999}; /// move to left of grid area
    while (gridX          < GridRight) // traverse horizontally from here to right of grid area, drawing vertical lines each 100m
        {
        Context.beginPath (); 
        Context.lineWidth = (10 * Math.floor (eastings100M/10) == eastings100M)? 1.5 : 0.5 ;
        convergence += RateOfChangeOfConvergence / 10 ;
        var eastingTopX    = gridX - convergence * GridTop ;
        var eastingTopY    = GridTop ;
        var eastingBottomX = gridX - convergence * GridBottom ;
        var eastingBottomY = GridBottom ;
        // deal with four special edge zone cases (two in each hemisphere) where the vertical line has to be shortened and one of the corresponding eastings labels has to be hidden
        var showEastingsTopLabel    = true ;
        var showEastingsBottomLabel = true ; 
        if (NorthingsLabelLeft  == undefined && Map.CentreLat > 0 && eastingTopX    < GridLeft ) {eastingTopY   = eastingBottomY + (eastingTopY     - eastingBottomY) * (eastingBottomX - GridLeft      )/(eastingBottomX - eastingTopX   ); eastingTopX    = GridLeft ; showEastingsTopLabel    = false};
        if (NorthingsLabelRight == undefined && Map.CentreLat > 0 && eastingTopX    > GridRight) {eastingTopY   = eastingBottomY + (eastingTopY     - eastingBottomY) * (GridRight      - eastingBottomX)/(eastingTopX    - eastingBottomX); eastingTopX    = GridRight; showEastingsTopLabel    = false};
        if (NorthingsLabelLeft  == undefined && Map.CentreLat < 0 && eastingBottomX < GridLeft ) {eastingBottomY = eastingTopY   + (eastingBottomY  - eastingTopY   ) * (eastingTopX    - GridLeft      )/(eastingTopX    - eastingBottomX); eastingBottomX = GridLeft ; showEastingsBottomLabel = false};
        if (NorthingsLabelRight == undefined && Map.CentreLat < 0 && eastingBottomX > GridRight) {eastingBottomY = eastingTopY   + (eastingBottomY  - eastingTopY   ) * (GridRight      - eastingTopX   )/(eastingBottomX - eastingTopX   ); eastingBottomX = GridRight; showEastingsBottomLabel = false};
        Context.moveTo (eastingTopX   , eastingTopY   );
        Context.lineTo (eastingBottomX, eastingBottomY);
        Context.stroke ();
        var eastingsLabel = (eastings100M < 10)? "00" + eastings100M : ((eastings100M < 100)? "0" + eastings100M : eastings100M);
        if (showEastingsTopLabel   ) Context.fillText (eastingsLabel, gridX - convergence * EastingsLabelTop    + eastLabelOffset, EastingsLabelTop   ); // label is to the east (right) of the line
        if (showEastingsBottomLabel) Context.fillText (eastingsLabel, gridX - convergence * EastingsLabelBottom + eastLabelOffset, EastingsLabelBottom);
        gridX += pp100m ;
        eastings100M++ ;
        if (eastings100M == 1000) eastings100M = 0 ;
        };

    var gridY = northAlign * pp100m ;
    while (gridY + pp100m < GridBottom) {gridY += pp100m ; northings100M--; if (northings100M < 0) northings100M = 999}; // move to bottom of grid area
    while (gridY          > GridTop   ) // traverse vertically from bottom of grid area to to top of grid area
        {
        Context.beginPath (); 
        Context.lineWidth = (10 * Math.floor (northings100M/10) == northings100M)? 1.5 : 0.5 ;

        var gridYOffset = gridY ;
        gridX = OriginOffset + eastAlign * pp100m + BaseConvergence * gridY ;
        convergence = BaseConvergence ;
        while (gridX - pp100m > GridLeft) {gridX -= pp100m; convergence -= RateOfChangeOfConvergence / 10 ; gridYOffset -= convergence * pp100m}; // move to left of grid area
        Context.moveTo (GridLeft, gridYOffset - convergence * (gridX - GridLeft)); // ensure northing starts at left edge of area
        Context.lineTo (gridX, gridYOffset); // first section of northing
        while (gridX + pp100m < GridRight) // traverse horizontally from left of grid area to right of grid area, drawing segmented horizontal lines each 100m
            {
            gridX += pp100m ;
            convergence += RateOfChangeOfConvergence / 10 ;
            gridYOffset += convergence * pp100m ;
            Context.lineTo (gridX, gridYOffset);
            };
        Context.lineTo (GridRight, gridYOffset + convergence * (GridRight - gridX)); // ensure northing ends at the right edge of area
        Context.stroke ();

        var northingsLabel = (northings100M < 10)? "00" + northings100M : ((northings100M < 100)? "0" + northings100M : northings100M);
        if (NorthingsLabelLeft  != undefined) Context.fillText (northingsLabel, NorthingsLabelLeft , gridY - northLabelOffset + ((OriginOffset == 0)? BaseConvergence * NorthingsLabelLeft  : 0)); // label is to the north of (above) the line
        if (NorthingsLabelRight != undefined) Context.fillText (northingsLabel, NorthingsLabelRight, gridY - northLabelOffset + ((OriginOffset == 0)? BaseConvergence * NorthingsLabelRight : 0));
        gridY -= pp100m ;
        northings100M++ ;
        if (northings100M == 1000) northings100M = 0 ;
        };
    if (NorthingsLabelLeft  == undefined) // draw zone western edge
        {
        Context.beginPath (); 
        Context.lineWidth = 1.5 ;
        Context.moveTo (GridLeft, GridBottom); 
        Context.lineTo (GridLeft, GridTop); 
        Context.stroke ();
        }; 
    if (NorthingsLabelRight == undefined) // draw zone eastern edge
        {
        Context.beginPath (); 
        Context.lineWidth = 1.5 ;
        Context.moveTo (GridRight, GridBottom); 
        Context.lineTo (GridRight, GridTop); 
        Context.stroke ();
        };
    }
else if (PPKM > 20) // draw kilometre grid
    {
    eastAlign  = (1000 - Easting .substring (2)) / 1000 ; // fraction of a km eastwards  to next grid line
    northAlign =         Northing.substring (2)  / 1000 ; // fraction of a km northwards to next grid line
    var eastingsKM  = parseInt (Easting .substr (0,2)) + 1 ;
    var northingsKM = parseInt (Northing.substr (0,2));
    convergence = BaseConvergence ;
    gridX = OriginOffset + eastAlign * PPKM ;
    while (gridX - PPKM > GridLeft ) {gridX -= PPKM ; eastingsKM-- ; convergence -= RateOfChangeOfConvergence ; if (eastingsKM < 0) eastingsKM = 99}; // move to left of grid area
    while (gridX        < GridRight) // traverse horizontally from left of grid area to right of grid area, drawing vertical lines each km
        {
        Context.beginPath (); 
        Context.lineWidth = (10 * Math.floor (eastingsKM/10) == eastingsKM)? 1.5 : 0.5 ;
        convergence += RateOfChangeOfConvergence ;
        eastingTopX    = gridX - convergence * GridTop ;
        eastingTopY    = GridTop ;
        eastingBottomX = gridX - convergence * GridBottom ;
        eastingBottomY = GridBottom ;
        // deal with four special edge zone cases (two in each hemisphere) where the vertical line has to be shortened and one of the corresponding eastings labels has to be hidden
        showEastingsTopLabel    = true ;
        showEastingsBottomLabel = true ; 
        if (NorthingsLabelLeft  == undefined && Map.CentreLat > 0 && eastingTopX    < GridLeft ) {eastingTopY   = eastingBottomY + (eastingTopY     - eastingBottomY) * (eastingBottomX - GridLeft      )/(eastingBottomX - eastingTopX   ); eastingTopX    = GridLeft ; showEastingsTopLabel    = false};
        if (NorthingsLabelRight == undefined && Map.CentreLat > 0 && eastingTopX    > GridRight) {eastingTopY   = eastingBottomY + (eastingTopY     - eastingBottomY) * (GridRight      - eastingBottomX)/(eastingTopX    - eastingBottomX); eastingTopX    = GridRight; showEastingsTopLabel    = false};
        if (NorthingsLabelLeft  == undefined && Map.CentreLat < 0 && eastingBottomX < GridLeft ) {eastingBottomY = eastingTopY   + (eastingBottomY  - eastingTopY   ) * (eastingTopX    - GridLeft      )/(eastingTopX    - eastingBottomX); eastingBottomX = GridLeft ; showEastingsBottomLabel = false};
        if (NorthingsLabelRight == undefined && Map.CentreLat < 0 && eastingBottomX > GridRight) {eastingBottomY = eastingTopY   + (eastingBottomY  - eastingTopY   ) * (GridRight      - eastingTopX   )/(eastingBottomX - eastingTopX   ); eastingBottomX = GridRight; showEastingsBottomLabel = false};
        Context.moveTo (eastingTopX   , eastingTopY   );
        Context.lineTo (eastingBottomX, eastingBottomY);
        Context.stroke ();
        eastingsLabel = (eastingsKM < 10)? "0" + eastingsKM : eastingsKM ;
        if (showEastingsTopLabel   ) Context.fillText (eastingsLabel, gridX - convergence * EastingsLabelTop    + eastLabelOffset, EastingsLabelTop   ); // label is to the east (right) of the line
        if (showEastingsBottomLabel) Context.fillText (eastingsLabel, gridX - convergence * EastingsLabelBottom + eastLabelOffset, EastingsLabelBottom);
        gridX += PPKM ;
        eastingsKM++ ;
        if (eastingsKM == 100) eastingsKM = 0 ;
        };

    gridY = northAlign * PPKM ;
    while (gridY + PPKM < GridBottom) {gridY += PPKM ; northingsKM--; if (northingsKM < 0) northingsKM = 99}; // move to bottom of grid area
    while (gridY        > GridTop   ) // traverse vertically from bottom of grid area to top of grid area
        {
        Context.beginPath (); 
        Context.lineWidth = (10 * Math.floor (northingsKM/10) == northingsKM)? 1.5 : 0.5 ;

        gridYOffset = gridY ;
        gridX = OriginOffset + eastAlign * PPKM + BaseConvergence * gridY ;
        convergence = BaseConvergence ;
        while (gridX - PPKM > GridLeft) {gridX -= PPKM ; convergence -= RateOfChangeOfConvergence; gridYOffset -= convergence * PPKM}; // move to left of grid area
        Context.moveTo (GridLeft, gridYOffset - convergence * (gridX - GridLeft)); // ensure northing starts at left edge of area
        Context.lineTo (gridX, gridYOffset); // first section of northing
        while (gridX + PPKM < GridRight) // traverse horizontally from left of grid area to right of grid area, drawing segmented horizontal lines each km
            {
            gridX += PPKM ;
            convergence += RateOfChangeOfConvergence ;
            gridYOffset += convergence * PPKM ;
            Context.lineTo (gridX, gridYOffset);
            };
        Context.lineTo (GridRight, gridYOffset + convergence * (GridRight - gridX)); // ensure northing ends at the right right edge of area
        Context.stroke ();

        northingsLabel = (northingsKM < 10)? "0" + northingsKM : northingsKM ;
        if (NorthingsLabelLeft  != undefined) Context.fillText (northingsLabel, NorthingsLabelLeft , gridY - northLabelOffset + ((OriginOffset == 0)? BaseConvergence * NorthingsLabelLeft  : 0)); // label is to the north of (above) the line
        if (NorthingsLabelRight != undefined) Context.fillText (northingsLabel, NorthingsLabelRight, gridY - northLabelOffset + ((OriginOffset == 0)? BaseConvergence * NorthingsLabelRight : 0));
        gridY -= PPKM ;
        northingsKM++ ;
        if (northingsKM == 100) northingsKM = 0 ;
        };
    if (NorthingsLabelLeft  == undefined) // draw zone western edge
        {
        Context.beginPath (); 
        Context.lineWidth = 1.5 ;
        Context.moveTo (GridLeft, GridBottom); 
        Context.lineTo (GridLeft, GridTop); 
        Context.stroke ();
        }; 
    if (NorthingsLabelRight == undefined) // draw zone eastern edge
        {
        Context.beginPath (); 
        Context.lineWidth = 1.5 ;
        Context.moveTo (GridRight, GridBottom); 
        Context.lineTo (GridRight, GridTop); 
        Context.stroke ();
        };
    }
else if (PPKM > 2) // draw 10 kilometre grid
    {
    var pp10km = 10 * PPKM ; // pixels per 10 km
    eastAlign  = (10000 - Easting .substring (1)) / 10000 ; // fraction of 10 km eastwards  to next grid line
    northAlign =          Northing.substring (1)  / 10000 ; // fraction of 10 km northwards to next grid line
    var eastings10KM    = parseInt (Easting .substr (0,1)) + 1 ;
    var eastingsLetter  = EastingLetter ;
    var northings10KM   = parseInt (Northing.substr (0,1));
    var northingsLetter = NorthingLetter ;
    convergence = BaseConvergence ; // + eastAlign * 10 * RateOfChangeOfConvergence ;
    gridX = OriginOffset + eastAlign * pp10km ;
    while (gridX - pp10km > GridLeft ) {gridX -= pp10km ; eastings10KM-- ; convergence -= 10 * RateOfChangeOfConvergence ; if (eastings10KM < 0) {eastings10KM = 9; eastingsLetter = PreviousMGRSEastingsLetter (eastingsLetter)}}; // move to left of grid area
    while (gridX          < GridRight) // traverse horizontally from left of grid area to right of grid area, drawing vertical lines each 10 km
        {
        Context.beginPath (); 
        Context.lineWidth = (eastings10KM == 0)? 1.5 : 0.5 ;
        convergence += 10 * RateOfChangeOfConvergence ;
        eastingTopX    = gridX - convergence * GridTop ;
        eastingTopY    = GridTop ;
        eastingBottomX = gridX - convergence * GridBottom ;
        eastingBottomY = GridBottom ;
        // deal with four special edge zone cases (two in each hemisphere) where the vertical line has to be shortened and one of the corresponding eastings labels has to be hidden
        showEastingsTopLabel    = true ;
        showEastingsBottomLabel = true ; 
        if (NorthingsLabelLeft  == undefined && Map.CentreLat > 0 && eastingTopX    < GridLeft ) {eastingTopY   = eastingBottomY + (eastingTopY     - eastingBottomY) * (eastingBottomX - GridLeft      )/(eastingBottomX - eastingTopX   ); eastingTopX    = GridLeft ; showEastingsTopLabel    = false};
        if (NorthingsLabelRight == undefined && Map.CentreLat > 0 && eastingTopX    > GridRight) {eastingTopY   = eastingBottomY + (eastingTopY     - eastingBottomY) * (GridRight      - eastingBottomX)/(eastingTopX    - eastingBottomX); eastingTopX    = GridRight; showEastingsTopLabel    = false};
        if (NorthingsLabelLeft  == undefined && Map.CentreLat < 0 && eastingBottomX < GridLeft ) {eastingBottomY = eastingTopY   + (eastingBottomY  - eastingTopY   ) * (eastingTopX    - GridLeft      )/(eastingTopX    - eastingBottomX); eastingBottomX = GridLeft ; showEastingsBottomLabel = false};
        if (NorthingsLabelRight == undefined && Map.CentreLat < 0 && eastingBottomX > GridRight) {eastingBottomY = eastingTopY   + (eastingBottomY  - eastingTopY   ) * (GridRight      - eastingTopX   )/(eastingBottomX - eastingTopX   ); eastingBottomX = GridRight; showEastingsBottomLabel = false};
        Context.moveTo (eastingTopX   , eastingTopY   );
        Context.lineTo (eastingBottomX, eastingBottomY);
        Context.stroke ();
        eastingsLabel = eastingsLetter + eastings10KM ; 
        if (showEastingsTopLabel   ) Context.fillText (eastingsLabel, gridX - convergence * EastingsLabelTop    + eastLabelOffset, EastingsLabelTop   ); // label is to the east (right) of the line
        if (showEastingsBottomLabel) Context.fillText (eastingsLabel, gridX - convergence * EastingsLabelBottom + eastLabelOffset, EastingsLabelBottom);
        gridX += pp10km ;
        eastings10KM++ ;
        if (eastings10KM == 10) {eastings10KM = 0 ; eastingsLetter = NextMGRSEastingsLetter (eastingsLetter)};
        };

    gridY = northAlign * pp10km ;
    while (gridY + pp10km < GridBottom) {gridY += pp10km ; northings10KM--; if (northings10KM < 0) {northings10KM = 9; northingsLetter = PreviousMGRSNorthingsLetter (northingsLetter)}}; // move to bottom of grid area
    while (gridY          > GridTop   ) // traverse vertically from bottom of grid area to top of grid area
        {
        Context.beginPath (); 
        Context.lineWidth = (northings10KM == 0)? 1.5 : 0.5 ;

        var northingsLabelLeftGridY  = undefined ;
        var northingsLabelRightGridY = undefined ;
        gridYOffset = gridY ;
        convergence = BaseConvergence ; // + eastAlign * 10 * RateOfChangeOfConvergence ;
        gridX = OriginOffset + eastAlign * pp10km + convergence * gridY ;
        while (gridX - pp10km > GridLeft) {gridX -= pp10km; convergence -= 10 * RateOfChangeOfConvergence; gridYOffset -= convergence * pp10km}; // move to left of grid area
        Context.moveTo (GridLeft, gridYOffset - convergence * (gridX - GridLeft)); // ensure northing starts at left edge of area
        Context.lineTo (gridX, gridYOffset); // first section of northing
            while (gridX + pp10km < GridRight) // traverse horizontally from left of grid area to right of grid area, drawing segmented horizontal lines each 10 km
            {
            if (NorthingsLabelLeft  != undefined && northingsLabelLeftGridY  == undefined && gridX > NorthingsLabelLeft ) northingsLabelLeftGridY  = gridYOffset ; // record position for northings left  label
            if (NorthingsLabelRight != undefined && northingsLabelRightGridY == undefined && gridX > NorthingsLabelRight) northingsLabelRightGridY = gridYOffset ; // record position for northings right label
            gridX += pp10km ;
            convergence += 10 * RateOfChangeOfConvergence ;
            gridYOffset += convergence * pp10km ;
            Context.lineTo (gridX, gridYOffset);
            };
        if (NorthingsLabelLeft  != undefined && northingsLabelLeftGridY  == undefined) northingsLabelLeftGridY  = gridYOffset ; // record position for northings left  label if not done already
        if (NorthingsLabelRight != undefined && northingsLabelRightGridY == undefined) northingsLabelRightGridY = gridYOffset ; // record position for northings right label if not done already
        Context.lineTo (GridRight, gridYOffset + convergence * (GridRight - gridX)); // ensure northing ends at the right edge of area
        Context.stroke ();

        northingsLabel = northingsLetter + northings10KM ; 
        if (NorthingsLabelLeft  != undefined) Context.fillText (northingsLabel, NorthingsLabelLeft , northingsLabelLeftGridY  - northLabelOffset); // label is to the north of (above) the line
        if (NorthingsLabelRight != undefined) Context.fillText (northingsLabel, NorthingsLabelRight, northingsLabelRightGridY - northLabelOffset); 
        gridY -= pp10km ;
        northings10KM++ ;
        if (northings10KM == 10) {northings10KM = 0 ; northingsLetter = NextMGRSNorthingsLetter (northingsLetter)};
        };
    if (NorthingsLabelLeft  == undefined) // draw zone western edge
        {
        Context.beginPath (); 
        Context.lineWidth = 1.5 ;
        Context.moveTo (GridLeft, GridBottom); 
        Context.lineTo (GridLeft, GridTop); 
        Context.stroke ();
        }; 
    if (NorthingsLabelRight == undefined) // draw zone eastern edge
        {
        Context.beginPath (); 
        Context.lineWidth = 1.5 ;
        Context.moveTo (GridRight, GridBottom); 
        Context.lineTo (GridRight, GridTop); 
        Context.stroke ();
        };
    }
else // draw 100 kilometre grid
    {
    var pp10km = 10 * PPKM ; // pixels per 10 km
    eastAlign  = (10000 - Easting .substring (1)) / 10000 ; // fraction of 10 km eastwards  to next grid line
    northAlign =          Northing.substring (1)  / 10000 ; // fraction of 10 km northwards to next grid line
    var eastings10KM    = parseInt (Easting .substr (0,1)) + 1 ;
    var eastingsLetter  = EastingLetter ;
    var northings10KM   = parseInt (Northing.substr (0,1));
    var northingsLetter = NorthingLetter ;
    convergence = BaseConvergence ; // + eastAlign * 10 * RateOfChangeOfConvergence ;
    gridX = OriginOffset + eastAlign * pp10km ;
    while (gridX - pp10km > GridLeft ) {gridX -= pp10km ; eastings10KM-- ; convergence -= 10 * RateOfChangeOfConvergence ; if (eastings10KM < 0) {eastings10KM = 9; eastingsLetter = PreviousMGRSEastingsLetter (eastingsLetter)}}; // move to left of grid area
    while (gridX          < GridRight) // traverse horizontally from left of grid area to right of grid area, drawing vertical lines each 10 km
        {
        Context.beginPath (); 
        Context.lineWidth = (eastings10KM == 0)? 1.5 : 0.5 ;
        convergence += 10 * RateOfChangeOfConvergence ;
        if (eastings10KM == 0)
            {
            eastingTopX    = gridX - convergence * GridTop ;
            eastingTopY    = GridTop ;
            eastingBottomX = gridX - convergence * GridBottom ;
            eastingBottomY = GridBottom ;
            // deal with four special edge zone cases (two in each hemisphere) where the vertical line has to be shortened and one of the corresponding eastings labels has to be hidden
            showEastingsTopLabel    = true ;
            showEastingsBottomLabel = true ; 
            if (NorthingsLabelLeft  == undefined && MapObj.CentreLat > 0 && eastingTopX    < GridLeft ) {eastingTopY    = eastingBottomY + (eastingTopY     - eastingBottomY) * (eastingBottomX - GridLeft      )/(eastingBottomX - eastingTopX   ); eastingTopX    = GridLeft ; showEastingsTopLabel    = false};
            if (NorthingsLabelRight == undefined && MapObj.CentreLat > 0 && eastingTopX    > GridRight) {eastingTopY    = eastingBottomY + (eastingTopY     - eastingBottomY) * (GridRight      - eastingBottomX)/(eastingTopX    - eastingBottomX); eastingTopX    = GridRight; showEastingsTopLabel    = false};
            if (NorthingsLabelLeft  == undefined && MapObj.CentreLat < 0 && eastingBottomX < GridLeft ) {eastingBottomY = eastingTopY    + (eastingBottomY  - eastingTopY   ) * (eastingTopX    - GridLeft      )/(eastingTopX    - eastingBottomX); eastingBottomX = GridLeft ; showEastingsBottomLabel = false};
            if (NorthingsLabelRight == undefined && MapObj.CentreLat < 0 && eastingBottomX > GridRight) {eastingBottomY = eastingTopY    + (eastingBottomY  - eastingTopY   ) * (GridRight      - eastingTopX   )/(eastingBottomX - eastingTopX   ); eastingBottomX = GridRight; showEastingsBottomLabel = false};
            Context.moveTo (eastingTopX   , eastingTopY   );
            Context.lineTo (eastingBottomX, eastingBottomY);
            Context.stroke ();
            eastingsLabel = eastingsLetter ; 
            if (showEastingsTopLabel   ) Context.fillText (eastingsLabel, gridX - convergence * EastingsLabelTop    + eastLabelOffset, EastingsLabelTop   ); // label is to the east (right) of the line
            if (showEastingsBottomLabel) Context.fillText (eastingsLabel, gridX - convergence * EastingsLabelBottom + eastLabelOffset, EastingsLabelBottom);
            };
        gridX += pp10km ;
        eastings10KM++ ;
        if (eastings10KM == 10) {eastings10KM = 0 ; eastingsLetter = NextMGRSEastingsLetter (eastingsLetter)};
        };

    gridY = northAlign * pp10km ;
    while (gridY + pp10km < GridBottom) 
        {
        gridY += pp10km ; 
        northings10KM--; 
        if (northings10KM < 0) {northings10KM = 9 ; northingsLetter = PreviousMGRSNorthingsLetter (northingsLetter)};
        }; // move to bottom of grid area
    while (gridY          > GridTop   ) // traverse vertically from bottom of grid area to top of grid area
        {
        Context.beginPath (); 
        Context.lineWidth = 0.5 ;
        if (northings10KM == 0)
            {
            northingsLabelLeftGridY  = undefined ;
            northingsLabelRightGridY = undefined ;
            var northingsLabelLeftGridX  = (NorthingsLabelLeft  != undefined)? NorthingsLabelLeft  : GridLeft  + 5  / MapObj.ScaleFactor ;
            var northingsLabelRightGridX = (NorthingsLabelRight != undefined)? NorthingsLabelRight : GridRight - 20 / MapObj.ScaleFactor ;
            gridYOffset = gridY ;
            convergence = BaseConvergence ;
            gridX = OriginOffset + eastAlign * pp10km + convergence * gridY ; // note working in 10km sections
            while (gridX - pp10km  > GridLeft) {gridX -= pp10km ; convergence -= 10  * RateOfChangeOfConvergence; gridYOffset -= convergence * pp10km }; // continue move to left of grid area in 10km jumps
            Context.moveTo (GridLeft, gridYOffset - convergence * (gridX - GridLeft)); // ensure northing starts at left edge of area
            Context.lineTo (gridX, gridYOffset); // first section of northing
            while (gridX + pp10km < GridRight) // traverse horizontally in 10km sections from left of grid area to right of grid area, drawing segmented horizontal lines each 10 km
                {
                if (northingsLabelLeftGridY  == undefined && gridX > northingsLabelLeftGridX ) northingsLabelLeftGridY  = gridYOffset ; // record position for northings left label  - note local variable northingsLabelLeftGridX  used here
                if (northingsLabelRightGridY == undefined && gridX > northingsLabelRightGridX) northingsLabelRightGridY = gridYOffset ; // record position for northings right label - note local variable northingsLabelRightGridX used here
                gridX += pp10km ;
                convergence += 10 * RateOfChangeOfConvergence ;
                gridYOffset += convergence * pp10km ;
                Context.lineTo (gridX, gridYOffset);
                };
            if (northingsLabelLeftGridY  == undefined) northingsLabelLeftGridY  = gridYOffset ; // record position for northings left  label if not done already - note local variable northingsLabelLeftGridX  used here
            if (northingsLabelRightGridY == undefined) northingsLabelRightGridY = gridYOffset ; // record position for northings right label if not done already - note local variable northingsLabelRightGridX used here
            Context.lineTo (GridRight, gridYOffset + convergence * (GridRight - gridX)); // ensure northing ends at the right edge of area
            Context.stroke ();

            northingsLabel = northingsLetter ;
            convergence = BaseConvergence + eastAlign * 100 * RateOfChangeOfConvergence ;
            Context.fillText (northingsLabel, northingsLabelLeftGridX , northingsLabelLeftGridY  - northLabelOffset); // label is to the north of (above) the line
            Context.fillText (northingsLabel, northingsLabelRightGridX, northingsLabelRightGridY - northLabelOffset); 
            };

        gridY -= pp10km ;
        northings10KM++ ;
        if (northings10KM == 10) {northings10KM = 0 ; northingsLetter = NextMGRSNorthingsLetter (northingsLetter)};
        };
    if (NorthingsLabelLeft  == undefined) // draw zone western edge
        {
        Context.beginPath (); 
        Context.lineWidth = 1.5 ;
        Context.moveTo (GridLeft, GridBottom); 
        Context.lineTo (GridLeft, GridTop); 
        Context.stroke ();
        }; 
    if (NorthingsLabelRight == undefined) // draw zone eastern edge
        {
        Context.beginPath (); 
        Context.lineWidth = 1.5 ;
        Context.moveTo (GridRight, GridBottom); 
        Context.lineTo (GridRight, GridTop); 
        Context.stroke ();
        };
    };

};// end of DrawMGRSGridLines function

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.