Git Product home page Git Product logo

Comments (11)

mike-000 avatar mike-000 commented on June 24, 2024

You need to prevent excessive overzooming of the original tiles during reprojection by limiting the maxZoom of the reprojected tiles, for example

tif_layer.getSource().setTileGridForProjection('EPSG:3857', createXYZ({maxZoom: 4}))

works in your CodeSandbox.

You could attempt to calculate an appropriate setting based on tif_view.resolutions.

from openlayers.

maxschmi avatar maxschmi commented on June 24, 2024

Thanks for the quick response. At first the sugested solution looked good, but having a closer look I realized it interpolates between cells to get the pixel value.
This should absolutly not be the case, as I really want to show the grid cells value in my application.

I forked the minimal example and added @mike-000's sugested solution.
Furthermore I added a hover to show the pixels value on mouse hover. The values in the test GeoTiffs are all integers, but as openalyer interpolates the values, they become float numbers.
See the codeSandbox for the updated version.

Unfortunatly I don't really get how setTileGridForProjection and createXYZ work, so I couldn't fix this problem. I could only play around and change the values, but it never happened to work out as excpected.

You could attempt to calculate an appropriate setting based on tif_view.resolutions.

How could I calculate this?

from openlayers.

mike-000 avatar mike-000 commented on June 24, 2024

The unwanted interpolation is only seen in Firefox, see #14099. It can be fixed with a polyfill such as

/*
 * Polyfill to make Gecko browser `CanvasRenderingContext2D.drawImage()` behavior when 
 * downscaling with `imageSmoothingEnabled` set to `false` consistent with other browsers
 * (no smoothing or interpolation of pixel values) and not as updated in
 * https://github.com/mdn/content/blob/main/files/en-us/mozilla/firefox/releases/56/index.md#canvas-and-webgl
 */
(function () {

    const canvas1 = document.createElement('canvas');
    canvas1.width = 2;
    canvas1.height = 2;
    const ctx1 = canvas1.getContext('2d');
    const imageData = ctx1.createImageData(2, 2);
    imageData.data[0] = 255;
    imageData.data[3] = 255;
    imageData.data[7] = 255;
    imageData.data[11] = 255;
    imageData.data[12] = 255;
    imageData.data[15] = 255;
    ctx1.putImageData(imageData, 0, 0);
    const canvas2 = document.createElement('canvas');
    canvas2.width = 1;
    canvas2.height = 1;
    const ctx2 = canvas2.getContext('2d');
    ctx2.imageSmoothingEnabled = false;
    ctx2.drawImage(canvas1, 0, 0, 2, 2, 0, 0, 1, 1);
    const data = ctx2.getImageData(0, 0, 1, 1).data;

    if (data[0] !== 0 && data[0] !== 255) {
      // browser interpolates, polyfill is needed
      const defaultDrawImage = CanvasRenderingContext2D.prototype.drawImage;

      CanvasRenderingContext2D.prototype.drawImage = function (
        image,
        ...params
      ) {
        // Calculate scales in case workaround is needed
        let scaleX = 1;
        let scaleY = 1;

        let sx = 0;
        let sy = 0;
        let sWidth = image.width;
        let sHeight = image.height;
        let dx;
        let dy;
        let dWidth = sWidth;
        let dHeight = sHeight;

        if (!this.imageSmoothingEnabled) {
          const transform = this.getTransform();
          scaleX = Math.sqrt(
            transform.a * transform.a + transform.b * transform.b
          );
          scaleY = Math.sqrt(
            transform.c * transform.c + transform.d * transform.d
          );

          if (params.length === 2) {
            [dx, dy] = params;
          } else if (params.length === 4) {
            [dx, dy, dWidth, dHeight] = params;
          } else if (params.length === 8) {
            [sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight] = params;
          }

          scaleX *= Math.abs(dWidth / sWidth);
          scaleY *= Math.abs(dHeight / sHeight);
        }

        if (dx !== undefined && dy !== undefined && scaleX < 1 && scaleY < 1) {
          // Image reduction workaround to avoid downscaling by
          // Gecko browsers when interpolation is not required
          let toKeepX = Math.abs(sWidth);
          let toKeepY = Math.abs(sHeight);
          let sWidthFinal = toKeepX;
          let sHeightFinal = toKeepY;
          // Reduce whichever dimension needs less reduction
          // keeping at least one row or column
          if (scaleX > scaleY) {
            toKeepX = Math.max(Math.floor(scaleX * toKeepX), 1);
            sWidthFinal = Math.min(toKeepX, scaleX * sWidthFinal);
          } else {
            toKeepY = Math.max(Math.floor(scaleY * toKeepY), 1);
            sHeightFinal = Math.min(toKeepY, scaleY * sHeightFinal);
          }

          const drawImage = document.createElement('canvas');
          drawImage.width = toKeepX;
          drawImage.height = toKeepY;
          const drawContext = drawImage.getContext('2d');
          drawContext.imageSmoothingEnabled = false;
          defaultDrawImage.call(
            drawContext,
            image,
            sx,
            sy,
            sWidth,
            sHeight,
            0,
            0,
            toKeepX,
            toKeepY
          );
          defaultDrawImage.call(
            this,
            drawImage,
            0,
            0,
            sWidthFinal,
            sHeightFinal,
            dx,
            dy,
            dWidth,
            dHeight
          );

          drawImage.width = 1;
          drawImage.height = 1;
        } else {
          defaultDrawImage.call(this, image, ...params);
        }
      };
    }

})();

