Search code examples
openlayersopenlayers-5

Change scale and measurement according to a projection in openlayer5


I am building a image tiles based map using openlayer where I changed the projection to a custom one instead of default EPSG. I need the scale to show the correct scale according to my distance to pixel ratio and also need the measurements according to that scale.

I changed metersPerUnit to match my need for scale but it seems to depend on the extents. 1. How exactly is extent used to in scale? 2. I can see the scale value change when moved vertically on my map which should not be the case since its a flat projection. (I have the global set as true which I assumed should have fixed that. Am I understanding something wrong about global?

Measurements are still very different even after changing metersPerUnit which I guess is because I need to change the getPointResolution function also? But I added which does not seem to be working correctly. Measurements are still different.

getPointResolution = (projection, resolution, point, opt_units) => {
        var pointResolution = resolution;
        pointResolution /= projection.getMetersPerUnit();
        return pointResolution;
    };

proj = new Projection({
      code: 'ZOOMIFY',
      units: 'pixels',
      extent: [0, 0, imgWidth, imgHeight],
      metersPerUnit: (constant_micronsperpixel * 0.000001 / imgWidth),
      global: true,
      getPointResolution: getPointProjection
    });

Another thing was, I needed to use imgWidth in metersPerUnit to correct the scale which seems to be a hack or I do not quite understand how extent is used for scale calculation. And whats really the difference between extent and globalExtent

Update: 27 May

I managed to figure some things.Extents are not needed for scale. Fixing the resolutions and implementing the correct getPointResolution for view ensure the measurements are correct along with scale. This also fixed my changing scale on moving vertically. However, the View does not query for images now. Any idea why?

Updated Code

this.getPointResolution = (resolution, point) => {
        var pointResolution = resolution;
        return pointResolution;
    }

    this.proj = new Projection({
      code: 'MORPHLE',
      units: 'm',
      metersPerUnit: 0.000001,
      global: true,
      getPointResolution: this.getPointResolution,

    });

    this.resolutions = [this.props.slide_data.uperpixel * 128,this.props.slide_data.uperpixel * 64,this.props.slide_data.uperpixel * 32, this.props.slide_data.uperpixel * 16, this.props.slide_data.uperpixel * 8, this.props.slide_data.uperpixel * 4, this.props.slide_data.uperpixel*2, this.props.slide_data.uperpixel];

    this.viewer = new Map({
      controls: [],
      target: null,
      overlays: [this.popup],
      layers: [
        new TileLayer({
          source: new XYZ({
            url: this.props.url,
            maxZoom: this.props.max_zoom - 1,
            minZoom: 0,
            wrapX: false,
            tileSize: [500, 500],
            projection: this.proj
          }),
          minResolution: (this.props.slide_data.uperpixel),
          maxResolution: (this.props.slide_data.uperpixel * 128)

        })
      ],
      view: new View({
        projection: this.proj,
        center: this.state.center,
        resolutions: this.resolutions,
        minZoom: 0,
        zoom: this.state.zoom,
        rotation: this.slideAngle
      })
    });

Images are not being queried though now as I see in Network Tab of browser.


Solution

  • After a lot of playing around, I solved my problem. Here are some insights that I learnt and are missing/hard to understand in documentation:

    1. Extent imgHeight and imgWidth needs to be the size of full resolution image i.e. in the most zoomed level X-images * Tile-X-Width and Y-Images * Tile-Y-Width.
    2. Resolutions added in TileGrid define the image url query where as resolutions in View only define the view resolutions. If they match(at some zooms), then images will be queries else the zoom will only be digital in nature.

    So, if someone wants to use a deep zoom using OpenLayer with custom projection and resolutions along with correct projection and measurements- The following would be the code:

    this.imgWidth = this.stitched_tile_width * this.x_max;
    this.imgHeight = this.tile_height * this.y_max;
    
    this.resolutions = [];
    let z_levels = this.props.slide_data.z_levels.split(",");
    (z_levels).forEach((level, index) => {
          this.resolutions.push(this.distperpixel * Math.pow(2, parseInt(level)));
    });
    
    this.resolutions = this.resolutions.reverse();
    
    this.getPointResolution = (resolution, point) => {
          var pointResolution = resolution;
          return pointResolution;
    }
    
        this.proj = new Projection({
          code: 'CUSTOM',
          units: 'pixels',
          extent: [0, 0, this.imgWidth, this.imgHeight],
          metersPerUnit: 0.000001,
          global: true,
          getPointResolution: this.getPointResolution,
        });
    
        this.layer = new TileLayer({
          extent: this.proj.getExtent(),
          source: new TileImage({
            tileGrid: new TileGrid({
              extent: this.proj.getExtent(),
              origin: [0, this.imgHeight],
              resolutions: this.resolutions,
              tileSize: [this.tile_width, this.tile_height],
            }),
            projection: this.proj,
            url: this.props.url,
            wrapX: false
          }),
        });
    
        this.viewer = new Map({
          layers: [
            this.layer
          ],
          view: new View({
            projection: this.proj,
            extent: this.proj.getExtent(),
            center: this.state.center,
            zoom: starting_zoom,
            maxResolution: this.resolutions[0],
            maxZoom: (this.resolutions.length - 1),
            rotation: this.slideAngle
          })
        });