diff --git a/package.json b/package.json index c1bbc34..3d6c4dd 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "dist/georaster.bundle.min.js", "browser": "./dist/georaster.browser.bundle.min.js", "unpkg": "./dist/georaster.browser.bundle.min.js", + "types": "dist/georaster.d.ts", "scripts": { "analyze": "ANALYZE_GEORASTER_BUNDLE=true npm run build", "clean": "rm -f ./dist/*", @@ -15,11 +16,13 @@ "test-all": "npm run test-dev && npm run test-prod", "test-dev": "npm run dev && GEORASTER_TEST_BUNDLE_NAME='georaster.bundle.js' node ./node_modules/.bin/mocha --reporter spec", "test-prod": "npm run build && GEORASTER_TEST_BUNDLE_NAME='georaster.bundle.min.js' node ./node_modules/.bin/mocha --reporter spec", + "test-types": "tsc test/types.ts --noEmit", "dev": "webpack --mode development --target node && webpack --mode development --target web", - "build": "npm run build:prod", + "build": "npm run build:prod && npm run build:types", "build:prod": "npm run build:prod:node && npm run build:prod:web", "build:prod:node": "webpack --mode production --target node", - "build:prod:web": "webpack --mode production --target web" + "build:prod:web": "webpack --mode production --target web", + "build:types": "cp src/index.d.ts dist/georaster.d.ts" }, "repository": { "type": "git", @@ -43,7 +46,7 @@ "dependencies": { "cross-fetch": "^3.0.4", "georaster-to-canvas": "0.2.0", - "geotiff": "1.0.0-beta.13", + "geotiff": "file://../geotiff", "geotiff-palette": "0.0.0", "threads": "^1.4.0", "tiny-worker": "^2.3.0", @@ -67,6 +70,7 @@ "mocha": "^6.2.0", "null-loader": "^4.0.1", "threads-plugin": "^1.3.1", + "typescript": "^4.3.5", "webpack": "^4.12.0", "webpack-bundle-analyzer": "^3.6.0", "webpack-cli": "^3.0.8" diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000..6699f75 --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,96 @@ +/** Typed array of data values, the basic building block of a georaster */ +type TypedArray = + | number[] + | Uint8Array + | Int8Array + | Uint16Array + | Int16Array + | Uint32Array + | Int32Array + | Float32Array + | Float64Array; + +declare function parseGeoraster( + /** raster pixel data, accepts variety of forms */ + data: object | string | Buffer | ArrayBuffer | TypedArray[][], + /** raster metadata */ + metadata?: parseGeoraster.GeorasterMetadata, + /** whether or not to print debug statements */ + debug?: boolean +): Promise; + +// Match default CJS export in index.js +export default parseGeoraster; + +// A namespace with the same name as the default export is needed to define additional type exports +// https://stackoverflow.com/a/51238234/4159809 +declare namespace parseGeoraster { + /** defines the new raster image to generate as a window in the source raster image. Resolution (cell size) is determined from this */ + export interface WindowOptions { + /** left side of the image window in pixel coordinates */ + left: number + /** top of the image window in pixel coordinates */ + top: number + /** right of the image window in pixel coordinates. Should be greater than left */ + right: number + /** bottom of the image window in pixel coordinates. Should be greater than top */ + bottom: number + /** width in pixels to make the resulting raster. Will resample and/or use overview if not same as right - left */ + width: number + /** height in pixels to make the resulting raster. Will resample and/or use overview if not same as bottom - top */ + height: number + /** method to map src raster values to result raster. Supports 'nearest' neighbor, defaults to 'bilinear' */ + resampleMethod?: string + } + + export interface Georaster { + /** raster values for one or more bands. Represented as [band, column, row] */ + values: TypedArray[][]; + /** raster height in units of projection */ + height: number; + /** raster width in units of projection */ + width: number; + /** raster height in pixels */ + pixelHeight: number; + /** raster width in pixels */ + pixelWidth: number; + /** Projection identifier */ + projection: number; + /** left boundary, in units of projection*/ + xmin: number; + /** right boundary, in units of projection */ + xmax: number; + /** bottom boundary, in units of projection */ + ymin: number; + /** top boundary, in units of projection */ + ymax: number; + /** cell value representing "no data" in raster */ + noDataValue: number; + /** number of raster bands */ + numberOfRasters: number; + /** Minimum cell value for each raster band. Indexed by band number */ + mins: number[]; + /** Maximum cell value for each raster band. Indexed by band number */ + maxs: number[]; + /** difference between max and min for each raster band. Indexed by band number */ + ranges: number[]; + /** if raster initialized with a URL, this method is available to fetch a + * specific subset or 'window' without reading the entire raster into memory. + * If the window options do not align exactly with the source image then a new + * one is generated using the resampleMethod. The best available overview will + * also be used if they are available. */ + getValues?: (options: WindowOptions) => Promise; + /** experimental! returns a canvas picture of the data. */ + toCanvas: (options: { height?: number; width?: number }) => ImageData + } + + export type GeorasterMetadata = Pick< + Georaster, + | "noDataValue" + | "projection" + | "xmin" + | "ymax" + | "pixelWidth" + | "pixelHeight" + >; +} diff --git a/test/types.ts b/test/types.ts new file mode 100644 index 0000000..f799c79 --- /dev/null +++ b/test/types.ts @@ -0,0 +1,83 @@ +// Internal test of Typescript types. Imports from src not dist. +// downstream TS users of geotiff should simply import the geotiff library and will get types along with the dist build + +import { assert } from "console"; +import parseGeoraster from "../src"; +import { countIn2D } from "../src/utils"; + +// Floating point number values + +const values = [ + [ + [0, 1, 2], + [0, 0, 0], + [2, 1, 1] + ] +]; + +const noDataValue = 3; +const projection = 4326; +const xmin = 10; // left +const ymax = 13; // top +const pixelWidth = 1; +const pixelHeight = 1; +const metadata = { + noDataValue, + projection, + xmin, + ymax, + pixelWidth, + pixelHeight, +}; + +parseGeoraster(values, metadata).then(georaster => { + const values = georaster.values + console.log('number raster values', values) + assert(values.length === 1) // single band + assert(values[0].length === 3) + values[0].forEach(row => assert(Array.isArray(row))) // Should be standard javascript Array type + assert(values[0][0][2] === 2) +}); + +//// Unsigned 8-bit integer values + +const unsignedValues = values.map(band => + band.map(row => new Uint8Array(row)) +); + +parseGeoraster(unsignedValues, metadata).then(georaster => { + const values = georaster.values + console.log('unsigned 8-bit int raster values', values) + assert(values.length === 1) // single band + assert(values[0].length === 3) + values[0].forEach(row => assert(typeof row === 'object')) // Typed arrays in Javascript are not of Array type + assert(values[0][0][2] === 2) // But they do behave like arrays for read access and return numbers +}); + +/// COG test + +const raster_url = "https://landsat-pds.s3.amazonaws.com/c1/L8/024/030/LC08_L1TP_024030_20180723_20180731_01_T1/LC08_L1TP_024030_20180723_20180731_01_T1_B1.TIF"; +parseGeoraster(raster_url).then(georaster => { + try { + const options = { + left: 0, + top: 0, + right: 4000, + bottom: 4000, + width: 10, + height: 10 + }; + if (!georaster.getValues) throw new Error('georaster configured without URL') + georaster.getValues(options).then(values => { + const numBands = values.length; + const numRows = values[0].length; + const numColumns = values[0][0].length; + + // checking histogram for first and only band + const histogram = countIn2D(values[0]); + assert(histogram[0] === 39) + }); + } catch (error) { + console.error('error:', error); + } +}); \ No newline at end of file