Skip to main content
cancel
Showing results for 
Search instead for 
Did you mean: 

Enhance your career with this limited time 50% discount on Fabric and Power BI exams. Ends August 31st. Request your voucher.

Reply
HerveB
Regular Visitor

using Leaflet in power Bi with pbiviz API 5.3.0

I am trying to configure Leaflet to work with pbiviz but it fails to load the tiles due to CSP policy "Refused to load the image '<URL>' because it violates the following Content Security Policy directive: "default-src <URL> data: blob: 'unsafe-inline' 'unsafe-eval'". Note that 'img-src' was not explicitly set, so 'default-src' is used as a fallback."

 

All examples of leaflet implementation on github use earlier API. Has anyone got a minimal reprex using latest API and latest Leaflet. Or alternative, is there anyway to set the CSP with pbiviz to allow the tiles to load? 

 

All works excepts tile loading.

 

I am running the visual in debug mode

 

 

 

 

{
    "visual": {
        "name": "pbiBubble2",
        "displayName": "pbiBubble2",
        "guid": "pbiBubble29A693D8F658E46768CC5CB9E2D6716F6",
        "visualClassName": "Visual",
        "version": "1.0.0.0",
        "description": "",
        "supportUrl": "",
        "gitHubUrl": ""
    },
    "apiVersion": "5.3.0",
    "author": {
        "name": "name",
        "email": "name@gmail.com"
    },
    "assets": {
        "icon": "assets/icon.png"
    },
    "style": "style/visual.less",
    "capabilities": "capabilities.json",
    "dependencies": null,
    "stringResources": [],
    "version": "1.0.0.0"
}
"use strict";

import "./../style/visual.less";

import "./../node_modules/leaflet/dist/leaflet.css";

import powerbi from "powerbi-visuals-api";
import { FormattingSettingsService } from "powerbi-visuals-utils-formattingmodel";
import * as d3 from "d3";


import { ArrayLike, color, Color, ColorCommonInstance, ColorFactory, ColorSpaceObject, csvParseRows, D3ZoomEvent, hierarchy, HierarchyCircularNode, HierarchyNode, Numeric, scaleOrdinal, ScaleThreshold, ZoomBehavior, ZoomTransform } from "d3";
type Selection<T extends d3.BaseType> = d3.Selection<T, any, any, any>;

import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions;
import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions;
import IVisual = powerbi.extensibility.visual.IVisual;
import ISelectionManager = powerbi.extensibility.ISelectionManager;

import DataView = powerbi.DataView;

import { VisualFormattingSettingsModel } from "./settings";

import * as L from "leaflet";

export class Visual implements IVisual {
    private target: HTMLElement; // Root element
    private map: L.Map;
    private Tooltip: Selection<HTMLElement>;   // Tooltip
    private svgroot: Selection<SVGElement>;    // svg
    private graph: Selection<SVGElement>;      // Graph group

    private formattingSettings: VisualFormattingSettingsModel;
    private formattingSettingsService: FormattingSettingsService;
    private selectionManager: ISelectionManager;

    constructor(options: VisualConstructorOptions) {
        console.log('Visual constructor', options);
        this.formattingSettingsService = new FormattingSettingsService();
        this.target = options.element;

        
        // Fill the target element with the Leaflet map
        this.target.style.width = "100%";
        this.target.style.height = "100%";

        // Base maps
        var Esri = L.tileLayer("https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", {attribution: 'ESRI', minZoom: 1, maxZoom: 20}); 
        var osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {attribution: '&copy; OSM', minZoom: 1, maxZoom: 20});
        var topo = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {attribution: '&copy; OpenTopoMap', minZoom: 1, maxZoom: 20});
        var white = L.tileLayer("data&colon;image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAAA1BMVEX///+nxBvIAAAAH0lEQVQYGe3BAQ0AAADCIPunfg43YAAAAAAAAAAA5wIhAAAB9aK9BAAAAABJRU5ErkJggg==");
        var localm = L.tileLayer('./basemaps//{z}/{x}/{y}.png', {tms: false, attribution: 'Local Map'});
        // Overlay layers
        var lyr = L.tileLayer('./basemaps/local_tiles/{z}/{x}/{y}.png', {tms: true, opacity: 8.0, attribution: "Local Map Layer"});
        
        var basemaps = {"OpenStreetMap": osm, "Esri Map": Esri, "OpenTopoMap":topo, "Without background": white, "Local": localm}
        var overlaymaps = {"Local": lyr}
        
        if (typeof document !== "undefined") {
            this.map = L.map(this.target, {
                center: [-32.3800067213876, 150.94998609762376],
                crs:L.CRS.EPSG3857,
                zoom: 13,
                minZoom: 1,
                maxZoom: 20,
                layers: [topo]});
                
            L.control.layers(basemaps, overlaymaps, {collapsed: true}).addTo(this.map);
            // Fit to overlay bounds (SW and NE points with (lat, lon))
            //this.map.fitBounds([[-32.4500134427752, 151.04997219524756], [-32.31, 150.84999999999997]]);        
        }


    }

    public update(options: VisualUpdateOptions) {
        this.formattingSettings = this.formattingSettingsService.populateFormattingSettingsModel(VisualFormattingSettingsModel, options.dataViews[0]);

        console.log('Visual update', options);
        if (this.svgroot) {
            this.svgroot.append('text').html('Here we go').attr('x',50).attr('y',50);
        }
    }

    /**
     * Returns properties pane formatting model content hierarchies, properties and latest formatting values, Then populate properties pane.
     * This method is called once every time we open properties pane or when the user edit any format property. 
     */
    public getFormattingModel(): powerbi.visuals.FormattingModel {
        return this.formattingSettingsService.buildFormattingModel(this.formattingSettings);
    }
}

 

 

 

 

1 REPLY 1
HerveB
Regular Visitor

After lot of head scratching, I found a workaround that is to set the WebAccess tag in capabilities.json. Though this does not change the img-src tag of the CSP policy and may not be the most secure way to load tiles from an external source. The settings I used are as below:

    "privileges": [{
        "name": "WebAccess",
        "essential": true,
        "parameters": [ "https://*.opentopomap.org", "https://*.openstreetmap.org", "https://*.arcgisonline.com", "https://*.stadiamaps.com"]
    }]

  

Helpful resources

Announcements
July 2025 community update carousel

Fabric Community Update - July 2025

Find out what's new and trending in the Fabric community.

July PBI25 Carousel

Power BI Monthly Update - July 2025

Check out the July 2025 Power BI update to learn about new features.

Join our Fabric User Panel

Join our Fabric User Panel

This is your chance to engage directly with the engineering team behind Fabric and Power BI. Share your experiences and shape the future.