Creating Maps with Google Earth Engine

Google Earth Engine (GEE) is a powerful cloud-based system for analysing massive amounts of remote sensing data. One area where Google Earth Engine shines is the ability to calculate time series of values extracted from a deep stack of imagery. While GEE is great at crunching numbers, it has limited cartographic capabilities. That’s where QGIS comes in. Using the Google Earth Engine Plugin for QGIS and Python, you can combine the computing power of GEE with the cartographic capabilities of QGIS. In this post, I will show how to write PyQGIS code to programmatically fetch time-series data, and render a map template to create an animated maps like below.

In QGIS, go to Plugins → Manage and Install Plugins and install the ‘Google Earth Engine’ plugin. After installation, you will be prompted to authenticate using your Google Earth Engine account.

I prepared a map layout using the QGIS Print Layout and saved it as a template file. Download the template ndvi_map.qpt and save it to your Desktop. Note that for each frame of the animation, we will have to extract the image data and render the template with values for date_label, ndvi_label, and season_label.

We will use the Sentinel-2 Surface Reflectance (SR) data to compute the NDVI values for the year 2019 over a farm in northern India. The concept behind extracting the time series from an image collection is nicely demonstrated in this tutorial by Nicholas Clinton. This example adapts this code for Python and adds a few enhancements to pick images where 100% of farm area is cloud-free.

Earth Engine code integrates seamlessly with PyQGIS code. You can use the Earth Engine Python API just the way you would use it elsewhere in the Python Console. Open Plugins → Python Console. Click ‘Show Editor’ button to open the built-in editor. Copy/Paste the code below and click ‘Run’ to execute the code.

import ee
from ee_plugin import Map
import os
from datetime import datetime

# Script assumes you put the ndvi_map.qpt file on your Desktop
home_dir = os.path.join(os.path.expanduser('~'))
template = os.path.join(home_dir, 'Desktop', 'ndvi_map.qpt')

geometry = ee.Geometry.Polygon([[
    [79.38757620268325, 27.45829434648896],
    [79.38834214903852, 27.459092793050313],
    [79.38789690234205, 27.459397436737895],
    [79.38718343474409, 27.458592985177017]
    ]])
farm = ee.Feature(geometry, {'name': 'farm'})
fc = ee.FeatureCollection([farm])
Map.centerObject(fc)
empty = ee.Image().byte();
outline = empty.paint(**{
  'featureCollection': fc,
  'color': 1,
  'width': 1
});
viz_params = {'bands': ['B4', 'B3', 'B2'], 'min': 0, 'max': 2000}

def maskCloudAndShadows(image):
    cloudProb = image.select('MSK_CLDPRB')
    snowProb = image.select('MSK_SNWPRB')
    cloud = cloudProb.lt(5)
    snow = snowProb.lt(5)
    scl = image.select('SCL');
    shadow = scl.eq(3) #3 = cloud shadow
    cirrus = scl.eq(10) # 10 = cirrus
    # Cloud and Snow probability less than 5% or cloud shadow classification
    mask = (cloud.And(snow)).And(cirrus.neq(1)).And(shadow.neq(1))
    return image.updateMask(mask);

def addNDVI(image):
    ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi')
    return image.addBands([ndvi])

start_date = '2019-01-01'
end_date = '2019-12-31'

collection = ee.ImageCollection('COPERNICUS/S2_SR')\
    .filterDate(start_date, end_date)\
    .map(maskCloudAndShadows)\
    .map(addNDVI)\
    .filter(ee.Filter.intersects('.geo', farm.geometry()))

def get_ndvi(image):
    stats = image.select('ndvi').reduceRegion(**{
        'geometry': farm.geometry(), 
        'reducer': ee.Reducer.mean().combine(**{
            'reducer2': ee.Reducer.count(),
            'sharedInputs': True}
            ).setOutputs(['mean', 'pixelcount']), 
        'scale': 10
        })
    ndvi = stats.get('ndvi_mean')
    pixelcount = stats.get('ndvi_pixelcount')
    return ee.Feature(None, {
        'ndvi': ndvi,
        'validpixels': pixelcount,
        'id': image.id(),
        'date': ee.Date(image.get('system:time_start')).format('YYYY-MM-dd')
      })
      

with_ndvi = collection.map(get_ndvi)
# Find how many pixels in the farm extent
max_validpixels = with_ndvi.aggregate_max('validpixels').getInfo()

