11//VERSION=3
22//Cloudless Mosaic with PlanetScope
3+ //adapted by András Zlinszky and Google Gemini to handle first quartile or median mosaicking
34
45function setup ( ) {
56 return {
67 input : [ "red" , "green" , "blue" , "dataMask" , "clear" ] ,
78 output : { bands : 4 } ,
8- mosaicking : "ORBIT" ,
9+ mosaicking : "ORBIT" , // 'ORBIT' or 'TILE' or 'NONE' - determines initial mosaicking behavior
10+ // Define a custom parameter for the mosaicking method
11+ processing : {
12+ // 'mosaickingMethod' is the name of your custom parameter.
13+ // Users can set this to 'median' or 'q1'.
14+ mosaickingMethod : {
15+ defaultValue : "q1" , // Default to first quartile
16+ validValues : [ "median" , "q1" ] ,
17+ } ,
18+ } ,
919 } ;
1020}
1121
@@ -26,52 +36,106 @@ function preProcessScenes(collections) {
2636function getLastObservation ( arr ) {
2737 for ( let i = arr . length - 1 ; i >= 0 ; i -- ) {
2838 if ( arr [ i ] !== 0 ) {
29- // optional check if you are sure all invalid observations are filtered out
3039 return arr [ i ] ;
3140 }
3241 }
3342 return 0 ;
3443}
3544
3645function getMedian ( sortedValues ) {
37- var index = Math . floor ( sortedValues . length / 2 ) ;
38- return sortedValues [ index ] ;
46+ const n = sortedValues . length ;
47+ if ( n === 0 ) {
48+ return undefined ;
49+ }
50+
51+ const mid = Math . floor ( n / 2 ) ;
52+
53+ if ( n % 2 === 1 ) {
54+ return sortedValues [ mid ] ;
55+ } else {
56+ return ( sortedValues [ mid - 1 ] + sortedValues [ mid ] ) / 2 ;
57+ }
58+ }
59+
60+ /**
61+ * Calculates the first quartile (Q1) of a sorted array of numbers.
62+ *
63+ * @param {number[] } sortedValues An array of numbers sorted in ascending order.
64+ * @returns {number } The first quartile (Q1) of the distribution, or undefined if array is empty.
65+ */
66+ function getFirstQuartile ( sortedValues ) {
67+ const n = sortedValues . length ;
68+ if ( n === 0 ) {
69+ return undefined ;
70+ }
71+
72+ const lowerHalfEndIndex = Math . floor ( n / 2 ) ;
73+ const lowerHalf = sortedValues . slice ( 0 , lowerHalfEndIndex ) ;
74+
75+ return getMedian ( lowerHalf ) ;
3976}
4077
41- function evaluatePixel ( samples , scenes ) {
78+ // *** CRITICAL CHANGE HERE: ADD 'properties' AS THE THIRD ARGUMENT ***
79+ function evaluatePixel ( samples , scenes , properties ) {
4280 var reds = [ ] ;
4381 var greens = [ ] ;
44- var blues = [ ] ; //empty arrays for reds greens and blues
45- var a = 0 ; //incrementer
82+ var blues = [ ] ;
4683
84+ // Collect clear samples
4785 for ( var i = 0 ; i < samples . length ; i ++ ) {
48- //for each sample
49- var sample = samples [ i ] ; //get current sample
50- var clear = sample . dataMask && sample . clear ; //0 for clouds OR datamask, 1 for neither
86+ var sample = samples [ i ] ;
87+ var clear = sample . dataMask && sample . clear ;
5188
5289 if ( clear === 1 ) {
53- //if not clouds nor datamask
54- reds [ a ] = sample . red ; //assign values for that sample to the channel arrays
55- blues [ a ] = sample . blue ;
56- greens [ a ] = sample . green ;
57- a = a + 1 ; //increment a to represent that at this specific pixel, a value was detected
90+ reds . push ( sample . red ) ;
91+ blues . push ( sample . blue ) ;
92+ greens . push ( sample . green ) ;
5893 }
5994 }
6095
6196 var rValue ;
6297 var gValue ;
6398 var bValue ;
99+ var transparency ;
100+
101+ if ( reds . length > 0 ) {
102+ // IMPORTANT: Sort the arrays by value before calculating statistics.
103+ reds . sort ( ( a , b ) => a - b ) ;
104+ greens . sort ( ( a , b ) => a - b ) ;
105+ blues . sort ( ( a , b ) => a - b ) ;
106+
107+ // *** Access the method from the 'properties' object ***
108+ const method = properties . mosaickingMethod ; // No 'processing' here, directly under properties
109+
110+ if ( method === "median" ) {
111+ rValue = getMedian ( reds ) ;
112+ gValue = getMedian ( greens ) ;
113+ bValue = getMedian ( blues ) ;
114+ } else if ( method === "q1" ) {
115+ rValue = getFirstQuartile ( reds ) ;
116+ gValue = getFirstQuartile ( greens ) ;
117+ bValue = getFirstQuartile ( blues ) ;
118+ } else {
119+ // Fallback in case of an unexpected method value (shouldn't happen with validValues)
120+ rValue = reds [ 0 ] ;
121+ gValue = greens [ 0 ] ;
122+ bValue = blues [ 0 ] ;
123+ }
64124
65- if ( a > 0 ) {
66- rValue = getMedian ( reds ) ; // or call getLastObservation - which is less guaranteed to remove hazy images
67- gValue = getMedian ( greens ) ;
68- bValue = getMedian ( blues ) ;
69125 transparency = 1 ;
70126 } else {
71- rValue = 1 ;
72- gValue = 1 ;
73- bValue = 1 ;
127+ // If no clear samples, default to black and fully transparent.
128+ rValue = 0 ;
129+ gValue = 0 ;
130+ bValue = 0 ;
74131 transparency = 0 ;
75132 }
76- return [ rValue / 3000 , gValue / 3000 , bValue / 3000 , transparency ] ;
77- }
133+
134+ // Scale values for display (e.g., to 0-1 range).
135+ return [
136+ Math . min ( 1 , Math . max ( 0 , rValue / 3000 ) ) ,
137+ Math . min ( 1 , Math . max ( 0 , gValue / 3000 ) ) ,
138+ Math . min ( 1 , Math . max ( 0 , bValue / 3000 ) ) ,
139+ transparency
140+ ] ;
141+ }
0 commit comments