|
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 |
|
} |
|
}] |
|
}; |
|
} |