Giter Club home page Giter Club logo

geowarp's Introduction

geowarp

Super Low-Level Raster Reprojection and Resampling Library

install

npm install -S geowarp

usage

const geowarp = require("geowarp");
const proj4 = require("proj4-fully-loaded");

const result = geowarp({
  // control the level of console log output
  // set debug_level to zero to turn off console logging
  debug_level: 2,

  // reproject from an [x, y] point in the input spatial reference system
  // to an [x, y] point in the output spatial reference system
  forward: proj4("EPSG:" + in_srs, "EPSG:3857").forward,

  // reproject from an [x, y] point in the output spatial reference system
  // to an [x, y] point in the input spatial reference system
  inverse: proj4("EPSG:" + in_srs, "EPSG:3857").inverse,

  // two-dimensional array of pixel data organized by band
  // usually [ r, g, b ] or [ r, g, b, a ]
  // pixel data for each band is usually flattened,
  // so the end of one row is immediately followed by the next row
  in_data: [
    [0, 123, 123, 162, ...],
    [213, 41, 62, 124, ...],
    [84, 52, 124, 235, ...]
  ],

  // bounding box of input data (in_data)
  // in [xmin, ymin, xmax, ymax] format
  in_bbox: [ -122.51, 40.97, -122.34, 41.11 ],

  // layout of the in_data using xdim layout syntax
  // see: https://github.com/danieljdufour/xdim
  in_layout: "[band][row,column]",

  // a number or array of numbers
  in_no_data: -99,

  // a number or string representing the spatial reference system of the input data
  // could be 4326 or "EPSG:4326"
  in_srs: 4326,

  // only necessary when in_data is skewed or rotated
  // 6-parameter geotransform using the order from https://gdal.org/tutorials/geotransforms_tut.html
  in_geotransform: [337934.48363, -0.142999, -0.576775, 7840518.4648, -0.57677, 0.14299],

  // how many pixels wide the input data is
  in_width: 1032,

  // how many pixels tall the input data is
  in_height: 1015,

  // optional array of constructor names for each array level
  // default is to use Array (untyped) for everything
  out_array_types: ["Array", "Array", "Uint8Array"],

  // optional array for sampling and/or changing band order
  // array is the order of the bands in the output with numbers
  // indicating the band index in the input (starting at zero)
  // for example, [13, 12, 11] skips the first 11 bands,
  // and takes the 12th, 13th, and 14th in reverse order
  out_bands: [13, 12, 11],

  // bounding box of output
  // this is the space that you want to paint
  // in same format as in_bbox
  out_bbox: [-13638811.83098057, 5028944.964938315, -13619243.951739563, 5028944.964938315],

  // optional
  // single or multi-dimensional array that will hold the output
  out_data: [[[[],[],[],...],[[],[],[],...],[[],[],[],...],[[],[],[],...],...]],

  // optional, default is null
  // the no data value for your output data
  out_no_data: -99,

  // layout of the result using xdim layout syntax
  // see: https://github.com/danieljdufour/xdim
  out_layout: "[row][column][band]",

  // a number or string representing the spatial reference system of the input data
  // could be 4326 or "EPSG:4326"
  out_srs: 3857,

  // optional
  // number of bands in the output
  out_pixel_depth: 3,

  // height of the output image in pixels
  out_height: 256,

  // width of the output image in pixels
  out_width: 256,

  // horizontal and vertical resolution
  // resolution of [0.25, 0.25] (i.e. 25%) means that you sample once for every 4 x 4 pixels
  // this is useful if you need your output to be a certain height or width (like 256 x 256)
  // but don't necessarily want to render data at that high resolution
  out_resolution: [0.5, 0.5],

  // method for sampling pixels
  // current supported methods are:
  // "near", "vectorize", "near-vectorize", "bilinear", "max", "mean", "median", "min", "mode", "mode-max", "mode-mean", "mode-median", and "mode-min"
  // you can also pass in a custom function that takes in ({ values }) and returns a number
  method: 'median',

  // round output pixel values to closest integer
  // do this if you will convert your output to a PNG or JPG
  round: true,

  // optional
  // the lowest possible pixel value considering the bit-depth of the data
  // this is used to speed up the min and mode-min resampling
  // if in_data is an array of typed arrays, this will be automatically calculated 
  theoretical_min: 0,

  // optional
  // the highest possible pixel value considering the bit-depth of the data
  // this is used to speed up the max and mode-max resampling
  // if in_data is an array of typed arrays, this will be automatically calculated 
  theoretical_max: 255,

  // optional
  // band math expression that maps a pixel from the read bands to the output
  // if expr is async (i.e. returns a promise), geowarp will return a promise
  expr: ({ pixel }) => {
    // clamp values above 100
    return pixel.map(value => Math.min(value, 100));
  },

  // optional
  // whether to insert or skip null values
  // "skip" - default, don't insert null values
  // "insert" - try to insert null values into output data
  insert_null_strategy: "skip",

  // optional
  // array of band indexes to read from
  // use this if your expr function only uses select bands
  read_bands: [0, 1, 2],
  
  // optional
  // polygon or multi-polygons defining areas to cut out (everything outside becomes no data)
  // terminology taken from https://gdal.org/programs/gdalwarp.html
  cutline: geojson,
  
  // if you specify a cutline, this is required
  cutline_srs: 4326,
  
  // function to reproject [x, y] point from cutline_srs to out_srs
  // required if you specify a cutline and the cutline is a different srs than out_srs,
  cutline_forward: proj4("EPSG:4326", "EPSG:3857").forward,

  // optional, default is "outside"
  // cut out the pixels "inside" or "outside" the cutline
  // in other words, if your cutline_strategy is "inside",
  // geowarp will set every pixel inside your cutline geometry to out_no_data
  cutline_strategy: "inside",

  // optional, default is false
  // enable experimental turbocharging via proj-turbo
  turbo: true,

  // completely optional and not recommended
  // you don't want this in most cases
  // over-ride the default function for inserting data into the output multidimensional array
  // useful if writing to an alternative object like a canvas
  insert_pixel: ({ row, column, pixel }) => {
    context.fillStyle = toColor(pixel);
    context.fillRect(column, row, 1, 1);
  },

  // completely optional and not recommended in most cases
  // take pixel values for a given sample located by sample row and column
  // and insert into the output multidimensional array
  // by default, this will call insert_pixel
  insert_sample: ({ row, column, pixel }) => {
    const [xmin, ymin, xmax, ymax] = scalePixel([column, row], [x_scale, y_scale]);
    for (let y = ymin; y < ymax; y++) {
      for (let x = xmin; x < xmax; x++) {
        insert_pixel({ row: y, column: x, pixel });
      }
    }
  },

  // skip writing a pixel if "any" or "all" its values are no data
  // default is undefined, meaning don't skip no data values
  skip_no_data_strategy: "any"
});

