Created
March 19, 2022 00:28
-
-
Save darrenwiens/f86c8979a5a00df44c0a0cc5a3a3abbc to your computer and use it in GitHub Desktop.
STAC assets in Blender
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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