mapboxgl.accessToken = "pk.eyJ1IjoibWluZHJvbmVzIiwiYSI6ImNqOWNxb2libzF5OXgycXQ0Y2tvOGhrbGMifQ.BhUb7T8FceWj45x-ikQmHg"; const earthRadius = 6371; // km const people = 7.6e9; const viewHeight = 300; // meters const horizonRatio = Math.acos(1 / (1 + viewHeight / (1000 * earthRadius))); // horizonRadius / earthRadius const POICenter = new mapboxgl.LngLat(2.2945, 48.858222); // eiffel tower const toDeg = radians => radians * 180 / Math.PI; const toRad = degrees => Math.PI * degrees / 180; const dLng = (dlat, lat) => Math.acos( (Math.cos(dlat) - Math.pow(Math.sin(lat), 2)) / Math.pow(Math.cos(lat), 2) ); const makeGeojson = coordinates => ({ type: "FeatureCollection", features: [{ type: "Feature", geometry: { type: "LineString", coordinates } }] }); const mapsStyles = { "light_v9": "mapbox://styles/mapbox/light-v9", "satellite_streets_v10": "mapbox://styles/mapbox/satellite-streets-v10", "streets_v10": "mapbox://styles/mapbox/streets-v10", } const map = new mapboxgl.Map({ container: "app", style: mapsStyles.streets_v10, center: POICenter.toArray(), zoom: 7, renderWorldCopies: false }); map.on("load", function() { map.addLayer({ "id": "humans", "type": "line", "source": { "type": "geojson", "data": makeSquare(POICenter) }, "layout": { "line-cap": "round", "line-join": "round" }, "paint": { "line-color": "red", "line-width": 2, "line-opacity": 1 } }); map.addLayer({ "id": "horizon", "type": "line", "source": { "type": "geojson", "data": makeHorizon(POICenter) }, "layout": { "line-cap": "round", "line-join": "round" }, "paint": { "line-color": "black", "line-width": 1, "line-opacity": 1 } }); ["move", "resize", "dragstart", "drag", "dragend", "zoomstart", "zoom", "zoomend" ].forEach(eventName => { map.on(eventName, () => { const center = map.getBounds().getCenter(); updateHumans(center) updateHorizon(center) }); }); }); function updateHumans(center) { map.getSource("humans").setData(makeSquare(center)); } function updateHorizon(center) { map.getSource("horizon").setData(makeHorizon(center)); } function makeSquare (center) { const peoplePerSide = Math.sqrt(people); const dLat = peoplePerSide / (2 * 1000 * earthRadius); const northLat = toRad(center.lat) + dLat; const southLat = toRad(center.lat) - dLat; const N = 10; const yStep = (northLat - southLat) / N; const coordinates = [] const sides = {west: [], east: []} for (let lat = southLat; lat <= northLat; lat += yStep) { const y = toDeg(lat); const dx = toDeg(dLng(dLat, lat)); sides.west.push([center.lng + dx, y]) sides.east.push([center.lng - dx, y]) } return geojsonLineString([ ...sides.west, ...sides.east.reverse(), sides.west[0] ]); } function makeHorizon (center) { return d3.geoCircle() .center(center.toArray()) .radius(toDeg(horizonRatio))(); } function geojsonLineString (coordinates) { return { type: "FeatureCollection", features: [{ type: "Feature", geometry: { type: "LineString", coordinates } }] }; }