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

Join us for an expert-led overview of the tools and concepts you'll need to become a Certified Power BI Data Analyst and pass exam PL-300. Register now.

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 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.

June 2025 community update carousel

Fabric Community Update - June 2025

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