javascriptleafletgis

How to get pixel position in non-geographical Leaflet map?


I have a problem, getting pixel coordinates in Leaflet. Any help would be greatly appreciated!

What I want to achieve:

I want to display an image using leaflet (no tiles - just a image, non-geographical map) and the user should have the option to click on the image, set a marker and I need the pixel position of that marker. For testing purposes, I've created a JSFiddle, that uses this image.

Displaying the image and setting the marker is not a problem. There are plenty of tutorials out there for that.

But getting that pixel position is - at least for me - somehow impossible. I've read every post / article / ... about the different methods provided by Leaflet, but still no success.

What I achieved so far:

In an image editing program I can see, that the top of this tower on the image has roughly these pixel coordinates: x=1350, y=625.

Image showing the pixel position of the top of the tower:

Image showing the pixel position of the top of the tower

So when the user clicks on the top of the tower, I get the LatLng of this point and I can set the marker. When the user does not change the zoom level, my code is actually working. Clicking on the top of the tower, I get pixel coordinates, that are correct (see the blue marker on the image, showing where i clicked, and the pixels calculated displayed on the console):

Image showing the correct calculated position, if the user didn't zoom:

Image showing the correct calculated position, if the user didn't zoom

My problem:

But when the user starts zooming, the calculated pixel coordinates are definitely wrong:

Image showing the wrong calculated position, after the user zoomed:

Image showing the wrong calculated position, after the user zoomed

My code:

I've created this JSFiddle to share my code: https://jsfiddle.net/q36f1ytu/73/

The code that handles the click event is basically this (see my JSFiddle for the whole code):

map.on("click", function (e) {
  const marker = new L.Marker(e.latlng).addTo(map)

    // Here I want to get the pixel coordinate of the image
  var imagePixelCoords = map.project(e.latlng, map.getZoom())
  console.log("imagePixelCoords: " + imagePixelCoords)
})

I am quite lost, how to calculate the pixel coordinates correctly, when the user zooms.

Thank you very much in advance!


Solution

  • Since you are using a custom CRS, use the image height and width for the bounds, set minZoom to a negative value such as -5 (here is why), and then simply use e.latlng to retrieve the image coordinates.

    But there is another caveat, x and y of an image usually starts in the top-left corner of the image, however, the coordinates in a leaflet map starts on the bottom-left. Therefore, you'll need to calculate imgHeight-lat to retrieve the correct y pixel.

    const imageUrl = 
      'https://upload.wikimedia.org/wikipedia/commons/7/7c/Kaohsiung_Skyline_2020.jpg';
    const imgWidth = 3236;
    const imgHeight = 2157;
    
    const map = L.map('map', {
      minZoom: -5,
      crs: L.CRS.Simple,
      zoomControl: false,
    });
    
    const bounds = [[0, 0], [imgHeight, imgWidth]];
    
    L.imageOverlay(imageUrl, bounds).addTo(map);
    
    map.fitBounds(bounds);
    
    map.on('click', (e) => {
    
      let {lat: y, lng: x} = e.latlng;
      
      // starting top-left instead bottom-left
      y = imgHeight - y;
    
      if (y < 0 || y > imgHeight || x < 0 || x > imgWidth) {
        console.log('outside of image');
        return;
      }
      
      console.log(x, y);
      new L.Marker(e.latlng).addTo(map)
      
    });
    

    Here is a JSFiddle.


    Addendum: This StackOverflow question is using a custom class CRSPixel to move the origin from the bottom left corner to the top left corner:

    // ...
    
    const CRSPixel = L.extend({}, L.CRS.Simple, {
      transformation: new L.Transformation(1, 0, 1, 0),
    });
    
    const map = L.map('map', {
      minZoom: -5,
      crs: CRSPixel,
      zoomControl: false,
    });
    
    // ...
    
    map.on('click', (e) => {
    
      const {lat: y, lng: x} = e.latlng;
    
      if (y < 0 || y > imgHeight || x < 0 || x > imgWidth) {
        console.log('outside of image');
        return;
      }
      
      console.log(x, y);
    
      new L.Marker(e.latlng).addTo(map)
      
    });
    

    Here is a JSFiddle with CRSPixel.