0

In my Leaflet application I used to display a background layer with an ImageOverlay. But since the image is too big and slow down Leaflet processing, I switch to a tiling approach. I used gdal2tiles-leaflet to generate my tiles. It worked fine.

But now my cartesian coordinates system in which my backround layer is projected (I use Leaflet CRS Simple), is no more effective.

Here are my image specs:

  • width: 21002 px
  • height: 14694 px
  • resolution: 0.02 (1 meter = 50px)

Here are my image's bounds when using an ImageOverlay: enter image description here

Here are my image's bounds when using a TileLayer: enter image description here

And this is what it looks like when I display them together: enter image description here

Does anyone know what's going on?

Martin Tovmassian
  • 1,010
  • 1
  • 10
  • 19

1 Answers1

0

As suggested by @TomazicM I investigated the leaflet-rastercoords plugin which is complementary to gdal2tiles-leaflet. Based on it, I implemented an API which allows to convert coordinates either in a raster or in a cartesian way.

To do so I firstly make the leaflet-rastercoords code available into a single method:

export let loadLeafletRastercoordsPlugin = (L: any) => {
    // rastercoords.js source code
}

Then I wrote a class to handle coordinates conversion:

import * as L from 'leaflet';
import { loadLeafletRastercoordsPlugin } from './leaflet-rastercoords';

export class RasterCoords {

    public rc: any;
    public map: L.Map;
    public rasterWidth: number;
    public rasterHeight: number;
    public resolution: number;

    constructor(map: L.Map, rasterWidth: number, rasterHeight: number, resolution: number) {
        loadLeafletRastercoordsPlugin(L);
        this.rc = new L['RasterCoords'](map, [rasterWidth, rasterHeight]);
        this.map = map;
        this.rasterWidth = rasterWidth;
        this.rasterHeight = rasterHeight;
        this.resolution = resolution;
    }

}

With one method to project raster coordinates into the original orthonormal plane of my image, based on its width, height and resolution, and by putting Y-axis in a bottom-up way:

public project(coordinates: L.LatLngTuple): L.LatLngTuple {
    coordinates = this.applyResolution(coordinates);
    const projectedCoordinates = this.rc.project(
        coordinates
    );

    return this.applyCartesianProjection([projectedCoordinates.y, projectedCoordinates.x] as L.LatLngTuple);
}

private applyResolution(coordinates: L.LatLngTuple): L.LatLngTuple {
    return coordinates.map((v: number) => v * this.resolution) as L.LatLngTuple;
}

private applyCartesianProjection(coordinates: L.LatLngTuple): L.LatLngTuple {
    return [(this.rasterHeight * this.resolution) - coordinates[0], coordinates[1]];
}

And with one method to "unproject" the cartesian coordinates (i.e. retroprocess point by point what the project method do):

public unproject(coordinates: L.LatLngTuple): L.LatLngTuple {
    coordinates = this.unapplyResolution(this.unapplyCartesianProjection(coordinates));

    return this.rc.unproject([coordinates[1], coordinates[0]]);
}

private unapplyResolution(coordinates: L.LatLngTuple): L.LatLngTuple {
    return coordinates.map((v: number) => v / this.resolution) as L.LatLngTuple;
}

private unapplyCartesianProjection(coordinates: L.LatLngTuple): L.LatLngTuple {
    return [Math.abs(coordinates[0] - (this.rasterHeight * this.resolution)), coordinates[1]];
}

Then the API helps me to effectively add objects to my map based on their cartesian coordinates:

const imageWidth = 21002;
const imageHeight = 14694;
const imageResolution = 0.02;
const map = L.map('map');
const rc = new RasterCoords(map, imageWidth, imageHeight, imageResolution);
map.setView(rc.unproject([imageWidth, imageHeight]), 2);
L.tileLayer('./image/{z}/{x}/{y}.png', {
    noWrap: true
}).addTo(map);
new L.CircleMarker(this.rc.unproject([293, 420])).addTo(map);
Martin Tovmassian
  • 1,010
  • 1
  • 10
  • 19