def select_color(ndvi):
    ndvi_colors = {
        0.3: QColor('#dfc27d'),
        0.5: QColor('#c2e699'),
        1: QColor('#31a354')}

    for max_value, color in ndvi_colors.items():
        if ndvi < max_value:
            return color

def select_season(date_str):
    seasons = {
        'Kharif': [7, 8, 9,10],
        'Rabi': [11, 12, 1, 2, 3],
        'Summer': [4, 5, 6]
    }
    date = datetime.strptime(date_str, '%Y-%m-%d')
    month = date.month
    for season, months in seasons.items():
        if month in months:
            return season
            
features = with_ndvi.getInfo()['features']
for feature in features:
    ndvi = feature['properties']['ndvi']
    validpixels = feature['properties']['validpixels']
    date = feature['properties']['date']
    id = feature['properties']['id']
    # The following condition ensures we pick images where 
    # all pixels in the farm geometry are unmasked
    if ndvi and (validpixels == max_validpixels):
        image = ee.Image(collection.filter(
            ee.Filter.eq('system:index', id)).first())
        Map.addLayer(image, viz_params, 'Image')
        Map.addLayer(outline, {'palette': '0000FF'}, 'farm')
        project = QgsProject.instance()
        layout = QgsLayout(project)
        layout.initializeDefaults()
        with open(template) as f:
            template_content = f.read()
        doc = QDomDocument()
        doc.setContent(template_content)
        # adding to existing items
        items, ok = layout.loadFromTemplate(doc, QgsReadWriteContext(), False)
        ndvi_label = layout.itemById('ndvi_label')
        ndvi_label.setText('{:.2f}'.format(ndvi))
        ndvi_label.setFontColor(select_color(ndvi))
        
        date_label = layout.itemById('date_label')
        date_label.setText(date)
        
        season = select_season(date)
        season_label = layout.itemById('season_label')
        season_label.setText(season)

        exporter = QgsLayoutExporter(layout)
        output_image = os.path.join(home_dir, 'Desktop', '{}.png'.format(date))
        exporter.exportToImage(output_image, QgsLayoutExporter.ImageExportSettings())

The script would iterate through each image and render the template with appropriate image and data. All of the processing is done in the cloud using Google Earth Engine and only the results are streamed to QGIS. If all went well, in a few minutes, the system would have crunched through Gigabytes of data and you will have a bunch of images on your desktop. You can animate them using a program like ezgif.com

11 Comments