result is

{
  // a multi-dimensional array of pixel values with the structure defined by out_layout
  data: [
    [ [...], [...], [...] ], // band 1
    // ...
  ]
}

geowarp's People

Contributors

danieljdufour avatar tomerigal avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

lp249839965

geowarp's Issues

cache functions

useful if functions need to make a call to another thread

perhaps, turn off by default for performance reasons and add flag for it like
{
...
cache_functions: true
...
}

bilinear resampling method outputs a distorted image

Hi,
I have found that the bilinear resampling method outputs a distorted image.
I have included a comparison image (left: using geotiff.js, right: using geotiff-tile) to demonstrate the issue.

(open in new window)
image

Test script to reproduce (using geotiff-tile):

<!DOCTYPE html>
<html>
  <head>
    <script src="./node_modules/chroma-js/chroma.min.js"></script>
    <script src="./node_modules/flug/index.js"></script>
    <script src="./node_modules/geotiff/dist-browser/geotiff.js"></script>
    <script src="./dist/web/geotiff-tile.min.js"></script>
    <script>
      window.process = {
        env: {
          // TEST_NAME: "pulling tile from 3-band Geographic GeoTIFF as layout [row][column][band]"
        }
      }
    </script>
  </head>
  <body>
    <script>
      function three_to_four_bands({ tile, height, width }) {
        const data = new Array(4 * height * width).fill(255);
        for (let b = 0; b <= 2; b++) {
          for (let r = 0; r < height; r++) {
            for (let c = 0; c < width; c++) {
              data[(r * 4 * width) + (4 * c) + b] = tile[b][r][c];
            }
          }
        }
        return data;
      }

      function displayTile({ tile, height, width }) {
        const data = Uint8ClampedArray.from(tile);
        const imageData = new ImageData(data, width, height);
        const canvas = document.createElement("CANVAS");
        canvas.height = imageData.height;
        canvas.width = imageData.width;
        const context = canvas.getContext("2d");
        context.webkitImageSmoothingEnabled = false;
        context.mozImageSmoothingEnabled = false;
        context.imageSmoothingEnabled = false;
        context.putImageData(imageData, 0, 0);
        context.webkitImageSmoothingEnabled = false;
        context.mozImageSmoothingEnabled = false;
        context.imageSmoothingEnabled = false;
        document.body.appendChild(canvas);
        canvas.style.background = "darkred";
        canvas.style.border = "5px solid chartreuse";
        canvas.style.margin = "10px";
        // canvas.style.height = "512px";
      }

      test("resampling", async ({ eq }) => {
        const geotiff = await GeoTIFF.fromUrl("./ndvi2.tiff");
        const scale = 5;
        const _image = await geotiff.getImage();
        const rasters = await geotiff.readRasters({
          window: [0, 0, _image.getWidth(), _image.getHeight()],
          interleave: true,
          width: _image.getWidth() * scale,
          height: _image.getHeight() * scale,
          resampleMethod: "bilinear"
        });
        
        displayTile({ tile: rasters, height: _image.getHeight() * scale, width: _image.getWidth() * scale });

        const { height, width, tile } = await geotiff_tile.createTile({
          debug_level: 0,
          geotiff,
          bbox: _image.getBoundingBox(),
          bbox_srs: _image.geoKeys.ProjectedCSTypeGeoKey,
          method: "bilinear",
          round: true,
          tile_height: _image.getHeight() * scale,
          tile_array_types_strategy: "auto",
          tile_srs: _image.geoKeys.ProjectedCSTypeGeoKey,
          tile_width: _image.getWidth() * scale,
          timed: true
        });
        
        eq(tile[0].constructor.name, "Uint8Array");
        const data = new Uint8Array(tile[0].length * 4);

        for (let i = 0; i < tile[0].length; i++) {
          data[i * 4] = tile[0][i];
          data[i * 4 + 1] = tile[1][i];
          data[i * 4 + 2] = tile[2][i];
          data[i * 4 + 3] = tile[3][i];
        }


        displayTile({ tile: data, height: _image.getHeight() * scale, width: _image.getWidth() * scale });
      });
    </script>
  </body>
