123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- /**
- * Resources for details of NTv2 file formats:
- * - https://web.archive.org/web/20140127204822if_/http://www.mgs.gov.on.ca:80/stdprodconsume/groups/content/@mgs/@iandit/documents/resourcelist/stel02_047447.pdf
- * - http://mimaka.com/help/gs/html/004_NTV2%20Data%20Format.htm
- */
- var loadedNadgrids = {};
- /**
- * Load a binary NTv2 file (.gsb) to a key that can be used in a proj string like +nadgrids=<key>. Pass the NTv2 file
- * as an ArrayBuffer.
- */
- export default function nadgrid(key, data) {
- var view = new DataView(data);
- var isLittleEndian = detectLittleEndian(view);
- var header = readHeader(view, isLittleEndian);
- if (header.nSubgrids > 1) {
- console.log('Only single NTv2 subgrids are currently supported, subsequent sub grids are ignored');
- }
- var subgrids = readSubgrids(view, header, isLittleEndian);
- var nadgrid = {header: header, subgrids: subgrids};
- loadedNadgrids[key] = nadgrid;
- return nadgrid;
- }
- /**
- * Given a proj4 value for nadgrids, return an array of loaded grids
- */
- export function getNadgrids(nadgrids) {
- // Format details: http://proj.maptools.org/gen_parms.html
- if (nadgrids === undefined) { return null; }
- var grids = nadgrids.split(',');
- return grids.map(parseNadgridString);
- }
- function parseNadgridString(value) {
- if (value.length === 0) {
- return null;
- }
- var optional = value[0] === '@';
- if (optional) {
- value = value.slice(1);
- }
- if (value === 'null') {
- return {name: 'null', mandatory: !optional, grid: null, isNull: true};
- }
- return {
- name: value,
- mandatory: !optional,
- grid: loadedNadgrids[value] || null,
- isNull: false
- };
- }
- function secondsToRadians(seconds) {
- return (seconds / 3600) * Math.PI / 180;
- }
- function detectLittleEndian(view) {
- var nFields = view.getInt32(8, false);
- if (nFields === 11) {
- return false;
- }
- nFields = view.getInt32(8, true);
- if (nFields !== 11) {
- console.warn('Failed to detect nadgrid endian-ness, defaulting to little-endian');
- }
- return true;
- }
- function readHeader(view, isLittleEndian) {
- return {
- nFields: view.getInt32(8, isLittleEndian),
- nSubgridFields: view.getInt32(24, isLittleEndian),
- nSubgrids: view.getInt32(40, isLittleEndian),
- shiftType: decodeString(view, 56, 56 + 8).trim(),
- fromSemiMajorAxis: view.getFloat64(120, isLittleEndian),
- fromSemiMinorAxis: view.getFloat64(136, isLittleEndian),
- toSemiMajorAxis: view.getFloat64(152, isLittleEndian),
- toSemiMinorAxis: view.getFloat64(168, isLittleEndian),
- };
- }
- function decodeString(view, start, end) {
- return String.fromCharCode.apply(null, new Uint8Array(view.buffer.slice(start, end)));
- }
- function readSubgrids(view, header, isLittleEndian) {
- var gridOffset = 176;
- var grids = [];
- for (var i = 0; i < header.nSubgrids; i++) {
- var subHeader = readGridHeader(view, gridOffset, isLittleEndian);
- var nodes = readGridNodes(view, gridOffset, subHeader, isLittleEndian);
- var lngColumnCount = Math.round(
- 1 + (subHeader.upperLongitude - subHeader.lowerLongitude) / subHeader.longitudeInterval);
- var latColumnCount = Math.round(
- 1 + (subHeader.upperLatitude - subHeader.lowerLatitude) / subHeader.latitudeInterval);
- // Proj4 operates on radians whereas the coordinates are in seconds in the grid
- grids.push({
- ll: [secondsToRadians(subHeader.lowerLongitude), secondsToRadians(subHeader.lowerLatitude)],
- del: [secondsToRadians(subHeader.longitudeInterval), secondsToRadians(subHeader.latitudeInterval)],
- lim: [lngColumnCount, latColumnCount],
- count: subHeader.gridNodeCount,
- cvs: mapNodes(nodes)
- });
- }
- return grids;
- }
- function mapNodes(nodes) {
- return nodes.map(function (r) {return [secondsToRadians(r.longitudeShift), secondsToRadians(r.latitudeShift)];});
- }
- function readGridHeader(view, offset, isLittleEndian) {
- return {
- name: decodeString(view, offset + 8, offset + 16).trim(),
- parent: decodeString(view, offset + 24, offset + 24 + 8).trim(),
- lowerLatitude: view.getFloat64(offset + 72, isLittleEndian),
- upperLatitude: view.getFloat64(offset + 88, isLittleEndian),
- lowerLongitude: view.getFloat64(offset + 104, isLittleEndian),
- upperLongitude: view.getFloat64(offset + 120, isLittleEndian),
- latitudeInterval: view.getFloat64(offset + 136, isLittleEndian),
- longitudeInterval: view.getFloat64(offset + 152, isLittleEndian),
- gridNodeCount: view.getInt32(offset + 168, isLittleEndian)
- };
- }
- function readGridNodes(view, offset, gridHeader, isLittleEndian) {
- var nodesOffset = offset + 176;
- var gridRecordLength = 16;
- var gridShiftRecords = [];
- for (var i = 0; i < gridHeader.gridNodeCount; i++) {
- var record = {
- latitudeShift: view.getFloat32(nodesOffset + i * gridRecordLength, isLittleEndian),
- longitudeShift: view.getFloat32(nodesOffset + i * gridRecordLength + 4, isLittleEndian),
- latitudeAccuracy: view.getFloat32(nodesOffset + i * gridRecordLength + 8, isLittleEndian),
- longitudeAccuracy: view.getFloat32(nodesOffset + i * gridRecordLength + 12, isLittleEndian),
- };
- gridShiftRecords.push(record);
- }
- return gridShiftRecords;
- }
|