Leave a Comment

  1. I try this code it’s work with this coordinates when I try it in my study area it run but there’s a problem with export images in desktop
    I sent mail and attached my code
    can you help me to solve the problem
    thanks in advance

  2. Thank you so much for sharing this work! It’s really amazing.

    I have a doubt about the code: in this example a temporary monitoring is done based on a single polygon defined in line 10.
    Is it possible to iterate on the different polygons within a .shp (being this uploaded as an asset to EE?

    I think the logic in pseudocode would be to iterate over the polygon or ROI, and then apply this code to get the images and load them into Qgis.

    Then, to export each image, you would have to center the zoom and extension on it in Qgis.

    Could you please help me with this? Thanks!
    Jorge

    • You don’t even need to upload the shapefile to Earth Engine. You can open the shapefile in QGIS, select the layer and run the following code to iterate over the features.

      View formatted code

      import json
      import ee
      from ee_plugin import Map

      layer = iface.activeLayer()
      for feature in layer.getFeatures():
      geom = feature.geometry()
      coordinates = json.loads(geom.asJson())[‘coordinates’][0]
      geometry = ee.Geometry.Polygon(coordinates)
      farm = ee.Feature(geometry, {‘name’: ‘farm’})
      fc = ee.FeatureCollection([farm])
      ……

  3. Hi Ujaval. I tried to run your code in qgis 3.10LTR. It worked but nvdi outputs (PGN files) were blank in my desktop, only your template but no NDVI image :(. There is no error in running the code, is it possible?

  4. Thanks Ujaval for sharing such application. As you described the Plugin would ask for authentication but the the version 0.0.2 not asking such authentication after installation rather showing a error message such as ‘Couldn’t load plugin ‘ee_plugin’ due to an error when calling its classFactory() method…’. Do you have any idea how to resolve it?

  5. Same problem as Nestor and faten above, the program works and NDVI images are properly mappes in canvas but the output is blank. Any indication to output the images is highly welcome! Thank you in advance!

    • You probably changed the location and tried other places. When you do so, you should also change the extent of Layour map from Layout Manager, in the file you downloaded from this webpage it is set to the locations used in this map.

  6. Hello sir, Excuse me! I am having a problem in ee_plugin of QGIS with the code of
    samples = image.sampleRegions({
    ‘collection’: classes,
    ‘properties’: [‘landcover’],
    ‘scale’: 30
    }).randomColumn(‘random’)

    I will share my code to be available to figure out my problem and I hope you could help me and I am looking forward to hearing from you soon!
    #################

    import ee
    from ee_plugin import Map

    #Start importing Landsat 8 and STRM 30meters

    L8 = ee.ImageCollection(‘LANDSAT/LC08/C01/T1_SR’) #we can obtain this code from google earth engine website
    SRTM = ee.Image(‘USGS/SRTMGL1_003’)

    #1/ Set up the map
    #Center the map to the region of interest using the region shapefile
    #1.1/Create ROI in polygon
    geometry = ee.FeatureCollection(
    [ee.Feature(
    ee.Geometry.Polygon(
    [[[-59.86659594299564, 8.183991509034232],
    [-59.48207445862064, 7.988203807396915],
    [-59.11952563049564, 7.879392130916319],
    [-58.87782641174564, 7.60723863000331],
    [-58.69105883362064, 7.389391151065465],
    [-58.51527758362064, 7.324015809133201],
    [-58.49330492737064, 7.0297093172255245],
    [-58.54823656799564, 6.844309047976527],
    [-58.32851000549564, 6.844309047976527],
    [-58.16371508362064, 6.7788563614612265],
    [-57.94398852112064, 6.516957567165574],
    [-57.73524828674564, 6.28768369655626],
    [-57.69130297424564, 6.080158189079884],
    [-57.46059008362064, 6.1347780402601995],
    [-57.35072680237064, 6.036458312021658],
    [-57.31776781799564, 5.774185907777155],
    [-57.17494555237064, 5.643003818748446],
    [-56.92226000549564, 5.806976712141076],
    [-56.44984789612064, 5.796046655541718],
    [-56.42787523987064, 5.894409498190616],
    [-56.50477953674564, 6.036458312021658],
    [-57.04310961487064, 6.069233551646879],
    [-57.04310961487064, 6.178469894471565],
    [-57.27382250549564, 6.331362818143105],
    [-57.51552172424564, 6.495126416878322],
    [-57.96596117737064, 6.909752761188248],
    [-58.30653734924564, 7.313118984292941],
    [-58.55922289612064, 7.694346835655885],
    [-59.19642992737064, 8.183991509034232],
    [-59.92152758362064, 8.575276164536835],
    [-60.08632250549564, 8.36881384889053]]]),
    {
    “system:index”: “0”
    })])

    #Set up polygon of ROI (region of interest)
    Map.addLayer(geometry, {‘color’: ‘FF0000’}, ‘geometry’) #add map layer with red color shape and name” geometry”

    #2) Set up Filtered Landsat Composite
    #2.1) Cloud Masking
    #Landsat data includes a ‘pixel_qa’ band which can be used to create a function to mask clouds

    def maskClouds(image) :
    #Bits 3 and 5 are cloud shadow and cloud, respectively. from https://www.usgs.gov/core-science-systems/nli/landsat/landsat-sr-derived-spectral-indices-pixel-quality-band
    cloudShadowBitMask = ee.Number(2).pow(3).int()
    cloudsBitMask = ee.Number(2).pow(5).int()
    #Get the pixel QA band.(the pixel quality assurance)
    qa = image.select(‘pixel_qa’)
    #Both flags should be set to zero, indicating clear conditions.
    mask = qa.bitwiseAnd(cloudShadowBitMask).eq(0) and(qa.bitwiseAnd(cloudsBitMask).eq(0))
    #Return the masked image, scaled to [0, 1].
    return image.updateMask(mask).divide(10000).copyProperties(image, [‘system:time_start’])

    #2) Set up Filtered Landsat Composite
    #2.1) Cloud Masking
    #Landsat data includes a ‘pixel_qa’ band which can be used to create a function to mask clouds

    def maskClouds(image) :
    #Bits 3 and 5 are cloud shadow and cloud, respectively. from https://www.usgs.gov/core-science-systems/nli/landsat/landsat-sr-derived-spectral-indices-pixel-quality-band
    cloudShadowBitMask = ee.Number(2).pow(3).int()
    cloudsBitMask = ee.Number(2).pow(5).int()

    #Get the pixel QA band.(the pixel quality assurance)
    qa = image.select(‘pixel_qa’)

    #Both flags should be set to zero, indicating clear conditions.
    mask = qa.bitwiseAnd(cloudShadowBitMask).eq(0) and(qa.bitwiseAnd(cloudsBitMask).eq(0))

    #Return the masked image, scaled to [0, 1].
    return image.updateMask(mask).divide(10000).copyProperties(image, [‘system:time_start’])

    #2.2) Adding Spectral Indices is for mathematic equations to apply on various spectral bands
    #This function maps spectral indices for Mangrove Mapping using Landsat 8 Imagery

    def addIndicesL8 (img):
    #NDVI Normalized Difference Vegetation Index
    ndvi = img.normalizedDifference([‘B5′,’B4’]).rename(‘NDVI’)

    #NDMI Normalized Difference Mangrove Index (https://doi.org/10.1080/2150704X.2016.1195935) – Shi et al 2016 – New spectral metrics for mangrove forest identification
    ndmi = img.normalizedDifference([‘B7′,’B3’]).rename(‘NDMI’)

    #MNDWI (Modified Normalized Difference Water Index – Hanqiu Xu, 2006)
    mndwi = img.normalizedDifference([‘B3′,’B6’]).rename(‘MNDWI’)

    #SR (Simple Ratio) (B4 is red and B5 is Near Infrared NIR)
    sr = img.select(‘B5’).divide(img.select(‘B4’)).rename(‘SR’)

    #Band Ratio 54 (I check in indices https://www.indexdatabase.de/db/i-single.php?id=20 I see it is ferrous oxides)
    ratio54 = img.select(‘B6’).divide(img.select(‘B5’)).rename(‘R54’) #band 6 is Short Wave Infrared #band 5 is Near Infrared

    #Band Ratio 35
    ratio35 = img.select(‘B4’).divide(img.select(‘B6’)).rename(‘R35’)

    #GCVI green chlorphyll vegetation Index (NIR and Green)
    gcvi = img.expression(‘(NIR/GREEN)-1’,{
    ‘NIR’:img.select(‘B5’),
    ‘GREEN’:img.select(‘B3′)
    }).rename(“GCVI”)
    return img\
    .addBands(ndvi)\
    .addBands(ndmi)\
    .addBands(mndwi)\
    .addBands(sr)\
    .addBands(ratio54)\
    .addBands(ratio35)\
    .addBands(gcvi)

    #Filter Landsat data by date and region
    #Temporal parameters
    #select desire temporal year
    #year = 2019
    #startDate = (year – 1) #+’ -01-01 ‘
    #endDate= (year + 1) #+’ -12-31 ‘

    #Apply Filters and Masks to Landsat 8 Imagery
    l8 = L8.filterDate(‘2018-01-01′,’2020-12-31’)\
    .map(maskClouds)\
    .map(addIndicesL8)

    #Composite the Landsat Image Collection
    composite = l8\
    .median()\
    .clip(geometry)

    #Mask to Areas of Low Elevation and High NDVI and MNDWI
    srtmClip = SRTM.clip(geometry)
    elevationMask = srtmClip.lt(65)
    NDVIMask = composite.select(‘NDVI’).gt(0.25)
    MNDWIMask = composite.select(‘MNDWI’).gt(-0.50)
    compositeNew = composite .updateMask(NDVIMask).updateMask(MNDWIMask) .updateMask(elevationMask)

    #Display Results
    visPar = {‘bands’:[‘B5′,’B6′,’B4’], ‘min’: 0,’max’: 0.35}
    Map.addLayer(compositeNew.clip(geometry), visPar, ‘Landsat Composite 2019’)

    #Add Training Areas for Mangrove Forest
    Mangrove = ee.FeatureCollection(
    [ee.Feature(
    ee.Geometry.Polygon(
    [[[-56.818476792365914, 5.985798163972564],
    [-56.81611644843281, 5.987420053248756],
    [-56.8102799616164, 5.987420053248756],
    [-56.80787670233906, 5.9892553432012665],
    [-56.80860626319111, 5.991090626986097],
    [-56.8122969827956, 5.991560117195954],
    [-56.81276905158222, 5.990151645355214],
    [-56.81637394049824, 5.98874316988139],
    [-56.819935914070015, 5.986438383998846]]]),
    {
    “landcover”: 1,
    “system:index”: “0”
    }),
    ee.Feature(
    ee.Geometry.Polygon(
    [[[-56.80015194037617, 5.9863103400535955],
    [-56.796504136115914, 5.987035922013213],
    [-56.797748681098824, 5.988444401889661],
    [-56.7992936334914, 5.989127299916325],
    [-56.800452347785836, 5.98635302137202]]]),
    {
    “landcover”: 1,
    “system:index”: “1”
    }),
    ee.Feature(
    ee.Geometry.Polygon(
    [[[-56.80259811499775, 5.985712801245717],
    [-56.80800544837177, 5.981914146403828],
    [-56.798950310737496, 5.982298281239187]]]),
    {
    “landcover”: 1,
    “system:index”: “2”
    }),
    ee.Feature(
    ee.Geometry.Polygon(
    [[[-57.00670566043602, 5.969390611000828],
    [-57.00314368686424, 5.968536957537676],
    [-57.00177039584862, 5.974939326086341],
    [-57.00739230594383, 5.970884501361134]]]),
    {
    “landcover”: 1,
    “system:index”: “3”
    }),
    ee.Feature(
    ee.Geometry.Polygon(
    [[[-57.01285471351216, 5.961982264200126],
    [-57.009292739940385, 5.957927343638916],
    [-57.00487245948384, 5.955793162891705],
    [-57.00491537482808, 5.959976149351207],
    [-57.009807724071244, 5.963177392963958],
    [-57.00817694099019, 5.964756666264999],
    [-57.01161016852925, 5.964372519123846]]]),
    {
    “landcover”: 1,
    “system:index”: “4”
    }),
    ee.Feature(
    ee.Geometry.Polygon(
    [[[-57.488664100697946, 6.334763619561866],
    [-57.48360009007783, 6.3341238194942955],
    [-57.475231597951364, 6.333995859385617],
    [-57.47488827519746, 6.337365464998087],
    [-57.485187957814645, 6.3389009741806195],
    [-57.48862118535371, 6.335531378596004]]]),
    {
    “landcover”: 1,
    “system:index”: “5”
    }),
    ee.Feature(
    ee.Geometry.Polygon(
    [[[-57.79273811396108, 6.581631352056014],
    [-57.7922016721581, 6.579265241928807],
    [-57.79170814569936, 6.5801392118471425],
    [-57.791858349404194, 6.581482138236861],
    [-57.79248062189565, 6.582164258186938]]]),
    {
    “landcover”: 1,
    “system:index”: “6”
    }),
    ee.Feature(
    ee.Geometry.Polygon(
    [[[-57.78853241022573, 6.58190846331547],
    [-57.789347801766255, 6.57932919099936],
    [-57.787373695931294, 6.578242055680411],
    [-57.785699997506, 6.58205767700651]]]),
    {
    “landcover”: 1,
    “system:index”: “7”
    })])
    Map.addLayer(Mangrove,{“color”: “98ff00″},”Mangrove_Forest”)

    #Add Training Areas for Non-Mangrove Forest
    NonMangrove = ee.FeatureCollection(
    [ee.Feature(
    ee.Geometry.Point([-57.79338184412465, 6.584253244704557]),
    {
    “landcover”: 0,
    “system:index”: “0”
    }),
    ee.Feature(
    ee.Geometry.Point([-57.79338184412465, 6.583656392310747]),
    {
    “landcover”: 0,
    “system:index”: “1”
    }),
    ee.Feature(
    ee.Geometry.Point([-57.81085872225377, 6.603437085745056]),
    {
    “landcover”: 0,
    “system:index”: “2”
    }),
    ee.Feature(
    ee.Geometry.Point([-57.81085872225377, 6.60254184153283]),
    {
    “landcover”: 0,
    “system:index”: “3”
    }),
    ee.Feature(
    ee.Geometry.Point([-57.81042956881139, 6.601561334109304]),
    {
    “landcover”: 0,
    “system:index”: “4”
    }),
    ee.Feature(
    ee.Geometry.Point([-57.81424903444859, 6.605057047346009]),
    {
    “landcover”: 0,
    “system:index”: “5”
    }),
    ee.Feature(
    ee.Geometry.Point([-57.810687060876816, 6.598875586440279]),
    {
    “landcover”: 0,
    “system:index”: “6”
    }),
    ee.Feature(
    ee.Geometry.Polygon(
    [[[-57.826694484277695, 6.612900996902869],
    [-57.827466960473984, 6.610172680734986],
    [-57.82536410860631, 6.6073590889184475],
    [-57.82450580172154, 6.609021667835791],
    [-57.82583617739293, 6.610343200936609]]]),
    {
    “landcover”: 0,
    “system:index”: “7”
    }),
    ee.Feature(
    ee.Geometry.Polygon(
    [[[-57.83555002683317, 6.6013392506755055],
    [-57.837609963356606, 6.593750906782822],
    [-57.83542128080045, 6.590937221651177],
    [-57.831902222572914, 6.594774061052865],
    [-57.8323313760153, 6.598312453283749]]]),
    {
    “landcover”: 0,
    “system:index”: “8”
    }),
    ee.Feature(
    ee.Geometry.Polygon(
    [[[-58.02768695698738, 6.744142136969142],
    [-58.034553412065506, 6.727435440231387],
    [-58.015327337846756, 6.720616215116252],
    [-58.01429736958504, 6.74959726019283]]]),
    {
    “landcover”: 0,
    “system:index”: “9”
    }),
    ee.Feature(
    ee.Geometry.Polygon(
    [[[-58.006267008140306, 6.7770269104871295],
    [-58.00641721184514, 6.7738946617575575],
    [-58.004056867912034, 6.7734258880888465],
    [-58.00264066155217, 6.77481089988553],
    [-58.00386374886296, 6.775876290868889]]]),
    {
    “landcover”: 0,
    “system:index”: “10”
    }),
    ee.Feature(
    ee.Geometry.Polygon(
    [[[-58.014270719840745, 6.774256895644079],
    [-58.012296614005784, 6.7737668144385355],
    [-58.00922816689275, 6.772722726731655],
    [-58.00944274361394, 6.77713344920177],
    [-58.01358407433293, 6.776387677704869]]]),
    {
    “landcover”: 0,
    “system:index”: “11”
    }),
    ee.Feature(
    ee.Geometry.Polygon(
    [[[-57.22473172938481, 5.979085344036318],
    [-57.224045083876995, 5.973963493971241],
    [-57.217006967421916, 5.975500054023467],
    [-57.21760778224125, 5.980280435486353]]]),
    {
    “landcover”: 0,
    “system:index”: “12”
    }),
    ee.Feature(
    ee.Geometry.Polygon(
    [[[-57.230739877578166, 5.999486903784929],
    [-57.229538247939495, 5.9970968023253635],
    [-57.22722081935063, 5.997608967805745],
    [-57.22764997279301, 5.999486903784929],
    [-57.22945241725102, 6.00042586934887]]]),
    {
    “landcover”: 0,
    “system:index”: “13”
    })])
    Map.addLayer(NonMangrove,{“color”: “0b4a8b”},”Non_mangrove_forest”)

    #3) Construct Random Forest Model

    #3.1) Prepare training data and predictors

    #After drawing training polygons, merge them together
    classes = Mangrove.merge(NonMangrove)

    #Define the bands you want to include in the model
    bands = [‘B5′,’B6′,’B4′,’NDVI’,’MNDWI’,’SR’,’GCVI’]

    #Create a variable called image to select the bands of interest and clip to geometry
    image = compositeNew.select(bands).clip(geometry)

    #Assemble Samples for the model
    samples = image.sampleRegions({
    ‘collection’: classes,
    ‘properties’: [‘landcover’],
    ‘scale’: 30
    }).randomColumn(‘random’)

  7. Hi Ujaval

    Can we export the image or image collection to local computer? If yes, could you please provide a working example?

    • You can only export to Google Drive or Cloud Storage. To get the exported data to your local computer, you will have to download it from Google Drive. To make this seamless, I export all assets to a specific folder in drive and have the Google Drive client on my computer sync that folder. So as soon as the asset is exported, it gets downloaded.

Leave a Reply to Güray Hatipoğlu Cancel reply