Skip to content

Instantly share code, notes, and snippets.

@darrenwiens
Created March 3, 2022 00:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save darrenwiens/2efce0f9c13860399f03361cb4de4099 to your computer and use it in GitHub Desktop.
Save darrenwiens/2efce0f9c13860399f03361cb4de4099 to your computer and use it in GitHub Desktop.
FMV Example, Mapbox
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>FMV Demo</title>
<meta
name="viewport"
content="initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<link
href="https://api.mapbox.com/mapbox-gl-js/v2.7.0/mapbox-gl.css"
rel="stylesheet"
/>
<script src="https://api.mapbox.com/mapbox-gl-js/v2.7.0/mapbox-gl.js"></script>
<style>
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
let stac_api = "<STAC_API_URL>"; // your STAC API url
let stac_collection_id = "<STAC_COLLECTION_NAME>"; // your STAC collection name
let stac_item_id = "<STAC_ITEM_ID>"; // your STAC item ID
let presigned_api = "<PRESIGNED_URL_PROXY_API>"; // your presigned url proxy api
let mapbox_api_token = "<MAPBOX_API_TOKEN>"; // your Mapbox API token
let item_endpoint = `${stac_api}/collections/${stac_collection_id}/items/${stac_item_id}`;
let sync_camera = false; // if true, camera location will always be set to the sensor location
let terrain_on = false;
let frame_geom_data;
let sensor_centers;
let frame_centers;
// this function returns a POST body for an api/lambda integration
// that will return a presigned url for the given s3 key
function createPresignedRequestBody(key) {
return {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ object_key: key }),
redirect: "follow",
};
}
// fetch the STAC item json
fetch(item_endpoint)
.then((res) => res.json())
.then((stac_item_json) => {
// parse properties and hrefs from item
let start_dt = Date.parse(
stac_item_json["properties"]["start_datetime"]
);
let end_dt = Date.parse(stac_item_json["properties"]["end_datetime"]);
let totalSeconds = (end_dt - start_dt) / 1000;
let video_href = stac_item_json["assets"]["video"]["href"];
let frame_geom_href =
stac_item_json["assets"]["video:frame-geometries"]["href"];
let frame_centers_href =
stac_item_json["assets"]["video:frame-centers"]["href"];
let sensor_centers_href =
stac_item_json["assets"]["video:sensor-centers"]["href"];
let frame_geom_data, frame_centers_data, sensor_centers_data;
let frame_geom_presigned,
frame_centers_presigned,
sensor_centers_presigned,
video_presigned;
// fetch presigned urls for all of the assets (video and geojson files)
Promise.all([
fetch(presigned_api, createPresignedRequestBody(video_href))
.then((resp) => resp.text())
.then((presigned_url) => {
video_presigned = presigned_url;
}),
fetch(presigned_api, createPresignedRequestBody(frame_geom_href))
.then((resp) => resp.text())
.then((presigned_url) => {
frame_geom_presigned = presigned_url;
}),
fetch(presigned_api, createPresignedRequestBody(frame_centers_href))
.then((resp) => resp.text())
.then((presigned_url) => {
frame_centers_presigned = presigned_url;
}),
fetch(
presigned_api,
createPresignedRequestBody(sensor_centers_href)
)
.then((resp) => resp.text())
.then((presigned_url) => {
sensor_centers_presigned = presigned_url;
}),
]).then(() => {
mapboxgl.accessToken = mapbox_api_token;
// fetch the geojson files
Promise.all([
fetch(frame_geom_presigned)
.then((resp) => resp.json())
.then((asset_json) => {
frame_geom_data = asset_json;
}),
fetch(frame_centers_presigned)
.then((resp) => resp.json())
.then((asset_json) => {
frame_centers_data = asset_json;
}),
fetch(sensor_centers_presigned)
.then((resp) => resp.json())
.then((asset_json) => {
sensor_centers_data = asset_json;
}),
]).then(() => {
// create video style
const videoStyle = {
version: 8,
sources: {
satellite: {
type: "raster",
url: "mapbox://mapbox.satellite",
tileSize: 256,
},
video: {
type: "video",
urls: [video_presigned],
coordinates: frame_geom_data["features"][0]["geometry"][
"coordinates"
][0].slice(0, 4),
},
},
layers: [
{
id: "background",
type: "background",
paint: {
"background-color": "rgb(4,7,14)",
},
},
{
id: "satellite",
type: "raster",
source: "satellite",
},
{
id: "video",
type: "raster",
source: "video",
},
],
};
// create map, including video style
const map = new mapboxgl.Map({
container: "map",
zoom: 16,
center:
frame_centers_data["features"][0]["geometry"]["coordinates"],
bearing: 90,
style: videoStyle,
});
map.addControl(
new mapboxgl.NavigationControl({
visualizePitch: true,
showZoom: false,
})
);
let totalFrames = frame_geom_data["features"].length;
const camera = map.getFreeCameraOptions();
// refresh the video corner coordinates, and optionally, camera location
function sourceCallback() {
let timer_created = false;
if (map.getSource("video") && map.isSourceLoaded("video")) {
if (!timer_created) {
setInterval(function () {
updateVideoCoords();
}, 30);
function updateVideoCoords() {
let curSeconds = map.getSource("video").video.currentTime;
let curFrameIndex = parseInt(
(curSeconds / totalSeconds) * totalFrames
);
let curFrameGeometry =
frame_geom_data["features"][curFrameIndex];
map
.getSource("video")
.setCoordinates(
curFrameGeometry["geometry"]["coordinates"][0].slice(
0,
4
)
);
if (sync_camera) {
let curSensorGeometry =
sensor_centers_data["features"][curFrameIndex][
"geometry"
]["coordinates"];
let curFrameCenter =
frame_centers_data["features"][curFrameIndex][
"geometry"
]["coordinates"];
zoom = 0.5;
let zoomed_alt;
if (terrain_on) {
zoomed_alt =
zoom * (curSensorGeometry[2] - curFrameCenter[2]) +
curFrameCenter[2];
} else {
zoomed_alt =
zoom *
5 *
(curSensorGeometry[2] - curFrameCenter[2]);
}
camera.position =
mapboxgl.MercatorCoordinate.fromLngLat(
{
lng:
zoom *
(curSensorGeometry[0] - curFrameCenter[0]) +
curFrameCenter[0],
lat:
zoom *
(curSensorGeometry[1] - curFrameCenter[1]) +
curFrameCenter[1],
},
zoomed_alt
);
camera.lookAtPoint({
lng: curFrameCenter[0],
lat: curFrameCenter[1],
});
map.setFreeCameraOptions(camera);
}
}
}
timer_created = true;
}
}
// start refreshing the video
map.on("sourcedata", sourceCallback);
});
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment