L.WorkedAllBritainIreland = L.LayerGroup.extend({
options: {
// Line and label color
color: 'rgba(80, 80, 80, 1)',
// Grid squares to draw
gbSquares: ["HP", "HT", "HU", "HW", "HX", "HY", "HZ", "NA", "NB", "NC", "ND", "NF", "NG", "NH", "NJ", "NK", "NL", "NM", "NN", "NO", "NR", "NS", "NT", "NU", "NW", "NX", "NY", "NZ", "OV", "SC", "SD", "SE", "SH", "SJ", "SK", "SM", "SN", "SO", "SP", "SR", "SS", "ST", "SU", "SV", "SW", "SX", "SY", "SZ", "TA", "TF", "TG", "TL", "TM", "TR", "TQ", "TV"],
ieSquares: ["B", "C", "D", "F", "G", "H", "J", "L", "M", "N", "O", "Q", "R", "S", "T", "V", "W", "X"],
ciSquares: ["WA", "WV"]
},
initialize: function (options) {
// Initialise the LayerGroup superclass and set the options for this class.
L.LayerGroup.prototype.initialize.call(this);
L.Util.setOptions(this, options);
// Workaround to load the geodesy modules in non-modular code. Once we have loaded all three modules, trigger a
// first draw.
import("https://misc.ianrenton.com/Leaflet.WorkedAllBritainIreland/modules/geodesy/osgridref.js")
.then(module => {
this._osGridLibrary = module;
if (this._ieGridLibrary && this._utmLibrary) {
this.redraw();
}
})
.catch(error => {
console.log("Error loading OS Grid Ref library, GB WAB squares may not be available.");
console.log(error);
});
import("https://misc.ianrenton.com/Leaflet.WorkedAllBritainIreland/modules/geodesy/iegridref.js")
.then(module => {
this._ieGridLibrary = module;
if (this._osGridLibrary && this._utmLibrary) {
this.redraw();
}
})
.catch(error => {
console.log("Error loading IE Grid Ref library, NI WAB squares may not be available.");
console.log(error);
});
import("https://misc.ianrenton.com/Leaflet.WorkedAllBritainIreland/modules/geodesy/utm_ci.js")
.then(module => {
this._utmLibrary = module;
if (this._osGridLibrary && this._ieGridLibrary) {
this.redraw();
}
})
.catch(error => {
console.log("Error loading UTM library, Channel Islands WAB squares may not be available.");
console.log(error);
});
},
onAdd: function (map) {
this._map = map;
var grid = this.redraw();
map.on('moveend', function () { grid.redraw(); });
map.on('zoomend', function () { grid.redraw(); });
this.eachLayer(map.addLayer, map);
},
onRemove: function (map) {
map.off('moveend', this.map);
map.off('zoomend', this.map);
this.eachLayer(this.removeLayer, this);
},
redraw: function () {
// Don't proceed unless we have a map object and our libraries are loaded
if (this._map && this._osGridLibrary && this._ieGridLibrary && this._utmLibrary) {
// Remove existing content
this.eachLayer(this.removeLayer, this);
// Determine detail level based on current map zoom.
const detailLevel = (map.getZoom() > 4) ? (map.getZoom() > 8) ? 2 : 1 : 0;
// Generate new content for the three grid systems.
this.options.gbSquares.forEach(squareRef => {
this._addWABGraphicsForSquare(squareRef, "GB", detailLevel);
});
this.options.ieSquares.forEach(squareRef => {
this._addWABGraphicsForSquare(squareRef, "IE", detailLevel);
});
this.options.ciSquares.forEach(squareRef => {
this._addWABGraphicsForSquare(squareRef, "CI", detailLevel);
});
}
return this;
},
// Add WAB graphics to the layer for the given square, using the given grid system ("GB", "IE" or "CI") and the
// required level of detail.
_addWABGraphicsForSquare: function (squareRef, gridSystem, detailLevel) {
if (detailLevel === 0 || detailLevel === 1) {
// If detail level is 0 or 1, we want a single large square.
const swCorner = this._gridRefToLatLon(squareRef + " 00000 00000", gridSystem);
const nwCorner = this._gridRefToLatLon(squareRef + " 99999 00000", gridSystem);
const neCorner = this._gridRefToLatLon(squareRef + " 99999 99999", gridSystem);
const seCorner = this._gridRefToLatLon(squareRef + " 00000 99999", gridSystem);
const centre = this._gridRefToLatLon(squareRef + " 50000 50000", gridSystem);
let square = L.polygon([swCorner, nwCorner, neCorner, seCorner], {color: this.options.color, interactive: false});
this.addLayer(square);
// Additionally if detail level is 1, we want to label it.
if (detailLevel === 1) {
let label = new L.marker(centre, {
icon: new L.DivIcon({
html: '' + squareRef + '',
className: 'wabSquareLabel' // Prevent default background & border and provide ability to customise
})
}, clickable=false);
this.addLayer(label);
}
} else if (detailLevel === 2) {
// If detail level is 2, we want to generate all the inner squares (with labels)
// instead of just one square. But, doing this for every square will cause CPU issues,
// so we only want to generate graphics if they would actually end up on screen.
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
// Bail out if we have a grid reference that doesn't apply. This is where GB grid overlaps with NI etc.
// are deconflicted.
if (this._validSmallSquare(squareRef, i, j)) {
// If we get this far, now calculate the coordinates of the box.
const swCorner = this._gridRefToLatLon(squareRef + " " + i + "0000 " + j + "0000", gridSystem);
const nwCorner = this._gridRefToLatLon(squareRef + " " + i + "9999 " + j + "0000", gridSystem);
const neCorner = this._gridRefToLatLon(squareRef + " " + i + "9999 " + j + "9999", gridSystem);
const seCorner = this._gridRefToLatLon(squareRef + " " + i + "0000 " + j + "9999", gridSystem);
const centre = this._gridRefToLatLon(squareRef + " " + i + "5000 " + j + "5000", gridSystem);
// Find out if this box is going to be on our screen. If not, don't draw anything.
if (map.getBounds().contains(swCorner) || map.getBounds().contains(nwCorner)
|| map.getBounds().contains(neCorner) || map.getBounds().contains(seCorner)) {
let square = L.polygon([swCorner, nwCorner, neCorner, seCorner], {color: this.options.color, interactive: false});
this.addLayer(square);
let label = new L.marker(centre, {
icon: new L.DivIcon({
html: '' + squareRef + i + j + '',
className: 'wabSquareLabelLong' // Prevent default background & border and provide ability to customise
})
}, clickable=false);
this.addLayer(label);
}
}
}
}
}
},
// Determine if a given small square is OK to draw. This is where e.g. overlapping GB and IE squares are
// deconflicted in the Irish Sea.
_validSmallSquare: function (squareRef, i, j) {
let valid = true;
if (squareRef === "WA" && j > 1) {
valid = false;
} else if (squareRef === "TR" && i > 4 && j < 5) {
valid = false;
} else if (squareRef === "SM" && i < 4) {
valid = false;
} else if (squareRef === "SM" && i < 7 && j > 4) {
valid = false;
} else if (squareRef === "TV" && i === 9 && j === 0) {
valid = false;
} else if (squareRef === "NW" && i < 9) {
valid = false;
} else if (squareRef === "NR" && i < 5 && j < 3) {
valid = false;
} else if (squareRef === "C" && j > 6) {
valid = false;
} else if (squareRef === "D" && (i > 5 || j > 5 || (i > 2 && j > 3))) {
valid = false;
} else if (squareRef === "J" && i > 6) {
valid = false;
} else if (squareRef === "O" && i > 8) {
valid = false;
} else if (squareRef === "T" && i > 6 && j < 5) {
valid = false;
}
return valid;
},
// Convert the given grid reference to lat/lon, using the given grid system ("GB", "IE" or "CI")
_gridRefToLatLon: function (grid, gridSystem) {
if (gridSystem === "GB") {
return this._osgbGridRefToLatLon(grid);
} else if (gridSystem === "IE") {
return this._osieGridRefToLatLon(grid);
} else if (gridSystem === "CI") {
return this._ciGridRefToLatLon(grid);
} else {
return null;
}
},
// OSGB grid reference to lat/lon
_osgbGridRefToLatLon: function (grid) {
if (this._osGridLibrary) {
return this._osGridLibrary.default.parse(grid).toLatLon();
} else {
return null;
}
},
// Lat/lon to OSGB grid reference
_latLonToOSGBGridRef: function (lat, lon) {
if (this._osGridLibrary) {
return new this._osGridLibrary.LatLon(lat, lon).toOsGrid();
} else {
return null;
}
},
// OSIE grid reference to lat/lon
_osieGridRefToLatLon: function (grid) {
if (this._ieGridLibrary) {
return this._ieGridLibrary.default.parse(grid).toLatLon();
} else {
return null;
}
},
// Lat/lon to OSIE grid reference
_latLonToOSIEGridRef: function (lat, lon) {
if (this._ieGridLibrary) {
return new this._ieGridLibrary.LatLon(lat, lon).toOsGrid();
} else {
return null;
}
},
// CI grid reference to lat/lon
_ciGridRefToLatLon: function (grid) {
if (this._utmLibrary) {
return this._utmLibrary.default.parseChannelIslandGrid(grid).toLatLon();
} else {
return null;
}
},
// Lat/lon to CI grid reference
_latLonToCIGridRef: function (lat, lon) {
if (this._utmLibrary) {
let utm = (new this._utmLibrary.LatLon(lat, lon)).toUtm();
// todo convert UTM coordinate system back to CI grid ref cells
return null;
} else {
return null;
}
}
});
L.workedAllBritainIreland = function (options) {
return new L.WorkedAllBritainIreland(options);
};