Capturing Screenshots of Maps in Jmix

Hello Jmix Support Team,

I am currently developing a React component that allows image editing through drawing, and I am seeking to add functionality to capture screenshots of a map view. Could you please provide guidance or suggest the best method to achieve this within the Jmix framework?

Thank you for your assistance.

Best regards,
Thibaut

Hello!

I didn’t investigate this deeply, but OpenLayers has an example of exporting a map to PNG… See OpenLayers :: Map Export.

If you just need to download an image, you can copy the code from the example and invoke it in the Map context:

@ViewComponent
private GeoMap map;
@Subscribe(id = "exportMap", subject = "clickListener")
public void onExportMapClick(final ClickEvent<JmixButton> event) {
    map.getElement().executeJs(" // code from example");
}

Note that you need to slightly change the code. In the OpenLayers example, the link is already added to the page, but you need to create it, invoke click(), and then remove it.

Export map to PNG in Jmix
@ViewComponent
private GeoMap map;

@Subscribe(id = "exportMap", subject = "clickListener")
public void onExportMapClick(final ClickEvent<JmixButton> event) {
    map.getElement().executeJs("""
                const mapCanvas = document.createElement('canvas');
                const size = this.olMap.getSize();
                mapCanvas.width = size[0];
                mapCanvas.height = size[1];
                const mapContext = mapCanvas.getContext('2d');
                Array.prototype.forEach.call(
                  this.olMap.getViewport().querySelectorAll('.ol-layer canvas, canvas.ol-layer'),
                  function (canvas) {
                    if (canvas.width > 0) {
                      const opacity =
                        canvas.parentNode.style.opacity || canvas.style.opacity;
                      mapContext.globalAlpha = opacity === '' ? 1 : Number(opacity);
                      let matrix;
                      const transform = canvas.style.transform;
                      if (transform) {
                        // Get the transform parameters from the style's transform matrix
                        matrix = transform
                          .match(/^matrix\(([^\(]*)\)$/)[1]
                          .split(',')
                          .map(Number);
                      } else {
                        matrix = [
                          parseFloat(canvas.style.width) / canvas.width,
                          0,
                          0,
                          parseFloat(canvas.style.height) / canvas.height,
                          0,
                          0,
                        ];
                      }
                      // Apply the transform to the export map context
                      CanvasRenderingContext2D.prototype.setTransform.apply(
                        mapContext,
                        matrix,
                      );
                      const backgroundColor = canvas.parentNode.style.backgroundColor;
                      if (backgroundColor) {
                        mapContext.fillStyle = backgroundColor;
                        mapContext.fillRect(0, 0, canvas.width, canvas.height);
                      }
                      mapContext.drawImage(canvas, 0, 0);
                    }
                  },
                );
                mapContext.globalAlpha = 1;
                mapContext.setTransform(1, 0, 0, 1, 0, 0);
    
                var a = document.createElement('a');
                document.body.appendChild(a);
                a.download = "map.png";
                a.href = mapCanvas.toDataURL();
                a.click();
                document.body.removeChild(a);
            """);
}

If you need to get image data on the client side, for instance from a different component, you can use the same code. However, you need to find the <jmix-openlayers-map> component using document and invoke getOpenLayersMap(). This method returns the OpenLayers map instance.

Hi Roman!

Thank you for your help, we succeeded to implement this solution in our app :slight_smile:

Best regards,
Thibaut