Published
Edited
Oct 6, 2022
7 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
let container = html`<div style='height:600px;' />`;

// 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,
center: [13.7964765, 25.7144583],
zoom: 2,
maxZoom: 20,
style: "mapbox://styles/mapbox/" + style,
// This is all one needs to set the projection. Values are derived from the input cell defined above.
projection: { name: projection },
scrollZoom: true,
cooperativeGestures: true
});

map.on("load", () => {
// Set the default atmosphere style
map.setFog({});

map.loadImage(
"https://docs.mapbox.com/mapbox-gl-js/assets/custom_marker.png",
(error, image) => {
if (error) throw error;
map.addImage("custom-marker", image);

map.addSource("points", {
type: "geojson",
data: gischat_twitter_user_locs
});

// Add a symbol layer
map.addLayer({
id: "points",
type: "symbol",
source: "points",
layout: {
"icon-image": "custom-marker",
"icon-size": [
"interpolate",
["linear"],
["zoom"],
0,
0.1,
3,
0.3,
5,
0.4,
7,
0.7,
10,
0.8,
12,
1
],
"icon-allow-overlap": true
// get the title name from the source's "title" property
// "text-field": ["get", "username"],
// "text-font": ["Open Sans Semibold", "Arial Unicode MS Bold"],
// "text-offset": [0, 1.25],
// "text-anchor": "top"
}
});

const popups = [];

const mouseEnter = (e) => {
//clear existing popups
popups.forEach((p) => p.remove());
popups.length = 0;
const coordinates = e.features[0].geometry.coordinates.slice();
let description = "";

e.features.forEach((f) => {
const props = f.properties;
description += `
<div style="display:flex">
<div style="flex:1; max-width:50px;">
<img src="${props.profile_image_url}" class="profile"/>
</div>
<div style="flex:1">
<strong>${props.name}</strong> <a href="https://www.twitter.com/${props.username}" target="_blank">@${props.username}</a> · <br/>
${props.location}
</div>
</div>
`;
});

// Ensure that if the map is zoomed out such that multiple
// copies of the feature are visible, the popup appears
// over the copy being pointed to.
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}

const popup = new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(description)
.addTo(map);
popups.push(popup);
};

map.on("mouseenter", "points", mouseEnter);

// Change the cursor to a pointer when the mouse is over the places layer.
map.on("mouseenter", "points", () => {
map.getCanvas().style.cursor = "pointer";
});

// Change it back to a pointer when it leaves.
map.on("mouseleave", "points", () => {
map.getCanvas().style.cursor = "";
});

if ("ontouchstart" in document.documentElement) {
// content for touch-screen (mobile) devices
map.on("click", "points", mouseEnter);
}
}
);
});

map.addControl(new mapboxgl.NavigationControl(), "top-right");

//Make it spin
// At low zooms, complete a revolution every two minutes.
const secondsPerRevolution = 90;
// Above zoom level 5, do not rotate.
const maxSpinZoom = 5;
// Rotate at intermediate speeds between zoom levels 3 and 5.
const slowSpinZoom = 3;

let userInteracting = false;
let spinEnabled = true;
let spinTimeout = 0;

function spinGlobe() {
const zoom = map.getZoom();
// console.log(autoSpin);
if (spinEnabled && !userInteracting && zoom < maxSpinZoom && autoSpin) {
let distancePerSecond = 360 / secondsPerRevolution;
if (zoom > slowSpinZoom) {
// Slow spinning at higher zooms
const zoomDif = (maxSpinZoom - zoom) / (maxSpinZoom - slowSpinZoom);
distancePerSecond *= zoomDif;
}
const center = map.getCenter();
center.lng += distancePerSecond;
// Smoothly animate the map over one second.
// When this animation is complete, it calls a 'moveend' event.
map.easeTo({ center, duration: 1000, easing: (n) => n });
setTimeout(() => {
spinGlobe();
}, 1000);
}
}

function delayedSpinGlobe() {
clearTimeout(spinTimeout);
spinTimeout = setTimeout(() => {
spinGlobe();
}, 3000);
}

// Pause spinning on interaction
map.on("mousedown", () => {
userInteracting = true;
});
map.on("zoomstart", () => {
userInteracting = true;
});

// Restart spinning the globe when interaction is complete
map.on("mouseup", () => {
userInteracting = false;
delayedSpinGlobe();
});

// These events account for cases where the mouse has moved
// off the map, so 'mouseup' will not be fired.
map.on("dragend", () => {
userInteracting = false;
delayedSpinGlobe();
});
map.on("pitchend", () => {
userInteracting = false;
delayedSpinGlobe();
});
map.on("rotateend", () => {
userInteracting = false;
delayedSpinGlobe();
});
map.on("zoomend", () => {
userInteracting = false;
delayedSpinGlobe();
});

// // When animation is complete, start spinning if there is no ongoing interaction
// map.on("moveend", () => {
// delayedSpinGlobe();
// });

//make it spin on startup
spinGlobe();

// Be careful to clean up the map's resources using \`map.remove()\` whenever?
// this cell is re-evaluated.
invalidation.then(() => map.remove());
}
Insert cell
mapboxgl = {
const gl = await require("mapbox-gl@2.10.0");
if (!gl.accessToken) {
gl.accessToken =
"pk.eyJ1Ijoic3BhdGlhbCIsImEiOiJINEMxWmw4In0.MXOfvjXYb65iOfIAKcRLbA";
const href = await require.resolve("mapbox-gl@2.10.0/dist/mapbox-gl.css");
document.head.appendChild(html`<link href=${href} rel=stylesheet>`);
}
return gl;
}
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more