-
Notifications
You must be signed in to change notification settings - Fork 303
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature(Parser): add parsers for GTX, ISG and GDF file formats
- Loading branch information
Showing
9 changed files
with
416 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import * as THREE from 'three'; | ||
import GeoidGrid from 'Core/Geographic/GeoidGrid'; | ||
import Extent from 'Core/Geographic/Extent'; | ||
import { BYTES_PER_DOUBLE } from 'Parser/GTXParser'; | ||
|
||
|
||
export function getHeaderAttribute(header, attributeName) { | ||
const attributeRow = header[header.indexOf(header.find(element => element.includes(attributeName)))].split(' ') | ||
.filter(value => value !== ''); | ||
return parseFloat(attributeRow[attributeRow.length - 1]); | ||
} | ||
|
||
|
||
/** | ||
* The `GDFParser` module provides a `[parse]{@link module:GDFParser.parse}` method. This method takes the content of a | ||
* GDF file in, and returns a `{@link GeoidGrid}`. the `{@link GeoidGrid}` contains all the necessary attributes and | ||
* methods to access the GDF data in iTowns. | ||
* | ||
* @module GDFParser | ||
*/ | ||
export default { | ||
/** | ||
* Parses a GDF file content and returns a corresponding `{@link GeoidGrid}`. | ||
* | ||
* @param {string} gdf The content of the GDF file to parse. | ||
* @param {Object} options An object gathering the optional parameters to pass to | ||
* the parser. | ||
* @param {Object} [options.in={}] Information on the GDF data. | ||
* @param {string} [options.in.crs='EPSG:4326'] The Coordinates Reference System (CRS) of the GDF data. | ||
* It must be a geographic CRS, and must be given as an EPSG | ||
* code. | ||
* | ||
* @returns {Promise<GeoidGrid>} A promise resolving with a `{@link GeoidGrid}`, which contains all the necessary | ||
* attributes and methods to access GDF file data. | ||
*/ | ||
parse(gdf, options = { in: {} }) { | ||
const rows = gdf.split('\n'); | ||
const firstMeasureLine = rows.indexOf(rows.find(row => row.includes('end_of_head'))) + 1; | ||
const rawHeaderData = rows.slice(0, firstMeasureLine); | ||
|
||
// ---------- GET METADATA FROM THE FILE : ---------- | ||
|
||
const metadata = { | ||
minX: getHeaderAttribute(rawHeaderData, 'longlimit_west'), | ||
maxX: getHeaderAttribute(rawHeaderData, 'longlimit_east'), | ||
minY: getHeaderAttribute(rawHeaderData, 'latlimit_south'), | ||
maxY: getHeaderAttribute(rawHeaderData, 'latlimit_north'), | ||
stepX: getHeaderAttribute(rawHeaderData, 'gridstep'), | ||
stepY: getHeaderAttribute(rawHeaderData, 'gridstep'), | ||
nRows: getHeaderAttribute(rawHeaderData, 'latitude_parallels'), | ||
nColumns: getHeaderAttribute(rawHeaderData, 'longitude_parallels'), | ||
}; | ||
|
||
// ---------- BUILD A DATA VIEWER FROM THE TEXT DATA : ---------- | ||
|
||
const data = new DataView( | ||
new ArrayBuffer(BYTES_PER_DOUBLE * metadata.nRows * metadata.nColumns), | ||
); | ||
|
||
let index = 0; | ||
for (let row of rows.slice(firstMeasureLine, rows.length)) { | ||
row = row.split(' ').filter(value => value !== ''); | ||
|
||
if (!row.length) { continue; } | ||
|
||
data.setFloat64(index * BYTES_PER_DOUBLE, parseFloat(row[2])); | ||
index++; | ||
} | ||
|
||
// ---------- CREATE A GeoidGrid FOR THE GIVEN FILE DATA : ---------- | ||
|
||
const dataExtent = new Extent( | ||
options.in.crs || 'EPSG:4326', | ||
metadata.minX, metadata.maxX, metadata.minY, metadata.maxY, | ||
); | ||
|
||
const dataStep = new THREE.Vector2(metadata.stepX, metadata.stepY); | ||
|
||
const getData = (verticalIndex, horizontalIndex) => | ||
data.getFloat64(( | ||
metadata.nColumns * (metadata.nRows - verticalIndex - 1) + horizontalIndex | ||
) * BYTES_PER_DOUBLE); | ||
|
||
return Promise.resolve(new GeoidGrid(dataExtent, dataStep, getData)); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import * as THREE from 'three'; | ||
import GeoidGrid from 'Core/Geographic/GeoidGrid'; | ||
import Extent from 'Core/Geographic/Extent'; | ||
|
||
|
||
export const BYTES_PER_DOUBLE = 8; | ||
export const BYTES_PER_FLOAT = 4; | ||
|
||
|
||
/** | ||
* The `GTXParser` module provides a `[parse]{@link module:GTXParser.parse}` method. This method takes the content of a | ||
* GTX file in, and returns a `{@link GeoidGrid}`. The `{@link GeoidGrid}` contains all the necessary attributes and | ||
* methods to access the GTX data in iTowns. | ||
* | ||
* @module GTXParser | ||
*/ | ||
export default { | ||
/** | ||
* Parses a GTX file content and returns a corresponding `{@link GeoidGrid}`. | ||
* | ||
* @param {ArrayBuffer} gtx The content of the GTX file to parse. | ||
* @param {Object} options An object gathering the optional parameters to pass to | ||
* the parser. | ||
* @param {Object} [options.in={}] Information on the GTX data. | ||
* @param {string} [options.in.crs='EPSG:4326'] The Coordinates Reference System (CRS) of the GTX data. | ||
* It must be a geographic CRS, and must be given as an | ||
* EPSG code. | ||
* @param {string} [options.in.dataType='float'] The encoding of geoid height data within the GTX file. | ||
* Must be `'float'` or `'double'`. | ||
* | ||
* @returns {Promise<GeoidGrid>} A promise resolving with a `{@link GeoidGrid}`, which contains all the necessary | ||
* attributes and methods to access GTX file data. | ||
*/ | ||
parse(gtx, options = { in: {} }) { | ||
const dataType = options.in.dataType || 'float'; | ||
if (!['float', 'double'].includes(dataType)) { | ||
throw new Error( | ||
'`dataType` parameter is incorrect for GTXParser.parse method. ' + | ||
'This parameter must be either `double` or `float`.', | ||
); | ||
} | ||
|
||
// ---------- GET METADATA FROM THE FILE : ---------- | ||
|
||
const headerView = new DataView(gtx, 0, 40); | ||
const metadata = { | ||
minX: headerView.getFloat64(8), | ||
minY: headerView.getFloat64(0), | ||
stepX: headerView.getFloat64(24), | ||
stepY: headerView.getFloat64(16), | ||
nColumns: headerView.getInt32(36), | ||
nRows: headerView.getInt32(32), | ||
}; | ||
|
||
// ---------- BUILD A DATA VIEWER : ---------- | ||
|
||
const dataView = new DataView(gtx, 40); | ||
|
||
// ---------- CREATE A GeoidGrid FOR THE GIVEN FILE DATA : ---------- | ||
|
||
// formula for the max longitude : maxLongitude = minLongitude + deltaLongitude * (nColumns - 1) | ||
const maxX = metadata.minX + metadata.stepX * (metadata.nColumns - 1); | ||
// formula for the max latitude : maxLatitude = minLatitude + deltaLatitude * (nRows - 1) | ||
const maxY = metadata.minY + metadata.stepY * (metadata.nRows - 1); | ||
|
||
const dataExtent = new Extent( | ||
options.in.crs || 'EPSG:4326', | ||
metadata.minX, maxX, metadata.minY, maxY, | ||
); | ||
|
||
const dataStep = new THREE.Vector2(metadata.stepX, metadata.stepY); | ||
|
||
const getData = (verticalIndex, horizontalIndex) => { | ||
// formula to get the index of a geoid height from a latitude and longitude indexes is : | ||
// ``(nColumns * latIndex + lonIndex) * nBytes``, where nBytes stands for the number of bytes geoid | ||
// height data are encoded on. | ||
if (dataType === 'float') { | ||
return dataView.getFloat32( | ||
(metadata.nColumns * verticalIndex + horizontalIndex) * BYTES_PER_FLOAT, | ||
); | ||
} else if (dataType === 'double') { | ||
return dataView.getFloat64( | ||
(metadata.nColumns * verticalIndex + horizontalIndex) * BYTES_PER_DOUBLE, | ||
); | ||
} | ||
}; | ||
|
||
return Promise.resolve(new GeoidGrid(dataExtent, dataStep, getData)); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import * as THREE from 'three'; | ||
import GeoidGrid from 'Core/Geographic/GeoidGrid'; | ||
import Extent from 'Core/Geographic/Extent'; | ||
import { getHeaderAttribute } from 'Parser/GDFParser'; | ||
import { BYTES_PER_DOUBLE } from 'Parser/GTXParser'; | ||
|
||
|
||
/** | ||
* The `ISGParser` module provides a `[parse]{@link module:ISGParser.parse}` method. This method takes the content of a | ||
* ISG file in, and returns a `{@link GeoidGrid}`. the `{@link GeoidGrid}` contains all the necessary attributes and | ||
* methods to access the ISG data in iTowns. | ||
* | ||
* @module ISGParser | ||
*/ | ||
export default { | ||
/** | ||
* Parses an ISG file content and returns a corresponding `{@link GeoidGrid}`. | ||
* | ||
* @param {string} isg The content of the ISG file to parse. | ||
* @param {Object} options An object gathering the optional parameters to pass to | ||
* the parser. | ||
* @param {Object} [options.in={}] Information on the ISG data. | ||
* @param {string} [options.in.crs='EPSG:4326'] The Coordinates Reference System (CRS) of the ISG data. | ||
* It must be a geographic CRS, and must be given as an EPSG | ||
* code. | ||
* | ||
* @returns {Promise<GeoidGrid>} A promise resolving with a `{@link GeoidGrid}`, which contains all the necessary | ||
* attributes and methods to access ISG file data. | ||
*/ | ||
parse(isg, options = { in: {} }) { | ||
const rows = isg.split('\n'); | ||
const firstMeasureLine = rows.indexOf(rows.find(row => row.includes('end_of_head'))) + 1; | ||
const rawHeaderData = rows.slice(0, firstMeasureLine); | ||
|
||
// ---------- GET METADATA FROM THE FILE : ---------- | ||
|
||
const metadata = { | ||
minX: getHeaderAttribute(rawHeaderData, 'lon min'), | ||
maxX: getHeaderAttribute(rawHeaderData, 'lon max'), | ||
minY: getHeaderAttribute(rawHeaderData, 'lat min'), | ||
maxY: getHeaderAttribute(rawHeaderData, 'lat max'), | ||
stepX: getHeaderAttribute(rawHeaderData, 'delta lon'), | ||
stepY: getHeaderAttribute(rawHeaderData, 'delta lat'), | ||
nRows: getHeaderAttribute(rawHeaderData, 'nrows'), | ||
nColumns: getHeaderAttribute(rawHeaderData, 'ncols'), | ||
}; | ||
|
||
// ---------- BUILD A DATA VIEWER FROM THE TEXT DATA : ---------- | ||
|
||
const data = new DataView( | ||
new ArrayBuffer(BYTES_PER_DOUBLE * metadata.nRows * metadata.nColumns), | ||
); | ||
|
||
let index = 0; | ||
for (let row of rows.slice(firstMeasureLine, rows.length)) { | ||
row = row.split(' ').filter(value => value !== ''); | ||
|
||
if (!row.length) { continue; } | ||
|
||
for (const value of row) { | ||
data.setFloat64(index * BYTES_PER_DOUBLE, parseFloat(value)); | ||
index++; | ||
} | ||
} | ||
|
||
// ---------- CREATE A GeoidGrid FOR THE GIVEN FILE DATA : ---------- | ||
|
||
const dataExtent = new Extent( | ||
options.in.crs || 'EPSG:4326', | ||
metadata.minX + metadata.stepX / 2, metadata.maxX - metadata.stepX / 2, | ||
metadata.minY + metadata.stepY / 2, metadata.maxY - metadata.stepY / 2, | ||
); | ||
|
||
const dataStep = new THREE.Vector2(metadata.stepX, metadata.stepY); | ||
|
||
const getData = (verticalIndex, horizontalIndex) => | ||
data.getFloat64((metadata.nColumns * verticalIndex + horizontalIndex) * BYTES_PER_DOUBLE); | ||
|
||
return Promise.resolve(new GeoidGrid(dataExtent, dataStep, getData)); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import assert from 'assert'; | ||
import HttpsProxyAgent from 'https-proxy-agent'; | ||
import Fetcher from 'Provider/Fetcher'; | ||
import GDFParser from 'Parser/GDFParser'; | ||
|
||
|
||
describe('GDFParser', function () { | ||
let text; | ||
|
||
before(async () => { | ||
const networkOptions = process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}; | ||
text = await Fetcher.text( | ||
'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/altitude-conversion-grids/' + | ||
'EGM2008.gdf', | ||
networkOptions, | ||
); | ||
}); | ||
|
||
it('should default `options.in.crs` parameter to `EPSG:4326`', async function () { | ||
const geoidGrid = await GDFParser.parse(text); | ||
assert.strictEqual(geoidGrid.extent.crs, 'EPSG:4326'); | ||
}); | ||
|
||
it('should parse text data into a GeoidGrid', async function () { | ||
const geoidGrid = await GDFParser.parse(text, { in: { crs: 'EPSG:4326' } }); | ||
assert.strictEqual(geoidGrid.extent.west, -180); | ||
assert.strictEqual(geoidGrid.extent.east, 180); | ||
assert.strictEqual(geoidGrid.extent.south, -90); | ||
assert.strictEqual(geoidGrid.extent.north, 90); | ||
assert.strictEqual(geoidGrid.step.x, 1); | ||
assert.strictEqual(geoidGrid.step.y, 1); | ||
}); | ||
|
||
it('should set a correct data reading method for `GeoidGrid`', async function () { | ||
const geoidGrid = await GDFParser.parse(text, { in: { crs: 'EPSG:4326' } }); | ||
assert.strictEqual(geoidGrid.getData(geoidGrid.dataSize.y - 2, 1), 13.813008707225); | ||
}); | ||
}); |
Oops, something went wrong.