Chapter 25: Agricultural Field Edge Detection¶
This chapter provides a partial implementation of Watkins and van Niekerk [3] to delineate agriculture field boundaries in the Vaalharts Irrigation Scheme, South Africa. The full GEE code can be found here.
Functions¶
/**
* Create and aggregate Canny Edge layers for Red, Green, Blue, and Near-Infrared
* @param {ee.Image} image Sentinel-2 Level-1C image
* @param {int or float} threshold Value for edge detection magnitude
* @param {int or float} sigma Value for gaussian filter applied before edge detection
* @return {ee.Image} Aggregate Canny Edges for R/G/B/NIR
*/
function aggregate_canny_edges(image, threshold, sigma) {
// Get Canny edge for Red, Green, Blue, and NIR
var canny_red = ee.Algorithms.CannyEdgeDetector({
image: image.select('B4'), threshold: threshold, sigma: sigma
});
var canny_green = ee.Algorithms.CannyEdgeDetector({
image: image.select('B3'), threshold: threshold, sigma: sigma
});
var canny_blue = ee.Algorithms.CannyEdgeDetector({
image: image.select('B2'), threshold: threshold, sigma: sigma
});
var canny_nir = ee.Algorithms.CannyEdgeDetector({
image: image.select('B8'), threshold: threshold, sigma: sigma
});
// Aggregate R/G/B/NIR Canny edges
var canny_aggregate = canny_red.add(canny_green).add(canny_blue).add(canny_nir)
.select(['B4'], ['canny_rgbn']);
return canny_aggregate;
}
/**
* Convert raster to vector
* @param {ee.Image} raster Primary or secondary rice field candidates
* @param {ee.FeatureCollection} study_area Study area boundary
* @param {ee.Number} max_pixels Maximum number of pixels to reduce
* @param {ee.Number} raster_scale Scale in meters
* @return {ee.FeatureCollection} Rice fields coverted to vector feature
*/
function raster_to_vector(raster, study_area, max_pixels, raster_scale) {
// Handle optional parameters
max_pixels = max_pixels || 1e7;
raster_scale = raster_scale || 30;
// Convert raster to vector
return raster.reduceToVectors({
geometry: study_area,
maxPixels: max_pixels,
scale: raster_scale
});
}
/**
* Export vector as shapefile to Google Drive or to GEE Asset
* @param {ee.FeatureCollection} vector Vector that will be exported
* @param {string} output_name Name of the output file (without extension)
* @param {string} output_method Name of the export method/location ("Drive" for Google Drive, "Asset" for GEE Asset)
* @return {string} output_message Message indicating the task to run to complete the export
*/
function export_vector(vector, output_name, output_method) {
// Handle optional parameters
output_method = output_method || 'drive';
// Create variable for output message
var output_message;
if (output_method.toLowerCase() == "drive") {
// Export vectors a shapefile to Google Drive
Export.table.toDrive({
collection: vector,
description: output_name,
fileFormat: 'SHP'
});
// Assign output message
output_message = "Task ready: " + output_name + "\nPlease run task to export to Google Drive.";
} else if (output_method.toLowerCase() == "asset") {
// Export vector to GEE Asset
Export.table.toAsset({
collection: vector,
description: output_name,
assetId: output_name
});
// Assign output message
output_message = "Task ready: " + output_name + "\nPlease run task to export to GEE Asset.";
} else {
// Assign output message
output_message = "Invalid export method. Please specify 'Drive' or 'Asset'.";
}
// Return output message
return output_message;
}
/**
* Export image/raster as GeoTiff to Google Drive or to GEE Asset
* @param {ee.Image} image Image/raster that will be exported
* @param {ee.FeatureCollection} study_area Study area boundary
* @param {ee.Number} max_pixels Maximum number of pixels to reduce
* @param {ee.Number} raster_scale Scale in meters
* @param {string} output_name Name of the output file (without extension)
* @param {string} output_method Name of the export method/location ("Drive" for Google Drive, "Asset" for GEE Asset)
* @return {string} output_message Message indicating the task to run to complete the export
*/
function export_raster(raster, study_area, raster_scale, max_pixels, output_name, output_method) {
// Handle optional parameters
output_method = output_method || 'drive';
raster_scale = raster_scale || 30;
max_pixels = max_pixels || 1e7;
// Create variable for output message
var output_message;
if (output_method.toLowerCase() == "drive") {
// Export image/raster as a GeoTiff to Google Drive
Export.image.toDrive({
image: raster,
region: study_area,
scale: raster_scale,
maxPixels: max_pixels,
description: output_name
});
// Assign output message
output_message = "Task ready: " + output_name + "\nPlease run task to export to Google Drive.";
} else if (output_method.toLowerCase() == "asset") {
// Export vector to GEE Asset
Export.image.toAsset({
image: raster,
geometry: study_area,
scale: raster_scale,
maxPixels: max_pixels,
description: output_name,
assetId: output_name
});
// Assign output message
output_message = "Task ready: " + output_name + "\nPlease run task to export to GEE Asset.";
} else {
// Assign output message
output_message = "Invalid export method. Please specify 'Drive' or 'Asset'.";
}
// Return output message
return output_message;
}
Data Acquisition & Preprocessing¶
// Set geometries
var vaalharts_irrigation_scheme =
/* color: #d63000 */
/* shown: false */
ee.Geometry.Polygon(
[[[24.65986591490829, -28.09412322046305],
[24.70106464537704, -28.054136830234732],
[24.754622994986416, -28.032319800398245],
[24.78071552428329, -28.03110761344614],
[24.790328561392666, -28.004436046690174],
[24.82466083678329, -27.9862471006372],
[24.87135273131454, -27.941367915030856],
[24.883712350455166, -27.90982002972256],
[24.86860614928329, -27.866123149358096],
[24.850753366080166, -27.811477263231893],
[24.83564716490829, -27.7798915448951],
[24.817794381705166, -27.750727353214696],
[24.806808053580166, -27.710613834239876],
[24.798568307486416, -27.674134193214723],
[24.79719501647079, -27.637642367457204],
[24.78346210631454, -27.62547571967499],
[24.790328561392666, -27.59018479545873],
[24.80268818053329, -27.58288180594306],
[24.81092792662704, -27.567056993780913],
[24.75599628600204, -27.508607155782826],
[24.738143502798916, -27.514697130897716],
[24.72578388365829, -27.532965033096218],
[24.705184518423916, -27.562187361647396],
[24.69282489928329, -27.59627024850316],
[24.70106464537704, -27.615741427797115],
[24.73402362975204, -27.61209084526035],
[24.743636666861416, -27.62547571967499],
[24.70106464537704, -27.64007553470646],
[24.67909198912704, -27.659538924478802],
[24.66810566100204, -27.6887275123932],
[24.680465280142666, -27.71304537681072],
[24.70106464537704, -27.721555348922188],
[24.705184518423916, -27.745865894894138],
[24.696944772330166, -27.78839629503583],
[24.69557148131454, -27.802974318988497],
[24.71754413756454, -27.813906553629074],
[24.779342233267666, -27.886759984989332],
[24.79275012793895, -27.90218116928733],
[24.810602911142077, -27.922204424412513],
[24.81678272071239, -27.935551201379933],
[24.79206348243114, -27.945256912915184],
[24.735758550790514, -27.964665719488632],
[24.702799566415514, -27.982858306056414],
[24.669840582040514, -27.999835284900435],
[24.650614507821764, -28.014384852612626],
[24.647867925790514, -28.031356864152194],
[24.642374761728014, -28.039841866403265],
[24.61628223243114, -28.045902172596353],
[24.5977428037202, -28.050750171693185],
[24.579203375009264, -28.04165999411288],
[24.56684375586864, -28.04105395495732],
[24.568217046884264, -28.057415813567268],
[24.57851672950145, -28.067716447153607],
[24.629328497079577, -28.098006704812327]]]);
// Get collection
var sentinel2_level1c = ee.ImageCollection("COPERNICUS/S2");
// Get monthly clear Sentinel-2 Level-1C images for November 2016 - April 2017
var nov_2016 = ee.Image('COPERNICUS/S2/20161126T080252_20161126T082438_T35JKK')
.clip(vaalharts_irrigation_scheme);
var dec_2016 = ee.Image('COPERNICUS/S2/20161229T081332_20161229T083640_T35JKK')
.clip(vaalharts_irrigation_scheme);
var jan_2017 = ee.Image('COPERNICUS/S2/20170128T081201_20170128T083151_T35JKK')
.clip(vaalharts_irrigation_scheme);
var feb_2017 = ee.Image('COPERNICUS/S2/20170217T081001_20170217T083329_T35JKK')
.clip(vaalharts_irrigation_scheme);
var mar_2017 = ee.Image('COPERNICUS/S2/20170326T080001_20170326T082243_T35JKK')
.clip(vaalharts_irrigation_scheme);
var apr_2017 = ee.Image('COPERNICUS/S2/20170428T081011_20170428T083712_T35JKK')
.clip(vaalharts_irrigation_scheme);
// Display images in Console
// print(nov_2016);
// print(dec_2016);
// print(jan_2017);
// print(feb_2017);
// print(mar_2017);
// print(apr_2017);
Data Processing¶
// Aggregate Canny Edges (R/G/B/NIR for all months)
var canny_agg_nov_2016 = aggregate_canny_edges(nov_2016, 500, 1.5);
var canny_agg_dec_2016 = aggregate_canny_edges(dec_2016, 500, 1.5);
var canny_agg_jan_2017 = aggregate_canny_edges(jan_2017, 500, 1.5);
var canny_agg_feb_2017 = aggregate_canny_edges(feb_2017, 500, 1.5);
var canny_agg_mar_2017 = aggregate_canny_edges(mar_2017, 500, 1.5);
var canny_agg_apr_2017 = aggregate_canny_edges(apr_2017, 500, 1.5);
// Display Canny aggregates in Console
// print(canny_agg_nov_2016);
// print(canny_agg_dec_2016);
// print(canny_agg_jan_2017);
// print(canny_agg_feb_2017);
// print(canny_agg_mar_2017);
// print(canny_agg_apr_2017);
// Aggregate all Canny aggregates into single image
var canny_agg_all = canny_agg_nov_2016
.add(canny_agg_dec_2016)
.add(canny_agg_jan_2017)
.add(canny_agg_feb_2017)
.add(canny_agg_mar_2017)
.add(canny_agg_apr_2017);
// Display new aggregate in Console
// print(canny_agg_all);
// Mask non-edge pixels
var edges = canny_agg_all.updateMask(canny_agg_all.gt(0));
// print(edges);
// Convert edges raster to vector
var edges_vector = raster_to_vector(edges.int(), vaalharts_irrigation_scheme, 1e8);
// NOTE: Printing the edges_vector to Console or adding to the map causes a memory limit error
Data Postprocessing¶
// No data postprocessing in this lab.
Data Visualization¶
// Set map options
Map.setCenter( 24.769355, -27.768264, 11);
Map.setOptions('SATELLITE');
// Add indvidual Canny aggregates to map
Map.addLayer(canny_agg_nov_2016, {}, 'Canny 4-Band Aggregate - Nov 2016');
Map.addLayer(canny_agg_dec_2016, {}, 'Canny 4-Band Aggregate - Dec 2016');
Map.addLayer(canny_agg_jan_2017, {}, 'Canny 4-Band Aggregate - Jan 2017');
Map.addLayer(canny_agg_feb_2017, {}, 'Canny 4-Band Aggregate - Feb 2017');
Map.addLayer(canny_agg_mar_2017, {}, 'Canny 4-Band Aggregate - Mar 2017');
Map.addLayer(canny_agg_apr_2017, {}, 'Canny 4-Band Aggregate - Apr 2017');
// Add all-month aggregate to map
Map.addLayer(canny_agg_all, {}, 'Canny 4-Band Aggregate - All Months');
// Add aedges (without background to map)
Map.addLayer(edges, {palette: ['white']}, 'Canny Edges - No Background');
Data Export¶
// Export edges - Raster
var raster_export = export_raster(edges,vaalharts_irrigation_scheme, 10, 1e8, 'edges_raster');
// Export edges - Vector
var vector_export = export_vector(edges_vector, 'edges_vector');