Published
Edited
Nov 30, 2020
Importers
6 stars
Insert cell
md`# Vector Map

A programmable world map using MapBoxGL and ESRI vector tiles

USAGE TODO`
Insert cell
makeMap()
Insert cell
function *makeMap(center=[-74.0060, 40.7128], zoom=11) {
// Thanks https://observablehq.com/@tmcw/using-mapbox-gl-js
let container = html`<div style='height:600px; border: solid 1px gainsboro;' />`;

// Give the container dimensions.
yield container;

// Create the \`map\` object with the mapboxgl.Map constructor, referencing
// the container div
let map = new mapboxgl.Map({
container,
style: esriStyle,
center,
customAttribution: "&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors, map layer by Esri",
zoom,
});

// Wait until the map loads.
map.on("load", () => {
container.value = map;
container.dispatchEvent(new CustomEvent("input"));
});
}
Insert cell
function addImage(map, name, image) {
if (map.hasImage(name)) {
map.removeImage(name);
}
map.addImage(name, image);
}
Insert cell
function clearMap(map) {
// Clear layers/listeners
removeAllLayers(map);
removeAllListeners(map);
clearPopup(map);
}
Insert cell
function addSource(map, name, data, options={}) {
const source = map.getSource(name);
if (source != null) {
map.removeSource(name);
}
map.addSource(name, {
type: 'geojson',
data,
generateId: true,
...options,
});
}
Insert cell
function clearPopup() {
if (popup.popup != null) {
popup.popup.remove();
popup.popup = null;
}
}
Insert cell
function addListener(map, event, layer, fn) {
listeners.push([event, layer, fn]);
map.on(event, layer, fn);
}
Insert cell
function addMapListener(map, event, fn) {
mapListeners.push([event, fn]);
map.on(event, fn);
}
Insert cell
function removeAllListeners(map) {
listeners.forEach(([event, layer, fn]) => map.off(event, layer, fn));
mapListeners.forEach(([event, fn]) => map.off(event, fn));
while (listeners.length) listeners.pop();
while (mapListeners.length) mapListeners.pop();
}
Insert cell
function addLayer(map, name, properties, behind=undefined) {
if (map.getLayer(name) != null) {
// Remove the layer first if needed
map.removeLayer(name);
}
map.addLayer({id: name, ...properties}, behind);
layers[name] = true;
}
Insert cell
function removeAllLayers(map) {
Object.keys(layers).forEach(name => {
if (map.getLayer(name) != null) {
map.removeLayer(name);
}
delete layers[name];
});
}
Insert cell
Insert cell
layers = ({})
Insert cell
listeners = ([])
Insert cell
mapListeners = ([])
Insert cell
esriStyle = {
// Adapted from https://gist.github.com/jgravois/51e2b30e3d6cf6c00f06b263a29108a2
// https://esri.com/arcgis-blog/products/arcgis-living-atlas/mapping/new-osm-vector-basemap
const styleUrl = "https://www.arcgis.com/sharing/rest/content/items/3e1a00aeae81496587988075fe529f71/resources/styles/root.json";

// first fetch the esri style file
// https://www.mapbox.com/mapbox-gl-js/style-spec
const response = await fetch(styleUrl);
const style = await response.json();
// next fetch metadata for the raw tiles
const metadataUrl = style.sources.esri.url;
const metadataResponse = await fetch(metadataUrl);
const metadata = await metadataResponse.json();
function format (style, metadata, styleUrl) {
// ArcGIS Pro published vector services dont prepend tile or tileMap urls with a /
style.sources.esri = {
type: 'vector',
scheme: 'xyz',
tilejson: metadata.tilejson || '2.0.0',
format: (metadata.tileInfo && metadata.tileInfo.format) || 'pbf',
/* mapbox-gl-js does not respect the indexing of esri tiles
because we cache to different zoom levels depending on feature density, in rural areas 404s will still be encountered.
more info: https://github.com/mapbox/mapbox-gl-js/pull/1377
*/
// index: metadata.tileMap ? style.sources.esri.url + '/' + metadata.tileMap : null,
maxzoom: 15,
tiles: [
style.sources.esri.url + '/' + metadata.tiles[0]
],
description: metadata.description,
name: metadata.name
};
return style;
}
return format(style, metadata, styleUrl);
}
Insert cell
mapboxgl = {
const gl = await require("mapbox-gl@1.12.0");
const href = await require.resolve("mapbox-gl@0.49/dist/mapbox-gl.css");
document.head.appendChild(html`<link href=${href} rel=stylesheet><style>
.mapboxgl-popup {
max-width: 400px !important;
backdrop-filter: blur(3px);
}
.mapboxgl-popup-content {
background: #ffffffa3;
}
</style>`);
return gl;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more