I am a beginner in Google Earth Engine code and am trying to apply the SLC-gap code to Landsat 7 Surface Reflectance images. Using resources available on StackOverflow, I generated the code below; however, when I bring the images into QGIS, there still appear to be gaps. Is my code incorrect or did I not properly apply it to the images?
First, I masked the clouds based on the pixel_qa band of Landsat SR data:
var cloudMaskL7 = function(image) {
var qa = image.select('pixel_qa');
var cloud = qa.bitwiseAnd(1 << 5)
.and(qa.bitwiseAnd(1 << 7))
.or(qa.bitwiseAnd(1 << 3));
Then, I removed edge pixels that don't occur in all bands:
var mask2 = image.mask().reduce(ee.Reducer.min());
return image.updateMask(cloud.not()).updateMask(mask2);
};
var l7 = ee.ImageCollection('LANDSAT/LE07/C01/T1_SR')
.filterDate('2004-09-15', '2004-12-31')
.map(cloudMaskL7);
var visParams = {
bands: ['B3', 'B2', 'B1'],
min: 0,
max: 3000,
gamma: 1.4,
};
Map.setCenter(36.197, 31.701,7);
Map.addLayer(l7.median(), visParams);
Then, I mapped the function over one year of Landsat 7 TOA data and took the median and mapped it for Jordan.
var composite = l7.map(cloudMaskL7)
.median();
Map.setCenter(36.124, 31.663);
Map.addLayer(composite, {bands: ['B4', 'B3', 'B2'], max: 0.3});
Then, I tried to fill the SLC Landsat 7 gaps by applying the USGS L7 Phase-2 Gap filling protocol, using a single kernel size.
var MIN_SCALE = 1/3;
var MAX_SCALE = 3;
var MIN_NEIGHBORS = 144;
var GapFill = function(src, fill, kernelSize) {
var kernel = ee.Kernel.square(kernelSize * 30, 'meters', false);
var common = src.mask().and(fill.mask());
var fc = fill.updateMask(common);
var sc = src.updateMask(common);
Then, I found the primary scaling factors with a regression and interleaved the bands for the regression (assumes the bands have the same names).
var regress = fc.addBands(sc);
regress = regress.select(regress.bandNames().sort());
var fit = regress.reduceNeighborhood(ee.Reducer.linearFit().forEach(src.bandNames()), kernel, null, false);
var offset = fit.select('.*_offset');
var scale = fit.select('.*_scale');
Then, I found the secondary scaling factors using just means and stddev.
var reducer = ee.Reducer.mean().combine(ee.Reducer.stdDev(), null, true);
var src_stats = src.reduceNeighborhood(reducer, kernel, null, false);
var fill_stats = fill.reduceNeighborhood(reducer, kernel, null, false);
var scale2 = src_stats.select('.*stdDev').divide(fill_stats.select('.*stdDev'));
var offset2 = src_stats.select('.*mean').subtract(fill_stats.select('.*mean').multiply(scale2));
var invalid = scale.lt(MIN_SCALE).or(scale.gt(MAX_SCALE));
scale = scale.where(invalid, scale2);
offset = offset.where(invalid, offset2);
I applied the scaling and mask off pixels that didn't have enough neighbors.
var count = common.reduceNeighborhood(ee.Reducer.count(), kernel, null, true, 'boxcar');
var scaled = fill.multiply(scale).add(offset)
.updateMask(count.gte(MIN_NEIGHBORS));
return src.unmask(scaled, true);
};
var source = ee.ImageCollection('LANDSAT/LE07/C01/T1_SR');
var fill = ee.ImageCollection('LANDSAT/LE07/C01/T1_SR');
I loaded a table of boundaries and filter.
var Jordan = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017')
.filter(ee.Filter.or(
ee.Filter.eq('country_co', 'JO')));
var clippedJordan = composite.clipToCollection(Jordan);
I displayed the results for Jordan; however, the SLC gaps appear not to be filled. I proceed to calculate the MSAVI2 values using these images, so the remaining gaps influence the results.
var mc = Map.setCenter(36.274, 31.682, 6);
var visParams = {bands: ['B3', 'B2', 'B1']};
Map.addLayer(clippedJordan, visParams, 'clipped composite');
Any advice would be greatly appreciated!
I am not up on the latest Landsat 7 gap-filling technology for SLC-off imagery, but here is a greatly simplified version of what you were trying to do. I removed a lot of (unnecessary?) stuff, increased the kernel size by a lot, and increased the timeframe over which the median replacement is generated. It might get close to what you want:
var cloudMaskL7 = function(image) {
var qa = image.select('pixel_qa');
var cloud = qa.bitwiseAnd(1 << 5)
.and(qa.bitwiseAnd(1 << 7))
.or(qa.bitwiseAnd(1 << 3));
var mask2 = image.mask().reduce(ee.Reducer.min());
return image.updateMask(cloud.not()).updateMask(mask2);
};
var l7 = ee.ImageCollection('LANDSAT/LE07/C01/T1_SR')
.map(cloudMaskL7);
var kernelSize = 10;
var kernel = ee.Kernel.square(kernelSize * 30, 'meters', false);
var GapFill = function(image) {
var start = image.date().advance(-1, 'year');
var end = image.date().advance(1, 'year');
var fill = l7.filterDate(start, end).median();
var regress = fill.addBands(image);
regress = regress.select(regress.bandNames().sort());
var fit = regress.reduceNeighborhood(ee.Reducer.linearFit().forEach(image.bandNames()), kernel, null, false);
var offset = fit.select('.*_offset');
var scale = fit.select('.*_scale');
var scaled = fill.multiply(scale).add(offset);
return image.unmask(scaled, true);
};
// TESTING CODE
var point = ee.Geometry.Point(36.124, 31.663);
Map.centerObject(point, 11);
var check = ee.ImageCollection('LANDSAT/LE07/C01/T1_SR')
.filterBounds(point)
.filterDate('2004-09-15', '2004-12-31');
var checkImage = ee.Image(check.first());
var visParams = {bands: ['B4', 'B3', 'B2'], min: 200, max: 5500};
Map.addLayer(checkImage, visParams, 'source');
// Test composite.
var checkStart = checkImage.date().advance(-1, 'year');
var checkEnd = checkImage.date().advance(1, 'year');
var composite = l7.filterDate(checkStart, checkEnd).median();
Map.addLayer(composite, visParams, 'median');
// Rough implementation for comparison.
var replaced = checkImage.unmask(composite);
Map.addLayer(replaced, visParams, 'simple');
// Fancy implementation.
var filled = ee.Image(check.map(GapFill).first());
Map.addLayer(filled, visParams, 'filled');
EDIT: The answer now shows how to map this over a collection. Watch out, because I don't know how well this will scale. If you decide to map it over a big area or long time series, you've been warned.
Related
I'm not that new to using Flash, but I mostly used it for making animations and I don't really use actionscript much but this time I wanted to try making layouts by code.
I'm trying to position square movieclips to form a grid like menu.
I positioned the each of the movieclips all in code but I think perhaps there's a better and more efficient way of doing this.
The code is very basic but I'll post it anyway so you have an image of what I'm trying to make it look like. I'm not really good at explaining so, I apologize.
//1st row//
var btn1:MovieClip = new dBtn();
btn1.x = -210;
btn1.y = -90;
addChild(btn1);
var btn2:MovieClip = new dBtn();
btn2.x = btn1.x+70;
btn2.y = btn1.y;
addChild(btn2);
var btn3:MovieClip = new dBtn();
btn3.x = btn2.x+70;
btn3.y = btn2.y;
addChild(btn3);
//2nd row//
var btn4:MovieClip = new dBtn();
btn4.x = btn1.x;
btn4.y = btn1.y+70;
addChild(btn4);
var btn5:MovieClip = new dBtn();
btn5.x = btn4.x+70;
btn5.y = btn4.y;
addChild(btn5);
var btn6:MovieClip = new dBtn();
btn6.x = btn5.x+70;
btn6.y = btn5.y;
addChild(btn6);
//3rd row//
var btn7:MovieClip = new dBtn();
btn7.x = btn4.x;
btn7.y = btn4.y+70;
addChild(btn7);
var btn8:MovieClip = new dBtn();
btn8.x = btn7.x+70;
btn8.y = btn7.y;
addChild(btn8);
var btn9:MovieClip = new dBtn();
btn9.x = btn8.x+70;
btn9.y = btn8.y;
addChild(btn9);
Loops + simple math.
var buttonsList:Array = new Array;
for (var i:int = 0; i < 9; i++)
{
// You can omit () with "new" operator if there are no arguments.
var aBut:MovieClip = new dBtn;
// Value of i % 3 goes 0 1 2 0 1 2 0 1 2.
aBut.x = -210 + 70 * (i % 3);
// Value of int(i / 3) goes 0 0 0 1 1 1 2 2 2.
aBut.y = -90 + 70 * int(i / 3);
addChild(aBut);
buttonsList[i] = aBut;
}
Then to address each one of them you can use their indices from 0 to 8 respectively:
// Make the central one semi-transparent.
buttonsList[4].alpha = 0.5;
I am trying to rasterize some SVG data to a PNG and it is not working. Could someone please tell me what I am doing wrong?
This code does not seem to have any data in the BitmapData object.
var color:uint = Math.floor( (Math.random() * 0xFFFF00) + 0x0000FF);
var graphic:Graphic = new Graphic();
graphic.graphics.beginFill(color);
var pathData:String = "M 0 0 L 0 40 L 40 40 L 40 40 Z";
var path:Path = new Path();
path.data = pathData;
path.x =0;
path.y=0;
path.width = 40;
path.height = 40;
path.stroke=new SolidColorStroke(100);
path.fill=new SolidColor(100);
path.winding = GraphicsPathWinding.EVEN_ODD;
graphic.addElement(path);
graphic.width = 40;
graphic.height = 40;
graphic.validateNow();
var FillColor = 0x00000000;
var bitMapData:BitmapData = new BitmapData(graphic.width,graphic.height, true, FillColor);
bitMapData.draw(graphic);
But this code does:
var graphic:Graphic = new Graphic();
graphic.graphics.beginFill(color);
var width:Number = Math.floor(Math.random() * (MAXWIDTH-MINWIDTH)) + MINWIDTH;
var height:Number = Math.floor(Math.random() * (MAXHEIGHT-MINHIEGHT)) + MINHIEGHT;
var radius:Number = Math.floor( (Math.random()*(MAXRADIUS-MINRADIUS)))+MINRADIUS;
width = height = radius*2;
graphic.graphics.drawCircle(radius, radius,radius );
graphic.graphics.endFill();
var FillColor = 0x00000000;
var bitMapData:BitmapData = new BitmapData(graphic.width,graphic.height, true, FillColor);
bitMapData.draw(graphic);
if I do:
var temp:Graphic = new Graphic();
temp.graphics.beginFill(0x000000);
temp.graphics.drawRect(0,0,width/2, height/2);
temp.graphics.endFill();
sprite.graphics.drawRect(0,0,width, height);
sprite.addElement(temp);
both rectangles draw on canvas, but
BitMapData.draw(sprite);
only shows the toplevel sprite.
So I figured it out. Paths use BeforeDraw(), Draw(), and EndDraw(), which performs the fill and stroke operations. The problem is that these functions dont get called until the path gets rendered on the canvas. So, I extended my path class and over-rode the EndDraw() function. In this function I dispatched an event. Then, when I catch the event I can get the DisplayObject from the path (which is now filled in) and pass that object into BitmapData().
I need to convert this function to v3 API. This function is used to create an ellipse under a marker when selected. I have tried with projection and overlay, but without success.
function makePolyPoints(_marker)
{
var polyPoints = Array();
var markerPoint= _marker.getLatLng();
var projection = G_NORMAL_MAP.getProjection();
var mapZoom = map.getZoom();
var clickedPixel = projection.fromLatLngToPixel(markerPoint, mapZoom);
var ellipseRadA = 20;
var ellipseRadB = 10;
var polyNumSides = 20;
var polySideLength = 18;
for (var a = 0; a<(polyNumSides+1); a++) {
var aRad = polySideLength*a*(Math.PI/180);
var pixelX = clickedPixel.x + ellipseRadA * Math.cos(aRad);
var pixelY = -3 + clickedPixel.y + ellipseRadB * Math.sin(aRad);
var polyPixel = new GPoint(pixelX,pixelY);
var polyPoint = projection.fromPixelToLatLng(polyPixel,mapZoom);
polyPoints.push(polyPoint);
}
return polyPoints;
}
Here the function for v3 that doesn't work, i think a problem of zoom level but I can't find how to replace projection.fromLatLngToPixel(markerPoint, mapZoom);
function makePolyPoints(_marker)
{
var polyPoints = Array();
var markerPoint= _marker.getPosition();
var projection = map.getProjection();
var mapZoom = map.getZoom();
var clickedPixel = projection.fromLatLngToPoint(markerPoint);
var ellipseRadA = 20;
var ellipseRadB = 10;
var polyNumSides = 20;
var polySideLength = 18;
for (var a = 0; a<(polyNumSides+1); a++) {
var aRad = polySideLength*a*(Math.PI/180);
var pixelX = clickedPixel.x + ellipseRadA * Math.cos(aRad);
var pixelY = -3 + clickedPixel.y + ellipseRadB * Math.sin(aRad);
var polyPixel = new google.maps.Point(pixelX,pixelY);
var polyPoint = projection.fromPointToLatLng(polyPixel);
polyPoints.push(polyPoint);
}
return polyPoints;
}
Hmm.. Im going to use one of my favorite gmap examples to help you out.
Using var clickedPixel = projection.fromLatLngToPoint(markerPoint); dont give you yet clickedPixel coordinates, they are still world coordinates. See projection specs.
Specifying own mercator projection when calculating that kind of stuff is useful. Look closely to following lines: (in this code example)
var worldCoordinate = projection.fromLatLngToPoint(chicago);
var pixelCoordinate = new google.maps.Point(
worldCoordinate.x * numTiles,
worldCoordinate.y * numTiles);
(you can view source with firebug or similar).
If you dont have it (in mozilla: Tools -> web developer -> page source)
Hopefully, this will help you get through the problem :)
I have an array (say 'origA') which contains 20 values and also another array (say "itemA" with only 1 value in it. I need to push any 10 random values of "origA" into "itemA". But i cannot push a same value which is already pushed into "itemA".
How can we do this?
You can create a copy of origA and remove from it the items you add to itemA:
Non optimized version:
var origA:Array = [1, 2, 3, 4, 5, 6, 7];
var itemA:Array = [0];
var copyA:Array = origA.concat();
var N:int = 10;
var n:int = Math.min(N, copyA.length);
for (var i:int = 0; i < n; i++) {
// Get random value
var index:int = Math.floor(Math.random() * copyA.length);
var value:int = copyA[index];
// Remove the selected value from copyA
copyA.splice(index, 1);
// Add the selected value to itemA
itemA.push(value);
}
trace(itemA);
//0,1,7,2,6,4,3,5
Optimized version (no calls to length, indexOf, splice or push inside the loop):
var origA:Array = [1, 2, 3, 4, 5, 6, 7];
var itemA:Array = [0];
var copyA:Array = origA.concat();
var copyALength:int = copyA.length;
var itemALength:int = itemA.length;
var N:int = 10;
var n:int = Math.min(N, copyALength);
for (var i:int = 0; i < n; i++) {
// Get random value
var index:int = Math.floor(Math.random() * copyALength);
var value:int = copyA[index];
// Remove the selected value from copyA
copyA[index] = copyA[--copyALength];
// Add the selected value to itemA
itemA[itemALength++] = value;
}
trace(itemA);
//0,2,5,7,4,1,3,6
Edit1: If your original array has only a few items, use my first version or any other solution in the other answers. But if it may have thousands items or more, then I recommend you use my optimized version.
Edit:2 Here is the time taken to copy 1,000 randomly chosen items from an array containing 1,000,000 items:
All other versions: 2000ms
Optimized version: 12ms
Optimized version without cloning the original array: 1ms
// Define how many random numbers are required.
const REQUIRED:int = 10;
// Loop until either the original array runs out of numbers,
// or the destination array reaches the required length.
while(origA.length > 0 && itemA.length < REQUIRED)
{
// Decide on a random index and pull the value from there.
var i:int = Math.random() * origA.length;
var r:Number = origA[i];
// Add the value to the destination array if it does not exist yet.
if(itemA.indexOf(r) == -1)
{
itemA.push(r);
}
// Remove the value we looked at this iteration.
origA.splice(i, 1);
}
Here's a real short one. Remove random items from the original array until you reach MAX, then concat to the target Array:
const MAX:int = 10;
var orig:Array = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
var target:Array = [];
var tmp:Array = [];
var i : int = -1;
var len : int = orig.length;
while (++i < MAX && len > 0) {
var index:int = int( Math.random()*len );
tmp[i] = orig[index];
orig[index] = orig[--len];
}
target = target.concat(tmp);
EDIT
Adopted #sch's way of removing items. It's his answer that should be accepted. I just kept this one for the while-loop.
I have a MovieClip holding an irregular shape such as this one:
I need to generate a random point on this shape.
I can use brute force by generating points within the bounding box and then hitTesting to see if they reside on the irregular shape. However, I'm sure there's a more efficient way to tackle this problem.
What is the most efficient way to generate a random point on an irregular shape?
You mentioned hitTest, but I assume you meant hitTestPoint().
If so, a function go get the random points you mention, would look a bit like this:
function getRandomPointsInClip(target:MovieClip,numPoints:int):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>(numPoints,true);
var width:Number = target.width,height:Number = target.height;
for(var i:int = 0; i < numPoints ; i++){
var point:Point = new Point(target.x+Math.random() * width,target.y+Math.random() * height);
if(target.hitTestPoint(point.x,point.y,true)) points[i] = point;//is the random coord inside ?
else i = i-1;//nope, go back one step - > retry above until it is inside
}
return points;
}
The other I hinted at in my comment involves looping through non transparent pixels in a bitmap data of your object. This method would insure you don't have many duplicates, as opposed to the previous method, but it also means, you have less control over the number of points created and there's extra memory used for creating the bitmap. Still, for documentation purposes, here is the function:
function getGridPointsInClip(target:MovieClip,res:int,offset:Number = 3):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>();
var x:int,y:int,alpha:int,w:int = int(target.width),h:int = int(target.height);
var bmd:BitmapData = new BitmapData(w,h,true,0x00FFFFFF);bmd.draw(target);
var pixels:Vector.<uint> = bmd.getVector(bmd.rect),numPixels:int = w*h;
for(var i:int = 0; i < numPixels; i+=res) {
x = i%bmd.width;
y = int(i/bmd.width);
alpha = pixels[i] >>> 24;
if(alpha > 0) points.push(new Point(x+random(-offset,offset),y+random(-offset,offset)));
}
return points;
}
function random(from:Number,to:Number):Number {
if (from >= to) return from;
var diff:Number = to - from;
return (Math.random()*diff) + from;
}
And here'a very basic test:
var pts:Vector.<Point> = getRandomPointsInClip(mc,300);
//var pts:Vector.<Point> = getGridPointsInClip(mc,100,4);
for(var i:int = 0 ; i < pts.length; i++) drawCircle(pts[i].x,pts[i].y,3,0x009900);
function getRandomPointsInClip(target:MovieClip,numPoints:int):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>(numPoints,true);
var width:Number = target.width,height:Number = target.height;
for(var i:int = 0; i < numPoints ; i++){
var point:Point = new Point(target.x+Math.random() * width,target.y+Math.random() * height);
if(target.hitTestPoint(point.x,point.y,true)) points[i] = point;//is the random coord inside ?
else i = i-1;//nope, go back one step - > retry above until it is inside
}
return points;
}
function getGridPointsInClip(target:MovieClip,res:int,offset:Number = 3):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>();
var x:int,y:int,alpha:int,w:int = int(target.width),h:int = int(target.height);
var bmd:BitmapData = new BitmapData(w,h,true,0x00FFFFFF);bmd.draw(target);
var pixels:Vector.<uint> = bmd.getVector(bmd.rect),numPixels:int = w*h;
for(var i:int = 0; i < numPixels; i+=res) {
x = i%bmd.width;
y = int(i/bmd.width);
alpha = pixels[i] >>> 24;
if(alpha > 0) points.push(new Point(x+random(-offset,offset),y+random(-offset,offset)));
}
return points;
}
function random(from:Number,to:Number):Number {
if (from >= to) return from;
var diff:Number = to - from;
return (Math.random()*diff) + from;
}
function drawCircle(x:Number,y:Number,radius:Number,color:uint):void{
graphics.lineStyle(1,color);
graphics.drawCircle(x-radius,y-radius,radius);
}
HTH
If you think of some non-blob like shapes, it's clear the check random pixel, try again method isn't really a good way. The bounding box area could be huge compared to the shape area.
What you could do to improve the effectiveness is getting a vector of the BitmapData of the shape. It should contain all pixels of the bounding box. Update - it would be nice now if we could pick a random point, and remove it from the vector if it isn't inside the shape. Unfortunately the vector only contains the pixels' colour, not the position which is implicit and only correct if we don't change the vector's length. Since we don't need to know the actual colour, we can omit all transparent pixels and store an inside pixel's position as it's value in the vector. This way we don't need to create a new object for each pixel of the shape (that would be quite expensive!).
var v:Vector.<uint> shapeBoxBitmap.getVector(shapeBoxBitmap.rect);
var pixelNum:int = v.length;
for(var i:uint = 0; i < pixelNum; i++) {
if( v[i] && 0xFF000000 == 0) { // transparent pixel, outside off shape
v.splice(i,1);
} else {
v[i] = i;
}
}
//get random point
var randomPixel:int = v[Math.floor(Math.random()*v.length)];
var point:Point = new Point(randomPixel%shapeBitmap.width,int(randomPixel/shapeBitmap.width));