Markers from SfMap on exported image

Hello,

I have an SfMap that I use custom images for the markers - see attached, and it works well

web.png


When I export the image to a base64 string using 

var exportString = await _mapReference?.ExportAsync(ExportType.PNG, "Map", null, false)!;

and view it - the markers do not showexported.png


5 Replies

IR Indumathi Ravi Syncfusion Team January 24, 2024 05:54 PM UTC

Hi Brad,


We are currently validating the reported scenario in the Maps component. However, we will analyze and update you with further details on January 25, 2024.



IR Indumathi Ravi Syncfusion Team January 25, 2024 07:03 PM UTC

Hi Brad,


The Syncfusion Maps component is an SVG-based component. The marker templates are used to render any HTML elements in the map. When exporting the Maps to image, we cannot export it along with marker templates. So, the cluster is not shown in the exported image. However, as a workaround, we can place the templates in a “foreignObject” element in the SVG element of the Maps for exporting it to an image. We made this change in the script file “export-map.js”, which is accessed through a JS interop method. Please find the code snippet for the same below.


Code Snippet:

[export-maps.js]

var mapsElement = function () {       

    return document.getElementById('maps');

};

 

var imageExport = function (type) {

    //..

    //..

};

var renderImages = function (canvasElement, tile, context, exportTileImg, tileElementCount, type, url) {

    var element = mapsObject.mapsElement();

    return new Promise(function (resolve, reject) {

        exportTileImg.onload = function () {

            mapsObject.exportedCount++;

            context.setTransform(1, 0, 0, 1, parseFloat(tile.style.left) + 10, parseFloat(tile.style.top) +

                (parseFloat(document.getElementById(element.id + '_tile_parent').style.top)));

            var tileBorder = 1;

            context.drawImage(exportTileImg, 0, 0, 256 + tileBorder, 256 + tileBorder);

            if (mapsObject.exportedCount === tileElementCount) {

                var svgParent_1 = document.getElementById(element.id + '_Tile_SVG_Parent');

                svgParent_1 = svgParent_1.cloneNode(true);

                var layerCollections = document.getElementById(element.id + "_LayerCollections");

                for (var i = 0; i < layerCollections.childElementCount; i++) {

                    var markerTemplateGroup = document.getElementById(element.id + "_LayerIndex_" + i + "_Markers_Template_Group");

                    for (var j = 0; j < markerTemplateGroup.childElementCount; j++) {

                        var markerIndex = parseInt(markerTemplateGroup.children[j].id.split('_MarkerIndex_')[1].split('_')[0]);

                        var dataIndex = parseInt(markerTemplateGroup.children[j].id.split('_dataIndex_')[1]);

                        var markerTemplate = document.getElementById(element.id).querySelectorAll('[id*= ' + element.id + '_LayerIndex_' + i + '_MarkerIndex_' + markerIndex + '_dataIndex_' + dataIndex + ']');

                        for (var k = 0; k < markerTemplate.length; k++) {

                            var foreign = document.createElementNS(http://www.w3.org/2000/svg, "foreignObject");

                            foreign.setAttribute("width", markerTemplate[k].getBoundingClientRect().width);

                            foreign.setAttribute("height", (markerTemplate[k].getBoundingClientRect().height));

                            foreign.setAttribute("x", markerTemplate[k].style.left);

                            foreign.setAttribute("y", markerTemplate[k].style.top);

                            foreign.innerHTML = markerTemplate[k].innerHTML;

                            svgParent_1.children[0].appendChild(foreign);

                        }

                    }

                }

                url = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent([(new XMLSerializer()).serializeToString(svgParent_1.children[0])]);

                var image_1 = new Image();

                image_1.onload = function () {

                    context.setTransform(1, 0, 0, 1, parseFloat(svgParent_1.style.left), parseFloat(svgParent_1.style.top));

                    context.drawImage(image_1, 0, 0);

                    var base64String = canvasElement.toDataURL('image/jpeg', 0.8);

                    console.log(base64String);

                    resolve(base64String);

                };

                image_1.src = url;

            }

            else {

                resolve(null);

            }

        };

    });

};

 

[Index.razor]

@using Syncfusion.Blazor.Maps;

@inject IJSRuntime JSRuntime

 

<button @onclick="ExportMap">Export</button>

<SfMaps ID="maps" @ref="maps" Height="800px" AllowImageExport="true">

        <MapsZoomSettings Enable="true" ZoomFactor="17" MouseWheelZoom="true" EnablePanning="true" MaxZoom="20">

        </MapsZoomSettings>

         <MapsCenterPosition Latitude="40.71361022997332" Longitude="-74.012822394847873" />

        <MapsLayers>

            <MapsLayer UrlTemplate=https://tile.openstreetmap.org/level/tileX/tileY.png TValue="string">

            </MapsLayer>

        <MapsLayer Type="Syncfusion.Blazor.Maps.Type.SubLayer" ShapeData='new {dataOptions ="linestringmaps.json"}' TValue="string">

                <MapsShapeSettings Fill="blue">

                    <MapsShapeBorder Width="15"></MapsShapeBorder>

                </MapsShapeSettings>

            <MapsMarkerSettings>

                <MapsMarker Visible="true" DataSource="MarkerDataSource" TValue="City">

                    <MarkerTemplate>

                        @{

                            var Data = context as City;

                            <div style="position: relative;width: 50px;height: 50px;border-radius: 50%;background-color: red;overflow: hidden;">

                                <div style="position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);color: #fff;font-size: 16px;">

                                    @Data.Value

                                </div>

                            </div>

                        }

                    </MarkerTemplate>

                </MapsMarker>

            </MapsMarkerSettings>

            </MapsLayer>

        </MapsLayers>

    </SfMaps>

 

@code {

    SfMaps maps;

    public string exportString;

    private async Task ExportMap()

    {

        exportString = await JSRuntime.InvokeAsync<string>("imageExport", "JPEG");

    }

 

}


We have created a sample to demonstrate the same and it can be downloaded from the below link.

https://www.syncfusion.com/downloads/support/directtrac/general/ze/MapsExport1162872340



You can find the script file used to export the Maps along with the marker template from the below link.

https://www.syncfusion.com/downloads/support/directtrac/general/ze/export-maps455059582


NOTE: Since the “foreignObject” element is not supported in some browsers, we do not have plans to integrate this solution into the script source of the Maps component. However, you can use this solution to meet your requirements. Furthermore, when exporting to PNG images, we faced a cropping issue in converting the HTML canvas element into an image in base64 string format. But when we add this canvas to the DOM of the web page, the tile images are drawn properly. We suspect that this cropping may be due to the conversion to an image in base64 string format. However, we have exported the Maps into an image in JPEG format with a resolution of 0.8.


Please let us know if the above sample meets your requirements and let us know if you need any further assistance.



BK Brad Knowles January 25, 2024 09:33 PM UTC

A very complex solution :) Is it possible to use .svg files as the custom image instead of .png and avoid this problem entirely? I notice that your built in cluster markers export correctly so I am guessing that they are .svg's?


