Tile Map Layers in JavaScript

How to make a tile-based maps in JavaScript with various base layers.


Plotly Studio: Transform any dataset into an interactive data application in minutes with AI. Try Plotly Studio now.

If your figure contains one or more traces of type Scattermap, Choroplethmap or Densitymap, the layout object in your figure contains configuration information for the map itself. The map is composed of various layers, of three different types.

  1. layout.map.style defines the lowest layers, also known as your "base map"
  2. The various traces in data are by default rendered above the base map (although this can be controlled via the below attribute).
  3. layout.map.layers is an array that defines more layers that are by default rendered above the traces in data (although this can also be controlled via the below attribute).

Base maps in `layout.map.style`

None of the built-in base maps require an account or an access token. The accepted values are:

  1. "white-bg" yields an empty white canvas, which makes no external HTTP requests
  2. "open-street-map" yields raster tiles from OpenStreetMap
  3. "basic", "streets", "outdoors", "light", "dark", "satellite", and "satellite-streets" yield vector tiles from Carto and ArcGIS
  4. "carto-positron", "carto-darkmatter", "carto-voyager", and their -nolabels variants yield the corresponding Carto tiles
  5. The URL of a MapLibre style definition, for any other provider. Some providers require an API key, which you append to the style URL — Stamen tiles, for example, are served by Stadia Maps and need a Stadia account
  6. A Map Style object, of the form described in the MapLibre GL JS documentation

Here is a simple map rendered with "open-street-map" tiles.

d3.csv(
	"https://raw.githubusercontent.com/plotly/datasets/master/2015_06_30_precipitation.csv",
	function(err, rows) {
		function unpack(rows, key) {
			return rows.map(function(row) {
				return row[key];
			});
		}

		var data = [
			{
				type: "scattermap",
				text: unpack(rows, "Globvalue"),
				lon: unpack(rows, "Lon"),
				lat: unpack(rows, "Lat"),
				marker: { color: "fuchsia", size: 4 }
			}
		];

		var layout = {
			dragmode: "zoom",
			map: { style: "open-street-map", center: { lat: 38, lon: -90 }, zoom: 3 },
			margin: { r: 0, t: 0, b: 0, l: 0 }
		};

		Plotly.newPlot("myDiv", data, layout);
	}
);

If you have access to your own private tile servers, or wish to use a tile server not included in the list above, the recommended approach is to set layout.map.style to "white-bg" and to use layout.map.layers with below to specify a custom base map. If you omit the below attribute when using this approach, your data will likely be hidden by fully-opaque raster tiles!

Here is an example of a map which uses a public USGS imagery map, specified in layout.map.layers, and which is rendered below the data layer.

d3.csv(
	"https://raw.githubusercontent.com/plotly/datasets/master/2015_06_30_precipitation.csv",
	function(err, rows) {
		function unpack(rows, key) {
			return rows.map(function(row) {
				return row[key];
			});
		}

var data = [
	{
		type: "scattermap",
		text: unpack(rows, "Globvalue"),
		lon: unpack(rows, "Lon"),
		lat: unpack(rows, "Lat"),
		marker: { color: "fuchsia", size: 4 }
	}
];

var layout = {
	dragmode: "zoom",
	map: {
		style: "white-bg",
		layers: [
			{
				sourcetype: "raster",
				source: ["https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}"],
				below: "traces"
			}
		],
		center: { lat: 38, lon: -90 },
		zoom: 3
	},
	margin: { r: 0, t: 0, b: 0, l: 0 }
};

Plotly.newPlot("myDiv", data, layout);
	}
);

Here is the same example, with in addition, a WMS layer from Environment Canada which displays near-real-time radar imagery in partly-transparent raster tiles, rendered above the go.Scattermap trace, as is the default.

d3.csv('https://raw.githubusercontent.com/plotly/datasets/master/2015_06_30_precipitation.csv', function(err, rows){
      function unpack(rows, key) {
          return rows.map(function(row) { return row[key]; });
		}

var data = [{
        type: 'scattermap', text: unpack(rows, 'Globvalue'),
        lon: unpack(rows, 'Lon'), lat: unpack(rows, 'Lat'),
        marker: {color: 'fuchsia', size: 4}
    }];

var layout = {
	dragmode: 'zoom',
	map: {
		style: 'white-bg',
		layers: [
			{
            "below": 'traces',
            "sourcetype": "raster",
            "source": [
                "https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}"
            ]
        },
			{
             sourcetype: "raster",
			 source: ["https://geo.weather.gc.ca/geomet/?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&BBOX={bbox-epsg-3857}&CRS=EPSG:3857&WIDTH=1000&HEIGHT=1000&LAYERS=RADAR_1KM_RDBR&TILED=true&FORMAT=image/png"]}],
		below: 'traces',
		center: {lat: 38, lon: -90}, zoom: 4},
	margin: {r: 0, t: 0, b: 0, l: 0},
	showlegend: false};

Plotly.newPlot('myDiv', data, layout);
  });
var url = "https://maplibre.org/maplibre-gl-js/docs/assets/significant-earthquakes-2015.geojson";

d3.json(url, (err, raw) => {
  var lon = raw.features.map(f => f.geometry.coordinates[0]);
  var lat = raw.features.map(f => f.geometry.coordinates[1]);
  var z = raw.features.map(f => f.properties.mag);

  var data = [
    { type: "scattermap", lon: lon, lat: lat, z: z, hoverinfo: "y" }
  ];

  var layout = {
    map: { style: "dark", zoom: 2, center: { lon: -150, lat: 60 } },
    margin: { t: 0, b: 0 }
  };

  Plotly.newPlot('myDiv', data, layout);
});