GeoTIFF / geoblaze

Blazing Fast JavaScript Raster Processing Engine
http://geoblaze.io
MIT License
181 stars 28 forks source link

Support Cloud Optimized GeoTIFFs #89

Open DanielJDufour opened 6 years ago

DanielJDufour commented 6 years ago

Use GeoRaster's getValues function that will be implemented in the future once geotiff.js supports reading COG's.

DanielJDufour commented 6 years ago

Approach could be to use GeoRaster to just get pixels inside a bounding box and then run intercept calculations if necessary. It could look like:

let values = georaster.getValues({xmin: -100, xmax: -90, ymin: 10, ymax: 20});
CarlQLange commented 5 years ago

Is there any update on this?

DanielJDufour commented 5 years ago

Hi, Carl. COG support has been added to GeoRaster, but not yet to GeoBlaze. However, adding COG support to GeoBlaze shouldn't be too hard. I believe the only updates would be updating get.module.js to run the getValues method of GeoRaster instead of looking for the values in a values property as well as writing a test of course.

Later on, you could add in a resolution parameter that would allow a user to specify the resolution of the values used for the calculations.

Want to work on it?

CarlQLange commented 5 years ago

identify also needs to use getValues instead of values, I noticed.

Yes, I have a fork already where I started work, but unfortunately I don't have quite enough of an understanding about how lat/long gets mapped to windows and so on. But I'll give it another go in the next few days and try it out - this would be really great functionality!

Do you have any other notes that I might find useful while trying to do this?

DanielJDufour commented 5 years ago

Unfortunately, there aren't a lot of great resources out there for your question. I'll try my best to summarize. GeoTIFFs are like any image file JPG, PNG, or regular TIFFs in that the pixels are represented in two dimensional tables (rows and columns of pixels). When locating something in a pixel you usually start at the top left. If you ever use the old MS Paint, and it showed you the pixel location when you hovered over a location in your image, that's the same thing. And that's the location information that you pass in and what geotiff.js uses "under the hood". GeoTIFF metadata tells us where on earth (often in lat/long) the top left of an image is and the width/height of each pixel in degrees (or sometimes meters). We can calculate where a pixel in an image is in relation to the earth (lat/long) by calculating the number of pixels from top edge and left edge it is and then using the distance of each pixel to figure it. I hope that helps a little.

twelch commented 3 years ago

In the meantime, or as a precursor, here's a technique I'm playing with to use georaster directly to fetch a smaller window from a larger COG based on the bbox of a polygon. The smaller raster then gets passed to the geoblaze function. Feedback welcome.

I'm using a 7.5GB raster with categorical data, 10 meter pixels, that has been reprojected to 4326. I'm keeping full image resolution here for accuracy, I suspect if I increased the resolution (pixel size) of the window it would automatically use the larger overviews of the COG.

  const local_raster_url = "http://127.0.0.1:8080/my_cog.tif";
  console.time("parse");
  const georaster = await parseGeoraster(local_raster_url);
  console.timeEnd("parse");

  const bboxToPixel = (bbox: number[], georaster: any) => {
    return {
      left: Math.floor((bbox[0] - georaster.xmin) / georaster.pixelWidth),
      bottom: Math.floor((georaster.ymax - bbox[1]) / georaster.pixelHeight),
      right: Math.floor((bbox[2] - georaster.xmin) / georaster.pixelWidth),
      top: Math.floor((georaster.ymax - bbox[3]) / georaster.pixelHeight),
    };
  };

  // test polygon
  const poly = {
    type: "Feature",
    geometry: {
      type: "Polygon",
      coordinates: [
        [
          [72.80315092590186, 7.085649871759048],
          [72.80538252380232, 7.086714560376918],
          [72.80692747619494, 7.086757147870481],
          [72.80868700530874, 7.084542592987691],
          [72.80731371429309, 7.082200263746901],
          [72.80611208465437, 7.082285439564114],
          [72.80293634918067, 7.083009433375327],
          [72.80315092590186, 7.085649871759048],
        ],
      ],
    },
    properties: {},
  };

  const polyBbox = bbox(poly);
  const window = bboxToPixel(polyBbox, georaster);

  const options = {
    left: window.left,
    top: window.top,
    right: window.right,
    bottom: window.bottom,
    width: window.right - window.left,
    height: window.bottom - window.top,
    resampleMethod: "nearest",
  };
  console.log("options", options);

  if (georaster.getValues) {
    console.time("read");
    const values = await georaster.getValues(options);
    console.timeEnd("read");
    console.time("reparse");
    const noDataValue = 0;
    const projection = 4326;
    const xmin = polyBbox[0]; // left
    const ymax = polyBbox[3]; // top
    const pixelWidth = georaster.pixelWidth;
    const pixelHeight = georaster.pixelHeight;
    const metadata = {
      noDataValue,
      projection,
      xmin,
      ymax,
      pixelWidth,
      pixelHeight,
    };

    const windowRaster = await parseGeoraster(values, metadata);
    console.timeEnd("reparse");

    const histogram = geoblaze.histogram(windowRaster, poly, {
      scaleType: "nominal",
    })[0];
    console.log("histogram", histogram);
  }
};

Output with raster served on localhost (over the Internet is slower as expected):

parse: 32.611ms
190067
30142
options {
  left: 6270,
  top: 2970,
  right: 6408,
  bottom: 3079,
  width: 138,
  height: 109,
  resampleMethod: 'nearest'
}
read: 69.812ms
reparse: 0.787ms
histogram { '1': 2281, '6': 58 }