from openlayers.

maxschmi avatar maxschmi commented on June 24, 2024

Thanks a lot this did the deal.

from openlayers.

mike-000 avatar mike-000 commented on June 24, 2024

You could attempt to calculate an appropriate setting based on tif_view.resolutions.

How could I calculate this?

The aim would be to avoid having target (reprojected) tile grid resolutions which would result in source resolutions calculated by https://github.com/openlayers/openlayers/blob/main/src/ol/reproj.js#L104 being smaller than those available in the source.

Maybe this could/should be attempted for all reprojections? However as in https://openlayers.org/en/latest/examples/geotiff-reprojection.html what might work well at the equator may not be suitable for polar regions or vice versa.

from openlayers.

maxschmi avatar maxschmi commented on June 24, 2024

Hmm even after hours of playing around with createXYZ I couldn't get the right parameters to prevent having columns where 2 grid cells get merged. So I allways end up having some merged columns or overzooming efects as in the beginning.

grafik

Here is my actual setup: codesandbox

I tried setting maxResolution, tileSize, minZoom, maxZoom, extent parameters of the createXYZ function, but it feels more like guessing around and maybe being lucky one day. Isn't there a more systematic approach on how to calculate this? Especially as the horizontal difference in the cell width between Mercator and WGS84 in my region is not very large I think that this should somehow be possible.

from openlayers.

mike-000 avatar mike-000 commented on June 24, 2024

In your case your source has non-square-pixels i.e. the rendered aspect ratio of tiles in the tile grid does not match the 256 : 256 ratio of their data. Reprojection only produces tiles with square pixels and scales up one dimension to achieve that.

This code will maintain alignment of columns as in the unreprojected source, but the row alignment is not precise

const source = tif_layer.getSource();
const tileGrid = source.getTileGrid();
const resolutions = tileGrid.getResolutions();
const maxZoom = resolutions.length - 1;
const resolution =
  (resolutions[maxZoom] * getWidth(map_proj.getExtent())) /
  getWidth(tif_view.projection.getExtent());

const reprojTilePixelRatio = Math.max.apply(
  null,
  tileGrid.getResolutions().map((r, z) => {
    const tileSize = toSize(tileGrid.getTileSize(z));
    const textureSize = source.getTileSize(z);
    return Math.max(textureSize[0] / tileSize[0], textureSize[1] / tileSize[1]);
  })
);

source.setTileGridForProjection(
  map_proj,
  createXYZ({
    extent: transformExtent(tif_view.extent, tif_view.projection, map_proj),
    maxResolution: resolution * reprojTilePixelRatio,
    maxZoom: maxZoom,
  })
);

from openlayers.

maxschmi avatar maxschmi commented on June 24, 2024

Thanks a lot @mike-000
I think now I get the problem, that the tiles have to be somehow squares. I tested your code but the alignment offset is too big for my project. So with my tiff-layer the tiles can't be splitted into squares, unless they would be very small -> resulting in a huge TileGrid.

Thats a bit disapointing, but then maybe openlayer was not the tool to go. :-(

from openlayers.

maxschmi avatar maxschmi commented on June 24, 2024

Just to show the resulting problem, I added some Polygons of the Grid shapes, where there border should be to the CodeSandbox.
As you can see the Borders ar not well aligned:
grafik

from openlayers.

mike-000 avatar mike-000 commented on June 24, 2024

Yes, that is to be expected as your non-square pixels are being drawn as square pixels in a canvas stitch context optimised for one dimension only before reprojection. Ideally your GeoTIFF should have better resolution, e.g. instead of one tile with 256 x 256 pixels have 2560 x 2560 pixels in 100 tiles.

While OpenLayers could potentially take an option to scale up before reprojecting (e.g. drawing 256 x 256 pixels as 2560 x 2560 pixels in a larger stitch context) that would use a lot of system resources compared with tiling for which GeoTIFFs can be optimised.

from openlayers.

maxschmi avatar maxschmi commented on June 24, 2024

Oh wow. I'm not completly sure, why this works now, but this was the missing tip. Thanks @mike-000
After changing the Tiffs tiling and pixel resolution I found one setup that works perfectly: CodeSandbox

Interestingly it did only work with this specific pixel resolution (used now 4 times as many pixels as before). If I went up It sstoped working again.

from openlayers.

Related Issues (20)

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.