Power BI is turning 10! Tune in for a special live episode on July 24 with behind-the-scenes stories, product evolution highlights, and a sneak peek at what’s in store for the future.
Save the dateEnhance your career with this limited time 50% discount on Fabric and Power BI exams. Ends August 31st. Request your voucher.
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: '© OSM', minZoom: 1, maxZoom: 20});
var topo = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {attribution: '© OpenTopoMap', minZoom: 1, maxZoom: 20});
var white = L.tileLayer("data: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);
}
}
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"]
}]
Check out the July 2025 Power BI update to learn about new features.
User | Count |
---|---|
6 | |
6 | |
3 | |
2 | |
2 |