</html>

Add forward and inverse as params

So if you don't specify the out_bbox, it can use forward to reproject the in_bbox to out_srs

reproject will be deprecated and aliased to inverse

enable proj4js objects / wkt

Maybe users should be able to pass in projection information for in_srs and out_srs instead of providing forward and inverse. Pro would be wouldn't need to rpc back up to main thread if running in a worker (without another library intercepting requests). Con would be increased maintenance cost and complexity. Not sure if this should be handled by a plugin/decorator or not.

expr returning null, undefined, or NaN strategy

if an expr returns null or undefined or NaN, we should follow a defined strategy

expr_nullish_strategy
"skip" - just skip inserting
"no-data" - replace with out_no_data if available and proceed as normal
"none" - don't do anything, pass along value to everything else

take proj-turbo to the next level

might be able to use proj-turbo to discover simple affine translation/scale between image points (i.e. [I, J]) in input data to image points in output data

warp from whole earth in 3857 to whole earth in 4326

{
    "cutline_srs": 4326,
    "debug_level": 9,
    "in_data": [...],
    "in_bbox": [
        -20057076.25595305,
        -10058531.080539025,
        20057076.18809704,
        12640208.839021027
    ],
    "in_geotransform": [
        -20057076.25595305,
        39135.75848200009,
        0,
        12640208.839021027,
        0,
        -39135.75848200009
    ],
    "in_layout": "[band][row,column]",
    "in_srs": 3857,
    "in_width": 1025,
    "in_height": 580,
    "method": "near-vectorize",
    "out_array_types": [
        "Array",
        "Array",
        "Uint8Array"
    ],
    "out_bbox": [
        -180,
        -90,
        180,
        90
    ],
    "out_height": 180,
    "out_layout": "[band][row][column]",
    "out_no_data": null,
    "out_resolution": [
        1,
        1
    ],
    "out_srs": 4326,
    "out_width": 360,
    "round": true,
    "theoretical_max": 255,
    "theoretical_min": 0,
    "turbo": false
}

add mask as option

mask should probably use dufour-peyton intersection algorithm

would also need mask_srs and mask_forward to project from mask_srs to out_srs

Output Viewport Does Not Match out_bbox

I am trying to warp this GeoTiff into EPSG:4326 to generate a tile for Mapbox.

input

Parsing with georaster seems to give the expected result:

  height: 570,
  width: 501,
  pixelHeight: 56,
  pixelWidth: 56,
  xmin: 450278,
  xmax: 478334,
  ymax: 1891090,
  ymin: 1859170,

When I geowarp() with these params, I get a correctly sized result that looks like the projection of a small piece of the top left corner of the source raster:

  const georaster = await parseGeoraster('https://nassgeodata.gmu.edu/webservice/nass_data_cache/CDL_2008_clip_20220323194315_1211782049.tif')

  const options = {
    left: 0,
    top: 0,
    right: georaster.width,
    bottom: georaster.height,
    width: georaster.width,
    height: georaster.height,
  }

  const values = await georaster.getValues(options)

  const result = geowarp({
    debug_level: 2,
    forward: albersConicalEqualArea.inverse,
    inverse: albersConicalEqualArea.forward,
    in_data: values[0],
    in_bbox: [xmin, ymin, xmax, ymax],
    in_layout: '[row][column,band]',
    in_pixel_depth: 1,
    in_srs: ALBERS_CONICAL_EQUAL_AREA,
    in_width: georaster.pixelWidth,
    in_height: georaster.pixelHeight,
    out_bbox: [west, south, east, north],
    out_layout: '[row][column,band]',
    out_srs: 4326,
    out_height: 256,
    out_width: 256,
    method: 'near',
    round: false,
    theoretical_min: 0,
    theoretical_max: 255,
  })

The requested output bbox is a direct projection of the bbox in the source tiff:

[geowarp] out_xmin: -90.703125
[geowarp] out_ymin: 39.6395375643667
[geowarp] out_xmax: -90.3515625
[geowarp] out_ymax: 39.909736234537185

The result is the correct size, but does not appear to match the requested output bbox:
temp

The result looks to be a projected version of a small, top left piece of the input tiff:
Screen Shot 2022-03-23 at 7 04 12 PM

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.