Skip to content

Instantly share code, notes, and snippets.

@darrenwiens
Created March 19, 2022 00:28
Show Gist options
  • Save darrenwiens/f86c8979a5a00df44c0a0cc5a3a3abbc to your computer and use it in GitHub Desktop.
Save darrenwiens/f86c8979a5a00df44c0a0cc5a3a3abbc to your computer and use it in GitHub Desktop.
STAC assets in Blender
import bpy
import datetime
import json
import numpy as np
import requests
import rasterio
from PIL import Image
from pyproj import CRS, Transformer
url = "https://earth-search.aws.element84.com/v0/search"
animation_length = 250 # desired number of animation frames
bbox = [
-102.5185775756836,
49.99703639107218,
-102.42725372314453,
50.05493451312689,
] # [w, s, e, n]
payload = {
"bbox": bbox,
"collections": ["sentinel-s2-l2a-cogs"],
"limit": 15,
"datetime": "2018-01-01/2019-01-01",
"query": {"eo:cloud_cover": {"lt": 10, "gt": 0}},
}
headers = {"Content-Type": "application/json"}
response = requests.request("POST", url, headers=headers, data=json.dumps(payload))
resp_json = response.json()
features = []
for feature in resp_json["features"]:
if (
feature["bbox"][0] < bbox[0]
and feature["bbox"][1] < bbox[1]
and feature["bbox"][2] > bbox[2]
and feature["bbox"][3] > bbox[3]
):
href = feature["assets"]["visual"]["href"]
timestamp = int(
datetime.datetime.strptime(
feature["properties"]["datetime"], "%Y-%m-%dT%H:%M:%SZ"
).timestamp()
)
features.append(
{
"href": href,
"timestamp": timestamp,
"datestring": feature["properties"]["datetime"],
}
)
else:
print("bad feature")
sorted_features = sorted(features, key=lambda d: d["timestamp"])[:15]
start_ts = sorted_features[0]["timestamp"]
end_ts = sorted_features[-1]["timestamp"]
start_dt = datetime.datetime.fromtimestamp(int(start_ts))
end_dt = datetime.datetime.fromtimestamp(int(end_ts))
for i, feature in enumerate(
sorted_features
): # might want to limit how many images we use
with rasterio.open(feature["href"]) as src:
crs_to_wgs84 = Transformer.from_crs(src.crs, CRS("EPSG:4326"), always_xy=True)
wgs84_to_crs = Transformer.from_crs(CRS("EPSG:4326"), src.crs, always_xy=True)
# create a bbox window in the data coordinate reference system
crs_bbox = []
for pt in wgs84_to_crs.itransform([(bbox[0], bbox[1]), (bbox[2], bbox[3])]):
crs_bbox.append(pt[0])
crs_bbox.append(pt[1])
# read the window from the remote COG
bands = []
for band in range(3):
bands.append(
src.read(
band + 1,
window=rasterio.windows.from_bounds(*crs_bbox, src.transform),
)
)
bands_arr = np.dstack(bands)
frame = int(
(feature["timestamp"] - start_ts) / (end_ts - start_ts) * animation_length
)
# save images as jpeg
im = Image.fromarray(bands_arr)
im_path = f"/tmp/ts_{frame}.jpeg"
im.save(im_path)
mat_name = "images"
mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(mat_name)
mat.use_nodes = True
nodes = mat.node_tree.nodes
links = mat.node_tree.links
# Create a mix shader that will animate transparency from 1 to 0 during the appropriate animation frames
if i > 0:
mixshader = nodes.new("ShaderNodeMixShader")
mixshader.inputs[0].default_value = 1.0
mixshader.inputs[0].keyframe_insert(
data_path="default_value", frame=prev_frame
)
mixshader.inputs[0].default_value = 0.0
mixshader.inputs[0].keyframe_insert(data_path="default_value", frame=frame)
tex_image = mat.node_tree.nodes.new("ShaderNodeTexImage")
tex_image.image = bpy.data.images.load(im_path)
links.new(tex_image.outputs["Color"], mixshader.inputs[1])
links.new(prev_node.outputs[0], mixshader.inputs[2])
prev_frame = frame
prev_node = mixshader
else:
tex_image = mat.node_tree.nodes.new("ShaderNodeTexImage")
tex_image.image = bpy.data.images.load(im_path)
prev_node = tex_image
prev_frame = frame
output_node = mat.node_tree.nodes.new("ShaderNodeOutputMaterial")
links.new(prev_node.outputs[0], output_node.inputs[0])
ob = bpy.context.scene.objects["Plane"] # Apply material to an object named "Plane"
if ob.data.materials:
ob.data.materials[0] = mat
else:
ob.data.materials.append(mat)
def change_text():
text = bpy.data.objects["Text"]
current_frame = bpy.context.scene.frame_current
text.data.body = datetime.datetime.strftime(
((((end_dt - start_dt) / animation_length) * current_frame) + start_dt),
"%Y-%m-%d",
)
def text_handler(scene):
change_text()
def register():
bpy.app.handlers.frame_change_post.append(text_handler)
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment