UNPKG

brc-atlas-bigr

Version:

Utility functions for dealing with grid reference systems used in the British Isles.

1,197 lines (1,119 loc) 28.7 kB
'use strict'; var proj4 = require('proj4'); var pkg = require('point-in-polygon'); /** @module src/km100 */ /** @constant * @description This the array from which the default object is derived. If you * need to work with an array of objects where the 100 km grid reference is a property * of the object alongside x, y, and proj, you can use this. * @type {array} */ const a100km = [ { "GridRef": "SV", "x": 0, "y": 0, "proj": "gb" }, { "GridRef": "NL", "x": 0, "y": 7, "proj": "gb" }, { "GridRef": "NF", "x": 0, "y": 8, "proj": "gb" }, { "GridRef": "NA", "x": 0, "y": 9, "proj": "gb" }, { "GridRef": "SW", "x": 1, "y": 0, "proj": "gb" }, { "GridRef": "SR", "x": 1, "y": 1, "proj": "gb" }, { "GridRef": "SM", "x": 1, "y": 2, "proj": "gb" }, { "GridRef": "NW", "x": 1, "y": 5, "proj": "gb" }, { "GridRef": "NR", "x": 1, "y": 6, "proj": "gb" }, { "GridRef": "NM", "x": 1, "y": 7, "proj": "gb" }, { "GridRef": "NG", "x": 1, "y": 8, "proj": "gb" }, { "GridRef": "NB", "x": 1, "y": 9, "proj": "gb" }, { "GridRef": "HW", "x": 1, "y": 10, "proj": "gb" }, { "GridRef": "SX", "x": 2, "y": 0, "proj": "gb" }, { "GridRef": "SS", "x": 2, "y": 1, "proj": "gb" }, { "GridRef": "SN", "x": 2, "y": 2, "proj": "gb" }, { "GridRef": "SH", "x": 2, "y": 3, "proj": "gb" }, { "GridRef": "SC", "x": 2, "y": 4, "proj": "gb" }, { "GridRef": "NX", "x": 2, "y": 5, "proj": "gb" }, { "GridRef": "NS", "x": 2, "y": 6, "proj": "gb" }, { "GridRef": "NN", "x": 2, "y": 7, "proj": "gb" }, { "GridRef": "NH", "x": 2, "y": 8, "proj": "gb" }, { "GridRef": "NC", "x": 2, "y": 9, "proj": "gb" }, { "GridRef": "HX", "x": 2, "y": 10, "proj": "gb" }, { "GridRef": "SY", "x": 3, "y": 0, "proj": "gb" }, { "GridRef": "ST", "x": 3, "y": 1, "proj": "gb" }, { "GridRef": "SO", "x": 3, "y": 2, "proj": "gb" }, { "GridRef": "SJ", "x": 3, "y": 3, "proj": "gb" }, { "GridRef": "SD", "x": 3, "y": 4, "proj": "gb" }, { "GridRef": "NY", "x": 3, "y": 5, "proj": "gb" }, { "GridRef": "NT", "x": 3, "y": 6, "proj": "gb" }, { "GridRef": "NO", "x": 3, "y": 7, "proj": "gb" }, { "GridRef": "NJ", "x": 3, "y": 8, "proj": "gb" }, { "GridRef": "ND", "x": 3, "y": 9, "proj": "gb" }, { "GridRef": "HY", "x": 3, "y": 10, "proj": "gb" }, { "GridRef": "HT", "x": 3, "y": 11, "proj": "gb" }, { "GridRef": "SZ", "x": 4, "y": 0, "proj": "gb" }, { "GridRef": "SU", "x": 4, "y": 1, "proj": "gb" }, { "GridRef": "SP", "x": 4, "y": 2, "proj": "gb" }, { "GridRef": "SK", "x": 4, "y": 3, "proj": "gb" }, { "GridRef": "SE", "x": 4, "y": 4, "proj": "gb" }, { "GridRef": "NZ", "x": 4, "y": 5, "proj": "gb" }, { "GridRef": "NU", "x": 4, "y": 6, "proj": "gb" }, { "GridRef": "NK", "x": 4, "y": 8, "proj": "gb" }, { "GridRef": "HZ", "x": 4, "y": 10, "proj": "gb" }, { "GridRef": "HU", "x": 4, "y": 11, "proj": "gb" }, { "GridRef": "HP", "x": 4, "y": 12, "proj": "gb" }, { "GridRef": "TV", "x": 5, "y": 0, "proj": "gb" }, { "GridRef": "TQ", "x": 5, "y": 1, "proj": "gb" }, { "GridRef": "TL", "x": 5, "y": 2, "proj": "gb" }, { "GridRef": "TF", "x": 5, "y": 3, "proj": "gb" }, { "GridRef": "TA", "x": 5, "y": 4, "proj": "gb" }, { "GridRef": "OV", "x": 5, "y": 5, "proj": "gb" }, { "GridRef": "TR", "x": 6, "y": 1, "proj": "gb" }, { "GridRef": "TM", "x": 6, "y": 2, "proj": "gb" }, { "GridRef": "TG", "x": 6, "y": 3, "proj": "gb" }, { "GridRef": "V", "x": 0, "y": 0, "proj": "ir" }, { "GridRef": "Q", "x": 0, "y": 1, "proj": "ir" }, { "GridRef": "L", "x": 0, "y": 2, "proj": "ir" }, { "GridRef": "F", "x": 0, "y": 3, "proj": "ir" }, { "GridRef": "A", "x": 0, "y": 4, "proj": "ir" }, { "GridRef": "W", "x": 1, "y": 0, "proj": "ir" }, { "GridRef": "R", "x": 1, "y": 1, "proj": "ir" }, { "GridRef": "M", "x": 1, "y": 2, "proj": "ir" }, { "GridRef": "G", "x": 1, "y": 3, "proj": "ir" }, { "GridRef": "B", "x": 1, "y": 4, "proj": "ir" }, { "GridRef": "X", "x": 2, "y": 0, "proj": "ir" }, { "GridRef": "S", "x": 2, "y": 1, "proj": "ir" }, { "GridRef": "N", "x": 2, "y": 2, "proj": "ir" }, { "GridRef": "H", "x": 2, "y": 3, "proj": "ir" }, { "GridRef": "C", "x": 2, "y": 4, "proj": "ir" }, { "GridRef": "Y", "x": 3, "y": 0, "proj": "ir" }, { "GridRef": "T", "x": 3, "y": 1, "proj": "ir" }, { "GridRef": "O", "x": 3, "y": 2, "proj": "ir" }, { "GridRef": "J", "x": 3, "y": 3, "proj": "ir" }, { "GridRef": "D", "x": 3, "y": 4, "proj": "ir" }, { "GridRef": "Z", "x": 4, "y": 0, "proj": "ir" }, { "GridRef": "U", "x": 4, "y": 1, "proj": "ir" }, { "GridRef": "P", "x": 4, "y": 2, "proj": "ir" }, { "GridRef": "K", "x": 4, "y": 3, "proj": "ir" }, { "GridRef": "E", "x": 4, "y": 4, "proj": "ir" }, { "GridRef": "WV", "x": 5, "y": 54, "proj": "ci" }, { "GridRef": "WA", "x": 5, "y": 55, "proj": "ci" } ]; /** @constant * @description The default export from this module is an object with a property * for every 100 km square reference for Britain (Brtish National Grid), * Ireland (Irish National Grid) and the Channel Islands (abbreviated UTM 30N). * Each grid reference references an object that has properties x, y and proj. * The x and y coordinates represent the centroid of the 100 km square in the * coordinate reference system corresponding to the aforementioned areas, respectively * epsg:27700, epsg:29903 and epsg:32630. Another property, proj, indicates the region/CRS * with two letter codes, respectively gb, ir and ci. * <p>An example of the object referenced through the property 'SO' is shown below:</p> * <pre> * { * "x": 3, * "y": 2, * "proj": "gb" * } * </pre> * @type {object} */ var km100s = a100km.reduce(function(acc, km100) { acc[km100.GridRef] = { x: km100.x, y: km100.y, proj: km100.proj }; return acc }, {}); /** @module src/checkGr */ function invalidGridRef(gr) { throw `The value '${gr}' is not recognised as a valid grid reference.` } /** * Given a grid reference (British National Grid, Irish Grid or UTM zone 30N shorthand), * check that this is a valid GR. If it is, return an object which includes the * GR precision in metres, the prefix and the two-letter projection code. * If an invalid grid reference is supplied throws an error. * @param {string} gr - the grid reference. * @returns {object} Object of the form {precision: n, prefix: 'prefix', projection: 'code'}. */ function checkGr (gr) { const r100km = RegExp('^[a-zA-Z]{1,2}$'); const rHectad = RegExp('^[a-zA-Z]{1,2}[0-9]{2}$'); const rQuandrant = RegExp('^[a-zA-Z]{1,2}[0-9]{2}[SsNn][WwEe]$'); const rTetrad = RegExp('^[a-zA-Z]{1,2}[0-9]{2}[a-np-zA-NP-Z]$'); const rMonad = RegExp('^[a-zA-Z]{1,2}[0-9]{4}$'); const r6fig = RegExp('^[a-zA-Z]{1,2}[0-9]{6}$'); const r8fig = RegExp('^[a-zA-Z]{1,2}[0-9]{8}$'); const r10fig = RegExp('^[a-zA-Z]{1,2}[0-9]{10}$'); const match = gr.match(/^[A-Za-z]+/); if (!match) invalidGridRef(gr); const prefix = match[0].toUpperCase(); const km100 = km100s[prefix]; if (!km100) invalidGridRef(gr); const ret = {precision: null, prefix: prefix, projection: km100.proj}; if (r100km.test(gr)) { // The GR is a 100 km square reference ret.precision = 100000; } else if (rHectad.test(gr)) { // The GR is a hectad ret.precision = 10000; } else if (rQuandrant.test(gr)) { // The GR is a quandrant ret.precision = 5000; } else if (rTetrad.test(gr)) { // The GR is a tetrad ret.precision = 2000; } else if (rMonad.test(gr)) { // The GR is a monad ret.precision = 1000; } else if (r6fig.test(gr)) { // The GR is a 6 figure GR ret.precision = 100; } else if (r8fig.test(gr)) { // The GR is a 8 figure GR ret.precision = 10; } else if (r10fig.test(gr)) { // The GR is a 10 figure GR ret.precision = 1; } else { invalidGridRef(gr); } return ret } /** @module src/projections */ /** @constant * @description This object describes the coordinate reference systems used in this project corresponding * to the British National Grid, Irish Grid, UTM zone 30N (used for the Channel Islands) and WGS 84. The object contains * four properties, each named with the two letter code used throughout this package to represent one of the * three systems: gb, ir, ci and wg. Each of these properties provides access to an object defining the name, * epsg code and proj4 string for the CRS. * @type {array} */ var projections = { gb: { name: 'OSGB 1936 / British National Grid', epsg: '27700', proj4: '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs', }, ir: { name: 'TM75 / Irish Grid', epsg: '29903', proj4: '+proj=tmerc +lat_0=53.5 +lon_0=-8 +k=1.000035 +x_0=200000 +y_0=250000 +ellps=mod_airy +towgs84=482.5,-130.6,564.6,-1.042,-0.214,-0.631,8.15 +units=m +no_defs', }, ci: { name: 'WGS 84 / UTM zone 30N', epsg: '32630', proj4: '+proj=utm +zone=30 +datum=WGS84 +units=m +no_defs', }, wg: { name: 'WGS 84', epsg: '4326', proj4: '+proj=longlat +datum=WGS84 +no_defs', } }; /** @module src/quadrants */ /** @constant * @description This object specifies the x, y offsets associated with suffixes for quandrant grid refs. * @type {Object} */ var qOffsets = { sw: { x: 0, y: 0 }, se: { x: 5000, y: 0 }, nw: { x: 0, y: 5000 }, ne: { x: 5000, y: 5000 } }; /** @module src/tetrads */ /** @constant * @description This object specifies the x, y offsets associated with suffixes for tetrad grid refs. * @type {Object} */ var tOffsets = { a: { x: 0, y: 0 }, b: { x: 0, y: 2000 }, c: { x: 0, y: 4000 }, d: { x: 0, y: 6000 }, e: { x: 0, y: 8000 }, f: { x: 2000, y: 0 }, g: { x: 2000, y: 2000 }, h: { x: 2000, y: 4000 }, i: { x: 2000, y: 6000 }, j: { x: 2000, y: 8000 }, k: { x: 4000, y: 0 }, l: { x: 4000, y: 2000 }, m: { x: 4000, y: 4000 }, n: { x: 4000, y: 6000 }, p: { x: 4000, y: 8000 }, q: { x: 6000, y: 0 }, r: { x: 6000, y: 2000 }, s: { x: 6000, y: 4000 }, t: { x: 6000, y: 6000 }, u: { x: 6000, y: 8000 }, v: { x: 8000, y: 0 }, w: { x: 8000, y: 2000 }, x: { x: 8000, y: 4000 }, y: { x: 8000, y: 6000 }, z: { x: 8000, y: 8000 } }; /** @module src/getCentroid */ /** * Given a grid reference (British National Grid, Irish Grid or UTM zone 30N shorthand), * and a two-letter code defining the requested output projection, this function * returns the centroid of the grid reference. * @param {string} gr - the grid reference * @param {string} toProjection - two letter code specifying the required output CRS. * @returns {object} - of the form {centroid: [x, y], proj: 'code'}; x and y are * coordinates in CRS specified by toProjection. The proj code indicates the source projection. */ function getCentroid (gr, toProjection) { let x, y, outCoords, suffix; const grType = checkGr(gr); const prefix = grType.prefix; const km100 = km100s[prefix]; switch(grType.precision) { case 100000: x = km100.x * 100000 + 50000; y = km100.y * 100000 + 50000; break; case 10000: x = km100.x * 100000 + Number(gr.substr(prefix.length,1)) * 10000 + 5000; y = km100.y * 100000 + Number(gr.substr(prefix.length+1,1)) * 10000 + 5000; break; case 5000: suffix = gr.substr(prefix.length+2,2).toLowerCase(); x = km100.x * 100000 + Number(gr.substr(prefix.length,1)) * 10000 + qOffsets[suffix].x + 2500; y = km100.y * 100000 + Number(gr.substr(prefix.length+1,1)) * 10000 + qOffsets[suffix].y + 2500; break case 2000: suffix = gr.substr(prefix.length+2,1).toLowerCase(); x = km100.x * 100000 + Number(gr.substr(prefix.length,1)) * 10000 + tOffsets[suffix].x + 1000; y = km100.y * 100000 + Number(gr.substr(prefix.length+1,1)) * 10000 + tOffsets[suffix].y + 1000; break case 1000: x = km100.x * 100000 + Number(gr.substr(prefix.length,2)) * 1000 + 500; y = km100.y * 100000 + Number(gr.substr(prefix.length+2,2)) * 1000 + 500; break case 100: x = km100.x * 100000 + Number(gr.substr(prefix.length,3)) * 100 + 50; y = km100.y * 100000 + Number(gr.substr(prefix.length+3,3)) * 100 + 50; break case 10: x = km100.x * 100000 + Number(gr.substr(prefix.length,4)) * 10 + 5; y = km100.y * 100000 + Number(gr.substr(prefix.length+4,4)) * 10 + 5; break default: x = km100.x * 100000 + Number(gr.substr(prefix.length,5)) + 0.5; y = km100.y * 100000 + Number(gr.substr(prefix.length+5,5)) + 0.5; } // If the required output projection does not match the projection of the input GR // then use proj4 to reproject if (toProjection !== km100.proj) { outCoords = proj4(projections[km100.proj].proj4, projections[toProjection].proj4, [x, y]); } else { outCoords = [x, y]; } return { centroid: outCoords, proj: km100.proj } } /** @module src/getGjson */ function convertCoords(fromProjection, toProjection, x, y) { let outCoords; // If the required output projection does not match the projection of the input GR // then use proj4 to reproject if (toProjection !== fromProjection) { outCoords = proj4(projections[fromProjection].proj4, projections[toProjection].proj4, [x, y]); } else { outCoords = [x, y]; } return outCoords } /** * Given a grid reference (British National Grid, Irish Grid or UTM zone 30N shorthand), * a two-letter code defining the requested output projection, and a string indicating * the shape of the required 'symbol', this function returns a GeoJson pth geometry object. * @param {string} gr - the grid reference. * @param {string} toProjection - two letter code specifying the required output CRS. * @param {string} shape - string specifying the requested output shape type. * @param {number} scale - number between 0 and 1 to scale the output object. * @returns {object} - a GeoJson path geometry object. * @todo Extend to return all symbol types */ function getGjson (gr, toProjection, shape, scale) { const size = scale ? scale : 1; const grType = checkGr(gr); const km100 = km100s[grType.prefix]; const centroid = getCentroid(gr, km100.proj).centroid; const xmin = centroid[0] - grType.precision / 2 * size; const xmax = centroid[0] + grType.precision / 2 * size; const ymin = centroid[1] - grType.precision / 2 * size; const ymax = centroid[1] + grType.precision / 2 * size; const xmid = xmin + (xmax-xmin)/2; const ymid = ymin + (ymax-ymin)/2; let coords; let type = "Polygon"; if (shape === "square") { coords = [[ convertCoords(km100.proj, toProjection, xmin, ymin), convertCoords(km100.proj, toProjection, xmax, ymin), convertCoords(km100.proj, toProjection, xmax, ymax), convertCoords(km100.proj, toProjection, xmin, ymax), convertCoords(km100.proj, toProjection, xmin, ymin) ]]; } else if (shape === "triangle-up") { coords = [[ convertCoords(km100.proj, toProjection, xmin, ymin), convertCoords(km100.proj, toProjection, xmax, ymin), convertCoords(km100.proj, toProjection, xmid, ymax), convertCoords(km100.proj, toProjection, xmin, ymin) ]]; } else if (shape === "triangle-down") { coords = [[ convertCoords(km100.proj, toProjection, xmid, ymin), convertCoords(km100.proj, toProjection, xmax, ymax), convertCoords(km100.proj, toProjection, xmin, ymax), convertCoords(km100.proj, toProjection, xmid, ymin) ]]; } else if (shape === "diamond") { coords = [[ convertCoords(km100.proj, toProjection, xmid, ymin), convertCoords(km100.proj, toProjection, xmax, ymid), convertCoords(km100.proj, toProjection, xmid, ymax), convertCoords(km100.proj, toProjection, xmin, ymid), convertCoords(km100.proj, toProjection, xmid, ymin) ]]; } else if (shape === "circle") { const rad = grType.precision / 2 * size; coords = [[]]; for(let deg = 0; deg <= 360; deg += 15){ const angle = deg * Math.PI / 180; const x = rad * Math.cos(angle) + centroid[0]; const y = rad * Math.sin(angle) + centroid[1]; coords[0].push(convertCoords(km100.proj, toProjection, x, y)); } } else if (shape === "circlerad") { coords = [[ convertCoords(km100.proj, toProjection, centroid[0], centroid[1]), convertCoords(km100.proj, toProjection, xmax, centroid[1]) ]]; } else if (shape === "cross") { type = "MultiLineString"; coords = [ [convertCoords(km100.proj, toProjection, xmin, ymin), convertCoords(km100.proj, toProjection, xmax, ymin), convertCoords(km100.proj, toProjection, xmax, ymax), convertCoords(km100.proj, toProjection, xmin, ymax), convertCoords(km100.proj, toProjection, xmin, ymin)], [convertCoords(km100.proj, toProjection, xmin, ymin), convertCoords(km100.proj, toProjection, xmax, ymax)], [convertCoords(km100.proj, toProjection, xmin, ymax), convertCoords(km100.proj, toProjection, xmax, ymin)] ]; } else if (shape === "point") { type = "Point"; coords = convertCoords(km100.proj, toProjection, centroid[0], centroid[1]); } return { "type": type, "coordinates": coords } } /** @module src/pntToArea */ const inside = pkg; const polyGb = [[-5.079346,54.356181],[-5.218856,54.352538],[-5.238015,54.577735],[-5.594707,55.067578],[-5.85957,55.233056],[-6.1989,55.442175],[-6.754907,55.520643],[-6.826016,56.098271],[-8.4242,56.025199],[-8.577402,56.917818],[-8.739734,57.810026],[-8.911963,58.7018],[-7.193942,58.782764],[-7.332053,59.676983],[-5.560279,59.737099],[-3.782533,59.77326],[-3.831945,60.670683],[-2.001839,60.683214],[-2.0019,61.58096],[-0.119146,61.568018],[-0.17173,60.67074],[-0.22103,59.773315],[-0.267328,58.875744],[-2.00173,58.887355],[-2.00168,57.989238],[-0.310874,57.978026],[-0.351892,57.080161],[-2.001633,57.090993],[-2.001588,56.192619],[-0.390579,56.182148],[-0.427115,55.283988],[1.145328,55.253591],[1.076342,54.356271],[1.011037,53.458761],[2.513016,53.411393],[2.420466,52.515203],[2.332666,51.618767],[2.249301,50.722093],[0.834724,50.76511],[0.781771,49.866861],[-0.609132,49.891897],[-2.001337,49.900236],[-3.39354,49.891859],[-4.784439,49.866784],[-6.172738,49.825069],[-7.55716,49.766807],[-7.662447,50.661841],[-6.252021,50.721974],[-6.33545,51.618645],[-5.822792,52.024604],[-5.051366,52.558456],[-5.051366,52.558457],[-4.952011,52.560978],[-5.013966,53.458674],[-5.079346,54.356181]]; const polyIr = [[-10.863443,51.218565],[-10.920638,52.116175],[-10.980909,53.013601],[-11.044479,53.91084],[-11.111595,54.80789],[-11.182531,55.704747],[-9.592679,55.735662],[-8.000759,55.74598],[-6.781597,55.739941],[-6.754907,55.520643],[-6.419264,55.473617],[-6.1989,55.442175],[-5.85957,55.233056],[-5.594707,55.067578],[-5.413363,54.820209],[-5.238015,54.577735],[-5.218856,54.352538],[-5.079346,54.356181],[-5.046624,53.9131],[-5.013966,53.458674],[-5.001631,53.283731],[-5.020559,53.013619],[-5.051336,52.558457],[-5.051366,52.558457],[-5.051366,52.558456],[-5.673366,52.129388],[-5.822792,52.024604],[-6.33545,51.618645],[-6.299809,51.241244],[-6.568641,51.244838],[-8.000721,51.253594],[-9.432801,51.24483],[-10.863443,51.218565]]; const polyCi = [[-1.614831,49.644257],[-1.639721,48.744984],[-3,48.753013],[-3,49.652543],[-3,49.895922],[-2.001337,49.900236],[-1.607534,49.899573],[-1.614831,49.644257]]; /** * Given a WGS 84 lon, lat pair, return the two letter code corresponding to the area * that the point is in. The three polygons describe the regions covered by the 100 km * squares for the British, Irish and Channel Island coordinate systems. These areas are * exclusive. Where they overlap, e.g. British and Irish, they have been divided by a line * through the Irish see roughly midway between the land masses. For the Channel Islands * and Britain, the CI UTM 30 grid is truncated where it overlaps the British grid. The * function returns the two-letter code corresponding to the area: gb, ir or ci. * It returns null if the point doesn't fall within any of these areas. * @param {number} lon - Longitude. * @param {number} lat - Latitude. * @returns {string} - Two-letter code corresponding to the area, or null. */ function pntToArea (lon, lat) { if (inside([ lon, lat ], polyGb)) return 'gb' if (inside([ lon, lat ], polyIr)) return 'ir' if (inside([ lon, lat ], polyCi)) return 'ci' return null } /** @module src/getGrFromCoords */ /** * Given a coordinate pair (British National Grid, Irish Grid, UTM zone 30N shorthand or WGS84), * a two-letter code defining the projection of the passed in coordinates, * a two-letter code defining the required output projection, and an array of numbers * indicating the required output precisions, returns an object with the grid references * at the requested precisions in the requested projection. If the requested projection is * an empty string, automatic selection will be used based on location of input coords. * @param {number} x - the x coordinate (longitude if in WGS 84). * @param {number} y - the y coordinate (latitude if in WGS 84). * @param {string} fromProjection - two letter code for projection of the passed in coords. * @param {string} toProjection - two letter code specifying the required output projection - leave empty for automatic selection. * @param {array<number>} precisions - array of numbers corresponding to the precisions of the requested grid references. * @returns {object} - of the form {p10000: 'gr-hectad', p100: 'gr-6fig'} etc, with a property for each of the requested grid reference precisions. */ function getGrFromCoords (x, y, fromProjection, toProjection, precisions) { // Set the output projection automatically if not aleady set if (!toProjection) { if (fromProjection === 'wg') { toProjection = pntToArea(x, y); } else { const lonlat = proj4(projections[fromProjection].proj4, projections['wg'].proj4, [x, y]); toProjection = pntToArea(lonlat[0], lonlat[1]); } } if (!toProjection) { const grs = {}; precisions.forEach(p => { grs[`p${p}`] = null; }); return grs } // Convert input coordinates if the input projection does not match requested output projection if (fromProjection !== toProjection) { const outCoords = proj4(projections[fromProjection].proj4, projections[toProjection].proj4, [x, y]); x = outCoords[0]; y = outCoords[1]; } let km100; for (let i = 0; i < a100km.length; i++) { if (a100km[i].proj === toProjection && x >= a100km[i].x * 100000 && x < (a100km[i].x + 1) * 100000 && y >= a100km[i].y * 100000 && y < (a100km[i].y + 1) * 100000) { km100 = a100km[i]; break } } if(!km100) { // The output coordinates do not fall within the range of 100 km grid squares defined for the output projection. // Return an empty array. return { p100000: null, p10000: null, p5000: null, p2000: null, p1000: null, p100: null, p10: null, p1: null } } const grs = {}; precisions.forEach(p => { let gr = km100.GridRef; if (p < 100000){ let divisor; if (p === 5000 || p === 2000) { divisor = 10000; } else { divisor = p; } const pad = { 10000: 1, 1000: 2, 100: 3, 10: 4, 1: 5 }; const dx = Math.floor((x - km100.x * 100000) / divisor); const dy = Math.floor((y - km100.y * 100000) / divisor); const sx = String(dx).padStart(pad[divisor], '0'); const sy = String(dy).padStart(pad[divisor], '0'); gr = `${gr}${sx}${sy}`; if (p === 5000 || p === 2000) { let rx = (x - km100.x * 100000) % divisor; let ry = (y - km100.y * 100000) % divisor; if (p === 5000) { for (const suffix in qOffsets) { if (rx >= qOffsets[suffix].x && rx < qOffsets[suffix].x + 5000 && ry >= qOffsets[suffix].y && ry < qOffsets[suffix].y + 5000) { gr = `${gr}${suffix.toUpperCase()}`; break } } } else { for (const suffix in tOffsets) { if (rx >= tOffsets[suffix].x && rx < tOffsets[suffix].x + 2000 && ry >= tOffsets[suffix].y && ry < tOffsets[suffix].y + 2000) { gr = `${gr}${suffix.toUpperCase()}`; break } } } } } grs[`p${p}`] = gr; }); return grs } /** @module src/getLowerResGrs */ /** * Given a grid reference (British National Grid, Irish Grid, UTM zone 30N shorthand), * return an object with a corresponsing GR for each precision. For all precisions higher * than the precision of the passed in GR, the return GR is set to null. In the special * returned Quadrant (5 km) grid references are in an array to allow for the case where * the input grid reference is a tetrad and overlaps more than one quadrant. * @param {string} gr - Grid reference. * @returns {object} - of the form {p100000: 'gr-100km', p10000: 'gr-hectad', p5000: ['gr_quad1', ...], p2000: 'gr-tetrad', ...}, with a property for each precisions. */ function getLowerResGrs (gr) { const grType = checkGr(gr); const ret = { p100000: grType.prefix, p10000: null, p5000: grType.precision <= 5000 ? [] : null, p2000: null, p1000: null, p100: null, p10: null, p1: null }; // Set the passed in GR in the return value if (grType.precision === 5000) { ret.p5000.push(gr); } else { ret[`p${grType.precision}`] = gr; } const c = getCentroid(gr, grType.projection).centroid; const precisions = [10000, 5000, 2000, 1000, 100, 10, 1].filter(p => p > grType.precision); const grs = getGrFromCoords(c[0], c[1], grType.projection, grType.projection, precisions); precisions.forEach(p => { if (p === 5000) { if (grType.precision === 2000) { const hectad = gr.substring(0,gr.length-1); if ('ABCFGHKLM'.indexOf(gr.substr(-1)) > -1) { ret.p5000.push(`${hectad}SW`); } if ('KLMQRSVWX'.indexOf(gr.substr(-1)) > -1) { ret.p5000.push(`${hectad}SE`); } if ('CDEHIJMNP'.indexOf(gr.substr(-1)) > -1) { ret.p5000.push(`${hectad}NW`); } if ('MNPSTUXYZ'.indexOf(gr.substr(-1)) > -1) { ret.p5000.push(`${hectad}NE`); } } else { ret.p5000.push(grs.p5000); } } else { ret[`p${p}`] = grs[`p${p}`]; } }); return ret } exports.checkGr = checkGr; exports.getCentroid = getCentroid; exports.getGjson = getGjson; exports.getGrFromCoords = getGrFromCoords; exports.getLowerResGrs = getLowerResGrs; exports.pntToArea = pntToArea;