I suspect this has something to do with MODIS data not being ingested with a polar CRS resulting in wonky transformations for low level pyramid layers. You can see in this Code Editor script that after changing the projection, high zoom level pyramid layers are complete, but at low zoom levels, high latitudes are masked. When generating the thumbnails in polar CRS, GEE is probably drawing from low zoom level pyramid layers.
You can alternatively use the GEE Python API with cartopy to reproject GEE Mercator thumbnails to a variety of projections, and then animate the frames with ffmpeg. Here is a ready-to-run Colab notebook that demonstrates it; the following is the same code, minus environment setup.
# Import packages
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import ee
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.path as mpath
import numpy as np
import os
import requests
# Initialize EE
ee.Initialize()
# Make visualization collection in Earth Engine
col = (ee.ImageCollection('MODIS/006/MOD13Q1')
.filterDate('2018-01-01', '2019-01-01'))
vis_params = {
'bands': 'NDVI',
'min': -2000,
'max': 8000,
'palette': ['black', 'blanchedalmond', '8FBC8F', '006400'],
'forceRgbOutput': True
}
aoi = {
'xmin': -180,
'ymin': 60,
'xmax': 180,
'ymax': 90
}
def vis_rgb(img):
return (img.visualize(**vis_params)
.unmask(0)
.set({'date': img.date().format('YYYY-MM-dd')}))
col_vis = (col.map(vis_rgb))
#Get thumbnails of the visualization collection downloaded locally
thumb_params = {
'dimensions': 5000,
'region': ee.Geometry.BBox(aoi['xmin'], aoi['ymin'], aoi['xmax'], aoi['ymax']),
'crs': 'EPSG:4326',
'format': 'PNG'
}
dates = col_vis.aggregate_array('date').getInfo()
frames = []
for count, date in enumerate(dates):
img_vis = col_vis.filter(ee.Filter.eq('date', date)).first()
img_url = img_vis.getThumbURL(thumb_params)
img_name = str(count).zfill(3) + '.png'
frames.append(img_name)
print(img_name)
response = requests.get(img_url)
with open(img_name, 'wb') as fd:
fd.write(response.content)
# Make a directory to hold altered images
alt_dir = 'alt'
if not os.path.exists(alt_dir):
os.mkdir(alt_dir)
# Use cartopy to change the projection of the downloaded images to North Polar Stereo, set the extent of the output plot, and clip by a circle.
def make_plot(frame):
fig = plt.figure(figsize=[8, 8])
ax = plt.axes(projection=ccrs.NorthPolarStereo())
ax.spines['geo'].set_edgecolor('none')
ax.set_extent([aoi['xmin'], aoi['xmax'], aoi['ymin'], aoi['ymax']], ccrs.PlateCarree())
ax.add_feature(cfeature.COASTLINE, edgecolor='grey')
ax.gridlines()
theta = np.linspace(0, 2*np.pi, 100)
center, radius = [0.5, 0.5], 0.5
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
circle = mpath.Path(verts * radius + center)
ax.set_boundary(circle, transform=ax.transAxes)
img = plt.imread(frame)
ax.imshow(img, extent=(aoi['xmin'], aoi['xmax'], aoi['ymin'], aoi['ymax']), origin='upper', transform=ccrs.PlateCarree())
fig.savefig(os.path.join(alt_dir, frame), bbox_inches='tight', pad_inches=0.5, facecolor='grey', edgecolor='none')
plt.close()
for frame in frames:
print(frame)
make_plot(frame)
# Make an animation from the frames
outfile = 'animation'
cmd = 'ffmpeg -framerate 10 -i alt/%03d.png -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2"'
cmd_mp4 = f'{cmd} {outfile}.mp4'
os.system(cmd_mp4)
cmd_gif = f'{cmd} {outfile}.gif'
os.system(cmd_gif)
# Download the MP4 and GIF animations
from google.colab import files
files.download(f'{outfile}.mp4')
files.download(f'{outfile}.gif')
You can use .copyProperties()
for this:
function maskS2clouds(image) {
var qa = image.select('QA60');
// Bits 10 and 11 are clouds and cirrus, respectively.
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
// Both flags should be set to zero, indicating clear conditions.
var mask = qa.bitwiseAnd(cloudBitMask).eq(0)
.and(qa.bitwiseAnd(cirrusBitMask).eq(0));
return image.updateMask(mask).divide(10000).copyProperties(image, ['system:time_start']);
}
Best Answer
When using the
expression
function you get a new image that has no properties. To copy the properties from the same image you are computing, you have to usecopyProperties
function.