Skip to content

Commit

Permalink
Merge branch 'release/1.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
ddohler committed Dec 12, 2022
2 parents 2e2d914 + 9bc08a6 commit dd243f2
Show file tree
Hide file tree
Showing 27 changed files with 862 additions and 79 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## UPCOMING

## 1.2.0 (2022-12-12)
- Add support for opening vector datasets
- Add GDALDataset.layerCount() to wrap GDALDatasetGetLayerCount()
- Add GDALDataset.vectorConvert() to wrap GDALVectorTranslate()
- Add wrappers for GDALGetRasterMinimum, GDALGetRasterMaximum, GDALGetRasterDataType, GDALGetRasterStatistics, and GDALGetRasterNoDataValue

## 1.1.2 (2022-10-12)
- Allow users to specify a valid absolute URL when the default URL is invalid

Expand Down
81 changes: 77 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,12 @@ A promise that resolves when Loam is initialized. All of the functions described
<br />
### `loam.open(file)`
### `loam.open(file, sidecars)`
Creates a new GDAL Dataset.
#### Parameters
- `file`: A Blob or File object that should be opened with GDAL. GDAL is compiled with TIFF, PNG, and JPEG support.
- `file`: A Blob or File object that should be opened with GDAL. GDAL is compiled with TIFF, PNG, and JPEG support. If you have a Blob, you may also control the name of the file that is shown to GDAL on the virtual filesystem by passing an object with the shape `{name: string, data: Blob}`. This can be useful if you are relying on GDAL behavior that uses file extensions to determine formats.
- `sidecars`: An array of additional files that will be made present in the virtual file system when opening `file`. Some data formats are composed of multiple files (for example, Shapefiles have `.shp`, `.shx`, and `.prj` files, among others). If you need to include multiple files in order to open a dataset, pass the "main" file as `file`, and pass the others to `sidecars`. For a Shapefile, this would mean passing the `.shp` file as `file` and the `.shx`, `.prj`, and friends to `sidecars`. If `file` is a File, then `sidecars` must be an Array\<File>. If `file` is a Blob or Object (see above), then `sidecars` must be an Array\<Object> where each element has the shape `{name: string, data: Blob}`.
#### Return value
A promise that resolves with an instance of `GDALDataset`.
Expand Down Expand Up @@ -116,9 +118,16 @@ A promise that resolves immediately with an empty list (for historical reasons).
<br />
### `GDALDataset.count()`
Get the number of bands in the dataset.
Get the number of raster bands in the dataset.
#### Return value
A promise which resolves to the number of raster bands in the dataset.
<br />
### `GDALDataset.layerCount()`
Get the number of vector layers in the dataset.
#### Return value
A promise which resolves to the number of bands in the dataset.
A promise which resolves to the number of vector layers in the dataset.
<br />
Expand Down Expand Up @@ -150,6 +159,59 @@ A promise which resolves to the affine transform.
<br />
### `GDALDataset.bandMinimum(bandNum)`
Get the actual minimum value or the minimum possible value of a band (depending on format).
#### Parameters
- `bandNum`: The number of the band for which to get the minimum value. Band numbering starts at 1.
#### Return value
A promise which resolves to a minimum value for the specified band.
<br />
### `GDALDataset.bandMaximum(bandNum)`
Get the actual maximum value or the maximum possible value of a band (depending on format).
#### Parameters
- `bandNum`: The number of the band for which to get the maximum value. Band numbering starts at 1.
#### Return value
A promise which resolves to a maximum value for the specified band.
<br />
### `GDALDataset.bandStatistics(bandNum)`
Get statistics about the values in a band.
#### Parameters
- `bandNum`: The number of the band for which to get statistics. Band numbering starts at 1.
#### Return value
A promise which resolves to an object containing statistics. The shape of the object will be:
```javascript
{
minimum: The calculated minimum value of the band
maximum: The calculated minimum value of the band
median: The calculated median value of the band
stdDev: The calculated standard deviation of the band
}
```
<br />
### `GDALDataset.bandNoDataValue(bandNum)`
Get the value representing "no data" within the band.
#### Parameters
- `bandNum`: The number of the band for which to get the no-data value. Band numbering starts at 1.
#### Return value
A promise which resolves to the no-data value for the specified band.
<br />
### `GDALDataset.bandDataType(bandNum)`
Get the data type of the band (Byte, UInt16, Float32, etc.)
#### Parameters
- `bandNum`: The number of the band for which to get the data type. Band numbering starts at 1.
#### Return value
A promise which resolves to a string containing the name of the data type for the specified band. For example, 'Byte', 'Float32', etc.
<br />
### `GDALDataset.bytes()`
Get the on-disk representation of the dataset, as an array of bytes.
#### Return value
Expand All @@ -168,6 +230,17 @@ A promise that resolves to a new `GDALDataset`.
<br />
### `GDALDataset.vectorConvert(args)`
Converts vector data between different formats. This is the equivalent of the [ogr2ogr](https://gdal.org/programs/ogr2ogr.html) command.
**Note**: This returns a new `GDALDataset` object but does not perform any immediate calculation. Instead, calls to `.vectorConvert()` are evaluated lazily. Each successive call to `.vectorConvert()` is stored in a list of operations on the dataset object. These operations are only evaluated when necessary in order to access some property of the dataset, such as its size, bytes, or layer count.
#### Parameters
- `args`: An array of strings, each representing a single command-line argument accepted by the `ogr2ogr` command. The `dst_datasource_name` and `src_datasource_name` parameters should be omitted; these are handled by `GDALDataset`. Example: `ds.vectorConvert(['-f', 'GeoJSON'])`.
#### Return value
A promise that resolves to a new `GDALDataset`.
<br />
### `GDALDataset.warp(args)`
Image reprojection and warping utility. This is the equivalent of the [gdalwarp](https://gdal.org/programs/gdalwarp.html) command.
Expand Down
21 changes: 16 additions & 5 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,25 @@
<title>Loam Test</title>
<script type="text/javascript" src="/loam.js"></script>
</head>

<body>
<form>
<input type="file" id="geotiff-file" />
</form>
<p>
Select a GeoTIFF using the Browse... button. Information about the file will be
displayed below.
Select a file using the Source file input and then click "Display metadata". Metadata
about the file will be displayed below. GeoTIFFs work best, but GeoJSON and Shapefiles
will work to some extent as well. Make sure to select sidecar files when relevant (e.g.
.prj, .dbf, .shx, etc. for Shapefiles).
</p>
<form>
<label>
Source file:
<input type="file" id="source-file" />
</label>
<label>
Additional files:
<input type="file" id="sidecar-files" multiple />
</label>
<button type="button" id="display-metadata-button">Display metadata</button>
</form>

<div id="gdalinfo"></div>
<script src="index.js"></script>
Expand Down
90 changes: 53 additions & 37 deletions demo/index.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,76 @@
/* global loam */

// Use the locally built version of loam, with a CDN copy of GDAL from unpkg.
loam.initialize('/', 'https://unpkg.com/gdal-js@2.0.0/');
loam.initialize('/', 'https://unpkg.com/gdal-js@2.1.0/');

const EPSG4326 =
'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]';

function displayInfo() {
const file = document.querySelector('#geotiff-file').files[0];
const sourceFile = document.querySelector('#source-file').files[0];
const sidecars = Array.from(document.querySelector('#sidecar-files').files);

const displayElem = document.getElementById('gdalinfo');

// Clear display text
displayElem.innerText = '';
// Use Loam to get GeoTIFF metadata
loam.open(file).then((ds) => {
return Promise.all([ds.width(), ds.height(), ds.count(), ds.wkt(), ds.transform()]).then(
([width, height, count, wkt, geoTransform]) => {
displayElem.innerText +=
'Size: ' + width.toString() + ', ' + height.toString() + '\n';
displayElem.innerText += 'Band count: ' + count.toString() + '\n';
displayElem.innerText += 'Coordinate system:\n' + wkt + '\n';

const cornersPx = [
[0, 0],
[width, 0],
[width, height],
[0, height],
loam.open(sourceFile, sidecars).then((ds) => {
return Promise.all([
ds.width(),
ds.height(),
ds.count(),
ds.layerCount(),
ds.wkt(),
ds.transform(),
]).then(([width, height, count, layerCount, wkt, geoTransform]) => {
displayElem.innerText += 'Size: ' + width.toString() + ', ' + height.toString() + '\n';
displayElem.innerText += 'Raster band count: ' + count.toString() + '\n';
displayElem.innerText += 'Vector layer count: ' + layerCount.toString() + '\n';
displayElem.innerText += 'Coordinate system:\n' + wkt + '\n';

const cornersPx = [
[0, 0],
[width, 0],
[width, height],
[0, height],
];
const cornersGeo = cornersPx.map(([x, y]) => {
return [
// http://www.gdal.org/gdal_datamodel.html
geoTransform[0] + geoTransform[1] * x + geoTransform[2] * y,
geoTransform[3] + geoTransform[4] * x + geoTransform[5] * y,
];
const cornersGeo = cornersPx.map(([x, y]) => {
return [
// http://www.gdal.org/gdal_datamodel.html
geoTransform[0] + geoTransform[1] * x + geoTransform[2] * y,
geoTransform[3] + geoTransform[4] * x + geoTransform[5] * y,
];
});

loam.reproject(wkt, EPSG4326, cornersGeo).then((cornersLngLat) => {
displayElem.innerText += 'Corner Coordinates:\n';
cornersLngLat.forEach(([lng, lat], i) => {
displayElem.innerText +=
'(' +
cornersGeo[i][0].toString() +
', ' +
cornersGeo[i][1].toString() +
') (' +
lng.toString() +
', ' +
lat.toString() +
')\n';
});
});

loam.reproject(wkt, EPSG4326, cornersGeo).then((cornersLngLat) => {
displayElem.innerText += 'Corner Coordinates:\n';
cornersLngLat.forEach(([lng, lat], i) => {
displayElem.innerText +=
'(' +
cornersGeo[i][0].toString() +
', ' +
cornersGeo[i][1].toString() +
') (' +
lng.toString() +
', ' +
lat.toString() +
')\n';
});
if (count > 0) {
ds.bandStatistics(1).then((stats) => {
displayElem.innerText += 'Band 1 min: ' + stats.minimum + '\n';
displayElem.innerText += 'Band 1 max: ' + stats.maximum + '\n';
displayElem.innerText += 'Band 1 median: ' + stats.median + '\n';
displayElem.innerText += 'Band 1 standard deviation: ' + stats.stdDev + '\n';
});
}
);
});
});
}

document.getElementById('geotiff-file').onchange = function () {
document.getElementById('display-metadata-button').onclick = function () {
displayInfo();
};
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "loam",
"version": "1.1.2",
"version": "1.2.0",
"description": "Javascript wrapper for GDAL in the browser",
"main": "lib/loam.js",
"scripts": {
"build": "webpack --config=webpack.dev.js && webpack --config=webpack.prod.js",
"dev": "webpack --progress --color --watch --config=webpack.dev.js",
"demo": "webpack serve --config=webpack.dev.js",
"format": "prettier --write ./src",
"format": "prettier --write ./src ./test ./demo",
"test": "karma start --single-run --browser ChromeHeadless karma.conf.js",
"test:watch": "karma start --auto-watch --browser ChromeHeadless karma.conf.js",
"test:ci": "prettier --check src/**/*.js && webpack --config=webpack.dev.js && webpack --config=webpack.prod.js && karma start --single-run --browser ChromeHeadless karma.conf.js"
Expand Down Expand Up @@ -58,7 +58,7 @@
"yargs": "^17.5.1"
},
"dependencies": {
"gdal-js": "2.0.0"
"gdal-js": "2.1.0"
},
"packageManager": "yarn@3.2.3"
}
8 changes: 4 additions & 4 deletions src/api.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { initWorker, clearWorker, runOnWorker } from './workerCommunication.js';
import { GDALDataset } from './gdalDataset.js';
import { DatasetSource, GDALDataset } from './gdalDataset.js';

function open(file) {
function open(file, sidecars = []) {
return new Promise((resolve, reject) => {
const ds = new GDALDataset({ func: 'GDALOpen', src: file, args: [] });
const ds = new GDALDataset(new DatasetSource('GDALOpen', file, sidecars));

return ds.open().then(
() => resolve(ds),
Expand All @@ -14,7 +14,7 @@ function open(file) {

function rasterize(geojson, args) {
return new Promise((resolve, reject) => {
resolve(new GDALDataset({ func: 'GDALRasterize', src: geojson, args: args }));
resolve(new GDALDataset(new DatasetSource('GDALRasterize', geojson, [], args)));
});
}

Expand Down
19 changes: 19 additions & 0 deletions src/gdalDataType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// In order to make enums available from JS it's necessary to use embind, which seems like
// overkill for something this small. This replicates the GDALDataType enum:
// https://gdal.org/api/raster_c_api.html#_CPPv412GDALDataType and will need to be changed
// if that enum changes. There is a smoke test that should warn us if it changes upstream.
export const GDALDataTypes = [
'Unknown',
'Byte',
'UInt16',
'Int16',
'UInt32',
'Int32',
'Float32',
'Float64',
'CInt16',
'CInt32',
'CFloat32',
'CFloat64',
'TypeCount',
];
Loading

0 comments on commit dd243f2

Please sign in to comment.