Or is there a way to use one of your built in markers and insert text into it ( numbers in my case ). 


Thanks




BK Brad Knowles January 26, 2024 09:07 AM UTC

I implemented your solution with a few changes for multiple background and text colors and it seems to work well.


Thanks




HP Hemanathan Pandian Syncfusion Team January 26, 2024 11:42 AM UTC

Hi Brad,


Please find the details for your queries from the below table.

Queries

Details

Is it possible to use .svg files as the custom image instead of .png and avoid this problem entirely?

The Syncfusion Maps component renders online maps such as OpenStreetMap, Azure Map, and ESRI Map through their tile server URL, which provides tile images. These images are rendered as HTML image elements in the component. So, when exporting the component to an image, we will draw these images on the canvas and convert the canvas into an image. So, it is not possible to export the component into SVG file when online maps are rendered.

I notice that your built in cluster markers export correctly so I am guessing that they are .svg's?

Since the markers (built-in markers) are rendered using SVG shape elements such as circle, rect and path, they can be easily exported to an SVG file. So, the clusters will also be available. But the marker templates are enclosed in a div element that is placed above the Maps component to make it appear as it is above the desired location. So, it is not exported as it is not within the SVG element of the Maps component, and it can render any HTML element. As a result, the clusters for the marker templates are also not available.

is there a way to use one of your built in markers and insert text into it

We do not support adding texts instead of default markers (built-in markers) in the Maps component. The marker templates are intended to display text instead of markers.

I implemented your solution with a few changes for multiple background and text colors and it seems to work well.

Thank you for the update.


Please let us know if you need any further assistance.


Loader.
Up arrow icon