├── FieldData.ipynb ├── FieldData_test.ipynb ├── License.txt ├── MapVisualisation.ipynb ├── MapVisualisation_test.ipynb ├── Masks.ipynb ├── Masks_test.ipynb ├── MergingLib.ipynb ├── MergingLib_test.ipynb ├── PlotToSat.ipynb ├── PlotToSat_test1.ipynb ├── PlotToSat_test2.ipynb ├── PlotsManager.ipynb ├── PlotsManager_test.ipynb ├── Polygons.ipynb ├── Polygons_test.ipynb ├── README.md ├── ResultsOfTestCases ├── folderSpain1 │ ├── MergedCsvs │ │ ├── outfeaturevectors_mean.csv │ │ └── outfeaturevectors_stdD.csv │ ├── outfeaturevectors_0000000000_0000000399_S1Asc_mean.csv │ ├── outfeaturevectors_0000000000_0000000399_S1Asc_stdD.csv │ ├── outfeaturevectors_0000000000_0000000399_S1Des_mean.csv │ ├── outfeaturevectors_0000000000_0000000399_S1Des_stdD.csv │ ├── outfeaturevectors_0000000000_0000000399_S2_mean.csv │ ├── outfeaturevectors_0000000000_0000000399_S2_stdD.csv │ ├── outfeaturevectors_0000000400_0000000799_S1Asc_mean.csv │ ├── outfeaturevectors_0000000400_0000000799_S1Asc_stdD.csv │ ├── outfeaturevectors_0000000400_0000000799_S1Des_mean.csv │ ├── outfeaturevectors_0000000400_0000000799_S1Des_stdD.csv │ ├── outfeaturevectors_0000000400_0000000799_S2_mean.csv │ └── outfeaturevectors_0000000400_0000000799_S2_stdD.csv └── folderSpain2 │ ├── MergedCsvs │ ├── r25_2020_mean.csv │ └── r25_2020_stdD.csv │ ├── r25_2020_0000000000_0000000299_S1_mean.csv │ ├── r25_2020_0000000000_0000000299_S1_stdD.csv │ ├── r25_2020_0000000000_0000000299_S2_mean.csv │ ├── r25_2020_0000000000_0000000299_S2_stdD.csv │ ├── r25_2020_0000000300_0000000599_S1_mean(1).csv │ ├── r25_2020_0000000300_0000000599_S1_mean.csv │ ├── r25_2020_0000000300_0000000599_S1_stdD.csv │ ├── r25_2020_0000000300_0000000599_S2_mean.csv │ └── r25_2020_0000000300_0000000599_S2_stdD.csv ├── Sentinel1.ipynb ├── Sentinel1_test.ipynb ├── Sentinel2b.ipynb ├── Sentinel2b_test.ipynb ├── Utils.ipynb ├── img ├── OnPlot.csv_bldec_S1.jpg ├── OnPlot.csv_bldec_S2.jpg ├── OutputExample.jpg ├── PlotToSatDiagram.jpg ├── drawpolygon.jpg └── drawpolygon1.jpg ├── requirements.txt └── samplePlots.csv /FieldData.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "author: Dr Milto Miltiadou\n", 8 | "version: 1.0" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "metadata": {}, 15 | "outputs": [], 16 | "source": [ 17 | "import sys\n", 18 | "\n", 19 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 20 | "modulename = 'ee'\n", 21 | "if modulename not in sys.modules: \n", 22 | " # import GEE and Authenticate, token or log in will be asked from web browser\n", 23 | " import ee\n", 24 | " #ee.Authenticate()\n", 25 | " ee.Initialize()\n", 26 | "else:\n", 27 | " print('GEE already imported')\n", 28 | " # google earth engine already imported and authenticated\n", 29 | "\n", 30 | "import pandas as pd\n", 31 | "import numbers\n", 32 | "from datetime import date\n", 33 | "import os" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "class FieldData: \n", 43 | " # constructor\n", 44 | " def __init__(self,properties):\n", 45 | " self.proj = None\n", 46 | " self.csvfile = None\n", 47 | " self.colX = None\n", 48 | " self.colY = None\n", 49 | " self.radius = None\n", 50 | " self.lengthX = None\n", 51 | " self.lengthY = None\n", 52 | " self.plotcode = None\n", 53 | " self.filedataFilewithIDs = None\n", 54 | " self.keyColumn = \"indexField\"\n", 55 | " self.exportStdVal = True\n", 56 | " self.exportMeanVal = True\n", 57 | " \n", 58 | " if ('keyColumn' in properties):\n", 59 | " self.keyColumn = properties['keyColumn']\n", 60 | " else:\n", 61 | " print (\"WARNING: No keyColumn provided. A new column named \", self.keyColumn, \" is added\")\n", 62 | " \n", 63 | " if ('outPlotFileWithIDs'in properties):\n", 64 | " self.filedataFilewithIDs = properties['outPlotFileWithIDs']\n", 65 | " else:\n", 66 | " raise Exception (\"ERROR: name of file with plots and ids to be exported not defined!\")\n", 67 | "\n", 68 | " if ('proj' in properties): \n", 69 | " self.proj = ee.Projection(properties['proj'])\n", 70 | " else:\n", 71 | " print('WARNING: projection was not defined. Default project is ', self.proj)\n", 72 | "\n", 73 | " if ('csvfilename' in properties):\n", 74 | " self.csvfile = properties['csvfilename']\n", 75 | " else:\n", 76 | " raise Exception(\"CSV input file should be defined as follows: 'csvfilename': \")\n", 77 | "\n", 78 | " if ('radius' in properties):\n", 79 | " radius = properties['radius']\n", 80 | " if(isinstance(radius,numbers.Number) and radius>0):\n", 81 | " self.radius=radius\n", 82 | " else:\n", 83 | " print(\"WARNING: Wrong radius provided : \", radius)\n", 84 | " #else: \n", 85 | " # Maybe it is a rectangular plot - further testing later\n", 86 | " \n", 87 | " if ('lengthX' in properties):\n", 88 | " lengthX = properties['lengthX']\n", 89 | " if(isinstance(lengthX,numbers.Number) and lengthX>0):\n", 90 | " self.lengthX = lengthX\n", 91 | " else:\n", 92 | " print(\"WARNING: Wrong radius provided : \", lengthX)\n", 93 | " # else:\n", 94 | " # Maybe it is a circular plot - further testing later\n", 95 | " \n", 96 | " if ('lengthY' in properties):\n", 97 | " lengthY = properties['lengthY']\n", 98 | " if(isinstance(lengthY,numbers.Number) and lengthY>0):\n", 99 | " self.lengthY = lengthY\n", 100 | " else:\n", 101 | " print(\"WARNING: Wrong radius provided : \", lengthY)\n", 102 | " # else:\n", 103 | " # Maybe it is a circular plot - further testing later\n", 104 | "\n", 105 | " if('xcol' in properties):\n", 106 | " self.colX = properties['xcol']\n", 107 | " else :\n", 108 | " print (\"WARNING: name of column containing X coordinates was not provided. Set to \\\"CX\\\"\")\n", 109 | " self.colX = \"CX\"\n", 110 | " if('ycol' in properties):\n", 111 | " self.colY = properties['ycol']\n", 112 | " else :\n", 113 | " print (\"WARNING: name of column containing Y coordinates was not provided. Set to \\\"CY\\\"\")\n", 114 | " self.colX = \"CY\"\n", 115 | " \n", 116 | " \n", 117 | " if (self.proj==None or self.csvfile == None or self.colX == None or self.colY == None):\n", 118 | " raise Exception (\"Field data: Variables not defined correctly\")\n", 119 | " \n", 120 | " if not(self.radius!=None or (self.lengthX!=None and self.lengthY!=None)): \n", 121 | " raise Exception(\"Either radius for circular plots or lengthX and legthY for rectangular plots need\",\n", 122 | " \" to be defined within the field data.\")\n", 123 | " \n", 124 | " \n", 125 | " self.df = pd.read_csv(self.csvfile, encoding='ISO-8859-1',low_memory=False,) \n", 126 | " # Removing Nan values\n", 127 | " self.df.dropna(subset=[self.colX],inplace=True)\n", 128 | " self.df.dropna(subset=[self.colY],inplace=True)\n", 129 | " self.df = self.df.reset_index()\n", 130 | " if self.keyColumn == \"indexField\" : \n", 131 | " self.df[self.keyColumn] = list(range(1,len(self.df)+1))\n", 132 | "\n", 133 | " self.bufferredPoints = None\n", 134 | " self.sampleSize = 300\n", 135 | "\n", 136 | " directory = os.path.dirname(self.filedataFilewithIDs)\n", 137 | " if directory and not os.path.exists(directory):\n", 138 | " os.makedirs(directory)\n", 139 | " # else directory and subdirectories exist\n", 140 | " self.exportPlotDataWithAddedIdentifiers(self.filedataFilewithIDs)\n", 141 | "\n", 142 | "\n", 143 | " # method that returns the number of rows/plot data contained within the csv file\n", 144 | " def getLen(self):\n", 145 | " return len(self.df.index)\n", 146 | " \n", 147 | " ## method that enables and disables exportation of Std values\n", 148 | " # @param[in] Bool is a boolean value True for enable and False for disable\n", 149 | " def exportStd(self,Bool):\n", 150 | " if (isinstance(Bool, bool)):\n", 151 | " self.exportStdVal = Bool\n", 152 | " else : \n", 153 | " print (\"WARNDING: function exportStd() takes as input a Boolean value!\")\n", 154 | " \n", 155 | " ## @brief method that enables and disables exportation of Mean Values\n", 156 | " # @param[in] Bool is a boolean value True for enable and False for disable\n", 157 | " def exportMean(self,Bool):\n", 158 | " if (isinstance(Bool, bool)):\n", 159 | " self.exportMeanVal = Bool\n", 160 | " else : \n", 161 | " print (\"WARNDING: function exportStd() takes as input a Boolean value!\")\n", 162 | " \n", 163 | " \n", 164 | " ## method that returns the smallest and bigger available year within the field data\n", 165 | " ## @param[in] yearCol the name of the column that states the years\n", 166 | " ## @returns [min,max] the smallest and bigger year included\n", 167 | " def getMinMaxYear(self,yearlabel):\n", 168 | " years = self.df[yearlabel]\n", 169 | " return([min(years),max(years)])\n", 170 | " \n", 171 | " # @brief method that keeps the years of interests (inclusive) and discards the rest\n", 172 | " def filterYearsOfInterest(self,startYear,endYear,yearlabel):\n", 173 | " self.df = self.df[self.df[yearlabel].isin(list(range(startYear,endYear+1)))]\n", 174 | " self.df = self.df.reset_index()\n", 175 | " return None\n", 176 | " \n", 177 | " def bufferPoint(self,feature):\n", 178 | " return feature.buffer(self.distance, 1)\n", 179 | " \n", 180 | " ## method that returns a dataframe containing the data of the year of interest\n", 181 | " # @param[in] year the year of interest\n", 182 | " # @param[in] yearlabel the name of the column containing the years\n", 183 | " def getYearOfInterest(self,year,yearlabel): \n", 184 | " tmpdf = self.df[self.df[yearlabel] == year]\n", 185 | " tmpdf = tmpdf.reset_index()\n", 186 | " return tmpdf\n", 187 | " \n", 188 | " ## NOT WORKING\n", 189 | " def createCircularBufferPoints(self,currentMin,currentMax):\n", 190 | " print(\"Creating Circular plots\")\n", 191 | " if (len(self.df)==0):\n", 192 | " # then dataframe has no rows\n", 193 | " return None \n", 194 | " tmpdf = self.df.iloc[currentMin:currentMax]\n", 195 | "\n", 196 | " if tmpdf.empty:\n", 197 | " return None\n", 198 | " # create a feature collection with the first location\n", 199 | " x = 0\n", 200 | " y = 0 \n", 201 | " indx = 0\n", 202 | " for index, row in tmpdf.iterrows():\n", 203 | " x = row[self.colX ]\n", 204 | " y = row[self.colY ]\n", 205 | " indx = row[self.keyColumn]\n", 206 | " break\n", 207 | "\n", 208 | " self.bufferredPoints = None\n", 209 | " # pointList is not defined in a loop to make sure memory allocation is preserved after \n", 210 | " # the loop is deleted\n", 211 | " self.bufferredPoints = ee.FeatureCollection(\n", 212 | " [ee.Feature(\n", 213 | " ee.Geometry.Point(\n", 214 | " [x,y],self.proj\n", 215 | " ),\n", 216 | " {\n", 217 | " self.keyColumn : indx,\n", 218 | " \"system:index\" : \"0\"\n", 219 | " }\n", 220 | " ).buffer(self.radius)]\n", 221 | " )\n", 222 | " \n", 223 | " # add other locations to the feature collection\n", 224 | " i = 0\n", 225 | " for index, row in tmpdf.iterrows():\n", 226 | " if i==0 :\n", 227 | " i = 1\n", 228 | " continue\n", 229 | " self.bufferredPoints = self.bufferredPoints.merge(ee.FeatureCollection(\n", 230 | " [ee.Feature(\n", 231 | " ee.Geometry.Point(\n", 232 | " [row[self.colX],row[self.colY]],self.proj\n", 233 | " ),\n", 234 | " {\n", 235 | " self.keyColumn : row[self.keyColumn],\n", 236 | " \"system:index\" : str(i)\n", 237 | " }\n", 238 | " ).buffer(self.radius)]\n", 239 | " ))\n", 240 | " \n", 241 | " return self.bufferredPoints\n", 242 | " \n", 243 | " def createRectangularPlots(self, currentMin, currentMax):\n", 244 | " print(\"Creating Rectangular plots\")\n", 245 | " if len(self.df) == 0:\n", 246 | " # the dataframe has no rows\n", 247 | " return None\n", 248 | "\n", 249 | " tmpdf = self.df.iloc[currentMin:currentMax]\n", 250 | "\n", 251 | " if tmpdf.empty:\n", 252 | " return None\n", 253 | "\n", 254 | " # create a feature collection with the first location\n", 255 | " x = 0\n", 256 | " y = 0\n", 257 | " indx = 0\n", 258 | " for index, row in tmpdf.iterrows():\n", 259 | " x = row[self.colX]\n", 260 | " y = row[self.colY]\n", 261 | " indx = row[self.keyColumn]\n", 262 | " break\n", 263 | "\n", 264 | " self.bufferedPoints = None\n", 265 | " # pointList is not defined in a loop to make sure memory allocation is preserved after\n", 266 | " # the loop is deleted\n", 267 | "\n", 268 | " self.bufferedPoints = ee.FeatureCollection([\n", 269 | " ee.Feature(\n", 270 | " ee.Geometry.Polygon(\n", 271 | " coords=[\n", 272 | " [x, y],\n", 273 | " [x, y + self.lengthY],\n", 274 | " [x + self.lengthX, y + self.lengthY],\n", 275 | " [x + self.lengthX, y]\n", 276 | " ],\n", 277 | " geodesic=True,\n", 278 | " proj=self.proj \n", 279 | " ),\n", 280 | " {\n", 281 | " self.plotcode: row[self.plotcode],\n", 282 | " self.keyColumn: indx,\n", 283 | " \"system:index\": \"0\"\n", 284 | " }\n", 285 | " )\n", 286 | " ])\n", 287 | " \n", 288 | " print(x, y)\n", 289 | " print(x, y + self.lengthY)\n", 290 | " print(x + self.lengthX, y + self.lengthY)\n", 291 | " print(x + self.lengthX, y)\n", 292 | " print(\"***********************\")\n", 293 | " \n", 294 | " geometry = ee.Geometry.Polygon(\n", 295 | " coords=[[x, y], [x, y + self.lengthY], [x + self.lengthX, y + self.lengthY], [x + self.lengthX, y]],\n", 296 | " proj=self.proj\n", 297 | " )\n", 298 | " #print(\"Polygon:\", geometry)\n", 299 | "\n", 300 | " # add other locations to the feature collection\n", 301 | " for i, (index, row) in enumerate(tmpdf.iterrows()):\n", 302 | " if i == 0:\n", 303 | " continue\n", 304 | " x = row[self.colX]\n", 305 | " y = row[self.colY]\n", 306 | " indx = row[self.keyColumn]\n", 307 | " print(x, y)\n", 308 | " print(x, y + self.lengthY)\n", 309 | " print(x + self.lengthX, y + self.lengthY)\n", 310 | " print(x + self.lengthX, y)\n", 311 | " print(\"***********************\")\n", 312 | " self.bufferedPoints = self.bufferedPoints.merge(ee.FeatureCollection([\n", 313 | " ee.Feature(\n", 314 | " ee.Geometry.Polygon(\n", 315 | " coords=[\n", 316 | " [x, y],\n", 317 | " [x, y + self.lengthY],\n", 318 | " [x + self.lengthX, y + self.lengthY],\n", 319 | " [x + self.lengthX, y]\n", 320 | " ],\n", 321 | " geodesic=True ,\n", 322 | " proj=self.proj\n", 323 | " ),\n", 324 | " {\n", 325 | " self.plotcode: row[self.plotcode],\n", 326 | " self.keyColumn: indx,\n", 327 | " \"system:index\": str(i)\n", 328 | " }\n", 329 | " )\n", 330 | " ]))\n", 331 | "\n", 332 | " return self.bufferedPoints\n", 333 | " \n", 334 | " \n", 335 | " ## method that reads the centres of currentMin to currentMax plots and creates a list of circular polygons with \n", 336 | " # centres the centre of each plot and radius the predefined radius of the plots\n", 337 | " # @param[in] currentMin used to load a sample of the data this is the min value of the field range to be loaded\n", 338 | " # @param[in] currentMax used to load a sample of the data this is the max value of the field date range to be loaded\n", 339 | " def createBufferedPoints(self,currentMin,currentMax):\n", 340 | " if (self.radius!=None):\n", 341 | " return self.createCircularBufferPoints(currentMin,currentMax) \n", 342 | " elif (self.lengthX!=None and self.lengthY!=None):\n", 343 | " return self.createRectangularPlots(currentMin,currentMax)\n", 344 | "\n", 345 | " \n", 346 | " def exportfeatureVectorsToDrive(self,collection, outCsvFeatureVectors, driveFolder, iscale):\n", 347 | " if (self.pointsList == None) : \n", 348 | " print (\"Plot data have not been read yet. Please call \\\"createBufferedPoints\", \\\n", 349 | " \"(currentMin,currentMax)\\\" first\")\n", 350 | " return\n", 351 | " \n", 352 | " training = collection.sampleRegions(\n", 353 | " collection = self.pointsList,\n", 354 | " properties = [self.keyColumn],\n", 355 | " scale = iscale,\n", 356 | " projection = self.proj,\n", 357 | " geometries = True\n", 358 | " )\n", 359 | " \n", 360 | " # TO DO: COMMENT WHEN NOT TESTING OUTPUT AS BATCH COMMANDS ARE LIMITED\n", 361 | " print(\"STARTING BATCH SCRIPT FOR EXPORTING FILE\")\n", 362 | " task = ee.batch.Export.table.toDrive(**{\n", 363 | " 'collection' : training,\n", 364 | " 'description' : outCsvFeatureVectors,\n", 365 | " 'folder' : driveFolder,\n", 366 | " 'fileFormat' : \"CSV\"\n", 367 | " })\n", 368 | " #task.start()\n", 369 | " print(\"END OF CALLING BATCH SCRIPT\") \n", 370 | "\n", 371 | " \n", 372 | "\n", 373 | "\n", 374 | " def exportPlotDataWithAddedIdentifiers(self, nameOfNewPlotFile):\n", 375 | " #print(\"exporting field data to \", nameOfNewPlotFile)\n", 376 | " self.df.to_csv(nameOfNewPlotFile)\n", 377 | "\n", 378 | " def printFieldData(self):\n", 379 | " print(self.df.to_string()) \n", 380 | "\n", 381 | "\n", 382 | "\n", 383 | " # get mean and std for one band of an image for each buffered point\n", 384 | " def getInfoRegions(self,collection,bandName, bpoints):\n", 385 | " # bnamestr = bandName.get('band')\n", 386 | " return collection.select([bandName]).reduceRegions(**{\n", 387 | " 'collection': bpoints.select(self.keyColumn),\n", 388 | " 'reducer': ee.Reducer.mean().combine(**{\n", 389 | " 'reducer2': ee.Reducer.stdDev(),\n", 390 | " 'sharedInputs': True\n", 391 | " }),\n", 392 | " 'scale': 10#,\n", 393 | " #'bestEffort': True # Use maxPixels if you care about scale.\n", 394 | " }).map(lambda feature: feature.set('bandName',bandName)) \\\n", 395 | " .filter(ee.Filter.neq('mean',None))\n", 396 | "\n", 397 | "\n", 398 | " def getFeatureCollection(self,collection,bpoints):\n", 399 | " bandNamesImg = collection.bandNames().getInfo()\n", 400 | " print('Band names: ', bandNamesImg)\n", 401 | " for band in bandNamesImg :\n", 402 | " if(not isinstance(band,str)):\n", 403 | " bandNamesImg.remove(band)\n", 404 | " featureCollection1 = ee.FeatureCollection([])\n", 405 | " for band in bandNamesImg:\n", 406 | " features = self.getInfoRegions(collection,band,bpoints)\n", 407 | " featureCollection1 = featureCollection1.merge(features) \n", 408 | " return featureCollection1\n", 409 | "\n", 410 | "\n", 411 | "\n", 412 | "\n", 413 | "\n", 414 | " def processMatchesMean(self,row):\n", 415 | " # Get the list of all features with a unique row ID.\n", 416 | " matches = ee.List(row.get('matches'))\n", 417 | " # Map a function over the list of rows to return a list of column ID and value.\n", 418 | " values = matches.map(lambda feature: [ee.Feature(feature).get('bandName'), ee.Feature(feature).get('mean')])\n", 419 | " # Return the row with its ID property and properties for all matching column IDs storing the output of the reducer.\n", 420 | " return row.select([self.keyColumn]).set(ee.Dictionary(values.flatten()))\n", 421 | "\n", 422 | " ## Format a table of triplets into a 2D table of rowId x colId.\n", 423 | " def formatMean (self,table):\n", 424 | " # Get a FeatureCollection with unique row IDs.\n", 425 | " rows = table.distinct(self.keyColumn)\n", 426 | " filterEq = ee.Filter.equals(leftField=self.keyColumn, rightField=self.keyColumn)\n", 427 | " innerJoin = ee.Join.saveAll('matches')\n", 428 | " toyJoin = innerJoin.apply(primary=rows, secondary=table, condition=filterEq)\n", 429 | " \n", 430 | " return toyJoin.map(algorithm = self.processMatchesMean)\n", 431 | "\n", 432 | " def processMatchesStd(self,row):\n", 433 | " # Get the list of all features with a unique row ID.\n", 434 | " matches = ee.List(row.get('matches'))\n", 435 | " # Map a function over the list of rows to return a list of column ID and value.\n", 436 | " values = matches.map(lambda feature: [ee.Feature(feature).get('bandName'),\n", 437 | " ee.Feature(feature).get('stdDev')])\n", 438 | " \n", 439 | " # Return the row with its ID property and properties for all matching column IDs storing the output of the reducer.\n", 440 | " return row.select([self.keyColumn]).set(ee.Dictionary(values.flatten()))\n", 441 | "\n", 442 | "\n", 443 | " ## Format a table of triplets into a 2D table of rowId x colId.\n", 444 | " def formatStd (self,table):\n", 445 | " # Get a FeatureCollection with unique row IDs.\n", 446 | " rows = table.distinct(self.keyColumn)\n", 447 | " filterEq = ee.Filter.equals(leftField=self.keyColumn, rightField=self.keyColumn)\n", 448 | " innerJoin = ee.Join.saveAll('matches')\n", 449 | " toyJoin = innerJoin.apply(primary=rows, secondary=table, condition=filterEq)\n", 450 | " return toyJoin.map(algorithm = self.processMatchesStd) \n", 451 | " \n", 452 | " \n", 453 | " # collection = s2bands\n", 454 | " def exportFeaturesMeanStdCSV(self,collection,ouutCsvFeatureVectors,driveFolder):\n", 455 | " if (self.bufferredPoints == None):\n", 456 | " raise Exception(\"Please call createBufferedPoints(currentMin,currentMax) function first\" )\n", 457 | " featureCollection = self.getFeatureCollection(collection,self.bufferredPoints)\n", 458 | "\n", 459 | " tableMean = self.formatMean(featureCollection)\n", 460 | " tableStd = self.formatStd (featureCollection)\n", 461 | " \n", 462 | " meanName = ouutCsvFeatureVectors+\"_mean\"\n", 463 | " stdName = ouutCsvFeatureVectors+\"_stdD\"\n", 464 | " print (\"START EXPORTING FEATURES VECTORS OF A SINGLE FILE\")\n", 465 | " if (self.exportMeanVal):\n", 466 | " task = ee.batch.Export.table.toDrive(**{\n", 467 | " 'collection':tableMean,\n", 468 | " 'description':meanName,\n", 469 | " 'folder': driveFolder,\n", 470 | " 'fileFormat':'CSV'\n", 471 | " })\n", 472 | " task.start()\n", 473 | "\n", 474 | " if (self.exportStdVal):\n", 475 | " task = ee.batch.Export.table.toDrive(**{\n", 476 | " 'collection':tableStd,\n", 477 | " 'description':stdName,\n", 478 | " 'folder': driveFolder,\n", 479 | " 'fileFormat':'CSV'\n", 480 | " })\n", 481 | " task.start()" 482 | ] 483 | }, 484 | { 485 | "cell_type": "code", 486 | "execution_count": null, 487 | "metadata": {}, 488 | "outputs": [], 489 | "source": [ 490 | "print (\"Class fieldData imported\")" 491 | ] 492 | } 493 | ], 494 | "metadata": { 495 | "kernelspec": { 496 | "display_name": ".venv", 497 | "language": "python", 498 | "name": "python3" 499 | }, 500 | "language_info": { 501 | "codemirror_mode": { 502 | "name": "ipython", 503 | "version": 3 504 | }, 505 | "file_extension": ".py", 506 | "mimetype": "text/x-python", 507 | "name": "python", 508 | "nbconvert_exporter": "python", 509 | "pygments_lexer": "ipython3", 510 | "version": "3.13.0" 511 | } 512 | }, 513 | "nbformat": 4, 514 | "nbformat_minor": 2 515 | } 516 | -------------------------------------------------------------------------------- /FieldData_test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "This is a test file for the FieldData class\n", 9 | "It requires input parameters from the command line as follow\n" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import sys\n", 19 | "\n", 20 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 21 | "modulename = 'ee'\n", 22 | "if modulename not in sys.modules: \n", 23 | " # import GEE and Authenticate, token or log in will be asked from web browser\n", 24 | " import ee\n", 25 | " ee.Authenticate()\n", 26 | " ee.Initialize()\n", 27 | "else:\n", 28 | " print('GEE already imported')\n", 29 | " # google earth engine already imported and authenticated\n", 30 | "\n", 31 | "\n", 32 | "import sys\n", 33 | "modulename = 'ipynb_FieldData'\n", 34 | "if modulename not in sys.modules:\n", 35 | " %run FieldData.ipynb\n", 36 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 37 | " sys.modules['FieldData'] = None \n", 38 | "#else\n", 39 | " # Utils modules has already been loaded somewhere else\n", 40 | "\n", 41 | "modulename = 'ipynb_Sentinel2b'\n", 42 | "if modulename not in sys.modules:\n", 43 | " %run Sentinel2b.ipynb\n", 44 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 45 | " sys.modules['ipynb_Sentinel2b'] = None \n", 46 | "#else\n", 47 | " # Utils modules has already been loaded somewhere else\n", 48 | "\n", 49 | "modulename = 'ipynb_MapVisualisation'\n", 50 | "if modulename not in sys.modules:\n", 51 | " %run MapVisualisation.ipynb\n", 52 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 53 | " sys.modules['ipynb_MapVisualisation'] = None \n", 54 | "#else\n", 55 | " # Utils modules has already been loaded somewhere else\n" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "# parsing command line inputs - Could not find a way to add the arguments in ipython\n", 65 | "\"\"\" parser = argparse.ArgumentParser()\n", 66 | "parser.add_argument(\"-in\",\n", 67 | " required=True,\n", 68 | " help=\"The input csv file with its directory containing field plot data\",\n", 69 | " metavar='')\n", 70 | "\n", 71 | "\n", 72 | "\n", 73 | "params = vars(parser.parse_args())\n", 74 | "csvFile = params[\"in\" ]\n", 75 | "\n", 76 | "\n", 77 | "print (\"imported csv file = \", csvFile ) \"\"\"\n", 78 | "\n", 79 | "# import a Sentinel 2 collection and crop Spain\n", 80 | "countries = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017')\n", 81 | "AOI = countries.filter(ee.Filter.eq('country_na', 'Spain'))\n", 82 | "\n", 83 | "geometry = ee.Geometry.Polygon(\n", 84 | " [[[-5.586640856054208, 40.07176423657564],\n", 85 | " [-5.391633531835458, 40.07176423657564],\n", 86 | " [-5.391633531835458, 40.17257546939766],\n", 87 | " [-5.6106734488276455, 40.170476760191924]]])\n", 88 | "\n", 89 | "\n", 90 | "START_DATE = '2021-01-01'\n", 91 | "END_DATE = '2021-01-28'\n", 92 | "masks = {}#'gsw': 30, 'lmask': 30, 'forestMask': {30,2021}}\n", 93 | "\n", 94 | "\n", 95 | "s2_col = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED') \\\n", 96 | " .filterBounds(geometry) \\\n", 97 | " .filterDate('2021-01-01', '2021-12-31') \\\n", 98 | " .median()\n", 99 | "\n", 100 | "bandNames = s2_col.bandNames()\n", 101 | "\n", 102 | "bandnames = s2_col.bandNames()\n", 103 | "bandnames = bandnames.getInfo()\n", 104 | " \n", 105 | "print(type(bandnames))\n", 106 | "print('Band names s2_col: ', bandnames)\n", 107 | "\n", 108 | "\n", 109 | "\n", 110 | "sentinel2Info = {\n", 111 | " \"identifier\": 'sentinel-2', \n", 112 | " \"collection\": \"COPERNICUS/S2_SR_HARMONIZED\",\n", 113 | " #\"launchDate\" : \"2015-05-23\",\n", 114 | " #\"retirmentDate\":todayDate, #not retired yet\n", 115 | " \"startDate\" : START_DATE,\n", 116 | " \"endDate\" : END_DATE,\n", 117 | " #\"added\" : False,\n", 118 | " \"clouds\" : 50,\n", 119 | " \"selectedBand\": ['B8', 'B4'],\n", 120 | " \"selectedIndices\": ['ndvi']\n", 121 | "} \n", 122 | "\n", 123 | "\n", 124 | "s2 = Sentinel2(AOI,sentinel2Info, masks)\n", 125 | "s2.byMonth(2021)\n", 126 | "\n", 127 | "\n", 128 | "\n", 129 | "# Create ee.Geometry.Point objects for the corners\n", 130 | "lower_left_point = ee.Geometry.Point(\n", 131 | " [292002.67, 4448723.55],\"EPSG:3042\"\n", 132 | " )\n", 133 | "upper_right_point =ee.Geometry.Point(\n", 134 | " [292402.67, 4449073.55],\"EPSG:3042\"\n", 135 | " )\n", 136 | "\n", 137 | "# Construct a rectangle from the list of ee.Geometry.Point objects\n", 138 | "polygon = ee.Geometry.Rectangle([lower_left_point, upper_right_point])\n", 139 | "\n", 140 | "\n", 141 | "polygon = ee.FeatureCollection([ee.Feature(ee.Geometry.Polygon(polygon.coordinates()))])\n", 142 | "\n", 143 | "\n", 144 | "# Coordinates of the rectangle's lower-left and upper-right corners\n", 145 | "x_min, y_min = 292002.67, 4448723.55\n", 146 | "x_max, y_max = 292402.67, 4449073.55\n", 147 | "\n", 148 | "MinPoint = ee.Geometry.Point([292002.67, 4448723.55], \"EPSG:3042\")\n", 149 | "MaxPoint = ee.Geometry.Point([292402.67, 4449073.55], \"EPSG:3042\")\n", 150 | "\n", 151 | "# Transform the point to latitudes and longitudes (WGS84)\n", 152 | "MinPoint_latlon = MinPoint.transform(\"EPSG:3857\")\n", 153 | "MaxPoint_latlon = MaxPoint.transform(\"EPSG:3857\")\n", 154 | "\n", 155 | "# Get the coordinates as a list\n", 156 | "coordinatesMinPoint = MinPoint_latlon.getInfo()[\"coordinates\"]\n", 157 | "coordinatesMaxPoint = MaxPoint_latlon.getInfo()[\"coordinates\"]\n", 158 | "\n", 159 | "# Extract latitude and longitude\n", 160 | "latitudeMinPoint, longitudeMinPoint = coordinatesMinPoint[1], coordinatesMinPoint[0]\n", 161 | "latitudeMaxPoint, longitudeMaxPoint = coordinatesMaxPoint[1], coordinatesMaxPoint[0]\n", 162 | "\n", 163 | "csvFile = \"./samplePlots.csv\"\n", 164 | "\n", 165 | "\n", 166 | "# Create a dictionary that holds the relevant plot information\n", 167 | "fieldData = {\n", 168 | " \"csvfilename\" : csvFile,\n", 169 | " \"proj\" : \"EPSG:3042\",\n", 170 | " \"radius\" : 200,\n", 171 | " \"xcol\" : \"CX\", \n", 172 | " \"ycol\" : \"CY\",\n", 173 | " \"outPlotFileWithIDs\" :\"fieldDataWithIdentifiers/FieldDataTest.csv\"\n", 174 | " }\n", 175 | "\n", 176 | "csvDF = FieldData(fieldData)\n", 177 | "# csvDF.filterYearsOfInterest(2017,2018,\"year\")\n", 178 | "\n", 179 | "# create a list of polygons for the field data 1-100\n", 180 | "bufferredPoints = csvDF.createBufferedPoints(1, 100)\n", 181 | "#print(bufferredPoints)\n", 182 | "\n", 183 | "s2bands = s2.getCollectionToBands()\n", 184 | "\n", 185 | "csvDF.exportFeaturesMeanStdCSV(s2bands,\"ouutCsvFeatureVectors2021_200_B4\",\"GEE\")\n", 186 | "\n", 187 | "\n", 188 | "\n", 189 | "# THIS IS THE OTHER METHOD THAT EXPORTS FOR EACH PIXEL - DOES NOT HANDLES GAPS SO NEEDS EXTRA POST-PROCESSSING\n", 190 | "# csvDF.exportfeatureVectorsToDrive(col,\"TestIndClassSentinel2c.csv\",\"earth_engine_demos\",10)\n", 191 | "# csvDF.mergeFeatureVectorsToPlotData(\"/home/milto/Documents/FLFScripts/sampleData/mergedcsv.csv\")\n", 192 | "# csvDF.exportPlotDataWithAddedIdentifiers(\"/home/milto/Documents/FLFScripts/sampleData/fieldDatawithIds.csv\")\n", 193 | "\n", 194 | "print(\"NOTE: in the example, there are no plots within the study area!\")\n", 195 | "print(\" *** EXIT ***\")\n" 196 | ] 197 | }, 198 | { 199 | "cell_type": "code", 200 | "execution_count": null, 201 | "metadata": {}, 202 | "outputs": [], 203 | "source": [ 204 | "#%run MapVisualisation.ipynb\n", 205 | "my_map = folium.Map(location=[40,-3], zoom_start=5, height=400)\n", 206 | "# Add custom basemaps\n", 207 | "basemaps['Google Maps'].add_to(my_map)\n", 208 | "basemaps['Google Satellite Hybrid'].add_to(my_map)\n", 209 | "# Add Land Mask to the map\n", 210 | "my_map.add_ee_layer(s2_col, {'bands': ['B4', 'B3', 'B2'], 'min': 0, 'max': 2500, 'gamma': 1.1}, 'median' )\n", 211 | "my_map.add_ee_layer(bufferredPoints, {},'bufferredPoints')\n", 212 | "\n", 213 | "my_map.add_ee_layer(geometry,{},\"geometry\")\n", 214 | "\n", 215 | "my_map.add_ee_layer(lower_left_point,{},\"lower_left_point\")\n", 216 | "\n", 217 | "# Add a layer control panel to the map.\n", 218 | "my_map.add_child(folium.LayerControl())\n", 219 | "plugins.Fullscreen().add_to(my_map)\n", 220 | "\n", 221 | "# Add a layer control panel to the map.\n", 222 | "my_map.add_child(folium.LayerControl())\n", 223 | "\n", 224 | "# Add fullscreen button\n", 225 | "plugins.Fullscreen().add_to(my_map)\n", 226 | "\n", 227 | "# Display the map.\n", 228 | "display(my_map)" 229 | ] 230 | } 231 | ], 232 | "metadata": { 233 | "kernelspec": { 234 | "display_name": ".venv", 235 | "language": "python", 236 | "name": "python3" 237 | }, 238 | "language_info": { 239 | "codemirror_mode": { 240 | "name": "ipython", 241 | "version": 3 242 | }, 243 | "file_extension": ".py", 244 | "mimetype": "text/x-python", 245 | "name": "python", 246 | "nbconvert_exporter": "python", 247 | "pygments_lexer": "ipython3", 248 | "version": "3.13.0" 249 | }, 250 | "orig_nbformat": 4 251 | }, 252 | "nbformat": 4, 253 | "nbformat_minor": 2 254 | } 255 | -------------------------------------------------------------------------------- /MapVisualisation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "The MapVisualisation module handles the map visualisation of the Google Earth Engine using the folium library\n", 9 | "\n", 10 | "Code modified from: https://colab.research.google.com/github/giswqs/qgis-earthengine-examples/blob/master/Folium/ee-api-folium-setup.ipynb#scrollTo=VP33EU7hJBq8\n", 11 | "\n", 12 | "Version : 1.0\n", 13 | "Date : Feb 2023\n", 14 | "Author : Dr Milto Miltiaodu" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "import sys\n", 24 | "\n", 25 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 26 | "modulename = 'ee'\n", 27 | "if modulename not in sys.modules: \n", 28 | " # import GEE and Authenticate, token or log in will be asked from web browser\n", 29 | " import ee\n", 30 | " ee.Authenticate()\n", 31 | " ee.Initialize()\n", 32 | "#else:\n", 33 | " # google earth engine already imported and authenticated\n", 34 | " \n", 35 | "\n", 36 | "import folium\n", 37 | "import pandas as pd\n", 38 | "import numpy as np\n", 39 | "from folium import plugins\n", 40 | "\n", 41 | "# Add custom basemaps to folium\n", 42 | "basemaps = {\n", 43 | " 'Google Maps': folium.TileLayer(\n", 44 | " tiles = 'https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}',\n", 45 | " attr = 'Google',\n", 46 | " name = 'Google Maps',\n", 47 | " overlay = True,\n", 48 | " control = True\n", 49 | " ),\n", 50 | " 'Google Satellite': folium.TileLayer(\n", 51 | " tiles = 'https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}',\n", 52 | " attr = 'Google',\n", 53 | " name = 'Google Satellite',\n", 54 | " overlay = True,\n", 55 | " control = True\n", 56 | " ),\n", 57 | " 'Google Terrain': folium.TileLayer(\n", 58 | " tiles = 'https://mt1.google.com/vt/lyrs=p&x={x}&y={y}&z={z}',\n", 59 | " attr = 'Google',\n", 60 | " name = 'Google Terrain',\n", 61 | " overlay = True,\n", 62 | " control = True\n", 63 | " ),\n", 64 | " 'Google Satellite Hybrid': folium.TileLayer(\n", 65 | " tiles = 'https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}',\n", 66 | " attr = 'Google',\n", 67 | " name = 'Google Satellite',\n", 68 | " overlay = True,\n", 69 | " control = True\n", 70 | " ),\n", 71 | " 'Esri Satellite': folium.TileLayer(\n", 72 | " tiles = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',\n", 73 | " attr = 'Esri',\n", 74 | " name = 'Esri Satellite',\n", 75 | " overlay = True,\n", 76 | " control = True\n", 77 | " )\n", 78 | "}\n", 79 | "\n", 80 | "# Define a method for displaying Earth Engine image tiles on a folium map.\n", 81 | "def add_ee_layer(self, ee_object, vis_params, name):\n", 82 | " \n", 83 | " try: \n", 84 | " # display ee.Image()\n", 85 | " if isinstance(ee_object, ee.image.Image): \n", 86 | " map_id_dict = ee.Image(ee_object).getMapId(vis_params)\n", 87 | " folium.raster_layers.TileLayer(\n", 88 | " tiles = map_id_dict['tile_fetcher'].url_format,\n", 89 | " attr = 'Google Earth Engine',\n", 90 | " name = name,\n", 91 | " overlay = True,\n", 92 | " control = True\n", 93 | " ).add_to(self)\n", 94 | " # display ee.ImageCollection()\n", 95 | " elif isinstance(ee_object, ee.imagecollection.ImageCollection): \n", 96 | " ee_object_new = ee_object.mosaic()\n", 97 | " map_id_dict = ee.Image(ee_object_new).getMapId(vis_params)\n", 98 | " folium.raster_layers.TileLayer(\n", 99 | " tiles = map_id_dict['tile_fetcher'].url_format,\n", 100 | " attr = 'Google Earth Engine',\n", 101 | " name = name,\n", 102 | " overlay = True,\n", 103 | " control = True\n", 104 | " ).add_to(self)\n", 105 | " # display ee.Geometry()\n", 106 | " elif isinstance(ee_object, ee.geometry.Geometry): \n", 107 | " folium.GeoJson(\n", 108 | " data = ee_object.getInfo(),\n", 109 | " name = name,\n", 110 | " overlay = True,\n", 111 | " control = True\n", 112 | " ).add_to(self)\n", 113 | " # display ee.FeatureCollection()\n", 114 | " elif isinstance(ee_object, ee.featurecollection.FeatureCollection): \n", 115 | " ee_object_new = ee.Image().paint(ee_object, 0, 2)\n", 116 | " map_id_dict = ee.Image(ee_object_new).getMapId(vis_params)\n", 117 | " folium.raster_layers.TileLayer(\n", 118 | " tiles = map_id_dict['tile_fetcher'].url_format,\n", 119 | " attr = 'Google Earth Engine',\n", 120 | " name = name,\n", 121 | " overlay = True,\n", 122 | " control = True\n", 123 | " ).add_to(self)\n", 124 | "\n", 125 | " except:\n", 126 | " print(\"Could not display {}\".format(name))\n", 127 | "\n", 128 | "\n", 129 | "\n", 130 | " \n", 131 | "# Add EE drawing method to folium.\n", 132 | "folium.Map.add_ee_layer = add_ee_layer" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": null, 138 | "metadata": {}, 139 | "outputs": [], 140 | "source": [ 141 | "print(\"MapVisualisation class imported - uses folium library\")\n" 142 | ] 143 | } 144 | ], 145 | "metadata": { 146 | "kernelspec": { 147 | "display_name": ".venv", 148 | "language": "python", 149 | "name": "python3" 150 | }, 151 | "language_info": { 152 | "codemirror_mode": { 153 | "name": "ipython", 154 | "version": 3 155 | }, 156 | "file_extension": ".py", 157 | "mimetype": "text/x-python", 158 | "name": "python", 159 | "nbconvert_exporter": "python", 160 | "pygments_lexer": "ipython3", 161 | "version": "3.13.0" 162 | }, 163 | "orig_nbformat": 4 164 | }, 165 | "nbformat": 4, 166 | "nbformat_minor": 2 167 | } 168 | -------------------------------------------------------------------------------- /MapVisualisation_test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "This following code contain test cases for the MapVisualisation Module.\n", 9 | "\n", 10 | "Version : 1.0\n", 11 | "Date : Feb 2023\n", 12 | "Author : Dr Milto Miltiaodu" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "%run MapVisualisation.ipynb\n", 22 | "\n", 23 | "\n", 24 | "# Set visualization parameters.\n", 25 | "vis_params = {\n", 26 | " 'min': 0,\n", 27 | " 'max': 4000,\n", 28 | " 'palette': ['006633', 'E5FFCC', '662A00', 'D8D8D8', 'F5F5F5']}\n", 29 | "\n", 30 | "# Create a folium map object.\n", 31 | "my_map = folium.Map(location=[40,-3], zoom_start=5, height=400)\n", 32 | "\n", 33 | "\n", 34 | "# Add custom basemaps\n", 35 | "basemaps['Google Maps'].add_to(my_map)\n", 36 | "basemaps['Google Satellite Hybrid'].add_to(my_map)\n", 37 | "\n", 38 | "# Add the elevation model to the map object.\n", 39 | "dem = ee.Image('USGS/SRTMGL1_003')\n", 40 | "my_map.add_ee_layer(dem.updateMask(dem.gt(0)), vis_params, 'DEM')\n", 41 | "\n", 42 | "# Display ee.Image\n", 43 | "dataset = ee.Image('JRC/GSW1_1/GlobalSurfaceWater')\n", 44 | "occurrence = dataset.select('occurrence');\n", 45 | "occurrenceVis = {'min': 0.0, 'max': 100.0, 'palette': ['ffffff', 'ffbbbb', '0000ff']}\n", 46 | "my_map.add_ee_layer(occurrence, occurrenceVis, 'JRC Surface Water')\n", 47 | "\n", 48 | "# Display ee.FeatureCollection\n", 49 | "countries = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017')\n", 50 | "geometry = countries.filter(ee.Filter.eq('country_na', 'Spain'))\n", 51 | "my_map.add_ee_layer(geometry, {}, 'Spain')\n", 52 | "\n", 53 | "\n", 54 | "# Add a layer control panel to the map.\n", 55 | "my_map.add_child(folium.LayerControl())\n", 56 | "plugins.Fullscreen().add_to(my_map)\n", 57 | "\n", 58 | "# Add a layer control panel to the map.\n", 59 | "my_map.add_child(folium.LayerControl())\n", 60 | "\n", 61 | "# Add fullscreen button\n", 62 | "plugins.Fullscreen().add_to(my_map)\n", 63 | "\n", 64 | "# Display the map.\n", 65 | "display(my_map)" 66 | ] 67 | } 68 | ], 69 | "metadata": { 70 | "kernelspec": { 71 | "display_name": "Python 3", 72 | "language": "python", 73 | "name": "python3" 74 | }, 75 | "language_info": { 76 | "codemirror_mode": { 77 | "name": "ipython", 78 | "version": 3 79 | }, 80 | "file_extension": ".py", 81 | "mimetype": "text/x-python", 82 | "name": "python", 83 | "nbconvert_exporter": "python", 84 | "pygments_lexer": "ipython3", 85 | "version": "3.13.0" 86 | }, 87 | "orig_nbformat": 4 88 | }, 89 | "nbformat": 4, 90 | "nbformat_minor": 2 91 | } 92 | -------------------------------------------------------------------------------- /Masks.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "The Masks Class handles, forest disturbances, surface water and land masks\n", 9 | "\n", 10 | "Version : 2.0\n", 11 | "Date : Feb 2023\n", 12 | "Author : Dr Milto Miltiadou" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "import sys\n", 22 | "\n", 23 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 24 | "modulename = 'ee'\n", 25 | "if modulename not in sys.modules: \n", 26 | " # import GEE and Authenticate, token or log in will be asked from web browser\n", 27 | " import ee\n", 28 | " ee.Authenticate()\n", 29 | " ee.Initialize()\n", 30 | "#else:\n", 31 | " # google earth engine already imported and authenticated\n", 32 | "\n", 33 | "modulename = 'ipynb_Utils'\n", 34 | "if modulename not in sys.modules:\n", 35 | " %run Utils.ipynb\n", 36 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 37 | " sys.modules['ipynb_Utils'] = None \n", 38 | "#else\n", 39 | " # Utils modules has already been loaded somewhere else" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "\n", 49 | "class Masks:\n", 50 | " # @param[in] self is this class\n", 51 | " # @param[in] geometry is a polygon\n", 52 | " # @param[in] self.gswBuffer is the pre-defined buffer used for surface water mask\n", 53 | " # @param[in] self.lmaskBuffer is the pre-defined buffer used for land surface mask\n", 54 | " # @param[in] self.forestMaskBuffer is the pre-defined buffer used for forest loss\n", 55 | " # @param[in] self.aspectAscBuffer is the pre-defined buffer used for aspects ascending maps masks - recommended 0\n", 56 | " # @param[in] self.aspectDesBuffer is the pre-defined buffer used for aspects descending maps masks - recommended 0\n", 57 | " def __init__(self, geometry, masks):\n", 58 | " self.gswBuffer = -1\n", 59 | " self.lmaskBuffer = -1\n", 60 | " self.forestMaskBuffer = -1\n", 61 | " self.forestMapStartDate = \"\"\n", 62 | " self.foestMapEndDate = \"\"\n", 63 | " self.aspectDesBuffer = -1\n", 64 | " self.aspectAscBuffer = -1\n", 65 | " self.forestYear = -1\n", 66 | " # aspects buffer will be 0 and not applied\n", 67 | " if(masks!={}):\n", 68 | " if ('gsw' in masks):\n", 69 | " self.gswBuffer = int(masks['gsw'])\n", 70 | "\n", 71 | " if ('lmask' in masks):\n", 72 | " self.lmaskBuffer = int(masks['lmask'])\n", 73 | "\n", 74 | " if ('aspectDes' in masks):\n", 75 | " self.aspectDesBuffer = int(masks['aspectDes'])\n", 76 | "\n", 77 | " if ('aspectAsc' in masks):\n", 78 | " self.aspectAscBuffer = int(masks['aspectAsc'])\n", 79 | "\n", 80 | " if ('forestMask' in masks):\n", 81 | " tmp = masks['forestMask']['buffer' ]\n", 82 | " self.startDate = masks['forestMask']['startDate']\n", 83 | " self.endDate = masks['forestMask']['endDate' ]\n", 84 | " self.forestMaskBuffer = tmp\n", 85 | " self.forestMapStartDate = self.startDate\n", 86 | " self.foestMapEndDate = self.endDate \n", 87 | " \n", 88 | "\n", 89 | " \n", 90 | " self.dem = ee.Image('NASA/NASADEM_HGT/001').select('elevation').clip(geometry)\n", 91 | " self.aspect = ee.Terrain.aspect(self.dem)\n", 92 | " \n", 93 | " self.asc = (self.aspect.gt(202.5).And(self.aspect.lt(337.5)))\n", 94 | " self.asc = filterSpeckles(self.asc,5,'circle')\n", 95 | "\n", 96 | " self.des = (self.aspect.gt(22.5).And(self.aspect.lt(157.5)))\n", 97 | " self.des = filterSpeckles(self.des,5,'circle')\n", 98 | " \n", 99 | " # load ground surface water\n", 100 | " gsw = ee.Image(\"JRC/GSW1_4/GlobalSurfaceWater\")\n", 101 | " self.occurrence = gsw.select('occurrence')\n", 102 | " # load a land mask\n", 103 | " self.landMask = ee.Image('CGIAR/SRTM90_V4').clip(geometry).mask()\n", 104 | " # Load Global Forest Change Data\n", 105 | " # \"The Hansen et al. (2013) Global Forest Change dataset in Earth\n", 106 | " # Engine represents forest change, at 30 meters resolution, globally, between 2000 and 2021.\"\n", 107 | " # These data are updated annually\n", 108 | " gfc2021 = ee.Image(\"UMD/hansen/global_forest_change_2022_v1_10\").clip(geometry)\n", 109 | " # extract the band that gives me which year was each tree covered area lost\n", 110 | " self.lossYear = gfc2021.select(['lossyear']).clip(geometry)\n", 111 | " self.loss = gfc2021.select(['loss']).clip(geometry)\n", 112 | "\n", 113 | " self.combinedMask = ee.Image(1).clip(geometry)\n", 114 | " self.isCombineMaskCalculated = False\n", 115 | " self.geometry = geometry\n", 116 | "\n", 117 | " def updateNoSurfaceWaterMask(self, image):\n", 118 | " if (self.gswBuffer > 0):\n", 119 | " gswMask = addBuffer(\n", 120 | " self.occurrence, self.gswBuffer).unmask(-999).eq(-999)\n", 121 | " return image.updateMask(gswMask)\n", 122 | " else:\n", 123 | " print(\"WARNING: Water mask not applied. Buffer needs to be > 0\")\n", 124 | " return image\n", 125 | "\n", 126 | " def updateLandMask(self, image):\n", 127 | " if (self.lmaskBuffer > 0):\n", 128 | " lmask = addBuffer(self.landMask, self.lmaskBuffer).add(1)\n", 129 | " return image.updateMask(lmask)\n", 130 | " else:\n", 131 | " print(\"WARNING: land mask not applied. Buffer needs to be > 0\")\n", 132 | " return image\n", 133 | "\n", 134 | " def updateAscMask(self, image):\n", 135 | " return image.updateMask(self.asc)\n", 136 | "\n", 137 | " def updateDesMask(self, image):\n", 138 | " return image.updateMask(self.des)\n", 139 | "\n", 140 | " def updateForestLostMask(self, image):\n", 141 | " year = 2017\n", 142 | " yearNo = year - 2000\n", 143 | " YOI = self.lossYear.where(self.lossYear.gt(yearNo), 0)\n", 144 | " result = YOI.where(YOI.gt(0), 1)\n", 145 | " resultUnmasked = result.unmask(0)\n", 146 | " return image.updateMask(addBufferRename(result, self.forestMaskBuffer,\"forestLost200017\").unmask(-999).eq(-999))\n", 147 | "\n", 148 | " def getAscAspects(self):\n", 149 | " return self.asc\n", 150 | "\n", 151 | " def getDesAspects(self):\n", 152 | " return self.des\n", 153 | "\n", 154 | " def getAspects(self):\n", 155 | " return self.aspect\n", 156 | "\n", 157 | " # @brief method that merges land surface and ocean/sea water into a single mask\n", 158 | " # @brief buffer amount of meters to be added around the water areas\n", 159 | " # @return the land mask of does not contain surface water\n", 160 | " def getNoSurfaceWaterMask(self, buffer):\n", 161 | " # Load a map containing the global surface water\n", 162 | " return addBufferRename(self.occurrence, buffer,\"gwmBuffered\").unmask(-999).eq(-999)\n", 163 | "\n", 164 | " # method that return land mask\n", 165 | " # buffer amount of meters to be added around the water areas\n", 166 | " # @return the land mask of does not contain surface water\n", 167 | " def getlandMask(self, buffer):\n", 168 | " return addBufferRename(self.landMask, buffer,\"landMaskBuffered \").add(1)\n", 169 | "\n", 170 | " def getForestLostMask(self, startdate, enddate, buffer):\n", 171 | " startyear = ee.Date(startdate).get('year')\n", 172 | " endyear = ee.Date(enddate).get('year')\n", 173 | " startyearNo = startyear.subtract(2000)\n", 174 | " endyearNo = endyear.subtract(2000)\n", 175 | "\n", 176 | " YOI = self.lossYear.where(self.lossYear.gt(endyearNo), -1)\n", 177 | " YOI = YOI.where(YOI.lt(startyearNo), 0)\n", 178 | " YOI = YOI.where(YOI.gt(startyearNo), 1)\n", 179 | " # buffer is required to clear previous history before unmasking\n", 180 | " return addBufferRename(YOI, buffer,\"forestLost\").unmask(-999).eq(-999)\n", 181 | "\n", 182 | " # method that merges land surface and ocean/sea water into a single mask\n", 183 | " # buffer amount of meters to be added around the water areas\n", 184 | " # @return the land mask of does not contain surface water\n", 185 | " def getNoSurfaceWaterMaskNoBuffer(self):\n", 186 | " # Load a map containing the global surface water\n", 187 | " return self.occurrence.unmask(-999).eq(-999)\n", 188 | "\n", 189 | " # method that return land mask\n", 190 | " # buffer amount of meters to be added around the water areas\n", 191 | " # @return the land mask of does not contain surface water\n", 192 | " def getlandMaskNoBuffer(self):\n", 193 | " return self.landMask.add(1)\n", 194 | "\n", 195 | " # Method that create a composite mask according to the input provided in the constructors\n", 196 | "\n", 197 | " def calculateCombinedMask(self):\n", 198 | " self.combinedMask = ee.Image(1).clip(self.geometry)\n", 199 | "\n", 200 | " if (self.gswBuffer > 0):\n", 201 | " self.combinedMask = self.combinedMask.And(\n", 202 | " self.getNoSurfaceWaterMask(self.gswBuffer))\n", 203 | "\n", 204 | " if (self.lmaskBuffer > 0):\n", 205 | " self.combinedMask = self.combinedMask.And(\n", 206 | " self.getlandMask(self.lmaskBuffer))\n", 207 | "\n", 208 | " if (self.forestMaskBuffer>0 and self.forestMapStartDate!=\"\" and self.foestMapEndDate!=\"\"):\n", 209 | " self.combinedMask = self.combinedMask.And(self.getForestLostMask( \\\n", 210 | " self.forestMapStartDate,self.foestMapEndDate,self.forestMaskBuffer))\n", 211 | "\n", 212 | " if (self.aspectDesBuffer == 0):\n", 213 | " self.combinedMask = self.combinedMask.And(self.des)\n", 214 | "\n", 215 | " if (self.aspectAscBuffer == 0):\n", 216 | " self.combinedMask = self.combinedMask.And(self.asc)\n", 217 | "\n", 218 | " self.isCombineMaskCalculated = True\n", 219 | " return self.combinedMask\n", 220 | "\n", 221 | " def updateCombinedMask(self, image):\n", 222 | " return image.updateMask(self.combinedMask)\n", 223 | "\n", 224 | " def exportCombinedMaskToDrive(self, scale, description, folder, projection):\n", 225 | " if (not self.isCombineMaskCalculated):\n", 226 | " self.calculateCombinedMask()\n", 227 | " # else:\n", 228 | " # combined mask has already been calculated\n", 229 | " # print(self.geometry.getInfo()['coordinates'])\n", 230 | "\n", 231 | " print(\"STARTING BATCH SCRIPT FOR EXPORTING FILE\")\n", 232 | " task = ee.batch.Export.image.toDrive(**{\n", 233 | " 'image': self.combinedMask,\n", 234 | " 'description': description,\n", 235 | " 'folder': folder,\n", 236 | " # 'crs' : projection,\n", 237 | " 'region': self.geometry.getInfo()['coordinates'],\n", 238 | " 'scale': scale,\n", 239 | " 'maxPixels': 1549491660\n", 240 | " })\n", 241 | " task.start()\n", 242 | "\n", 243 | " print(\"***END OF CALLING BATCH SCRIPT\")\n" 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": null, 249 | "metadata": {}, 250 | "outputs": [], 251 | "source": [ 252 | "print(\"Masks class imported\")" 253 | ] 254 | } 255 | ], 256 | "metadata": { 257 | "kernelspec": { 258 | "display_name": ".venv", 259 | "language": "python", 260 | "name": "python3" 261 | }, 262 | "language_info": { 263 | "codemirror_mode": { 264 | "name": "ipython", 265 | "version": 3 266 | }, 267 | "file_extension": ".py", 268 | "mimetype": "text/x-python", 269 | "name": "python", 270 | "nbconvert_exporter": "python", 271 | "pygments_lexer": "ipython3", 272 | "version": "3.13.0" 273 | }, 274 | "orig_nbformat": 4 275 | }, 276 | "nbformat": 4, 277 | "nbformat_minor": 2 278 | } 279 | -------------------------------------------------------------------------------- /Masks_test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "@Authors Dr Milto Miltiadou" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "\n", 17 | "import sys\n", 18 | "\n", 19 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 20 | "modulename = 'Masks'\n", 21 | "if modulename not in sys.modules:\n", 22 | " # import Masks Class\n", 23 | " %run Masks.ipynb\n", 24 | "# else:\n", 25 | " # Mask class already imported and authenticated\n", 26 | "\n", 27 | "\n", 28 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 29 | "modulename = 'ee'\n", 30 | "if modulename not in sys.modules:\n", 31 | " # import GEE and Authenticate, token or log in will be asked from web browser\n", 32 | " import ee\n", 33 | " # ee.Authenticate()\n", 34 | " ee.Initialize()\n", 35 | "# else:\n", 36 | " # google earth engine already imported and authenticated\n", 37 | "\n", 38 | "\n", 39 | "\n", 40 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 41 | "modulename = 'ipynb_Sentinel1'\n", 42 | "if modulename not in sys.modules:\n", 43 | " %run Sentinel1.ipynb\n", 44 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 45 | " sys.modules['ipynb_Sentinel1'] = None \n", 46 | "#else\n", 47 | " # Sentinel1 modules has already been loaded somewhere else\n", 48 | "\n", 49 | "\n", 50 | "# Visualisation\n", 51 | "modulename = 'ipynb_MapVisualisation'\n", 52 | "if modulename not in sys.modules:\n", 53 | " %run MapVisualisation.ipynb\n", 54 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 55 | " sys.modules['ipynb_MapVisualisation'] = None \n", 56 | "#else\n", 57 | " # MapVisualisation module has already been loaded somewhere else\n" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": null, 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "startDate = '2017-01-01'\n", 67 | "endDate = '2017-12-31'\n", 68 | "\n", 69 | "countries = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017')\n", 70 | "geometry = countries.filter(ee.Filter.eq('country_na', 'Cyprus'))\n", 71 | "\n", 72 | "fstartDate = '2001-01-01'\n", 73 | "fendDate = '2022-12-31'\n", 74 | "\n", 75 | "masks = {\n", 76 | " 'gsw': 300, \n", 77 | " 'lmask': 30, \n", 78 | " #'aspectAsc':0, \n", 79 | " 'forestMask': {\n", 80 | " 'buffer':30, \n", 81 | " 'startDate':fstartDate, \n", 82 | " 'endDate':fendDate}\n", 83 | " } #,'aspectDes':0, # 'aspectAsc':0}\n", 84 | "#masks = {}\n", 85 | "\n", 86 | "s1 = Sentinel1(geometry,startDate,endDate,masks,True)\n", 87 | "\n", 88 | "# remove images from the collection \n", 89 | "# in Sentinel 1 there is an internal removePeriod command for removing the selected period \n", 90 | "# for all VV, VH asc and desc respectively \n", 91 | "# This is why the removePeriodFromCollection() function is not used\n", 92 | "s1.removePeriod(startDate,'2017-01-10')\n", 93 | "s1.byMonth(2017)\n", 94 | "\n", 95 | "VVAsc = s1.getVVAsc()\n", 96 | "VHAsc = s1.getVHAsc()\n", 97 | "VVDes = s1.getVVDes()\n", 98 | "VHDes = s1.getVHDes()\n", 99 | "\n", 100 | "\n", 101 | "\n", 102 | "VVAscMedian = s1.getVVAscMedian()\n", 103 | "VHAscMedian = s1.getVHAscMedian()\n", 104 | "VVDesMedian = s1.getVVDesMedian()\n", 105 | "VHDesMedian = s1.getVHDesMedian()\n", 106 | "\n" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "\n", 116 | "# Set visualization parameters.\n", 117 | "#vis_params = {\n", 118 | "# 'min': 0,\n", 119 | "# 'max': 4000,\n", 120 | "# 'palette': ['006633', 'E5FFCC', '662A00', 'D8D8D8', 'F5F5F5']}\n", 121 | "\n", 122 | "# Create a folium map object.\n", 123 | "my_map = folium.Map(location=[33,34], zoom_start=6)\n", 124 | "\n", 125 | "\n", 126 | "# Add custom basemaps\n", 127 | "basemaps['Google Maps'].add_to(my_map)\n", 128 | "basemaps['Google Satellite Hybrid'].add_to(my_map)\n", 129 | "\n", 130 | "\n", 131 | "s1.byMonth(2017)\n", 132 | "\n", 133 | "# Add VVAsc to the map\n", 134 | "my_map.add_ee_layer(VVAscMedian, {'min':-30,'max':0}, 'VVAscMedian')\n", 135 | "# Add VHAsc tp the map \n", 136 | "my_map.add_ee_layer(VHAscMedian, {'min':-30,'max':0}, 'VHAscMedian')\n", 137 | "# Add VVDes to the map \n", 138 | "my_map.add_ee_layer(VVDesMedian, {'min':-30,'max':0}, 'VVDesMedian')\n", 139 | "# Add descending aspects map to the map\n", 140 | "my_map.add_ee_layer(VHDesMedian, {'min':-30,'max':0}, 'VHDesMedian')\n", 141 | "\n", 142 | "\n", 143 | "# remove images from the collection \n", 144 | "# NEVER apply this to a single aspect, always call internal Sentinel 1 member function\n", 145 | "# to be applied to all VVAsc, VHAsc, VVDes, VHDes\n", 146 | "# VHAsc = removePeriodFromCollection(VHAsc,startDate,'2017-04-30')\n", 147 | "# ** Use this method for removing period instead before requesting the data*** \n", 148 | "# s1 = s1.removePeriod(startDate,'2017-04-30')\n", 149 | "\n", 150 | "#VV12 = VVDes.filter(ee.Filter.calendarRange(1, 3, 'month'))#.mean().set('month',1)\n", 151 | "#my_map.add_ee_layer(VV12, {'min':-30,'max':0}, 'VV12')\n", 152 | "\n", 153 | "#print(VHAsc.getInfo())\n", 154 | "\n", 155 | "#print(\"No remaining images: \", getNoOfBands(VVAsc))\n", 156 | "# Add a layer control panel to the map.\n", 157 | "my_map.add_child(folium.LayerControl())\n", 158 | "\n", 159 | "\n", 160 | "plugins.Fullscreen().add_to(my_map)\n", 161 | "\n", 162 | "# Add a layer control panel to the map.\n", 163 | "my_map.add_child(folium.LayerControl())\n", 164 | "\n", 165 | "# Add fullscreen button\n", 166 | "plugins.Fullscreen().add_to(my_map)\n", 167 | "\n", 168 | "# Display the map.\n", 169 | "display(my_map)\n", 170 | "\n" 171 | ] 172 | } 173 | ], 174 | "metadata": { 175 | "kernelspec": { 176 | "display_name": ".venv", 177 | "language": "python", 178 | "name": "python3" 179 | }, 180 | "language_info": { 181 | "codemirror_mode": { 182 | "name": "ipython", 183 | "version": 3 184 | }, 185 | "file_extension": ".py", 186 | "mimetype": "text/x-python", 187 | "name": "python", 188 | "nbconvert_exporter": "python", 189 | "pygments_lexer": "ipython3", 190 | "version": "3.13.0" 191 | }, 192 | "orig_nbformat": 4 193 | }, 194 | "nbformat": 4, 195 | "nbformat_minor": 2 196 | } 197 | -------------------------------------------------------------------------------- /MergingLib.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 3, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import sys\n", 10 | "import os\n", 11 | "import shutil\n", 12 | "import glob\n", 13 | "\n", 14 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 15 | "modulename = 'PlotsManager'\n", 16 | "if modulename not in sys.modules: \n", 17 | " # import GEE and Authenticate, token or log in will be asked from web browser\n", 18 | " %run PlotsManager.ipynb\n", 19 | "else:\n", 20 | " print('PlotsManager already imported')\n", 21 | " # google earth engine already imported and authenticated" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 4, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "\n", 31 | "\n", 32 | "class MergingLib:\n", 33 | " # @param[in] nameOfCSVFolderDir the directory that contains all the exported\n", 34 | " # .csv files. You need to download and extract this folder from Google Drive\n", 35 | " # @param[in] fieldDataWithIdentifiers after each run, this is found at the \n", 36 | " # same directory as PlotToSat .ipynb files. It is the field data with an extra\n", 37 | " # column named \"indexField\". This column saves some identifiers used to merge\n", 38 | " # the field data with the exported EO spectral temporal signatures\n", 39 | " def __init__(self,nameOfCSVFolderDir,fieldDataWithIdentifiers):\n", 40 | " self.plotsStd = PlotsManager(fieldDataWithIdentifiers)\n", 41 | " self.plotsMean = PlotsManager(fieldDataWithIdentifiers)\n", 42 | " self.nameOfCSVFolderDir = nameOfCSVFolderDir\n", 43 | "\n", 44 | " self.ResDir = os.path.join(self.nameOfCSVFolderDir,\"MergedCsvs\")\n", 45 | " if os.path.isdir(self.ResDir):\n", 46 | " print (\"MergedCsvs Exist. Deleting all of its content!\")\n", 47 | " shutil.rmtree(self.ResDir)\n", 48 | " os.mkdir(self.ResDir)\n", 49 | " self.ListS1Mean=[]\n", 50 | " self.ListS1StdD=[]\n", 51 | " self.ListS2Mean=[]\n", 52 | " self.ListS2StdD=[]\n", 53 | " self.ListS1MeanAsc=[]\n", 54 | " self.ListS1StdDAsc=[]\n", 55 | " self.ListS1MeanDes=[]\n", 56 | " self.ListS1StdDDes=[] \n", 57 | "\n", 58 | " \n", 59 | "\n", 60 | " for file1 in glob.glob(nameOfCSVFolderDir+\"/*.csv\"):\n", 61 | " S1Mean=\"S1_mean.csv\"\n", 62 | " S1StdD=\"S1_stdD.csv\"\n", 63 | " S2Mean=\"S2_mean.csv\"\n", 64 | " S2StdD=\"S2_stdD.csv\"\n", 65 | " S1MeanAsc=\"S1Asc_mean.csv\"\n", 66 | " S1StdDAsc=\"S1Asc_stdD.csv\"\n", 67 | " S1MeanDes=\"S1Des_mean.csv\"\n", 68 | " S1StdDDes=\"S1Des_stdD.csv\"\n", 69 | " if(len(file1)>11):\n", 70 | " if (file1[len(file1)-11:len(file1)]==S1Mean):\n", 71 | " self.ListS1Mean=self.ListS1Mean+[file1]\n", 72 | " elif (file1[len(file1)-11:len(file1)]==S1StdD):\n", 73 | " self.ListS1StdD=self.ListS1StdD+[file1]\n", 74 | "\n", 75 | " elif (file1[len(file1)-14:len(file1)]==S1MeanAsc):\n", 76 | " self.ListS1MeanAsc=self.ListS1MeanAsc+[file1]\n", 77 | " elif (file1[len(file1)-14:len(file1)]==S1StdDAsc):\n", 78 | " self.ListS1StdDAsc=self.ListS1StdDAsc+[file1]\n", 79 | " elif (file1[len(file1)-14:len(file1)]==S1MeanDes):\n", 80 | " self.ListS1MeanDes=self.ListS1MeanDes+[file1]\n", 81 | " elif (file1[len(file1)-14:len(file1)]==S1StdDDes):\n", 82 | " self.ListS1StdDDes=self.ListS1StdDDes+[file1]\n", 83 | " \n", 84 | " elif (file1[len(file1)-11:len(file1)]==S2Mean):\n", 85 | " self.ListS2Mean=self.ListS2Mean+[file1]\n", 86 | " elif (file1[len(file1)-11:len(file1)]==S2StdD):\n", 87 | " self.ListS2StdD=self.ListS2StdD+[file1]\n", 88 | " elif (file1[len(file1)-11:len(file1)]==\"tifiers.csv\"): #in case field data are added in the nameOfCSVFolderDir\n", 89 | " print(file1 , \" is suspected to be the field data file\")\n", 90 | " else :\n", 91 | " print(\"WARNING: \", file1, \" is ignored since it is not recognised as an output of the system\")\n", 92 | "\n", 93 | "\n", 94 | "\n", 95 | " print (\"ListS1Mean \",len(self.ListS1Mean),\n", 96 | " \" ListS2Mean \", len(self.ListS2Mean), \n", 97 | " \" ListS2MeanAsc \", len(self.ListS1MeanAsc), \n", 98 | " \" ListS1MeanDes \", len(self.ListS1MeanDes), \n", 99 | " \"\\nListS1StdD \", len(self.ListS1StdD), \n", 100 | " \" ListS2StdD \", len(self.ListS2StdD), \n", 101 | " \" ListS1StdDAsc \", len(self.ListS1StdDAsc), \n", 102 | " \" ListS1StdDDes \", len(self.ListS1StdDDes))\n", 103 | "\n", 104 | "\n", 105 | " ## @brief method that merges all the exported data from PlotToSat into \n", 106 | " # a single .csv file\n", 107 | " def mergeAll(self):\n", 108 | " # first item used to name merged files - check if no data where exported\n", 109 | " firstItem = None\n", 110 | " if(self.ListS1Mean!=[]):\n", 111 | " firstItem=self.ListS1Mean[0]\n", 112 | " elif (self.ListS2Mean!=[]):\n", 113 | " firstItem=self.ListS2Mean[0]\n", 114 | " elif (self.ListS1MeanDes!=[]):\n", 115 | " firstItem=self.ListS1MeanDes[0]\n", 116 | " elif (self.ListS1MeanAsc!=[]):\n", 117 | " firstItem=self.ListS1MeanAsc[0]\n", 118 | " else :\n", 119 | " raise Exception (\"ERROR: no data found. Both Sentinel-1 and Sentinel-2 lists are empty\")\n", 120 | " \n", 121 | " for filename in self.ListS1Mean :\n", 122 | " self.plotsMean.addInfo(filename)\n", 123 | "\n", 124 | " for filename in self.ListS1MeanAsc :\n", 125 | " self.plotsMean.addInfo(filename)\n", 126 | " for filename in self.ListS1MeanDes :\n", 127 | " self.plotsMean.addInfo(filename)\n", 128 | " \n", 129 | " for filename in self.ListS2Mean :\n", 130 | " self.plotsMean.addInfo(filename)\n", 131 | "\n", 132 | " for filename in self.ListS1StdD :\n", 133 | " self.plotsStd.addInfo(filename)\n", 134 | " \n", 135 | " for filename in self.ListS1StdDAsc :\n", 136 | " self.plotsStd.addInfo(filename)\n", 137 | " for filename in self.ListS1StdDDes :\n", 138 | " self.plotsStd.addInfo(filename)\n", 139 | "\n", 140 | " for filename in self.ListS2StdD :\n", 141 | " self.plotsStd.addInfo(filename)\n", 142 | " \n", 143 | " #self.plotsMean.print()\n", 144 | " \n", 145 | " fileNames = firstItem[0:len(firstItem)-34] \n", 146 | " head, fileNames = os.path.split(fileNames)\n", 147 | " outMean = self.ResDir+\"/\"+fileNames+\"_mean.csv\"\n", 148 | " outstdD = self.ResDir+\"/\"+fileNames+\"_stdD.csv\"\n", 149 | "\n", 150 | " self.plotsMean.exportTo(outMean)\n", 151 | " self.plotsStd.exportTo(outstdD)\n", 152 | " \n", 153 | " print(\"Results are stored in \", self.ResDir)" 154 | ] 155 | } 156 | ], 157 | "metadata": { 158 | "kernelspec": { 159 | "display_name": ".venv", 160 | "language": "python", 161 | "name": "python3" 162 | }, 163 | "language_info": { 164 | "codemirror_mode": { 165 | "name": "ipython", 166 | "version": 3 167 | }, 168 | "file_extension": ".py", 169 | "mimetype": "text/x-python", 170 | "name": "python", 171 | "nbconvert_exporter": "python", 172 | "pygments_lexer": "ipython3", 173 | "version": "3.13.0" 174 | }, 175 | "orig_nbformat": 4 176 | }, 177 | "nbformat": 4, 178 | "nbformat_minor": 2 179 | } 180 | -------------------------------------------------------------------------------- /MergingLib_test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "\n", 10 | "\n", 11 | "%run MergingLib.ipynb\n" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "# \"outPlotFileWithIDs\" used when defining the plot related dictionary\n", 21 | "\n", 22 | "gdriveFolderDir = r\"ResultsOfTestCases\\folderSpain1\"\n", 23 | "fieldDataWithIdentifiers = r\"plotsWithIDs\\SpainIDs_1.csv\"\n", 24 | "mergingTool = MergingLib(gdriveFolderDir,fieldDataWithIdentifiers)\n", 25 | "\n", 26 | "mergingTool.mergeAll()" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [] 33 | } 34 | ], 35 | "metadata": { 36 | "kernelspec": { 37 | "display_name": "Python 3", 38 | "language": "python", 39 | "name": "python3" 40 | }, 41 | "language_info": { 42 | "codemirror_mode": { 43 | "name": "ipython", 44 | "version": 3 45 | }, 46 | "file_extension": ".py", 47 | "mimetype": "text/x-python", 48 | "name": "python", 49 | "nbconvert_exporter": "python", 50 | "pygments_lexer": "ipython3", 51 | "version": "3.13.0" 52 | }, 53 | "orig_nbformat": 4 54 | }, 55 | "nbformat": 4, 56 | "nbformat_minor": 2 57 | } 58 | -------------------------------------------------------------------------------- /PlotToSat.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "This module manages brings together all the classes for extracting time-series at multiple plot locations\n", 9 | " \n", 10 | "author: Dr Milto Miltiadou\n", 11 | "version: 1.0" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import sys\n", 21 | "\n", 22 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 23 | "modulename = 'ee'\n", 24 | "if modulename not in sys.modules: \n", 25 | " # import GEE and Authenticate, token or log in will be asked from web browser\n", 26 | " import ee\n", 27 | " ee.Authenticate()\n", 28 | " ee.Initialize()\n", 29 | " print (\"Importing GEE\")\n", 30 | "else:\n", 31 | " print('GEE already imported')\n", 32 | " # google earth engine already imported and authenticated\n", 33 | "\n", 34 | "\n", 35 | "modulename = 'ipynb_masks'\n", 36 | "if modulename not in sys.modules:\n", 37 | " %run Masks.ipynb\n", 38 | " sys.modules['ipynb_masks'] = None\n", 39 | "#else\n", 40 | " # module already loaded\n", 41 | "\n", 42 | "modulename = 'ipynb_Utils'\n", 43 | "if modulename not in sys.modules:\n", 44 | " import Utils\n", 45 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 46 | " sys.modules['ipynb_Utils'] = None \n", 47 | "#else\n", 48 | " # Utils modules has already been loaded somewhere else\n", 49 | "\n", 50 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 51 | "modulename = 'ipynb_Sentinel1'\n", 52 | "if modulename not in sys.modules:\n", 53 | " %run Sentinel1.ipynb\n", 54 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 55 | " sys.modules['ipynb_Sentinel1'] = None \n", 56 | "#else\n", 57 | " # Sentinel1 modules has already been loaded somewhere else\n", 58 | "\n", 59 | "modulename = 'ipynb_Sentinel2b'\n", 60 | "if modulename not in sys.modules:\n", 61 | " %run Sentinel2b.ipynb\n", 62 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 63 | " sys.modules['ipynb_Sentinel2b'] = None \n", 64 | "#else\n", 65 | " # Utils modules has already been loaded somewhere else\n", 66 | "\n", 67 | "import sys\n", 68 | "modulename = 'ipynb_FieldData'\n", 69 | "if modulename not in sys.modules:\n", 70 | " %run FieldData.ipynb\n", 71 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 72 | " sys.modules['FieldData'] = None \n", 73 | "#else\n", 74 | " # Utils modules has already been loaded somewhere else\n", 75 | "\n", 76 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 77 | "modulename = 'Masks'\n", 78 | "if modulename not in sys.modules:\n", 79 | " # import Masks Class\n", 80 | " %run Masks.ipynb\n", 81 | "# else:\n", 82 | " # Mask class already imported and authenticated\n", 83 | " \n", 84 | "modulename = 'Polygons'\n", 85 | "if modulename not in sys.modules:\n", 86 | " %run Polygons.ipynb\n", 87 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 88 | " sys.modules['ipynb_Polygons'] = None \n", 89 | "#else\n", 90 | " # Utils modules has already been loaded somewhere else\n", 91 | "\n", 92 | "\n" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "import pandas as pd\n", 102 | "import numbers\n", 103 | "from datetime import date\n", 104 | "\n", 105 | "class PlotToSat:\n", 106 | "## @brief method that sets the year of interest\n", 107 | " def setYear(self,year):\n", 108 | " # check each collection to see if year exist and set valid to false if year doesn't exist\n", 109 | " self.year = year\n", 110 | " startdate = str(self.year)+'-01-01'\n", 111 | " enddate = str(self.year)+'-12-31'\n", 112 | " today = date.today()\n", 113 | " cy = today.year\n", 114 | " if (cy==year): \n", 115 | " enddate = today.strftime(\"%Y-%m-%d\")\n", 116 | " # S1\n", 117 | " ly = int(self.sentinel1Info[\"launchDate\" ][0:4])\n", 118 | " ry = int(self.sentinel1Info[\"retirmentDate\"][0:4])\n", 119 | " if (yearry):\n", 120 | " print(\"WARNING: Sentinel-1 data will not be exported as the year requested is before launched date or after retirement date.\")\n", 121 | " self.sentinel1Info[\"startDate\"]=\"\"\n", 122 | " self.sentinel1Info[\"endDate\" ]=\"\"\n", 123 | " elif (year==ly):\n", 124 | " self.sentinel1Info[\"startDate\"]=self.sentinel1Info[\"launchDate\"]\n", 125 | " self.sentinel1Info[\"endDate\"]=enddate\n", 126 | " elif (year==ry):\n", 127 | " self.sentinel1Info[\"startDate\"]=startdate\n", 128 | " self.sentinel1Info[\"endDate\"]=self.sentinel1Info[\"retirmentDate\"]\n", 129 | " else :\n", 130 | " self.sentinel1Info[\"startDate\"]=startdate\n", 131 | " self.sentinel1Info[\"endDate\"]=enddate\n", 132 | " # S2\n", 133 | " ly = int(self.sentinel2Info[\"launchDate\"][0:4])\n", 134 | " if (yearry):\n", 135 | " print(\"WARNING: Sentinel-2 data will not be exported as the year requested is before launched date.\")\n", 136 | " self.sentinel2Info[\"startDate\"]=\"\"\n", 137 | " self.sentinel2Info[\"endDate\" ]=\"\"\n", 138 | " elif (year==ly):\n", 139 | " self.sentinel2Info[\"startDate\"]=self.sentinel2Info[\"launchDate\"]\n", 140 | " self.sentinel2Info[\"endDate\"]=enddate\n", 141 | " elif (year==ry):\n", 142 | " self.sentinel2Info[\"startDate\"]=startdate\n", 143 | " self.sentinel2Info[\"endDate\"]=self.sentinel2Info[\"retirmentDate\"]\n", 144 | " else :\n", 145 | " self.sentinel2Info[\"startDate\"]=startdate\n", 146 | " self.sentinel2Info[\"endDate\"]=enddate\n", 147 | "\n", 148 | " print (\"Sentinel1: \",self.sentinel1Info[\"startDate\"] ,self.sentinel1Info[\"endDate\"])\n", 149 | " print (\"Sentinel2: \",self.sentinel2Info[\"startDate\"] ,self.sentinel2Info[\"endDate\"])\n", 150 | "\n", 151 | "\n", 152 | "\n", 153 | "\n", 154 | " ## Method that adds the field data to the Manager\n", 155 | " # \n", 156 | " # @param[in] properties: a dictionary containing the name of the csv file containing the \n", 157 | " # plot iformation, the projection and \n", 158 | " # the name of the column that give the year that the field data were collected\n", 159 | " # e.g. {'csvfilename':csvFile,'proj':\"EPSG:3042\", labelOfyearCol':\"year\"}\n", 160 | "\n", 161 | " def addFieldData(self,properties): # csvfile,proj,yearlabel):\n", 162 | "\n", 163 | " self.csvDF = FieldData(properties)\n", 164 | " self.maxSize = self.csvDF.getLen()\n", 165 | "\n", 166 | "\n", 167 | "\n", 168 | " def __init__(self,geometry,fieldDataProperties,year):\n", 169 | " self.samplingSize = 400\n", 170 | " self.sampleCount = 0\n", 171 | " self.maxSize = 0\n", 172 | " self.csvDF = None\n", 173 | " self.year = 2017\n", 174 | " self.shp = None\n", 175 | " self.shpPolygons = None\n", 176 | " self.shpPolKeyCol = None\n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " # else already false and reading plot data from csv file\n", 181 | " self.filedataFilewithIDs = \"fieldDataWithIdentifiers/FieldDataWithIdentifiers.csv\"\n", 182 | " #----------------------------------------------------\n", 183 | " # Initialise collection \n", 184 | " #----------------------------------------------------\n", 185 | " todayDate = date.today().strftime(\"%Y-%m-%d\")\n", 186 | " #----------------------------------------------------\n", 187 | " self.sentinel1Info = {\n", 188 | " \"identifier\" : 'sentinel-1',\n", 189 | " \"collection\" : \"COPERNICUS/S1_GRD\",\n", 190 | " \"launchDate\" : \"2014-04-01\",\n", 191 | " \"retirmentDate\" : todayDate, #not retired yet\n", 192 | " \"startDate\" : \"\",\n", 193 | " \"endDate\" : \"\",\n", 194 | " \"added\" : False,\n", 195 | " \"aspects\" : False,\n", 196 | " \"selectedBand\" : [],\n", 197 | " \"selectedIndices\": [],\n", 198 | " } \n", 199 | " #----------------------------------------------------\n", 200 | " self.sentinel2Info = {\n", 201 | " \"identifier\" : 'sentinel-2', \n", 202 | " \"collection\" : \"COPERNICUS/S2_SR_HARMONIZED\",\n", 203 | " \"launchDate\" : \"2015-05-23\",\n", 204 | " \"retirmentDate\" :todayDate, #not retired yet\n", 205 | " \"startDate\" : \"\",\n", 206 | " \"endDate\" : \"\",\n", 207 | " \"added\" : False,\n", 208 | " \"clouds\" : 50,\n", 209 | " \"selectedBand\" : ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B9', 'B11', 'B12'],\n", 210 | " \"selectedIndices\": [],\n", 211 | " } \n", 212 | "\n", 213 | "\n", 214 | "\n", 215 | " self.numberOfAvailableCols = 2\n", 216 | " # the labels of the available collections\n", 217 | " self.listOfAvailableCollections = ['sentinel-1', 'sentinel-2']\n", 218 | " self.listOfAvailCollections = [sentinel1Info,sentinel2Info]\n", 219 | " # else list have the smae size and it is correct\n", 220 | " self.geometry = geometry\n", 221 | "\n", 222 | " self.fiedplotData = None\n", 223 | " self.proj = \"EPSG:3042\"\n", 224 | " self.masks = {}\n", 225 | " self.year = 0\n", 226 | " self.radius = 30\n", 227 | " self.csvfile = None\n", 228 | " self.bufferredPoints = None\n", 229 | " self.colX = \"CX\"\n", 230 | " self.colY = \"CY\"\n", 231 | " \n", 232 | " if (isinstance(fieldDataProperties, str)):\n", 233 | " self.shp = fieldDataProperties\n", 234 | " print (\"Shapefile Loaded!\")\n", 235 | " return\n", 236 | " # else get information about plot data\n", 237 | " \n", 238 | " print (\"Return didn't work\")\n", 239 | " if not ('outPlotFileWithIDs' in fieldDataProperties):\n", 240 | " fieldDataProperties['outPlotFileWithIDs'] = self.filedataFilewithIDs\n", 241 | " # else:\n", 242 | " # outFlotFileWithIDs has been defined by the user \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " self.setYear(year)\n", 247 | " \n", 248 | " if \"shpfilename\" in fieldDataProperties:\n", 249 | " self.shp = fieldDataProperties[\"shpfilename\"]\n", 250 | " if (\"polygonKeyColumn\" in fieldDataProperties):\n", 251 | " self.shpPolygons = Polygons(self.shp, fieldDataProperties[\"polygonKeyColumn\"])\n", 252 | " else :\n", 253 | " raise Exception (\"PlotToSat: \\\"polygonKeyColumn\\\" not provided!\" )\n", 254 | " else : \n", 255 | " self.addFieldData(fieldDataProperties)\n", 256 | " if(self.csvDF == None or self.maxSize==0):\n", 257 | " raise Exception(\"Manager: Plot data loaded are not correct!\")\n", 258 | " \n", 259 | " \n", 260 | " def availableIndices(collection): \n", 261 | " if collection == \"sentinel-2\" : \n", 262 | " Sentinel2.printAvailableIndices()\n", 263 | " \n", 264 | " ## method that enables and disables exportation of Std values\n", 265 | " # @param[in] Bool is a boolean value True for enable and False for disable\n", 266 | " def exportStd(self,Bool):\n", 267 | " if (self.csvDF != None):\n", 268 | " self.csvDF.exportStd(Bool)\n", 269 | " elif self.shpPolygons!=None:\n", 270 | " self.shpPolygons.exportStd(Bool)\n", 271 | " else:\n", 272 | " print (\"WARNING: exportStd did nothing as neither a csv file or a shp file are loaded\")\n", 273 | " \n", 274 | " ## method that enables and disables exportation of Std values\n", 275 | " # @param[in] Bool is a boolean value True for enable and False for disable\n", 276 | " def exportMean(self,Bool):\n", 277 | " if (self.csvDF != None):\n", 278 | " self.csvDF.exportMean(Bool)\n", 279 | " elif self.shpPolygons!=None:\n", 280 | " self.shpPolygons.exportMean(Bool)\n", 281 | " else:\n", 282 | " print (\"WARNING: exportMean did nothing as neither a csv file or a shp file are loaded\")\n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " ## @brief method that defines the bands of interest \n", 287 | " # @param[in] collection of interest\n", 288 | " # @param[in] bands of interest\n", 289 | " # @note Currently only supporting Sentinel-2\n", 290 | " def selectBands(self, collection, selectedBands ): \n", 291 | " if (collection == \"sentinel-1\"):\n", 292 | " self.sentinel1Info[\"selectedBand\"] = selectedBands\n", 293 | " elif (collection == \"sentinel-2\"):\n", 294 | " self.sentinel2Info[\"selectedBand\"] = selectedBands\n", 295 | " \n", 296 | " ## @brief method that defines the indices of interest for Sentinel-2\n", 297 | " def selectIndices(self, collection, selectedIndices ): \n", 298 | " if (collection == \"sentinel-1\"):\n", 299 | " self.sentinel1Info[\"selectedIndices\"] = selectedIndices\n", 300 | " elif (collection == \"sentinel-2\"):\n", 301 | " self.sentinel2Info[\"selectedIndices\"] = selectedIndices\n", 302 | " \n", 303 | " \n", 304 | "\n", 305 | " ## @brief method that prints the collections that are available within the system\n", 306 | " def printAvailableCollections(self):\n", 307 | " # new approach\n", 308 | " print(\"There are \", self.numberOfAvailableCols,\" collections available within the system:\")\n", 309 | " \n", 310 | " labels = [self.sentinel1Info[\"identifier\"],\n", 311 | " self.sentinel2Info[\"identifier\"]]\n", 312 | " \n", 313 | " collections = [self.sentinel1Info[\"collection\"],\n", 314 | " self.sentinel2Info[\"collection\"]]\n", 315 | " \n", 316 | " data = {\"label\": labels, \"collection\":collections}\n", 317 | " df = pd.DataFrame(data)\n", 318 | " print(df)\n", 319 | " \n", 320 | " \n", 321 | "\n", 322 | " \n", 323 | " ## Method that adjust the sampling of the plot data to avoid processing all of them at once and crusing\n", 324 | " # newSampling the the sampling limit\n", 325 | " def setSampling(self, newSampling):\n", 326 | " self.samplingSize = newSampling\n", 327 | " if self.shp is not None:\n", 328 | " self.shpPolygons.setSampling(newSampling)\n", 329 | " print(\"NEW SAMPLING: = \", self.samplingSize)\n", 330 | "\n", 331 | "\n", 332 | " \n", 333 | "\n", 334 | " ## @brief method that sets the masks that will be applied \n", 335 | " # @param[in] newMasks the masks to be used\n", 336 | " def setMasks(self, newMasks):\n", 337 | " self.masks = newMasks\n", 338 | "\n", 339 | "\n", 340 | "\n", 341 | "\n", 342 | "\n", 343 | "\n", 344 | " ## method that adds a collection to the manager\n", 345 | " # @param[in] label the label of the collection to be added e.g., 'sentinel-1'\n", 346 | " # @param[in] a variable that may be used if needed e.g., cloudpercentage in sentinel2\n", 347 | " def addCollection(self,label, vari):\n", 348 | " if(label=='sentinel-1'):\n", 349 | " self.sentinel1Info[\"added\"] = True\n", 350 | " if(isinstance(vari,bool)):\n", 351 | " self.sentinel1Info[\"aspects\"] = vari\n", 352 | " else :\n", 353 | " print(\"WARNING: Variable imported along with sentinel-1 is not a Boolean (True or False) value - Aspect maps will not be applied\")\n", 354 | " print(\"Sentinel 1 added\")\n", 355 | " elif (label == 'sentinel-2'):\n", 356 | " self.sentinel2Info[\"added\"] = True\n", 357 | " if(isinstance(vari,numbers.Number) and vari>0 and vari<100):\n", 358 | " self.sentinel2Info[\"clouds\"] = vari\n", 359 | " else :\n", 360 | " print(\"WARNINGING: Cloud value should be a number between (0,100). A 50 cloud coverage will be used.\")\n", 361 | " self.sentinel2Info[\"clouds\"] = 50\n", 362 | " print(\"Sentinel 2 added\")\n", 363 | " \n", 364 | " #loop through each collection and export data \n", 365 | "\n", 366 | " def exportCollections(self,startOffeaturesFilenamesWithSampling,gdrivefolder):\n", 367 | " if(self.sentinel1Info[\"startDate\"]!=\"\" and \n", 368 | " self.sentinel1Info[\"endDate\"]!=\"\" and \n", 369 | " self.sentinel1Info[\"added\"]):\n", 370 | " print(\"Sentinel 1 collection parameters loaded are valid and system is now intepreting data\")\n", 371 | " s1 = Sentinel1(\n", 372 | " self.geometry,\n", 373 | " self.sentinel1Info[\"startDate\"],\n", 374 | " self.sentinel1Info[\"endDate\"],\n", 375 | " self.masks,\n", 376 | " self.sentinel1Info[\"aspects\"]\n", 377 | " )\n", 378 | " lys = int(self.sentinel1Info[\"startDate\" ][0:4])\n", 379 | " lye = int(self.sentinel1Info[\"endDate\" ][0:4])\n", 380 | " if (lys!=lye):\n", 381 | " raise Exception(\"ERROR: start end end data should have been in the same year\")\n", 382 | " else: \n", 383 | " s1.byMonth(lys)\n", 384 | " if(self.sentinel1Info[\"aspects\"]):\n", 385 | " s1bandsAsc = s1.getCollectionToBandsAsc()\n", 386 | " s1bandsDes = s1.getCollectionToBandsDes()\n", 387 | " if (self.shp is None):\n", 388 | " self.csvDF.exportFeaturesMeanStdCSV(s1bandsAsc,startOffeaturesFilenamesWithSampling+\"_S1Asc\",gdrivefolder)\n", 389 | " self.csvDF.exportFeaturesMeanStdCSV(s1bandsDes,startOffeaturesFilenamesWithSampling+\"_S1Des\",gdrivefolder)\n", 390 | " else:\n", 391 | " self.shpPolygons.exportFeaturesMeanStdCSV(s1bandsAsc,startOffeaturesFilenamesWithSampling+\"_S1Asc\",gdrivefolder)\n", 392 | " self.shpPolygons.exportFeaturesMeanStdCSV(s1bandsDes,startOffeaturesFilenamesWithSampling+\"_S1Des\",gdrivefolder)\n", 393 | " else :\n", 394 | " s1bands = s1.getCollectionToBands() \n", 395 | " if (self.shp is None):\n", 396 | " self.csvDF.exportFeaturesMeanStdCSV(s1bands,startOffeaturesFilenamesWithSampling+\"_S1\",gdrivefolder)\n", 397 | " else :\n", 398 | " self.shpPolygons.exportFeaturesMeanStdCSV(s1bands,startOffeaturesFilenamesWithSampling+\"_S1\",gdrivefolder)\n", 399 | "\n", 400 | " else:\n", 401 | " print(\"WARNING: Sentinel 1 collection parameters loaded are NOT valid and system is skipping this collection\")\n", 402 | "\n", 403 | "\n", 404 | " if(self.sentinel2Info[\"added\"]):\n", 405 | " print(\"Sentinel 2 collection parameters loaded are valid and system is now intepreting data\")\n", 406 | " s2 = Sentinel2(\n", 407 | " self.geometry,\n", 408 | " self.sentinel2Info,\n", 409 | " self.masks\n", 410 | " )\n", 411 | " \n", 412 | " lys = int(self.sentinel2Info[\"startDate\" ][0:4])\n", 413 | " lye = int(self.sentinel2Info[\"endDate\" ][0:4])\n", 414 | " if (lys!=lye):\n", 415 | " raise Exception(\"ERROR: start end end data should have been in the same year\")\n", 416 | " else: \n", 417 | " s2.byMonth(lys)\n", 418 | " s2bands = s2.getCollectionToBands()\n", 419 | " if (self.shp is None):\n", 420 | " self.csvDF.exportFeaturesMeanStdCSV(s2bands,startOffeaturesFilenamesWithSampling+\"_S2\",gdrivefolder)\n", 421 | " else:\n", 422 | " self.shpPolygons.exportFeaturesMeanStdCSV(s2bands,startOffeaturesFilenamesWithSampling+\"_S2\",gdrivefolder)\n", 423 | " else:\n", 424 | " print(\"WARNING: Sentinel 2 collection parameters loaded are NOT valid and system is skipping this collection\")\n", 425 | "\n", 426 | " def exprtFeaturesMinMax(self,gdrivefolder,startOffeaturesFilename, min,max):\n", 427 | " currentMin = min\n", 428 | " currentMax = max\n", 429 | " strCurrentMin = str(currentMin ).rjust(10,'0')\n", 430 | " strCurrentMax = str(currentMax-1).rjust(10,'0')\n", 431 | " startOffeaturesFilenamesWithSampling = startOffeaturesFilename+\"_\"+strCurrentMin+\"_\"+strCurrentMax\n", 432 | " if self.shp is None :\n", 433 | " bufferredPoints = self.csvDF.createBufferedPoints(currentMin,currentMax)\n", 434 | " if bufferredPoints == None :\n", 435 | " print (\"WARNING: exprtFeaturesMinMax: No data exported because range provided does not exist.\")\n", 436 | " return\n", 437 | " else :\n", 438 | " polygons = self.shpPolygons.createPolygons(currentMin,currentMax)\n", 439 | " \n", 440 | " if ((self.shp is not None and polygons is not None) or self.csvDF is not None):\n", 441 | " print (\"exporting collections\")\n", 442 | " self.exportCollections(startOffeaturesFilenamesWithSampling,gdrivefolder)\n", 443 | " else :\n", 444 | " print( \"WARNING: Skipping exportation as polygons from \", currentMin, \" to \", currentMax, \n", 445 | " \" haven't been created\")\n", 446 | " \n", 447 | " ## method that exports a set of files containing the feature vectors\n", 448 | " def exportFeatures(self,gdrivefolder,startOffeaturesFilename):\n", 449 | " if (self.sentinel1Info[\"added\"] == False and self.sentinel2Info[\"added\"] == False):\n", 450 | " raise Exception(\"ERROR: No collections have been added!\")\n", 451 | " # else at least one collection has been \n", 452 | " lenOfFieldData = 0\n", 453 | " if self.shp is not None:\n", 454 | " lenOfFieldData = self.shpPolygons.getLen()\n", 455 | " else :\n", 456 | " lenOfFieldData = self.csvDF.getLen()\n", 457 | " currentMin = 0\n", 458 | " currentMax = self.samplingSize\n", 459 | " print (\"Self.sampling size \" , self.samplingSize)\n", 460 | " while currentMin=len(self.plots):\n", 110 | " self.startcount = 0\n", 111 | " for i in range(lastcount):\n", 112 | " if self.plots[i].isthis(idi):\n", 113 | " self.startcount = i\n", 114 | " return i \n", 115 | " print(\"Warning: Index not found\")\n", 116 | " return -1 # \"Plot not found, wrong index given\"\n", 117 | "\n", 118 | " # adds missing labels and let you know the indices where the new items \n", 119 | " # shall be added (of the same file)\n", 120 | " # @returns unwantedIndex the indices of the columns to be removed\n", 121 | " # @returns index_list the order the items will be added once the unwantedIndex are removed\n", 122 | " # @notes first remove unwantedIndices because otherwice indices in index_list will not match \n", 123 | " # the self.labels indices\n", 124 | " def find_indexes(self, B):\n", 125 | " index_list = []\n", 126 | " unwantedIndex = []\n", 127 | "\n", 128 | " for i, element in enumerate(B):\n", 129 | " if element in self.labels:\n", 130 | " index_list.append(self.labels.index(element))\n", 131 | " else:\n", 132 | " if element not in self.unwantedlabels: \n", 133 | " self.labels.append(element)\n", 134 | " index_list.append(len(self.labels)-1)\n", 135 | " else :\n", 136 | " unwantedIndex.append(B.index(element))\n", 137 | " return index_list, unwantedIndex\n", 138 | " \n", 139 | " \n", 140 | " ## method that add data of given CSV file to the relevant plots\n", 141 | " def addInfo(self,filename):\n", 142 | " file = open(filename, 'r')\n", 143 | " first_line = file.readline().strip()\n", 144 | " if not first_line:\n", 145 | " print(\"WARNING: File\", filename, \"is empty\")\n", 146 | " file.close()\n", 147 | " return\n", 148 | " \n", 149 | " # else file is not empty\n", 150 | " labels = first_line.split(',')\n", 151 | " \n", 152 | " ilist, unwanted = self.find_indexes(labels)\n", 153 | " \n", 154 | " filtered_labels = [labels[i] for i in range(len(labels)) if i not in unwanted]\n", 155 | "\n", 156 | " # Ensure \"indexField\" exists in filtered labels\n", 157 | " try:\n", 158 | " plotindexindex = filtered_labels.index(\"indexField\")\n", 159 | " except ValueError:\n", 160 | " print(\"indexField not found in given file.\",\n", 161 | " \"The files should be the one exported from PlotToSat as that column \",\n", 162 | " \"is appended by PlotToSat and essential for merging the files\")\n", 163 | " file.close()\n", 164 | " return\n", 165 | " \n", 166 | " plotindexindex = filtered_labels.index(\"indexField\")\n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " while True:\n", 171 | " line = file.readline().strip()\n", 172 | " if not line:\n", 173 | " break\n", 174 | " # linelist contains more items than labels because .geo column contains\n", 175 | " # many commas (,) but it because .geo is at the end and the extra columns do not\n", 176 | " # interfere with the indexes (columns) to be added to plots\n", 177 | " linelist = line.split(',')\n", 178 | "\n", 179 | " filtered_linelist = [linelist[i] for i in range(len(linelist)) if i not in unwanted]\n", 180 | " filtered_linelist = filtered_linelist[:len(filtered_labels)]\n", 181 | " #filtered_linelist = [float(item) if item != \"\" else item for item in filtered_linelist]\n", 182 | "\n", 183 | " plotIndex = self.getPlotIndex(filtered_linelist[plotindexindex])\n", 184 | " assert(plotIndex!=-1)\n", 185 | " self.plots[plotIndex].addFeatures(filtered_linelist,ilist,self.labels)\n", 186 | "\n", 187 | " \n", 188 | " return\n", 189 | "\n", 190 | " def print(self):\n", 191 | " print(self.labels)\n", 192 | " for plot in self.plots:\n", 193 | " plot.print()\n", 194 | "\n", 195 | "\n", 196 | " def printLabels(self):\n", 197 | " print(self.labels)\n", 198 | "\n", 199 | "\n", 200 | " def exportTo(self,csvfile):\n", 201 | " file = open(csvfile, \"w\")\n", 202 | " labels_string = \",\".join(self.labels) + \"\\n\"\n", 203 | " file.write(labels_string)\n", 204 | " for plot in self.plots:\n", 205 | " file.write(plot.getString(self.labels))\n", 206 | " file.close()" 207 | ] 208 | } 209 | ], 210 | "metadata": { 211 | "kernelspec": { 212 | "display_name": "Python 3", 213 | "language": "python", 214 | "name": "python3" 215 | }, 216 | "language_info": { 217 | "codemirror_mode": { 218 | "name": "ipython", 219 | "version": 3 220 | }, 221 | "file_extension": ".py", 222 | "mimetype": "text/x-python", 223 | "name": "python", 224 | "nbconvert_exporter": "python", 225 | "pygments_lexer": "ipython3", 226 | "version": "3.11.1" 227 | } 228 | }, 229 | "nbformat": 4, 230 | "nbformat_minor": 2 231 | } 232 | -------------------------------------------------------------------------------- /PlotsManager_test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import sys\n", 10 | "\n", 11 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 12 | "modulename = 'PlotsManager'\n", 13 | "if modulename not in sys.modules: \n", 14 | " # import GEE and Authenticate, token or log in will be asked from web browser\n", 15 | " %run PlotsManager.ipynb\n", 16 | "else:\n", 17 | " print('PlotsManager already imported')\n", 18 | " # google earth engine already imported and authenticated" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 2, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "# test filed data filename\n", 28 | "\n", 29 | "csvfileName = \"./plotsWithIDs/SpainIDs_2.csv\"\n", 30 | "\n", 31 | "csvfile = PlotsManager(csvfileName)\n", 32 | "\n" 33 | ] 34 | } 35 | ], 36 | "metadata": { 37 | "kernelspec": { 38 | "display_name": ".venv", 39 | "language": "python", 40 | "name": "python3" 41 | }, 42 | "language_info": { 43 | "codemirror_mode": { 44 | "name": "ipython", 45 | "version": 3 46 | }, 47 | "file_extension": ".py", 48 | "mimetype": "text/x-python", 49 | "name": "python", 50 | "nbconvert_exporter": "python", 51 | "pygments_lexer": "ipython3", 52 | "version": "3.13.0" 53 | } 54 | }, 55 | "nbformat": 4, 56 | "nbformat_minor": 2 57 | } 58 | -------------------------------------------------------------------------------- /Polygons.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "author: Dr Milto Miltiadou\n", 8 | "version: 1.0\n", 9 | "created Oct 2024" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import sys\n", 19 | "\n", 20 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 21 | "modulename = 'ee'\n", 22 | "if modulename not in sys.modules: \n", 23 | " # import GEE and Authenticate, token or log in will be asked from web browser\n", 24 | " import ee\n", 25 | " ee.Authenticate()\n", 26 | " ee.Initialize()\n", 27 | "else:\n", 28 | " print('GEE already imported')\n", 29 | " # google earth engine already imported and authenticated\n", 30 | "\n", 31 | "import pandas as pd\n", 32 | "import numbers\n", 33 | "from datetime import date\n", 34 | "import os\n" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "class Polygons:\n", 44 | " \n", 45 | " def __init__(self,shpfilename,polKeyCol):\n", 46 | " self.shp = shpfilename\n", 47 | " self.bufferredPoints = None\n", 48 | " self.df = None\n", 49 | " self.noOfShpPolygons = None\n", 50 | " self.shpPolygons = None\n", 51 | " self.polygonsList = None\n", 52 | " self.sampleSize = 400\n", 53 | " \n", 54 | " self.shpPolygons = ee.FeatureCollection(self.shp)\n", 55 | " self.noOfShpPolygons = self.shpPolygons.size().getInfo()\n", 56 | " self.polygonsList = self.shpPolygons.toList(self.noOfShpPolygons)\n", 57 | " print (\"Shapefile \", self.shp, \" is loaded with \", self.noOfShpPolygons, \" polygons\")\n", 58 | " self.currentSubset = None\n", 59 | " self.polygonID = polKeyCol\n", 60 | " self.exportStdVal = True\n", 61 | " self.exportMeanVal = True\n", 62 | " \n", 63 | " def getPolygons(self):\n", 64 | " return self.shpPolygons\n", 65 | " \n", 66 | " \n", 67 | " ## @brief method that returns the number of polygons\n", 68 | " def getLen(self):\n", 69 | " return self.noOfShpPolygons\n", 70 | " \n", 71 | " ## @brief method that sets a new sampling number (how many polygons will be processed in each iteration)\n", 72 | " # @param[in] newSampling the new samplig size\n", 73 | " def setSampling(self,newSampling):\n", 74 | " self.sampleSize = newSampling\n", 75 | " print(\"Sampling size updated to\", newSampling)\n", 76 | " \n", 77 | " \n", 78 | " # There is a copy of this function in Polygons \n", 79 | " # Update both functions if something chang\n", 80 | " def createPolygons(self,currentMin,currentMax):\n", 81 | " self.currentSubset = None\n", 82 | " tmpList = self.polygonsList.slice(currentMin,min(currentMax,self.noOfShpPolygons))\n", 83 | " self.currentSubset=ee.FeatureCollection(tmpList)\n", 84 | " return self.currentSubset\n", 85 | " \n", 86 | " ## @brief method that returns the number of polygons\n", 87 | " def getLenCurrentSub(self):\n", 88 | " return self.currentSubset.size().getInfo()\n", 89 | " \n", 90 | " # Copied from FieldData - \n", 91 | " def getFeatureCollection(self,collection):\n", 92 | " bandNamesImg = collection.bandNames().getInfo()\n", 93 | " print('Band names: ', bandNamesImg)\n", 94 | " for band in bandNamesImg :\n", 95 | " if(not isinstance(band,str)):\n", 96 | " bandNamesImg.remove(band)\n", 97 | " featureCollection1 = ee.FeatureCollection([])\n", 98 | " for band in bandNamesImg:\n", 99 | " features = self.getInfoRegions(collection,band)\n", 100 | " featureCollection1 = featureCollection1.merge(features) \n", 101 | " return featureCollection1\n", 102 | " \n", 103 | " # Modified from FieldData get mean and std for one band of an image for each buffered point\n", 104 | " def getInfoRegions(self,collection,bandName):\n", 105 | " # bnamestr = bandName.get('band')\n", 106 | " return collection.select([bandName]).reduceRegions(**{\n", 107 | " 'collection': self.currentSubset.select(self.polygonID),\n", 108 | " 'reducer': ee.Reducer.mean().combine(**{\n", 109 | " 'reducer2': ee.Reducer.stdDev(),\n", 110 | " 'sharedInputs': True\n", 111 | " }),\n", 112 | " 'scale': 10#,\n", 113 | " #'bestEffort': True # Use maxPixels if you care about scale.\n", 114 | " }).map(lambda feature: feature.set('bandName',bandName)) \\\n", 115 | " .filter(ee.Filter.neq('mean',None))\n", 116 | " \n", 117 | " def processMatchesMean(self,row):\n", 118 | " # Get the list of all features with a unique row ID.\n", 119 | " matches = ee.List(row.get('matches'))\n", 120 | " # Map a function over the list of rows to return a list of column ID and value.\n", 121 | " values = matches.map(lambda feature: [ee.Feature(feature).get('bandName'), ee.Feature(feature).get('mean')])\n", 122 | " # Return the row with its ID property and properties for all matching column IDs storing the output of the reducer.\n", 123 | " return row.select([self.polygonID]).set(ee.Dictionary(values.flatten()))\n", 124 | "\n", 125 | " ## Fielddata: Format a table of triplets into a 2D table of rowId x colId.\n", 126 | " def formatMean (self,table):\n", 127 | " # Get a FeatureCollection with unique row IDs.\n", 128 | " rows = table.distinct(self.polygonID)\n", 129 | " filterEq = ee.Filter.equals(leftField=self.polygonID, rightField=self.polygonID)\n", 130 | " innerJoin = ee.Join.saveAll('matches')\n", 131 | " toyJoin = innerJoin.apply(primary=rows, secondary=table, condition=filterEq)\n", 132 | " \n", 133 | " return toyJoin.map(algorithm = self.processMatchesMean)\n", 134 | " \n", 135 | " ## method that enables and disables exportation of Std values\n", 136 | " # @param[in] Bool is a boolean value True for enable and False for disable\n", 137 | " def exportStd(self,Bool):\n", 138 | " if (isinstance(Bool, bool)):\n", 139 | " self.exportStdVal = Bool\n", 140 | " else : \n", 141 | " print (\"WARNDING: function exportStd() takes as input a Boolean value!\")\n", 142 | " \n", 143 | " ## @brief method that enables and disables exportation of Mean Values\n", 144 | " # @param[in] Bool is a boolean value True for enable and False for disable\n", 145 | " def exportMean(self,Bool):\n", 146 | " if (isinstance(Bool, bool)):\n", 147 | " self.exportMeanVal = Bool\n", 148 | " else : \n", 149 | " print (\"WARNDING: function exportStd() takes as input a Boolean value!\")\n", 150 | " \n", 151 | " \n", 152 | " # Fielddata: \n", 153 | " def processMatchesStd(self,row):\n", 154 | " # Get the list of all features with a unique row ID.\n", 155 | " matches = ee.List(row.get('matches'))\n", 156 | " # Map a function over the list of rows to return a list of column ID and value.\n", 157 | " values = matches.map(lambda feature: [ee.Feature(feature).get('bandName'),\n", 158 | " ee.Feature(feature).get('stdDev')])\n", 159 | " \n", 160 | " # Return the row with its ID property and properties for all matching column IDs storing the output of the reducer.\n", 161 | " return row.select([self.polygonID]).set(ee.Dictionary(values.flatten()))\n", 162 | "\n", 163 | "\n", 164 | " ## Fielddata: Format a table of triplets into a 2D table of rowId x colId.\n", 165 | " def formatStd (self,table):\n", 166 | " # Get a FeatureCollection with unique row IDs.\n", 167 | " rows = table.distinct(self.polygonID)\n", 168 | " filterEq = ee.Filter.equals(leftField=self.polygonID, rightField=self.polygonID)\n", 169 | " innerJoin = ee.Join.saveAll('matches')\n", 170 | " toyJoin = innerJoin.apply(primary=rows, secondary=table, condition=filterEq)\n", 171 | " return toyJoin.map(algorithm = self.processMatchesStd) \n", 172 | " \n", 173 | " # collection = s2bands\n", 174 | " def exportFeaturesMeanStdCSV(self,collection,ouutCsvFeatureVectors,driveFolder):\n", 175 | " if (self.currentSubset == None):\n", 176 | " raise Exception(\"Please call check _init_ since shp has not been loaded\" )\n", 177 | " featureCollection = self.getFeatureCollection(collection)\n", 178 | "\n", 179 | " tableMean = self.formatMean(featureCollection)\n", 180 | " tableStd = self.formatStd (featureCollection)\n", 181 | " \n", 182 | " meanName = ouutCsvFeatureVectors+\"_mean\"\n", 183 | " stdName = ouutCsvFeatureVectors+\"_stdD\"\n", 184 | " print (\"START EXPORTING FEATURES VECTORS OF A SINGLE FILE\")\n", 185 | " if (self.exportMeanVal):\n", 186 | " task = ee.batch.Export.table.toDrive(**{\n", 187 | " 'collection':tableMean,\n", 188 | " 'description':meanName,\n", 189 | " 'folder': driveFolder,\n", 190 | " 'fileFormat':'CSV'\n", 191 | " })\n", 192 | " task.start()\n", 193 | "\n", 194 | " if (self.exportStdVal):\n", 195 | " task = ee.batch.Export.table.toDrive(**{\n", 196 | " 'collection':tableStd,\n", 197 | " 'description':stdName,\n", 198 | " 'folder': driveFolder,\n", 199 | " 'fileFormat':'CSV'\n", 200 | " })\n", 201 | " task.start()\n", 202 | " " 203 | ] 204 | } 205 | ], 206 | "metadata": { 207 | "kernelspec": { 208 | "display_name": ".venv", 209 | "language": "python", 210 | "name": "python3" 211 | }, 212 | "language_info": { 213 | "codemirror_mode": { 214 | "name": "ipython", 215 | "version": 3 216 | }, 217 | "file_extension": ".py", 218 | "mimetype": "text/x-python", 219 | "name": "python", 220 | "nbconvert_exporter": "python", 221 | "pygments_lexer": "ipython3", 222 | "version": "3.13.0" 223 | } 224 | }, 225 | "nbformat": 4, 226 | "nbformat_minor": 2 227 | } 228 | -------------------------------------------------------------------------------- /Polygons_test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Author: Milto Miltiadou\n", 8 | "Version 1" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "metadata": {}, 15 | "outputs": [], 16 | "source": [ 17 | "import sys\n", 18 | "\n", 19 | "modulename = 'Polygons'\n", 20 | "if modulename not in sys.modules: \n", 21 | " %run Polygons.ipynb\n", 22 | " print (\"Importing Polygons\")\n", 23 | "#else:\n", 24 | "# print('Polygons already imported')\n", 25 | "\n", 26 | "modulename = 'MapVisualisation'\n", 27 | "if modulename not in sys.modules: \n", 28 | " %run MapVisualisation.ipynb\n" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": null, 34 | "metadata": {}, 35 | "outputs": [], 36 | "source": [ 37 | "shp = {\n", 38 | " \"shpfilename\" : \"projects/ee-flf-milto/assets/BufferedSamplePoints\",\n", 39 | " \"polygonKeyColumn\" : \"PLOTCODE\" # column that ids of polygons are stored\n", 40 | "}\n", 41 | "\n", 42 | "pol = Polygons(shp[\"shpfilename\"], shp[\"polygonKeyColumn\"])\n", 43 | "polygons_ee = pol.getPolygons()\n", 44 | "\n", 45 | "NoOfPolygon = pol.getLen()\n", 46 | "print(\"number of All polygons: \" , NoOfPolygon)\n", 47 | "\n", 48 | "# Get a subset:\n", 49 | "currentMin = 3\n", 50 | "currentMax = 8 \n", 51 | "subsetOfPols = pol.createPolygons(currentMin,currentMax)\n", 52 | "\n", 53 | "# getting number of sampled polygons\n", 54 | "NoOfPolys_subset = pol.getLenCurrentSub()\n", 55 | "print (\"Length of subset: \", NoOfPolys_subset)\n", 56 | "\n", 57 | "\n", 58 | "\n" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "# Visualisation\n", 68 | "\n", 69 | "\n", 70 | "# Set visualization parameters.\n", 71 | "vis_params = {\n", 72 | " 'min': 0,\n", 73 | " 'max': 4000,\n", 74 | " 'palette': ['006633', 'E5FFCC', '662A00', 'D8D8D8', 'F5F5F5']}\n", 75 | "\n", 76 | "# Create a folium map object.\n", 77 | "my_map = folium.Map(location=[40,-3], zoom_start=5, height=400)\n", 78 | "\n", 79 | "\n", 80 | "# Add custom basemaps\n", 81 | "basemaps['Google Maps'].add_to(my_map)\n", 82 | "basemaps['Google Satellite Hybrid'].add_to(my_map)\n", 83 | "\n", 84 | "# Add the elevation model to the map object.\n", 85 | "dem = ee.Image('USGS/SRTMGL1_003')\n", 86 | "my_map.add_ee_layer(dem.updateMask(dem.gt(0)), vis_params, 'DEM')\n", 87 | "my_map.add_ee_layer(subsetOfPols,{},\"subsetOfPols\")\n", 88 | "my_map.add_ee_layer(polygons_ee,{},\"polygons\")\n", 89 | "\n", 90 | "# Add a layer control panel to the map.\n", 91 | "my_map.add_child(folium.LayerControl())\n", 92 | "plugins.Fullscreen().add_to(my_map)\n", 93 | "my_map.add_child(folium.LayerControl())\n", 94 | "plugins.Fullscreen().add_to(my_map)\n", 95 | "display(my_map)\n", 96 | "\n" 97 | ] 98 | } 99 | ], 100 | "metadata": { 101 | "kernelspec": { 102 | "display_name": ".venv", 103 | "language": "python", 104 | "name": "python3" 105 | }, 106 | "language_info": { 107 | "codemirror_mode": { 108 | "name": "ipython", 109 | "version": 3 110 | }, 111 | "file_extension": ".py", 112 | "mimetype": "text/x-python", 113 | "name": "python", 114 | "nbconvert_exporter": "python", 115 | "pygments_lexer": "ipython3", 116 | "version": "3.13.0" 117 | } 118 | }, 119 | "nbformat": 4, 120 | "nbformat_minor": 2 121 | } 122 | -------------------------------------------------------------------------------- /Sentinel1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import sys\n", 10 | "\n", 11 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 12 | "modulename = 'ee'\n", 13 | "if modulename not in sys.modules: \n", 14 | " # import GEE and Authenticate, token or log in will be asked from web browser\n", 15 | " import ee\n", 16 | " ee.Authenticate()\n", 17 | " ee.Initialize()\n", 18 | "else:\n", 19 | " print('GEE already imported')\n", 20 | " # google earth engine already imported and authenticated\n", 21 | "\n", 22 | "\n", 23 | "modulename = 'ipynb_masks'\n", 24 | "if modulename not in sys.modules:\n", 25 | " %run Masks.ipynb\n", 26 | " sys.modules['ipynb_masks'] = None\n", 27 | "#else\n", 28 | " # module already loaded\n", 29 | "\n", 30 | "modulename = 'ipynb_Utils'\n", 31 | "if modulename not in sys.modules:\n", 32 | " %run Utils.ipynb\n", 33 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 34 | " sys.modules['ipynb_Utils'] = None \n", 35 | "#else\n", 36 | " # Utils modules has already been loaded somewhere else\n", 37 | "\n" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": null, 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "from datetime import date\n", 47 | "\n", 48 | "#Define global variables related to Sentinel-1\n", 49 | "sentinel1Info = {\n", 50 | " \"collection\": \"COPERNICUS/S1_GRD\",\n", 51 | " \"startYear\" : \"2014-04-01\",\n", 52 | " \"endDate\" : None\n", 53 | "}\n", 54 | "\n", 55 | "## method that returns the name of the collection used in the Sentinel1 class\n", 56 | "def getSentinel1Col():\n", 57 | " return sentinel1Info[\"collection\"]\n", 58 | " \n", 59 | "## method that sets the Sentinel1 collection \n", 60 | "# this is useful in case an updated class is released in GEE or for consistency an \n", 61 | "# older version of Sentinel1 col needs to be used\n", 62 | "# @param[in] newcol: the name of the collection that will replace the collection \n", 63 | "# used in this class\n", 64 | "# @note: Please use with caution as a wrong collection will crash the entire class\n", 65 | "def setSentinel1Col(newCol):\n", 66 | " global sentinel1Info\n", 67 | " sentinel1Info[\"collection\"] = newCol\n", 68 | "\n", 69 | "## method that returns the the start end end date of the collection\n", 70 | "# @param[in] type 'whole' or 'year' get the entire date or just the year\n", 71 | "def getStartEndDateS1(type):\n", 72 | " startDate = sentinel1Info[\"startYear\"]\n", 73 | " endDate = sentinel1Info[\"endDate\" ]\n", 74 | " if(endDate == None):\n", 75 | " today = date.today()\n", 76 | " endDate = today.strftime(\"%Y-%m-%d\")\n", 77 | " if type == 'year' :\n", 78 | " return [getYearMonthOrDay(startDate,'year'),getYearMonthOrDay(endDate,'year')]\n", 79 | " elif type == 'whole':\n", 80 | " return [startDate,endDate]\n", 81 | " else: # type not defined\n", 82 | " raise Exception(\"ERROR: type for getting start end end date/year of Sentinel-1 not defined\\n\")\n", 83 | " \n" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": null, 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "class Sentinel1:\n", 93 | " \"\"\"\n", 94 | " # @brief method that takes as input a collection, calculates pixelwise average for each month and returns a new collection\n", 95 | " def byMonthIndermidateFunction(self,m):\n", 96 | " phenCol = self.sentinel1.filter(ee.Filter.calendarRange(m, m, 'month')).mean().set('month', m) \n", 97 | " return phenCol\n", 98 | " \n", 99 | " \n", 100 | " # @param[in] collection : the collection to be processed\n", 101 | " # @return a collection annual monthly average pixel values\n", 102 | "\n", 103 | " def byMonth (self):\n", 104 | " months = ee.List.sequence(1, 12)\n", 105 | " return ee.ImageCollection.fromImages(months.map(algorithm = self.byMonthIndermidateFunction))\n", 106 | " \"\"\"\n", 107 | " def renameVVAsc(self,img):\n", 108 | " return img.rename('VVAsc')\n", 109 | " \n", 110 | " def renameVHAsc(self,img):\n", 111 | " return img.rename('VHAsc')\n", 112 | "\n", 113 | " def renameVVDes(self,img):\n", 114 | " return img.rename('VVDes')\n", 115 | "\n", 116 | " def renameVHDes(self,img):\n", 117 | " return img.rename('VHDes')\n", 118 | " \n", 119 | " # @brief initialisation of Sentinel1 class\n", 120 | " # @param[in] geometry: area of interest\n", 121 | " # @param[in] startDate: start date of the collection to be retrived\n", 122 | " # @param[in] endDate: end date of the collection to be retrived\n", 123 | " # @param[in] cloudfilter: percentage of non acceptable clouds for removing images\n", 124 | " # @param[in] masks: a dictonary containing the masks to be applied \n", 125 | " # @param[in] applyAspects: is a boolean that defines whether the corresponding ascpets will be applied\n", 126 | " # Format of masks should be the following, but it should include only the masks \n", 127 | " # that we want to be applied\n", 128 | " # masks = {'gsw':buffer, 'lmask': buffer, 'forestMask': {buffer,year}}\n", 129 | " def __init__(self,geometry, startDate,endDate, masks, applyAspects):\n", 130 | " self.startDate = startDate\n", 131 | " self.endDate = endDate\n", 132 | " self.sentinel1 = ee.ImageCollection('COPERNICUS/S1_GRD') \\\n", 133 | " .filterDate(startDate,endDate) \\\n", 134 | " .filterBounds(geometry) \\\n", 135 | " .filter(ee.Filter.listContains('transmitterReceiverPolarisation','VV')) \\\n", 136 | " .filter(ee.Filter.listContains('transmitterReceiverPolarisation','VH')) \\\n", 137 | " .filter(ee.Filter.eq('instrumentMode', 'IW'))\n", 138 | "\n", 139 | " # apply water and land mask\n", 140 | " # create forest lost mask \n", 141 | " #buffer = 30\n", 142 | "\n", 143 | " year = ee.Date(endDate).get('year') \n", 144 | " masksHandler = Masks(geometry,masks)\n", 145 | " masksHandler.calculateCombinedMask() # combined mask does not include aspects\n", 146 | " self.sentinel1 = self.sentinel1.map(algorithm = masksHandler.updateCombinedMask)\n", 147 | " \n", 148 | " self.VVAsc = self.sentinel1.filter(ee.Filter.eq('orbitProperties_pass','ASCENDING' )) \\\n", 149 | " .select('VV') \\\n", 150 | " .map(filterSpeckles3x3) \\\n", 151 | " .map(algorithm = self.renameVVAsc)\n", 152 | " self.VHAsc = self.sentinel1.filter(ee.Filter.eq('orbitProperties_pass', 'ASCENDING' )) \\\n", 153 | " .select('VH') \\\n", 154 | " .map(filterSpeckles3x3) \\\n", 155 | " .map(algorithm = self.renameVHAsc)\n", 156 | " self.VVDes = self.sentinel1.filter(ee.Filter.eq('orbitProperties_pass', 'DESCENDING')) \\\n", 157 | " .select('VV') \\\n", 158 | " .map(filterSpeckles3x3) \\\n", 159 | " .map(algorithm = self.renameVVDes)\n", 160 | " self.VHDes = self.sentinel1.filter(ee.Filter.eq('orbitProperties_pass', 'DESCENDING')).select('VH') \\\n", 161 | " .map(filterSpeckles3x3) \\\n", 162 | " .map(algorithm = self.renameVHDes)\n", 163 | " self.tmpCol = None\n", 164 | " self.tmpyear = 0\n", 165 | " self.addBands = ['VHAsc', 'VVAsc', 'VHDes', 'VVDes']\n", 166 | " self.selectedbands = ['VHAsc', 'VVAsc', 'VHDes', 'VVDes']\n", 167 | " \n", 168 | " if(\"selectedBand\" in sentinel1Info and sentinel1Info[\"selectedBand\"]!=None):\n", 169 | " print (\"WARNING: Selecting bands has not been implemented for Sentinel-1\")\n", 170 | " print (\"WARNING: Default bands are used: \", self.selectedBands)\n", 171 | "\n", 172 | " if(applyAspects):\n", 173 | " masksHandlerAscAsp = Masks(geometry,{'aspectAsc':0})\n", 174 | " masksHandlerDesAsp = Masks(geometry,{'aspectDes':0})\n", 175 | " masksHandlerAscAsp.calculateCombinedMask() \n", 176 | " masksHandlerDesAsp.calculateCombinedMask() \n", 177 | " self.VVAsc = self.VVAsc.map(algorithm = masksHandlerAscAsp.updateAscMask)\n", 178 | " self.VHAsc = self.VHAsc.map(algorithm = masksHandlerAscAsp.updateAscMask) \n", 179 | " self.VVDes = self.VVDes.map(algorithm = masksHandlerDesAsp.updateDesMask) \n", 180 | " self.VHDes = self.VHDes.map(algorithm = masksHandlerDesAsp.updateDesMask)\n", 181 | "\n", 182 | " years = [2017]\n", 183 | " months = list(range(1,13))\n", 184 | " #self.VHDes = monthly_Avg(self.VHDes,years,months)\n", 185 | " #self.VHDes = byMonth(self.VHDes)\n", 186 | " #self.byMonthVHDesVHDes()\n", 187 | "\n", 188 | " def getVVAsc(self):\n", 189 | " return self.VVAsc\n", 190 | "\n", 191 | " def getVHAsc(self):\n", 192 | " return self.VHAsc\n", 193 | "\n", 194 | " def getVVDes(self):\n", 195 | " return self.VVDes\n", 196 | "\n", 197 | " def getCollectionToBands(self):\n", 198 | " VHAscBands = self.VHAsc.toBands()\n", 199 | " VVAscBands = self.VVAsc.toBands()\n", 200 | " VHDesBands = self.VHDes.toBands()\n", 201 | " VVDesBands = self.VVDes.toBands()\n", 202 | " return VHAscBands.addBands(VVAscBands).addBands(VHDesBands).addBands(VVDesBands)\n", 203 | " \n", 204 | " def getCollectionToBandsAsc(self):\n", 205 | " VHAscBands = self.VHAsc.toBands()\n", 206 | " VVAscBands = self.VVAsc.toBands()\n", 207 | " return VHAscBands.addBands(VVAscBands)\n", 208 | " \n", 209 | " def getCollectionToBandsDes(self):\n", 210 | " VHDesBands = self.VHDes.toBands()\n", 211 | " VVDesBands = self.VVDes.toBands()\n", 212 | " return VHDesBands.addBands(VVDesBands)\n", 213 | "\n", 214 | "\n", 215 | "\n", 216 | " def getVHDes(self):\n", 217 | " return self.VHDes \n", 218 | " \n", 219 | " def getVVAscMedian(self):\n", 220 | " return self.VVAsc.median()\n", 221 | "\n", 222 | " def getVHAscMedian(self):\n", 223 | " return self.VHAsc.median()\n", 224 | "\n", 225 | " def getVVDesMedian(self):\n", 226 | " return self.VVDes.median()\n", 227 | "\n", 228 | " def getVHDesMedian(self):\n", 229 | " return self.VHDes.median()\n", 230 | " \n", 231 | " \n", 232 | " def getAveOfMonthVHDes(self,m): \n", 233 | " return self.VHDes.filter(ee.Filter.calendarRange(m, m, 'month')) #\\\n", 234 | " # .mean() \\\n", 235 | " # .set('month', m) \n", 236 | "\n", 237 | " def byMonthVHDesVHDes(self,i_year,col):\n", 238 | " startDate = ee.Date.fromYMD(i_year, 1, 1)\n", 239 | " endDate = startDate.advance(1, 'year')\n", 240 | " tmpCol = col.filter(ee.Filter.date(startDate, endDate))\n", 241 | " return ee.ImageCollection.fromImages(ee.List([\n", 242 | " (tmpCol.filter(ee.Filter.calendarRange( 1, 1, 'month')).mean().set('month', 1)),\n", 243 | " (tmpCol.filter(ee.Filter.calendarRange( 2, 2, 'month')).mean().set('month', 2)),\n", 244 | " (tmpCol.filter(ee.Filter.calendarRange( 3, 3, 'month')).mean().set('month', 3)),\n", 245 | " (tmpCol.filter(ee.Filter.calendarRange( 4, 4, 'month')).mean().set('month', 4)),\n", 246 | " (tmpCol.filter(ee.Filter.calendarRange( 5, 5, 'month')).mean().set('month', 5)),\n", 247 | " (tmpCol.filter(ee.Filter.calendarRange( 6, 6, 'month')).mean().set('month', 6)),\n", 248 | " (tmpCol.filter(ee.Filter.calendarRange( 7, 7, 'month')).mean().set('month', 7)),\n", 249 | " (tmpCol.filter(ee.Filter.calendarRange( 8, 8, 'month')).mean().set('month', 8)),\n", 250 | " (tmpCol.filter(ee.Filter.calendarRange( 9, 9, 'month')).mean().set('month', 9)),\n", 251 | " (tmpCol.filter(ee.Filter.calendarRange(10, 10, 'month')).mean().set('month',10)),\n", 252 | " (tmpCol.filter(ee.Filter.calendarRange(11, 11, 'month')).mean().set('month',11)),\n", 253 | " (tmpCol.filter(ee.Filter.calendarRange(12, 12, 'month')).mean().set('month',12))]))\n", 254 | " \n", 255 | " def byMonth(self,i_year):\n", 256 | " self.VHAsc = self.byMonthVHDesVHDes(i_year,self.VHAsc)\n", 257 | " self.VVAsc = self.byMonthVHDesVHDes(i_year,self.VVAsc)\n", 258 | " self.VHDes = self.byMonthVHDesVHDes(i_year,self.VHDes)\n", 259 | " self.VVDes = self.byMonthVHDesVHDes(i_year,self.VVDes)\n", 260 | " \n", 261 | " # method that removed a given period from the dataset\n", 262 | " def removePeriod(self,startDate, endDate):\n", 263 | " badDataFilter = ee.Filter.date(startDate,endDate)\n", 264 | " self.VHAsc = self.VHAsc.filter(badDataFilter.Not())\n", 265 | " self.VVAsc = self.VVAsc.filter(badDataFilter.Not())\n", 266 | " self.VHDes = self.VHDes.filter(badDataFilter.Not())\n", 267 | " self.VVDes = self.VVDes.filter(badDataFilter.Not())\n", 268 | " print(\"Period from \", startDate, \" to \", endDate, \" removed\")" 269 | ] 270 | }, 271 | { 272 | "cell_type": "code", 273 | "execution_count": null, 274 | "metadata": {}, 275 | "outputs": [], 276 | "source": [ 277 | "print (\"Sentinel1 class imported\")" 278 | ] 279 | } 280 | ], 281 | "metadata": { 282 | "kernelspec": { 283 | "display_name": "Python 3", 284 | "language": "python", 285 | "name": "python3" 286 | }, 287 | "language_info": { 288 | "codemirror_mode": { 289 | "name": "ipython", 290 | "version": 3 291 | }, 292 | "file_extension": ".py", 293 | "mimetype": "text/x-python", 294 | "name": "python", 295 | "nbconvert_exporter": "python", 296 | "pygments_lexer": "ipython3", 297 | "version": "3.11.1" 298 | }, 299 | "orig_nbformat": 4, 300 | "vscode": { 301 | "interpreter": { 302 | "hash": "cab1d7ef7b90e69a2393a883ac82077044fd5f1d4df4dae9ffefa7c49ee44033" 303 | } 304 | } 305 | }, 306 | "nbformat": 4, 307 | "nbformat_minor": 2 308 | } 309 | -------------------------------------------------------------------------------- /Sentinel1_test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import sys\n", 10 | "\n", 11 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 12 | "modulename = 'ipynb_Sentinel1'\n", 13 | "if modulename not in sys.modules:\n", 14 | " %run Sentinel1.ipynb\n", 15 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 16 | " sys.modules['ipynb_Sentinel1'] = None \n", 17 | "#else\n", 18 | " # Sentinel1 modules has already been loaded somewhere else\n", 19 | "\n", 20 | "\n", 21 | "# Visualisation\n", 22 | "modulename = 'ipynb_MapVisualisation'\n", 23 | "if modulename not in sys.modules:\n", 24 | " %run MapVisualisation.ipynb\n", 25 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 26 | " sys.modules['ipynb_MapVisualisation'] = None \n", 27 | "#else\n", 28 | " # MapVisualisation module has already been loaded somewhere else\n", 29 | "\n" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "startDate = '2019-01-01'\n", 39 | "endDate = '2019-12-31'\n", 40 | "\n", 41 | "countries = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017')\n", 42 | "geometry = countries.filter(ee.Filter.eq('country_na', 'Spain'))\n", 43 | "\n", 44 | "geometry = ee.Geometry.Polygon(\n", 45 | " [[[-2.97404756802236, 41.99472813320794],\n", 46 | " [-2.97404756802236, 41.8107480842418],\n", 47 | " [-2.66643038052236, 41.8107480842418],\n", 48 | " [-2.66643038052236, 41.99472813320794]]])\n", 49 | "\n", 50 | "fstartDate = '2000-01-01'\n", 51 | "fendDate = '2019-12-31'\n", 52 | "\n", 53 | "# in the masks only one of the two aspects should be included each time 'aspctDes' or 'aspctAsc'\n", 54 | "# if both of them are included then the entire area is masked out\n", 55 | "# if we want to apply both aspects then we can enable the tag \n", 56 | "\n", 57 | "masks = {'gsw': 50}#, 'lmask': 30, 'forestMask': \\\n", 58 | " #{'buffer':30,'startDate': fstartDate,'endDate':fendDate}}#,'aspectDes':0}#,'aspectAsc':0}\n", 59 | "#masks = {} # compolsulsory apsects for SAR\n", 60 | "\n", 61 | "\n", 62 | "applyAspects = True\n", 63 | "s1 = Sentinel1(geometry,startDate,endDate,masks,applyAspects)\n", 64 | "# remove images from the collection \n", 65 | "# in Sentinel 1 there is an internal removePeriod command for removing the selected period \n", 66 | "# for all VV, VH asc and desc respectively \n", 67 | "# This is why the removePeriodFromCollection() function is not used\n", 68 | "s1.removePeriod(startDate,'2017-01-10')\n", 69 | "s1.byMonth(2019)\n", 70 | "\n", 71 | "VVAsc = s1.getVVAscMedian()\n", 72 | "VHAsc = s1.getVHAscMedian()\n", 73 | "VVDes = s1.getVVDesMedian()\n", 74 | "VHDes = s1.getVHDesMedian()\n", 75 | "\n", 76 | "S1bands = s1.getCollectionToBands()\n", 77 | "\n", 78 | "\n" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "\n", 88 | "\n", 89 | "# Set visualization parameters.\n", 90 | "#vis_params = {\n", 91 | "# 'min': 0,\n", 92 | "# 'max': 4000,\n", 93 | "# 'palette': ['006633', 'E5FFCC', '662A00', 'D8D8D8', 'F5F5F5']}\n", 94 | "\n", 95 | "# Create a folium map object.\n", 96 | "my_map = folium.Map(location=[42,-3], zoom_start=11)\n", 97 | "\n", 98 | "\n", 99 | "# Add custom basemaps\n", 100 | "basemaps['Google Maps'].add_to(my_map)\n", 101 | "basemaps['Google Satellite Hybrid'].add_to(my_map)\n", 102 | "\n", 103 | "\n", 104 | "# Add VVAsc to the map\n", 105 | "my_map.add_ee_layer(VVAsc, {'min':-30,'max':0}, 'VVAsc')\n", 106 | "# Add VHAsc tp the map \n", 107 | "my_map.add_ee_layer(VHAsc, {'min':-30,'max':0}, 'VHAsc')\n", 108 | "# Add VVDes to the map \n", 109 | "my_map.add_ee_layer(VVDes, {'min':-30,'max':0}, 'VVDes')\n", 110 | "# Add descending aspects map to the map\n", 111 | "my_map.add_ee_layer(VHDes, {'min':-30,'max':0}, 'VHDes')\n", 112 | "\n", 113 | "\n", 114 | "\n", 115 | "#print(\"No remaining images: \", getNoOfBands(VVAsc))\n", 116 | "# Add a layer control panel to the map.\n", 117 | "my_map.add_child(folium.LayerControl())\n", 118 | "\n", 119 | "\n", 120 | "plugins.Fullscreen().add_to(my_map)\n", 121 | "\n", 122 | "# Add a layer control panel to the map.\n", 123 | "my_map.add_child(folium.LayerControl())\n", 124 | "\n", 125 | "# Add fullscreen button\n", 126 | "plugins.Fullscreen().add_to(my_map)\n", 127 | "\n", 128 | "# Display the map.\n", 129 | "display(my_map)\n", 130 | "\n" 131 | ] 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "metadata": {}, 136 | "source": [] 137 | } 138 | ], 139 | "metadata": { 140 | "kernelspec": { 141 | "display_name": ".venv", 142 | "language": "python", 143 | "name": "python3" 144 | }, 145 | "language_info": { 146 | "codemirror_mode": { 147 | "name": "ipython", 148 | "version": 3 149 | }, 150 | "file_extension": ".py", 151 | "mimetype": "text/x-python", 152 | "name": "python", 153 | "nbconvert_exporter": "python", 154 | "pygments_lexer": "ipython3", 155 | "version": "3.13.0" 156 | }, 157 | "orig_nbformat": 4 158 | }, 159 | "nbformat": 4, 160 | "nbformat_minor": 2 161 | } 162 | -------------------------------------------------------------------------------- /Sentinel2b.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [] 8 | }, 9 | { 10 | "cell_type": "code", 11 | "execution_count": null, 12 | "metadata": {}, 13 | "outputs": [], 14 | "source": [ 15 | "#@title Copyright 2020 The Earth Engine Community Authors { display-mode: \"form\" }\n", 16 | "#\n", 17 | "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", 18 | "# you may not use this file except in compliance with the License.\n", 19 | "# You may obtain a copy of the License at\n", 20 | "#\n", 21 | "# https://www.apache.org/licenses/LICENSE-2.0\n", 22 | "#\n", 23 | "# Unless required by applicable law or agreed to in writing, software\n", 24 | "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", 25 | "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", 26 | "# See the License for the specific language governing permissions and\n", 27 | "# limitations under the License." 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "from datetime import date\n", 37 | "import pandas as pd\n", 38 | "\n", 39 | "#Define global variables related to Sentinel-1\n", 40 | "sentinel2Info = {\n", 41 | " \"collection\": \"COPERNICUS/S2_SR_HARMONIZED\",\n", 42 | " \"startYear\" : \"2014-04-01\",\n", 43 | " \"endDate\" : None\n", 44 | "}\n", 45 | "\n", 46 | "\n", 47 | "\n", 48 | "## method that returns the name of the collection used in the Sentinel1 class\n", 49 | "def getSentinel2Col():\n", 50 | " return sentinel2Info[\"collection\"]\n", 51 | "\n", 52 | "## method that sets the Sentinel1 collection \n", 53 | "# this is useful in case an updated class is released in GEE or for consistency an \n", 54 | "# older version of Sentinel1 col needs to be used\n", 55 | "# @param[in] newcol: the name of the collection that will replace the collection \n", 56 | "# used in this class\n", 57 | "# @note: Please use with caution as a wrong collection will crash the entire class\n", 58 | "def setSentinel2Col(newCol):\n", 59 | " global sentinel2Col\n", 60 | " sentinel2Info[\"collection\"] = newCol\n", 61 | "\n", 62 | "\n", 63 | "## method that returns the the start end end date of the collection\n", 64 | "# @param[in] type 'whole' or 'year' get the entire date or just the year\n", 65 | "def getStartEndDateS2(type):\n", 66 | " startDate = sentinel2Info[\"startYear\"]\n", 67 | " endDate = sentinel2Info[\"endDate\" ]\n", 68 | " if(endDate == None):\n", 69 | " today = date.today()\n", 70 | " endDate = today.strftime(\"%Y-%m-%d\")\n", 71 | " if type == 'year' :\n", 72 | " return [getYearMonthOrDay(startDate,'year'),getYearMonthOrDay(endDate,'year')]\n", 73 | " elif type == 'whole':\n", 74 | " return [startDate,endDate]\n", 75 | " else: # type not defined\n", 76 | " raise Exception(\"ERROR: type for getting start end end date/year of Sentinel-1 not defined\\n\")\n", 77 | " " 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "import sys\n", 87 | "\n", 88 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 89 | "modulename = 'ee'\n", 90 | "if modulename not in sys.modules: \n", 91 | " # import GEE and Authenticate, token or log in will be asked from web browser\n", 92 | " import ee\n", 93 | " ee.Authenticate()\n", 94 | " ee.Initialize()\n", 95 | "#else:\n", 96 | " # google earth engine already imported and authenticated\n", 97 | " \n", 98 | "\n", 99 | "modulename = 'ipynb_masks'\n", 100 | "if modulename not in sys.modules:\n", 101 | " %run Masks.ipynb\n", 102 | " sys.modules['ipynb_masks'] = None\n", 103 | "#else\n", 104 | " # module already loaded\n", 105 | "\n", 106 | "modulename = 'ipynb_Utils'\n", 107 | "if modulename not in sys.modules:\n", 108 | " %run Utils.ipynb\n", 109 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 110 | " sys.modules['ipynb_Utils'] = None \n", 111 | "#else\n", 112 | " # Utils modules has already been loaded somewhere else\n", 113 | "\n", 114 | "modulename = 'ipynb_masks'\n", 115 | "if modulename not in sys.modules:\n", 116 | " %run Masks.ipynb\n", 117 | " sys.modules['ipynb_masks'] = None\n", 118 | "#else\n", 119 | " # module already loaded" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": null, 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "# Public info for printing the available Indices of Sentinel-2 and the relevant information\n", 129 | "\n", 130 | "indiceKeys = ['ndvi']\n", 131 | "IndexName = ['Normalised Difference Vegetation Index']\n", 132 | "formula = ['(B8 - B4) / (B8 + B4)']\n", 133 | "\n", 134 | "availableIndices = pd.DataFrame(data={\n", 135 | " \"Key\": indiceKeys, \n", 136 | " \"Index\": IndexName, \n", 137 | " \"Formula\": formula}\n", 138 | " ) \n", 139 | "\n", 140 | "\n", 141 | " \n", 142 | "def printAvailableIndicesS2():\n", 143 | " print(availableIndices)\n" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": null, 149 | "metadata": {}, 150 | "outputs": [], 151 | "source": [ 152 | "class Sentinel2:\n", 153 | " \n", 154 | " def add_cloud_bands(self,img):\n", 155 | " # Get s2cloudless image, subset the probability band.\n", 156 | " cld_prb = ee.Image(img.get('s2cloudless')).select('probability')\n", 157 | "\n", 158 | " # Condition s2cloudless by the probability threshold value.\n", 159 | " is_cloud = cld_prb.gt(self.CLD_PRB_THRESH).rename('clouds')\n", 160 | "\n", 161 | " # Add the cloud probability layer and cloud mask as image bands.\n", 162 | " return img.addBands(ee.Image([cld_prb, is_cloud]))\n", 163 | "\n", 164 | " def add_shadow_bands(self,img):\n", 165 | " # Identify water pixels from the SCL band.\n", 166 | " not_water = img.select('SCL').neq(6)\n", 167 | "\n", 168 | " # Identify dark NIR pixels that are not water (potential cloud shadow pixels).\n", 169 | " SR_BAND_SCALE = 1e4\n", 170 | " dark_pixels = img.select('B8').lt(self.NIR_DRK_THRESH*SR_BAND_SCALE).multiply(not_water).rename('dark_pixels')\n", 171 | "\n", 172 | " # Determine the direction to project cloud shadow from clouds (assumes UTM projection).\n", 173 | " shadow_azimuth = ee.Number(90).subtract(ee.Number(img.get('MEAN_SOLAR_AZIMUTH_ANGLE')));\n", 174 | "\n", 175 | " # Project shadows from clouds for the distance specified by the CLD_PRJ_DIST input.\n", 176 | " cld_proj = (img.select('clouds').directionalDistanceTransform(shadow_azimuth, self.CLD_PRJ_DIST*10)\n", 177 | " .reproject(**{'crs': img.select(0).projection(), 'scale': 100})\n", 178 | " .select('distance')\n", 179 | " .mask()\n", 180 | " .rename('cloud_transform'))\n", 181 | "\n", 182 | " # Identify the intersection of dark pixels with cloud shadow projection.\n", 183 | " shadows = cld_proj.multiply(dark_pixels).rename('shadows')\n", 184 | "\n", 185 | " # Add dark pixels, cloud projection, and identified shadows as image bands.\n", 186 | " return img.addBands(ee.Image([dark_pixels, cld_proj, shadows]))\n", 187 | "\n", 188 | " def add_cld_shdw_mask(self,img):\n", 189 | " # Add cloud component bands.\n", 190 | " img_cloud = self.add_cloud_bands(img)\n", 191 | "\n", 192 | " # Add cloud shadow component bands.\n", 193 | " img_cloud_shadow = self.add_shadow_bands(img_cloud)\n", 194 | "\n", 195 | " # Combine cloud and shadow mask, set cloud and shadow as value 1, else 0.\n", 196 | " is_cld_shdw = img_cloud_shadow.select('clouds').add(img_cloud_shadow.select('shadows')).gt(0)\n", 197 | "\n", 198 | " # Remove small cloud-shadow patches and dilate remaining pixels by BUFFER input.\n", 199 | " # 20 m scale is for speed, and assumes clouds don't require 10 m precision.\n", 200 | " is_cld_shdw = (is_cld_shdw.focal_min(2).focal_max(self.BUFFER*2/20)\n", 201 | " .reproject(**{'crs': img.select([0]).projection(), 'scale': 20})\n", 202 | " .rename('cloudmask'))\n", 203 | "\n", 204 | " img_cloud_shadow = img_cloud_shadow.addBands(is_cld_shdw) \n", 205 | " # Add the final cloud-shadow mask to the image.\n", 206 | " return img_cloud_shadow\n", 207 | "\n", 208 | " def applyCloudMask(self,img):\n", 209 | " #result = img.where(img.select('cloudmask').gt(0),1)\n", 210 | " #resultUnmasked = result.unmask(0)\n", 211 | " #mask = resultUnmasked.unmask(-999).eq(-999)\n", 212 | " cloudmask = img.select('cloudmask').selfMask()\n", 213 | " tmp1 = cloudmask.where(cloudmask.gt(0),0)\n", 214 | " tmp1 = tmp1.unmask(1)\n", 215 | " return img.updateMask(tmp1)\n", 216 | " \n", 217 | " \n", 218 | " def addNDVIimg(self, image):\n", 219 | " # NDVI = (NIR - Red) / (NIR + Red)\n", 220 | " ndvi = image.expression(\n", 221 | " '(nir - red) / (nir + red)', {\n", 222 | " 'nir': image.select('B8'),\n", 223 | " 'red': image.select('B4')\n", 224 | " }\n", 225 | " )\n", 226 | " return image.addBands(ndvi.rename('ndvi'))\n", 227 | " \n", 228 | " # @brief initialisation of Sentinel2 class\n", 229 | " # @param[in] geometry: area of interest\n", 230 | " # @param[in] startDate: start date of the collection to be retrived\n", 231 | " # @param[in] endDate: end date of the collection to be retrived\n", 232 | " # @param[in] cloudfilter: percentage of non acceptable clouds for removing images\n", 233 | " # @param[in] masks: a dictonary containing the masks to be applied \n", 234 | " # Format of masks should be the following, but it should include only the masks \n", 235 | " # that we want to be applied\n", 236 | " # masks = {'gsw':buffer, 'lmask': buffer, 'forestMask': {buffer,year}}\n", 237 | " def __init__(self,geometry, sentinel2Info, masks):\n", 238 | " self.allbands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B9', 'B11', 'B12', 'AOT', 'WVP', 'SCL', 'TCI_R', 'TCI_G', 'TCI_B', 'MSK_CLDPRB', 'MSK_SNWPRB', 'QA10', 'QA20', 'QA60', 'MSK_CLASSI_OPAQUE', 'MSK_CLASSI_CIRRUS', 'MSK_CLASSI_SNOW_ICE', 'probability', 'clouds', 'dark_pixels', 'cloud_transform', 'shadows', 'cloudmask']\n", 239 | " self.selectedBands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B9', 'B11', 'B12']\n", 240 | " self.selectedIndices = []\n", 241 | " self.startDate = '2019-01-01'\n", 242 | " self.endDate = '2019-12-31'\n", 243 | " self.CLOUD_FILTER = 50\n", 244 | " self.CLD_PRB_THRESH = 50\n", 245 | " self.NIR_DRK_THRESH = 0.15\n", 246 | " self.CLD_PRJ_DIST = 1\n", 247 | " self.BUFFER = 50\n", 248 | " \n", 249 | " if(\"startDate\" in sentinel2Info and sentinel2Info[\"startDate\"]!=\"\"):\n", 250 | " self.startDate = str(sentinel2Info[\"startDate\"])\n", 251 | " if(\"endDate\" in sentinel2Info and sentinel2Info[\"endDate\"]!=\"\"):\n", 252 | " self.endDate = str(sentinel2Info[\"endDate\"])\n", 253 | " else:\n", 254 | " print (\"WARNING: Either startDate or endDate were not defined, default values used\")\n", 255 | " print (\"startDate: \", self.startDate, \" & endDate: \", self.endDate)\n", 256 | " else:\n", 257 | " print (\"WARNING: Either startDate or endDate were not defined, default values used\")\n", 258 | " print (\"startDate: \", self.startDate, \" & endDate: \", self.endDate)\n", 259 | " \n", 260 | " if(\"selectedBand\" in sentinel2Info and sentinel2Info[\"clouds\"]!=None):\n", 261 | " self.CLOUD_FILTER = sentinel2Info[\"clouds\"]\n", 262 | " else : \n", 263 | " print(\"WARNING: Cloud percentage threshold not define. Default value is \", self.CLOUD_FILTER)\n", 264 | " \n", 265 | " \n", 266 | " if(\"selectedBand\" in sentinel2Info and sentinel2Info[\"selectedBand\"]!=None):\n", 267 | " self.selectedBands = sentinel2Info[\"selectedBand\"]\n", 268 | " else : \n", 269 | " print (\"WARNING: Default bands are used: \", self.selectedBands)\n", 270 | " if(\"selectedIndices\" in sentinel2Info and sentinel2Info[\"selectedIndices\"]!=None):\n", 271 | " self.selectedIndices = sentinel2Info[\"selectedIndices\"]\n", 272 | " else:\n", 273 | " print (\"WARNING: No indices selected for interpretation. \",\n", 274 | " \"Use printAvailableIndicesS2() to print available indices\")\n", 275 | " \n", 276 | " # Import and filter S2 SR.\n", 277 | " s2_sr_col = (ee.ImageCollection(sentinel2Info[\"collection\"])\n", 278 | " .filterBounds(geometry)\n", 279 | " .filterDate(self.startDate, self.endDate)\n", 280 | " .filter(ee.Filter.lte('CLOUDY_PIXEL_PERCENTAGE', self.CLOUD_FILTER)))\n", 281 | "\n", 282 | " # Import and filter s2cloudless.\n", 283 | " s2_cloudless_col = (ee.ImageCollection('COPERNICUS/S2_CLOUD_PROBABILITY')\n", 284 | " .filterBounds(geometry)\n", 285 | " .filterDate(self.startDate, self.endDate))\n", 286 | "\n", 287 | " # Join the filtered s2cloudless collection to the SR collection by the 'system:index' property.\n", 288 | " s2_sr_cld_col_eval = ee.ImageCollection(ee.Join.saveFirst('s2cloudless').apply(**{\n", 289 | " 'primary': s2_sr_col,\n", 290 | " 'secondary': s2_cloudless_col,\n", 291 | " 'condition': ee.Filter.equals(**{\n", 292 | " 'leftField': 'system:index',\n", 293 | " 'rightField': 'system:index'\n", 294 | " })\n", 295 | " }))\n", 296 | "\n", 297 | "\n", 298 | " self.s2_sr_cld_col_eval_disp = s2_sr_cld_col_eval.map(self.add_cld_shdw_mask)\n", 299 | "\n", 300 | " self.s2_sr_cld_col_eval_disp = self.s2_sr_cld_col_eval_disp.map(algorithm = self.applyCloudMask)\n", 301 | " \n", 302 | " self.selectedIndicesRefined = []\n", 303 | " if ('ndvi' in self.selectedIndices):\n", 304 | " self.s2_sr_cld_col_eval_disp = self.s2_sr_cld_col_eval_disp.map(algorithm = self.addNDVIimg)\n", 305 | " self.selectedIndicesRefined = self.selectedIndicesRefined + ['ndvi']\n", 306 | " # else ndvi was not chosen to be included in the calculations\n", 307 | " \n", 308 | " self.selectedBandsRefined = [] \n", 309 | " for band in self.selectedBands:\n", 310 | " if band in self.allbands :\n", 311 | " self.selectedBandsRefined = self.selectedBandsRefined + [band]\n", 312 | " \n", 313 | " selectedBandsNIndices = []\n", 314 | " if(self.selectedBandsRefined==[]):\n", 315 | " selectedBandsNIndices = self.selectedIndicesRefined \n", 316 | " elif (self.selectedIndicesRefined == []):\n", 317 | " selectedBandsNIndices = self.selectedBandsRefined\n", 318 | " else : \n", 319 | " selectedBandsNIndices = self.selectedBands + self.selectedIndicesRefined \n", 320 | " \n", 321 | " print(\"selectedBandsNIndices:\", selectedBandsNIndices)\n", 322 | " self.s2_sr_cld_col_eval_disp = self.s2_sr_cld_col_eval_disp.select(selectedBandsNIndices)\n", 323 | "\n", 324 | " \n", 325 | " masksHandler = Masks(geometry,masks)\n", 326 | " masksHandler.calculateCombinedMask()\n", 327 | " self.s2_sr_cld_col_eval_disp = self.s2_sr_cld_col_eval_disp.map(algorithm = masksHandler.updateCombinedMask)\n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " def getMedian(self):\n", 333 | " return self.s2_sr_cld_col_eval_disp.median() \n", 334 | "\n", 335 | " \n", 336 | " def byMonth(self,i_year):\n", 337 | " self.s2_sr_cld_col_eval_disp = byMonth(i_year,self.s2_sr_cld_col_eval_disp) \n", 338 | "\n", 339 | " def getCollection(self):\n", 340 | " return self.s2_sr_cld_col_eval_disp\n", 341 | "\n", 342 | " def getCollectionToBands(self):\n", 343 | " return self.s2_sr_cld_col_eval_disp.toBands()\n", 344 | " \n", 345 | " def getCollectionToListFlatten(self):\n", 346 | " return self.s2_sr_cld_col_eval_disp.toList().flatten()\n", 347 | "\n", 348 | " " 349 | ] 350 | }, 351 | { 352 | "cell_type": "code", 353 | "execution_count": null, 354 | "metadata": {}, 355 | "outputs": [], 356 | "source": [ 357 | "print(\"Sentinel2b class imported\")\n", 358 | "\n" 359 | ] 360 | } 361 | ], 362 | "metadata": { 363 | "kernelspec": { 364 | "display_name": ".venv", 365 | "language": "python", 366 | "name": "python3" 367 | }, 368 | "language_info": { 369 | "codemirror_mode": { 370 | "name": "ipython", 371 | "version": 3 372 | }, 373 | "file_extension": ".py", 374 | "mimetype": "text/x-python", 375 | "name": "python", 376 | "nbconvert_exporter": "python", 377 | "pygments_lexer": "ipython3", 378 | "version": "3.13.0" 379 | }, 380 | "orig_nbformat": 4 381 | }, 382 | "nbformat": 4, 383 | "nbformat_minor": 2 384 | } 385 | -------------------------------------------------------------------------------- /Sentinel2b_test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import sys\n", 10 | "import ee\n", 11 | "%run Sentinel2b.ipynb\n", 12 | "ee.Authenticate()\n", 13 | "ee.Initialize()\n", 14 | "\n", 15 | "modulename = 'ee'\n", 16 | "if modulename not in sys.modules:\n", 17 | " # import GEE and Authenticate, token or log in will be asked from web browser\n", 18 | " import ee\n", 19 | " # TO DO : UNCOMMENT THIS IF IT IS THE FIRST TIME AUTHINTICATE GEE ON THIS MACHINE\n", 20 | " # ee.Authenticate()\n", 21 | " ee.Initialize()\n", 22 | "# else:\n", 23 | " # google earth engine already imported and authenticated\n", 24 | "\n", 25 | "\n", 26 | "modulename = 'ipynb_MapVisualisation'\n", 27 | "if modulename not in sys.modules:\n", 28 | " %run MapVisualisation.ipynb\n", 29 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 30 | " sys.modules['ipynb_MapVisualisation'] = None\n", 31 | "# else\n", 32 | " # Utils modules has already been loaded somewhere else\n", 33 | "\n", 34 | "\n", 35 | "modulename = 'ipynb_Sentinel2b'\n", 36 | "if modulename not in sys.modules:\n", 37 | " %run Sentinel2b.ipynb\n", 38 | " # adding an identifier to sys.modules to avoiding loading the same file multiple times\n", 39 | " sys.modules['ipynb_Sentinel2b'] = None\n", 40 | "# else\n", 41 | " # Utils modules has already been loaded somewhere else\n" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "metadata": {}, 48 | "outputs": [], 49 | "source": [ 50 | "\n", 51 | "# Area of Interest\n", 52 | "AOI = ee.Geometry.Polygon(\n", 53 | " [[[-2.97404756802236, 41.99472813320794],\n", 54 | " [-2.97404756802236, 41.8107480842418],\n", 55 | " [-2.66643038052236, 41.8107480842418],\n", 56 | " [-2.66643038052236, 41.99472813320794]]])\n", 57 | "\n", 58 | "# cloudmask01 = getCloudMask(median)\n", 59 | "\n", 60 | "START_DATE = '2019-01-01'\n", 61 | "END_DATE = '2019-12-31'\n", 62 | "\n", 63 | "\n", 64 | "\n", 65 | "masks = { 'gsw': 1}#,'forestMask': {'buffer':10, 'startDate':'2000-01-01', 'endDate':'2022-12-31',}} # # , 'lmask': 30,\n", 66 | "\n", 67 | "printAvailableIndicesS2()\n", 68 | "\n", 69 | "sentinel2Info = {\n", 70 | " \"identifier\": 'sentinel-2', \n", 71 | " \"collection\": \"COPERNICUS/S2_SR_HARMONIZED\",\n", 72 | " #\"launchDate\" : \"2015-05-23\",\n", 73 | " #\"retirmentDate\":todayDate, #not retired yet\n", 74 | " \"startDate\" : '2019-01-01',\n", 75 | " \"endDate\" : '2019-12-31',\n", 76 | " #\"added\" : False,\n", 77 | " \"clouds\" : 50,\n", 78 | " \"selectedBand\": ['B8', 'B4'],\n", 79 | " \"selectedIndices\": ['ndvi']\n", 80 | "} \n", 81 | "\n", 82 | "\n", 83 | "s2 = Sentinel2(AOI, sentinel2Info, masks)\n", 84 | "\n", 85 | "\n", 86 | "# s2.byMonth(2020)\n", 87 | "\n", 88 | "median = s2.getMedian()\n", 89 | "bandNames = median.bandNames()\n", 90 | "print('Band names medianS2: ', bandNames.getInfo())\n", 91 | "\n", 92 | "# Code used for testing cloud masks - Cloud masking used by default so no need to worry about this code\n", 93 | "#med = s2.getCollection().first()\n", 94 | "#clouds = med.select('clouds').selfMask()\n", 95 | "#shadows = med.select('shadows').selfMask()\n", 96 | "#dark_pixels = med.select('dark_pixels') # .selfMask()\n", 97 | "#probability = med.select('probability')\n", 98 | "#cloudmask = med.select('cloudmask')\n", 99 | "#cloudmaskMasked = cloudmask # .selfMask()\n", 100 | "#cloud_transform = med.select('cloud_transform')\n", 101 | "\n", 102 | "# Selecting RGB bands for visualisation purposes\n", 103 | "#medianS2 = median.select(['B4', 'B3', 'B2'])\n", 104 | "\n", 105 | "\n", 106 | "# Calculate NDVI on the median image\n", 107 | "medianB84 = median.select(['B4', 'B8'])\n", 108 | "medianB84NDVI = s2.addNDVIimg(medianB84)\n", 109 | "\n", 110 | "\n", 111 | "# export image to GEE - adding maxPixels is a bad habit, used carefully\n", 112 | "task = ee.batch.Export.image.toDrive(**{\n", 113 | " 'image': medianB84NDVI,\n", 114 | " 'description': 'medRGB1_GB1_2019',\n", 115 | " 'folder': 'earth_engine_demos',\n", 116 | " 'scale': 10,\n", 117 | " 'region': AOI.getInfo()['coordinates'],\n", 118 | " 'maxPixels': 1549491660\n", 119 | "})\n", 120 | "task.start()\n", 121 | "\n" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "metadata": {}, 128 | "outputs": [], 129 | "source": [ 130 | "%run MapVisualisation.ipynb\n", 131 | "# These are the coordinates of Spain\n", 132 | "my_map = folium.Map(location=[42, -3], zoom_start=11)\n", 133 | "# Add custom basemaps\n", 134 | "basemaps['Google Maps'].add_to(my_map)\n", 135 | "basemaps['Google Satellite Hybrid'].add_to(my_map)\n", 136 | "\n", 137 | "\n", 138 | "def update_cld_shdw_mask(img):\n", 139 | " print('test')\n", 140 | "\n", 141 | "\n", 142 | "\n", 143 | "# Add Land Mask to the map\n", 144 | "my_map.add_ee_layer(medianB84NDVI, {\n", 145 | " 'bands': ['B4', 'B3', 'B2'], 'min': 0, 'max': 2500, 'gamma': 1.1}, 'median')\n", 146 | "my_map.add_ee_layer(AOI, {}, 'AOI')\n", 147 | "\n", 148 | "\n", 149 | "\n", 150 | "# Add a layer control panel to the map.\n", 151 | "my_map.add_child(folium.LayerControl())\n", 152 | "plugins.Fullscreen().add_to(my_map)\n", 153 | "\n", 154 | "# Display the map.\n", 155 | "display(my_map)\n" 156 | ] 157 | } 158 | ], 159 | "metadata": { 160 | "kernelspec": { 161 | "display_name": ".venv", 162 | "language": "python", 163 | "name": "python3" 164 | }, 165 | "language_info": { 166 | "codemirror_mode": { 167 | "name": "ipython", 168 | "version": 3 169 | }, 170 | "file_extension": ".py", 171 | "mimetype": "text/x-python", 172 | "name": "python", 173 | "nbconvert_exporter": "python", 174 | "pygments_lexer": "ipython3", 175 | "version": "3.13.0" 176 | }, 177 | "orig_nbformat": 4 178 | }, 179 | "nbformat": 4, 180 | "nbformat_minor": 2 181 | } 182 | -------------------------------------------------------------------------------- /Utils.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "This is a module containing many useful functions that can be used throughout different classes\n", 9 | "It is written as a module for better mantainance\n", 10 | "\n", 11 | "Version : 1.0\n", 12 | "Date : Feb 2023\n", 13 | "Author : Milto Miltiadou" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 6, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import sys\n", 23 | "\n", 24 | "# check if GEE is already imported to avoid requesting authenticatiation multiple times\n", 25 | "modulename = 'ee'\n", 26 | "if modulename not in sys.modules: \n", 27 | " # import GEE and Authenticate, token or log in will be asked from web browser\n", 28 | " import ee\n", 29 | " ee.Authenticate()\n", 30 | " ee.Initialize()\n", 31 | "#else:\n", 32 | " # google earth engine already imported and authenticated\n", 33 | " " 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 7, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "## Function that adds a buffer around a given raster\n", 43 | "# image: imported raster to be buffered \n", 44 | "# buffer: number of meters to add around the given raster\n", 45 | "def addBuffer (image, buffer):\n", 46 | " if (buffer == 0):\n", 47 | " #print(\"ERROR: Utils: Buffer must not be equal to zero\") \n", 48 | " return image\n", 49 | " return ee.Image(1).cumulativeCost(image, buffer, True)\n", 50 | " \n", 51 | "## Function that adds a buffer around a given raster\n", 52 | "# image: imported raster to be buffered \n", 53 | "# buffer: number of meters to add around the given raster\n", 54 | "# imgName: the new name of the image # otherwise the name of the bands is \"cumulative_cost\"\n", 55 | "def addBufferRename (image, buffer, name):\n", 56 | " if (buffer == 0):\n", 57 | " #print(\"ERROR: Utils: Buffer must not be equal to zero\") \n", 58 | " return image.rename(name)\n", 59 | " return ee.Image(1).cumulativeCost(image, buffer, True).rename(name)\n", 60 | " \n", 61 | " \n", 62 | "\n", 63 | "\n", 64 | "## This is a function that takes as input an image and applies a circlular \n", 65 | "# median filter with kernel size 3x3\n", 66 | "# @param[in] img: image to be filtered\n", 67 | "# @returns the filtered image\n", 68 | "def filterSpeckles3x3 (img):\n", 69 | " #Apply a focal median filter\n", 70 | " return img.focalMedian(3,'circle','pixels').copyProperties(img, ['system:time_start'])\n", 71 | "\n", 72 | "## This is a function that takes as input an image and applies a median filter \n", 73 | "# @param[in] img: image to be filtered\n", 74 | "# @param[in] filterSize: size of filter to be applied e.g. 3 implies 3x3\n", 75 | "# @param[in] filterShape: the shape of the filter. Options include: 'circle', 'square', 'cross', 'plus', octagon' and 'diamond'\n", 76 | "# @returns the filtered image\n", 77 | "def filterSpeckles (img, filterSize,filterShape):\n", 78 | " #Apply a focal median filter\n", 79 | " return img.focalMedian(filterSize,filterShape,'pixels').copyProperties(img, ['system:time_start'])\n", 80 | "\n", 81 | "\n", 82 | "\n", 83 | "\n", 84 | "\n", 85 | "# @brief method that takes as input a collection, calculates pixelwise average for each month and returns a new collection\n", 86 | "# @param[in] collection : the collection to be processed\n", 87 | "# @return a collection annual monthly average pixel values\n", 88 | "def byMonth (col):\n", 89 | " months = ee.List.sequence(1, 12)\n", 90 | " #print(months)\n", 91 | "# return ee.ImageCollection.fromImages(\n", 92 | "# months.map(\n", 93 | "# }))\n", 94 | "\n", 95 | "\n", 96 | "\n", 97 | "\n", 98 | "# this function was taken from https://gis.stackexchange.com/questions/426662/image-collection-monthly-averages-using-geemap-package\n", 99 | "def monthly_Avg (collection, years, months):\n", 100 | " avg = []\n", 101 | " for year in years:\n", 102 | " for month in months: \n", 103 | " print(\"month :) \")\n", 104 | " Monthly_avg = collection.filter(ee.Filter.calendarRange(year, year, 'year')) \\\n", 105 | " .filter(ee.Filter.calendarRange(month, month, 'month')) \\\n", 106 | " .mean() \\\n", 107 | " .set({'month': month, 'year': year})\n", 108 | " avg.append (Monthly_avg)\n", 109 | " return ee.ImageCollection.fromImages(avg)\n", 110 | "\n", 111 | "\n", 112 | "## @brief method that takes as input a period and removes it from the collection\n", 113 | "# @brief param[in] sentinel1 the sentinel1 collection (or any collection)\n", 114 | "# @brief param[in] start the start date of the bad period of data to be removed 'YYYY-MM-DD'\n", 115 | "# @brief param[in] end the end data of bad data to be removed 'YYYY-MM-DD'\n", 116 | "def removePeriodFromCollection (col,start,end): \n", 117 | " badDataFilter = ee.Filter.date(start,end)\n", 118 | " newCol= col.filter(badDataFilter.Not()) \n", 119 | " return newCol\n", 120 | "\n", 121 | " \n", 122 | "## method that takes as input a date and returns the year, day or month\n", 123 | "# @param[in] date: the date in the form \"YYYY-MM-DD\"\n", 124 | "# @param[in] what: what to be extracted 'year', 'month', 'day'\n", 125 | "def getYearMonthOrDay(date,what):\n", 126 | " my_list = date.split(\"-\")\n", 127 | " if what == 'year':\n", 128 | " return int(my_list[0])\n", 129 | " elif what == 'month':\n", 130 | " return int(my_list[1])\n", 131 | " elif what == 'day':\n", 132 | " return int(my_list[2])\n", 133 | " else: # wrong input \n", 134 | " return None\n", 135 | " \n", 136 | "\n", 137 | "## Method that takes as input a year and a collection and returns a collection\n", 138 | "# of 12 images representing each calendar month. Each image contains the \n", 139 | "# pixelwise average of its corresponding calendar month\n", 140 | "# @param i_year The year of interest\n", 141 | "# @param col The colection to be intepreted \n", 142 | "def byMonth(i_year,col):\n", 143 | " startDate = ee.Date.fromYMD(i_year, 1, 1)\n", 144 | " endDate = startDate.advance(1, 'year')\n", 145 | " tmpCol = col.filter(ee.Filter.date(startDate, endDate))\n", 146 | " return ee.ImageCollection.fromImages(ee.List([\n", 147 | " (tmpCol.filter(ee.Filter.calendarRange( 1, 1, 'month')).mean().set('month', 1)),#.select(\"0_B4\"),\n", 148 | " (tmpCol.filter(ee.Filter.calendarRange( 2, 2, 'month')).mean().set('month', 2)),\n", 149 | " (tmpCol.filter(ee.Filter.calendarRange( 3, 3, 'month')).mean().set('month', 3)),\n", 150 | " (tmpCol.filter(ee.Filter.calendarRange( 4, 4, 'month')).mean().set('month', 4)),\n", 151 | " (tmpCol.filter(ee.Filter.calendarRange( 5, 5, 'month')).mean().set('month', 5)),\n", 152 | " (tmpCol.filter(ee.Filter.calendarRange( 6, 6, 'month')).mean().set('month', 6)),\n", 153 | " (tmpCol.filter(ee.Filter.calendarRange( 7, 7, 'month')).mean().set('month', 7)),\n", 154 | " (tmpCol.filter(ee.Filter.calendarRange( 8, 8, 'month')).mean().set('month', 8)),\n", 155 | " (tmpCol.filter(ee.Filter.calendarRange( 9, 9, 'month')).mean().set('month', 9)),\n", 156 | " (tmpCol.filter(ee.Filter.calendarRange(10, 10, 'month')).mean().set('month',10)),\n", 157 | " (tmpCol.filter(ee.Filter.calendarRange(11, 11, 'month')).mean().set('month',11)),\n", 158 | " (tmpCol.filter(ee.Filter.calendarRange(12, 12, 'month')).mean().set('month',12))]))\n", 159 | "\n", 160 | "\n", 161 | "\n", 162 | "\n", 163 | "## Method that removed a given period from the dataset\n", 164 | "# @param startDate The starting date of the period to be removed\n", 165 | "# @param endDate The ending date of the period to be removed\n", 166 | "# @param col The collection to be interpreted\n", 167 | "# @return The new collection that does not contain the removed period\n", 168 | "def removePeriod(self,startDate, endDate, col):\n", 169 | " badDataFilter = ee.Filter.date(startDate,endDate)\n", 170 | " print(\"Period from \", startDate, \" to \", endDate, \" removed\")\n", 171 | " return col.filter(badDataFilter.Not())\n", 172 | "\n", 173 | "\n", 174 | "\n", 175 | "## Method that Get mean and SD in every band by combining reducers.\n", 176 | "def stats (image,aoi,scale):\n", 177 | " return image.reduceRegion(**{\n", 178 | " 'reducer': ee.Reducer.mean().combine(**{\n", 179 | " 'reducer2': ee.Reducer.stdDev(),\n", 180 | " 'sharedInputs': True\n", 181 | " }),\n", 182 | " 'geometry': aoi,\n", 183 | " 'scale': scale,\n", 184 | " 'bestEffort': True # Use maxPixels if you care about scale.\n", 185 | "})\n", 186 | "\n", 187 | "\n", 188 | "def reduce_salt(image,aoi):\n", 189 | " reduced = image.reduceRegion(\n", 190 | " reducer=ee.Reducer.sum(),\n", 191 | " geometry=aoi,\n", 192 | " scale=30)\n", 193 | " return ee.Feature(None, reduced)\n", 194 | "\n", 195 | "#salt_marsh_area = salt_marsh_extents.map(reduce_salt)\n", 196 | "\n", 197 | "#task = ee.batch.Export.table.toDrive(\n", 198 | "# collection=salt_marsh_area,\n", 199 | "# description='reduced',\n", 200 | "# fileFormat='CSV'\n", 201 | "#)\n", 202 | "\n" 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": 8, 208 | "metadata": {}, 209 | "outputs": [ 210 | { 211 | "name": "stdout", 212 | "output_type": "stream", 213 | "text": [ 214 | "Utils imported\n" 215 | ] 216 | } 217 | ], 218 | "source": [ 219 | "print(\"Utils imported\")" 220 | ] 221 | } 222 | ], 223 | "metadata": { 224 | "kernelspec": { 225 | "display_name": "Python 3", 226 | "language": "python", 227 | "name": "python3" 228 | }, 229 | "language_info": { 230 | "codemirror_mode": { 231 | "name": "ipython", 232 | "version": 3 233 | }, 234 | "file_extension": ".py", 235 | "mimetype": "text/x-python", 236 | "name": "python", 237 | "nbconvert_exporter": "python", 238 | "pygments_lexer": "ipython3", 239 | "version": "3.11.1" 240 | }, 241 | "orig_nbformat": 4, 242 | "vscode": { 243 | "interpreter": { 244 | "hash": "cab1d7ef7b90e69a2393a883ac82077044fd5f1d4df4dae9ffefa7c49ee44033" 245 | } 246 | } 247 | }, 248 | "nbformat": 4, 249 | "nbformat_minor": 2 250 | } 251 | -------------------------------------------------------------------------------- /img/OnPlot.csv_bldec_S1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Art-n-MathS/PlotToSat/0e2be3b317160c6ec7908cb91757f4c11c9e718c/img/OnPlot.csv_bldec_S1.jpg -------------------------------------------------------------------------------- /img/OnPlot.csv_bldec_S2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Art-n-MathS/PlotToSat/0e2be3b317160c6ec7908cb91757f4c11c9e718c/img/OnPlot.csv_bldec_S2.jpg -------------------------------------------------------------------------------- /img/OutputExample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Art-n-MathS/PlotToSat/0e2be3b317160c6ec7908cb91757f4c11c9e718c/img/OutputExample.jpg -------------------------------------------------------------------------------- /img/PlotToSatDiagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Art-n-MathS/PlotToSat/0e2be3b317160c6ec7908cb91757f4c11c9e718c/img/PlotToSatDiagram.jpg -------------------------------------------------------------------------------- /img/drawpolygon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Art-n-MathS/PlotToSat/0e2be3b317160c6ec7908cb91757f4c11c9e718c/img/drawpolygon.jpg -------------------------------------------------------------------------------- /img/drawpolygon1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Art-n-MathS/PlotToSat/0e2be3b317160c6ec7908cb91757f4c11c9e718c/img/drawpolygon1.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asttokens==3.0.0 2 | attrs==24.3.0 3 | branca==0.8.1 4 | cachetools==5.5.0 5 | certifi==2024.12.14 6 | charset-normalizer==3.4.1 7 | colorama==0.4.6 8 | comm==0.2.2 9 | debugpy==1.8.11 10 | decorator==5.1.1 11 | earthengine-api==1.4.3 12 | executing==2.1.0 13 | fastjsonschema==2.21.1 14 | folium==0.19.3 15 | google-api-core==2.24.0 16 | google-api-python-client==2.156.0 17 | google-auth==2.37.0 18 | google-auth-httplib2==0.2.0 19 | google-cloud-core==2.4.1 20 | google-cloud-storage==2.19.0 21 | google-crc32c==1.6.0 22 | google-resumable-media==2.7.2 23 | googleapis-common-protos==1.66.0 24 | httplib2==0.22.0 25 | idna==3.10 26 | ipykernel==6.29.5 27 | ipython==8.31.0 28 | jedi==0.19.2 29 | Jinja2==3.1.5 30 | jsonschema==4.23.0 31 | jsonschema-specifications==2024.10.1 32 | jupyter_client==8.6.3 33 | jupyter_core==5.7.2 34 | MarkupSafe==3.0.2 35 | matplotlib-inline==0.1.7 36 | nbformat==5.10.4 37 | nest-asyncio==1.6.0 38 | numpy==2.2.1 39 | packaging==24.2 40 | pandas==2.2.3 41 | parso==0.8.4 42 | platformdirs==4.3.6 43 | prompt_toolkit==3.0.48 44 | proto-plus==1.25.0 45 | protobuf==5.29.2 46 | psutil==6.1.1 47 | pure_eval==0.2.3 48 | pyasn1==0.6.1 49 | pyasn1_modules==0.4.1 50 | Pygments==2.18.0 51 | pyparsing==3.2.1 52 | python-dateutil==2.9.0.post0 53 | pytz==2024.2 54 | pywin32==308 55 | pyzmq==26.2.0 56 | referencing==0.35.1 57 | requests==2.32.3 58 | rpds-py==0.22.3 59 | rsa==4.9 60 | six==1.17.0 61 | stack-data==0.6.3 62 | tornado==6.4.2 63 | traitlets==5.14.3 64 | tzdata==2024.2 65 | uritemplate==4.1.1 66 | urllib3==2.3.0 67 | wcwidth==0.2.13 68 | xyzservices==2024.9.0 69 | -------------------------------------------------------------------------------- /samplePlots.csv: -------------------------------------------------------------------------------- 1 | PLOTCODE,CX,CY,variableA,VariableB 2 | 1164,236011,4205274,2019,6.669486056 3 | 1165,246011,4205274,2014,2.866404694 4 | 1166,256011,4205274,2016,2.431983333 5 | 1167,266011,4205274,2015,9.445488778 6 | 1168,276011,4205274,2014,20.41845456 7 | 1169,286011,4205274,2020,37.49806314 8 | 1170,296011,4205274,2019,21.23824847 9 | 1171,306011,4205274,2019,29.73142294 10 | 1172,316011,4205274,2020,22.69333467 11 | 1173,326011,4205274,2015,12.73784167 12 | 1174,336011,4205274,2020,2.708372222 13 | 1175,346011,4205274,2020,6.845673583 14 | 1176,356011,4205274,2020,9.344373 15 | 1177,366011,4205274,2019,17.95048767 16 | 1178,376011,4205274,2018,7.558686667 17 | 1179,386011,4205274,2018,11.23770083 18 | 1180,396011,4205274,2017,10.17498622 19 | 1181,406011,4205274,2016,20.48743499 20 | 1182,416011,4205274,2014,14.91654017 21 | 1183,426011,4205274,2019,15.62703998 22 | 1184,436011,4205274,2014,12.60615068 23 | 1185,446011,4205274,2019,23.45708119 24 | 1186,456011,4205274,2015,5.126139243 25 | 1187,466011,4205274,2014,17.31801896 26 | 1188,476011,4205274,2020,17.62877102 27 | 1189,486011,4205274,2014,13.56446942 28 | 1190,496011,4205274,2019,8.000025214 29 | 1191,506011,4205274,2016,21.10010211 30 | 1192,516011,4205274,2014,10.59150478 31 | 1193,526011,4205274,2022,6.210013764 32 | 1194,536011,4205274,2019,7.793627224 33 | 1195,546011,4205274,2021,15.29404073 34 | 1196,556011,4205274,2021,20.82691925 35 | 1197,566011,4205274,2017,16.8012591 36 | 1198,576011,4205274,2017,16.06703169 37 | 1199,586011,4205274,2020,4.469262861 38 | 1200,596011,4205274,2021,14.18352993 39 | 1201,606011,4205274,2021,19.09926803 40 | 1202,616011,4205274,2021,6.786577516 41 | 1203,626011,4205274,2019,12.99712612 42 | 1204,636011,4205274,2022,14.15972569 43 | 1205,236011,4215274,2015,21.74794892 44 | 1206,246011,4215274,2017,15.81948011 45 | 1207,256011,4215274,2016,7.876425479 46 | 1208,266011,4215274,2017,19.70338013 47 | 1209,276011,4215274,2016,7.517301108 48 | 1210,286011,4215274,2017,16.75897049 49 | 1211,296011,4215274,2017,15.00401958 50 | 1212,306011,4215274,2017,4.462387137 51 | 1213,316011,4215274,2017,14.12405517 52 | 1214,326011,4215274,2018,10.60068437 53 | 1215,336011,4215274,2022,6.380917003 54 | 1216,346011,4215274,2017,8.339772637 55 | 1217,356011,4215274,2019,16.57200235 56 | 1218,366011,4215274,2016,7.042140189 57 | 1219,376011,4215274,2014,21.72915351 58 | 1220,386011,4215274,2021,4.591230667 59 | 1221,396011,4215274,2022,11.38165907 60 | 1222,406011,4215274,2014,19.81283732 61 | 1223,416011,4215274,2019,20.04828556 62 | 1224,426011,4215274,2020,6.413416406 63 | 1225,436011,4215274,2015,14.17591338 64 | 1226,446011,4215274,2016,20.08166727 65 | 1227,456011,4215274,2022,16.16037846 66 | 1228,466011,4215274,2014,15.67474942 67 | 1229,476011,4215274,2015,22.17114646 68 | 1230,486011,4215274,2014,4.789626235 69 | 1231,496011,4215274,2021,6.820314648 70 | 1232,506011,4215274,2020,11.07201296 71 | 1233,516011,4215274,2017,11.93240118 72 | 1234,526011,4215274,2020,16.8238554 73 | 1235,536011,4215274,2020,9.729129351 74 | 1236,546011,4215274,2015,22.84492065 75 | 1237,556011,4215274,2021,23.66067838 76 | 1238,566011,4215274,2014,9.775347985 77 | 1239,576011,4215274,2015,12.26010255 78 | 1240,586011,4215274,2020,4.16184368 79 | 1241,596011,4215274,2020,12.09336411 80 | 1242,606011,4215274,2018,18.05797397 81 | 1243,616011,4215274,2018,22.36690227 82 | 1244,626011,4215274,2016,15.26273902 83 | 1245,636011,4215274,2014,5.303757524 84 | 1246,236011,4225274,2020,16.06325089 85 | 1247,246011,4225274,2019,12.4395471 86 | 1248,256011,4225274,2014,11.89018076 87 | 1249,266011,4225274,2016,18.59249517 88 | 1250,276011,4225274,2018,8.413022527 89 | 1251,286011,4225274,2021,20.47559645 90 | 1252,296011,4225274,2018,18.76370376 91 | 1253,306011,4225274,2014,18.01168927 92 | 1254,316011,4225274,2022,19.72123534 93 | 1255,326011,4225274,2015,23.51922556 94 | 1256,336011,4225274,2017,6.209548488 95 | 1257,346011,4225274,2015,10.72106901 96 | 1258,356011,4225274,2022,18.78811241 97 | 1259,366011,4225274,2017,21.53021136 98 | 1260,376011,4225274,2021,8.486400087 99 | 1261,386011,4225274,2019,17.81799319 100 | 1262,396011,4225274,2022,18.53919981 101 | 1263,406011,4225274,2020,23.64245844 102 | 1264,416011,4225274,2017,4.950658337 103 | 1265,426011,4225274,2016,17.95801288 104 | 1266,436011,4225274,2019,20.47623804 105 | 1267,446011,4225274,2018,21.90717854 106 | 1268,456011,4225274,2020,16.34683375 107 | 1269,466011,4225274,2019,17.90836193 108 | 1270,476011,4225274,2022,8.995403603 109 | 1271,486011,4225274,2022,10.50593525 110 | 1272,496011,4225274,2019,18.83599106 111 | 1273,506011,4225274,2019,7.046929011 112 | 1274,516011,4225274,2020,9.776816892 113 | 1275,526011,4225274,2016,22.86812472 114 | 1276,536011,4225274,2022,13.90213094 115 | 1277,546011,4225274,2018,18.28081588 116 | 1278,556011,4225274,2022,13.23962273 117 | 1279,566011,4225274,2016,22.2358468 118 | 1280,576011,4225274,2019,22.58420378 119 | 1281,586011,4225274,2021,16.54041263 120 | 1282,596011,4225274,2016,9.846651053 121 | 1283,606011,4225274,2017,12.75256804 122 | 1284,616011,4225274,2017,19.17876137 123 | 1285,626011,4225274,2019,13.56232905 124 | 1286,636011,4225274,2021,5.854172587 125 | 1287,236011,4235274,2019,15.85463627 126 | 1288,246011,4235274,2018,14.97680063 127 | 1289,256011,4235274,2016,23.65672031 128 | 1290,266011,4235274,2022,5.648176844 129 | 1291,276011,4235274,2014,23.45738798 130 | 1292,286011,4235274,2017,18.41075671 131 | 1293,296011,4235274,2015,23.56430043 132 | 1294,306011,4235274,2016,21.9340883 133 | 1295,316011,4235274,2020,13.25056719 134 | 1296,326011,4235274,2016,23.13231406 135 | 1297,336011,4235274,2022,10.62139018 136 | 1298,346011,4235274,2018,7.430683921 137 | 1299,356011,4235274,2022,21.3624249 138 | 1300,366011,4235274,2019,7.346417221 139 | 1301,376011,4235274,2021,10.2096665 140 | 1302,386011,4235274,2022,18.40040173 141 | 1303,396011,4235274,2020,4.156750772 142 | 1304,406011,4235274,2015,4.932170149 143 | 1305,416011,4235274,2022,9.959096594 144 | 1306,426011,4235274,2014,13.23599719 145 | 1307,436011,4235274,2017,9.762516909 146 | 1308,446011,4235274,2017,19.53261699 147 | 1309,456011,4235274,2016,13.68661889 148 | 1310,466011,4235274,2017,16.31934909 149 | 1311,476011,4235274,2019,12.67302047 150 | 1312,486011,4235274,2022,5.183244637 151 | 1313,496011,4235274,2021,12.93927977 152 | 1314,506011,4235274,2016,16.13353009 153 | 1315,516011,4235274,2019,8.798073516 154 | 1316,526011,4235274,2020,21.05326678 155 | 1317,536011,4235274,2017,7.3586902 156 | 1318,546011,4235274,2016,17.96417759 157 | 1319,556011,4235274,2021,13.90097078 158 | 1320,566011,4235274,2019,19.24821037 159 | 1321,576011,4235274,2022,21.06748095 160 | 1322,586011,4235274,2021,15.69278614 161 | 1323,596011,4235274,2016,4.397632212 162 | 1324,606011,4235274,2020,17.79809713 163 | 1325,616011,4235274,2017,16.01408713 164 | 1326,626011,4235274,2017,11.02305657 165 | 1327,636011,4235274,2020,22.68068141 166 | 1328,236011,4245274,2019,13.57401064 167 | 1329,246011,4245274,2017,13.00452295 168 | 1330,256011,4245274,2020,14.04616386 169 | 1331,266011,4245274,2021,6.315497941 170 | 1332,276011,4245274,2015,17.22732574 171 | 1333,286011,4245274,2015,7.925632085 172 | 1334,296011,4245274,2015,15.41038251 173 | 1335,306011,4245274,2015,5.695084047 174 | 1336,316011,4245274,2019,17.34624449 175 | 1337,326011,4245274,2014,19.64266427 176 | 1338,336011,4245274,2015,19.59441236 177 | 1339,346011,4245274,2019,11.22132397 178 | 1340,356011,4245274,2019,18.57142745 179 | 1341,366011,4245274,2019,8.020205752 180 | 1342,376011,4245274,2019,19.01428165 181 | 1343,386011,4245274,2016,15.40530094 182 | 1344,396011,4245274,2017,18.184608 183 | 1345,406011,4245274,2022,17.86234878 184 | 1346,416011,4245274,2014,7.723644829 185 | 1347,426011,4245274,2020,22.99589026 186 | 1348,436011,4245274,2018,6.820352001 187 | 1349,446011,4245274,2015,23.51792689 188 | 1350,456011,4245274,2021,13.00617194 189 | 1351,466011,4245274,2015,17.38947751 190 | 1352,476011,4245274,2018,20.88054225 191 | 1353,486011,4245274,2018,5.037888877 192 | 1354,496011,4245274,2021,17.31502426 193 | 1355,506011,4245274,2016,11.60603222 194 | 1356,516011,4245274,2017,17.50847703 195 | 1357,526011,4245274,2017,15.80637574 196 | 1358,536011,4245274,2020,23.76955614 197 | 1359,546011,4245274,2017,17.37107317 198 | 1360,556011,4245274,2019,21.72403003 199 | 1361,566011,4245274,2017,6.720637993 200 | 1362,576011,4245274,2018,23.64490043 201 | 1363,586011,4245274,2014,23.8995919 202 | 1364,596011,4245274,2021,20.21366192 203 | 1365,606011,4245274,2017,9.957737444 204 | 1366,616011,4245274,2017,22.97888687 205 | 1367,626011,4245274,2020,22.09804463 206 | 1368,636011,4245274,2020,12.01267286 207 | 1369,236011,4255274,2022,12.83515638 208 | 1370,246011,4255274,2018,10.74864102 209 | 1371,256011,4255274,2021,19.82010636 210 | 1372,266011,4255274,2019,12.72424813 211 | 1373,276011,4255274,2014,18.28750278 212 | 1374,286011,4255274,2016,20.65379991 213 | 1375,296011,4255274,2020,16.80452557 214 | 1376,306011,4255274,2019,9.643473869 215 | 1377,316011,4255274,2015,5.688578269 216 | 1378,326011,4255274,2022,20.82245132 217 | 1379,336011,4255274,2015,17.24274126 218 | 1380,346011,4255274,2015,15.71789123 219 | 1381,356011,4255274,2021,8.21395034 220 | 1382,366011,4255274,2016,4.478463739 221 | 1383,376011,4255274,2022,16.38125477 222 | 1384,386011,4255274,2017,19.49002039 223 | 1385,396011,4255274,2015,20.19680647 224 | 1386,406011,4255274,2021,8.32327126 225 | 1387,416011,4255274,2022,17.50154231 226 | 1388,426011,4255274,2016,19.81387765 227 | 1389,436011,4255274,2017,6.794235438 228 | 1390,446011,4255274,2019,21.99711924 229 | 1391,456011,4255274,2014,7.584459011 230 | 1392,466011,4255274,2017,15.37433864 231 | 1393,476011,4255274,2021,8.907809313 232 | 1394,486011,4255274,2022,4.255098785 233 | 1395,496011,4255274,2018,22.94531993 234 | 1396,506011,4255274,2018,12.39973587 235 | 1397,516011,4255274,2021,23.56194057 236 | 1398,526011,4255274,2021,6.921031162 237 | 1399,536011,4255274,2016,21.92994594 238 | 1400,546011,4255274,2018,16.14125397 239 | 1401,556011,4255274,2014,20.42796875 240 | 1402,566011,4255274,2020,23.00752764 241 | 1403,576011,4255274,2022,14.84977631 242 | 1404,586011,4255274,2017,23.00519954 243 | 1405,596011,4255274,2014,22.283287 244 | 1406,606011,4255274,2022,13.08648832 245 | 1407,616011,4255274,2019,18.55664666 246 | 1408,626011,4255274,2022,19.18118889 247 | 1409,636011,4255274,2020,23.83888347 248 | 1410,236011,4265274,2022,6.064723783 249 | 1411,246011,4275274,2016,9.285744525 250 | 1412,256011,4285274,2014,13.1986203 251 | 1413,266011,4295274,2017,4.951568245 252 | 1414,276011,4305274,2015,15.00287663 253 | 1415,286011,4315274,2017,9.737703318 254 | 1416,296011,4325274,2017,8.63509286 255 | 1417,306011,4335274,2020,9.966285896 256 | 1418,316011,4345274,2018,11.21180339 257 | 1419,326011,4355274,2020,14.25313635 258 | 1420,336011,4365274,2014,11.39770301 259 | 1421,346011,4375274,2018,20.63707522 260 | 1422,356011,4385274,2018,11.97713582 261 | 1423,366011,4395274,2022,21.54344078 262 | 1424,376011,4405274,2019,23.25872673 263 | 1425,386011,4415274,2019,12.37605069 264 | 1426,396011,4425274,2020,23.94820668 265 | 1427,406011,4435274,2022,11.729518 266 | 1428,416011,4445274,2020,8.283439514 267 | 1429,426011,4455274,2019,14.84156776 268 | 1430,436011,4465274,2019,4.679729621 269 | 1431,446011,4475274,2015,11.33844585 270 | 1432,456011,4485274,2020,11.64094173 271 | 1433,466011,4495274,2015,21.33409957 272 | 1434,476011,4505274,2019,16.96429389 273 | 1435,486011,4515274,2017,18.30803878 274 | 1436,496011,4525274,2014,20.46945033 275 | 1437,506011,,2019,14.60036889 276 | 1438,516011,,2017,12.67184306 277 | 1439,526011,,2020,9.154442289 278 | 1440,536011,,2014,6.297019021 279 | 1441,546011,,2016,7.768317964 280 | 1442,556011,,2017,23.43619988 281 | 1443,566011,,2022,23.37945335 282 | 1444,576011,,2014,8.623232572 283 | 1445,586011,,2021,16.09987134 284 | 1446,596011,,2017,16.17464306 285 | 1447,606011,,2018,23.41390066 286 | 1448,616011,,2017,5.127561044 287 | 1449,626011,,2022,6.284709323 288 | 1450,636011,,2018,6.019869186 289 | 1451,236011,4535274,2021,13.97110597 290 | 1452,246011,4535274,2017,16.26274152 291 | 1453,256011,4535274,2014,12.30643736 292 | 1454,266011,4535274,2014,13.11866795 293 | 1455,276011,4535274,2016,19.62021251 294 | 1456,286011,4535274,2020,14.98102789 295 | 1457,296011,4535274,2022,22.94378082 296 | 1458,306011,4535274,2022,23.71081315 297 | 1459,316011,4535274,2014,22.43601095 298 | 1460,326011,4535274,2015,6.739036199 299 | 1461,336011,4535274,2017,8.494992164 300 | 1462,346011,4535274,2019,18.19627166 301 | 1463,356011,4535274,2016,5.707536388 302 | 1464,366011,4535274,2016,8.392666625 303 | 1465,376011,4535274,2016,16.51189323 304 | 1466,386011,4535274,2019,10.89675651 305 | 1467,396011,4535274,2021,22.08842433 306 | 1468,406011,4535274,2019,8.149646313 307 | 1469,416011,4535274,2014,18.89819762 308 | 1470,426011,4535274,2019,18.55657625 309 | 1471,436011,4535274,2017,19.84180362 310 | 1472,446011,4535274,2017,8.136806085 311 | 1473,456011,4535274,2020,14.32669379 312 | 1474,466011,4535274,2021,4.008956561 313 | 1475,476011,4535274,2016,4.592427114 314 | 1476,486011,4535274,2022,8.264885771 315 | 1477,496011,4535274,2014,8.077475409 316 | 1478,506011,4535274,2020,5.919289976 317 | 1479,516011,4535274,2015,5.074937783 318 | 1480,526011,4535274,2020,14.24969249 319 | 1481,536011,4535274,2019,15.98299836 320 | 1482,546011,4535274,2021,18.81329651 321 | 1483,556011,4535274,2020,20.81375172 322 | 1484,566011,4535274,2021,23.15557823 323 | 1485,576011,4535274,2022,10.4022208 324 | 1486,586011,4535274,2014,12.01377989 325 | 1487,596011,4535274,2014,12.09451177 326 | 1488,606011,4535274,2014,7.620181084 327 | 1489,616011,4535274,2016,6.235078558 328 | 1490,626011,4535274,2021,10.55004605 329 | 1491,636011,4535274,2021,6.811729622 330 | 1492,236011,4545274,2020,22.32821769 331 | 1493,246011,4545274,2015,11.14541797 332 | 1494,256011,4545274,2021,21.71985488 333 | 1495,266011,4545274,2021,19.55869146 334 | 1496,276011,4545274,2021,17.47242735 335 | 1497,286011,4545274,2016,23.69264022 336 | 1498,296011,4545274,2017,12.70773033 337 | 1499,306011,4545274,2019,4.226766037 338 | 1500,316011,4545274,2014,4.913753679 339 | 1501,326011,4545274,2017,8.613707749 340 | 1502,336011,4545274,2022,19.51193524 341 | 1503,346011,4545274,2016,5.116859147 342 | 1504,356011,4545274,2015,16.5751403 343 | 1505,366011,4545274,2015,21.23890457 344 | 1506,376011,4545274,2020,10.75351 345 | 1507,386011,4545274,2021,19.9724098 346 | 1508,396011,4545274,2015,18.73066203 347 | 1509,406011,4545274,2016,8.295396136 348 | 1510,416011,4545274,2022,15.34354272 349 | 1511,426011,4545274,2020,23.65260748 350 | 1512,436011,4545274,2014,22.24991702 351 | 1513,446011,4545274,2015,7.568546078 352 | 1514,456011,4545274,2020,12.20541296 353 | 1515,466011,4545274,2016,15.259841 354 | 1516,476011,4545274,2017,22.88078975 355 | 1517,486011,4545274,2021,16.26417821 356 | 1518,496011,4545274,2022,12.60563404 357 | 1519,506011,4545274,2018,18.79979348 358 | 1520,516011,4545274,2022,10.04019124 359 | 1521,526011,4545274,2016,15.90078894 360 | 1522,536011,4545274,2020,17.36864834 361 | 1523,546011,4545274,2017,10.52429456 362 | 1524,556011,4545274,2022,7.684150056 363 | 1525,566011,4545274,2020,20.44350293 364 | 1526,576011,4545274,2020,20.30876633 365 | 1527,586011,4545274,2015,14.27670802 366 | 1528,596011,4545274,2019,13.11878404 367 | 1529,606011,4545274,2015,14.18695538 368 | 1530,616011,4545274,2021,5.31953053 369 | 1531,626011,4545274,2015,23.26595779 370 | 1532,636011,4545274,2017,18.63890261 371 | 1533,236011,4555274,2020,6.464985854 372 | 1534,246011,4555274,2019,20.19398279 373 | 1535,256011,4555274,2019,5.296935654 374 | 1536,266011,4555274,2021,7.516360285 375 | 1537,276011,4555274,2021,20.08426414 376 | 1538,286011,4555274,2019,15.20680475 377 | 1539,296011,4555274,2020,12.86775354 378 | 1540,306011,4555274,2015,4.023490231 379 | 1541,316011,4555274,2021,7.665411892 380 | 1542,326011,4555274,2015,4.034709157 381 | 1543,336011,4555274,2020,8.676583697 382 | 1544,346011,4555274,2018,18.21434411 383 | 1545,356011,4555274,2020,4.242208916 384 | 1546,366011,4555274,2016,4.381502215 385 | 1547,376011,4555274,2022,8.02903655 386 | 1548,386011,4555274,2018,22.58548144 387 | 1549,396011,4555274,2020,9.314107143 388 | 1550,406011,4555274,2014,14.31078069 389 | 1551,416011,4555274,2021,20.33075755 390 | 1552,426011,4555274,2014,4.805183054 391 | 1553,436011,4555274,2019,21.20468593 392 | 1554,446011,4555274,2021,23.76600984 393 | 1555,456011,4555274,2021,17.88069343 394 | 1556,466011,4555274,2017,17.20559811 395 | 1557,476011,4555274,2018,22.1618671 396 | 1558,486011,4555274,2018,4.88756669 397 | 1559,496011,4555274,2021,23.10805306 398 | 1560,506011,4555274,2014,19.16061339 399 | 1561,516011,4555274,2015,11.25382537 400 | 1562,526011,4555274,2021,11.21064717 401 | 1563,536011,4555274,2021,11.26035031 402 | 1564,546011,4555274,2019,21.6199396 403 | 1565,556011,4555274,2021,10.11319854 404 | 1566,566011,4555274,2014,16.62998264 405 | 1567,576011,4555274,2020,5.406224856 406 | 1568,586011,4555274,2015,15.01694743 407 | 1569,596011,4555274,2018,20.38694732 408 | 1570,606011,4555274,2015,15.34869736 409 | 1571,616011,4555274,2016,18.08124596 410 | 1572,626011,4555274,2022,7.427405126 411 | 1573,636011,4555274,2015,16.59032997 412 | 1574,236011,4565274,2021,15.65441022 413 | 1575,246011,4565274,2022,8.097170616 414 | 1576,256011,4565274,2016,21.213192 415 | 1577,266011,4565274,2020,20.88261578 416 | 1578,276011,4565274,2019,11.32519161 417 | 1579,286011,4565274,2019,15.41294938 418 | 1580,296011,4565274,2021,12.7367547 419 | 1581,306011,4565274,2018,16.14936645 420 | 1582,316011,4565274,2019,20.42992641 421 | 1583,326011,4565274,2022,19.57530759 422 | 1584,336011,4565274,2021,18.11670387 423 | 1585,346011,4565274,2019,19.82293774 424 | 1586,356011,4565274,2017,17.0209809 425 | 1587,366011,4565274,2022,5.012922428 426 | 1588,376011,4565274,2019,7.20815295 427 | 1589,386011,4565274,2014,14.9540697 428 | 1590,396011,4565274,2017,8.13823584 429 | 1591,406011,4565274,2021,11.19045823 430 | 1592,416011,4565274,2015,17.17649856 431 | 1593,426011,4565274,2020,5.514480413 432 | 1594,436011,4565274,2018,7.709648504 433 | 1595,446011,4565274,2020,23.4009084 434 | 1596,456011,4565274,2018,11.02266513 435 | 1597,466011,4565274,2018,4.909006923 436 | 1598,476011,4565274,2014,12.84725533 437 | 1599,486011,4565274,2022,14.33117495 438 | 1600,496011,4565274,2019,17.95253982 439 | 1601,506011,4565274,2014,18.3875736 440 | 1602,516011,4565274,2015,22.92038875 441 | 1603,526011,4565274,2014,4.308742508 442 | 1604,536011,4565274,2015,13.40219499 443 | 1605,546011,4565274,2019,18.3530263 444 | 1606,556011,4565274,2014,11.72872749 445 | 1607,566011,4565274,2016,19.29766595 446 | 1608,576011,4565274,2022,18.78090958 447 | 1609,586011,4565274,2022,11.45440016 448 | 1610,596011,4565274,2021,5.486212687 449 | 1611,606011,4565274,2017,7.502483131 450 | 1612,616011,4565274,2020,5.585152319 451 | 1613,626011,4565274,2022,21.62070108 452 | 1614,636011,4565274,2014,9.929825408 453 | 1615,236011,4575274,2018,20.39711104 454 | 1616,246011,4575274,2022,7.451661832 455 | 1617,256011,4575274,2018,22.19703715 456 | 1618,266011,4575274,2017,4.131771751 457 | 1619,276011,4575274,2015,8.725019312 458 | 1620,286011,4575274,2019,19.25004421 459 | 1621,296011,4575274,2021,4.759344588 460 | 1622,306011,4575274,2015,5.179739641 461 | 1623,316011,4575274,2022,12.53126503 462 | 1624,326011,4575274,2017,13.85836282 463 | 1625,336011,4575274,2015,15.88708546 464 | 1626,346011,4575274,2021,12.50306384 465 | 1627,356011,4575274,2017,19.24592648 466 | 1628,366011,4575274,2018,17.62983897 467 | 1629,376011,4575274,2020,20.1863368 468 | 1630,386011,4575274,2019,14.23708177 469 | 1631,396011,4575274,2019,6.550541744 470 | 1632,406011,4575274,2020,21.94556305 471 | 1633,416011,4575274,2017,4.214792331 472 | 1634,426011,4575274,2021,11.91605962 473 | 1635,436011,4575274,2017,18.67584887 474 | 1636,446011,4575274,2022,12.90036329 475 | 1637,456011,4575274,2022,12.31911354 476 | 1638,466011,4575274,2017,22.04976092 477 | 1639,476011,4575274,2016,19.94277017 478 | 1640,486011,4575274,2016,5.353214091 479 | 1641,496011,4575274,2020,15.29705526 480 | 1642,506011,4575274,2021,15.35615438 481 | 1643,516011,4575274,2021,23.82193315 482 | 1644,526011,4575274,2017,17.33012067 483 | 1645,536011,4575274,2014,23.10365679 484 | 1646,546011,4575274,2015,9.297377916 485 | 1647,556011,4575274,2015,6.740243516 486 | 1648,566011,4575274,2018,15.10122051 487 | 1649,576011,4575274,2016,7.070174637 488 | 1650,586011,4575274,2021,18.23244516 489 | 1651,596011,4575274,2016,4.514299849 490 | 1652,606011,4575274,2019,12.47170183 491 | 1653,616011,4575274,2015,16.63040048 492 | 1654,626011,4575274,2022,18.57873361 493 | 1655,636011,4575274,2022,14.31667543 494 | 1656,236011,4585274,2016,4.604633028 495 | 1657,246011,4585274,2018,17.06265728 496 | 1658,256011,4585274,2016,11.29348247 497 | 1659,266011,4585274,2020,14.00294386 498 | 1660,276011,4585274,2019,15.5554688 499 | 1661,286011,4585274,2021,22.70057478 500 | 1662,296011,4585274,2019,17.33440813 501 | 1663,306011,4585274,2018,9.893786621 502 | 1664,316011,4585274,2022,7.976665104 503 | 1665,326011,4585274,2019,9.159636476 504 | 1666,336011,4585274,2014,4.919526793 505 | 1667,346011,4585274,2016,6.554662609 506 | 1668,356011,4585274,2017,19.26690015 507 | 1669,366011,4585274,2016,11.10452949 508 | 1670,376011,4585274,2014,17.33839456 509 | 1671,386011,4585274,2016,17.9032931 510 | 1672,396011,4585274,2019,10.54874291 511 | 1673,406011,4585274,2020,12.32000589 512 | 1674,416011,4585274,2020,9.936386089 513 | 1675,426011,4585274,2018,22.99894581 514 | 1676,436011,4585274,2016,20.21721461 515 | 1677,446011,4585274,2018,10.17326167 516 | 1678,456011,4585274,2017,5.418683568 517 | 1679,466011,4585274,2020,13.053539 518 | 1680,476011,4585274,2018,14.57142642 519 | 1681,486011,4585274,2022,13.88017082 520 | 1682,496011,4585274,2018,21.28194645 521 | 1683,506011,4585274,2019,9.289881373 522 | 1684,516011,4585274,2018,6.488407548 523 | 1685,526011,4585274,2015,5.469054457 524 | 1686,536011,4585274,2014,19.18011116 525 | 1687,546011,4585274,2021,5.636698356 526 | 1688,556011,4585274,2020,13.75313074 527 | 1689,566011,4585274,2020,15.87383907 528 | 1690,576011,4585274,2018,15.11035358 529 | 1691,586011,4585274,2019,10.76958588 530 | 1692,596011,4585274,2020,8.836649353 531 | 1693,606011,4585274,2018,16.36807919 532 | 1694,616011,4585274,2020,20.15265682 533 | 1695,626011,4585274,2019,22.83801714 534 | 1696,636011,4585274,2021,20.21479429 535 | 1697,236011,4595274,2014,13.28051813 536 | 1698,246011,4595274,2022,18.75773643 537 | 1699,256011,4595274,2015,22.11225247 538 | 1700,266011,4595274,2018,20.80997424 539 | 1701,276011,4595274,2015,4.123439712 540 | 1702,286011,4595274,2018,5.479762519 541 | 1703,296011,4595274,2020,7.593782004 542 | 1704,306011,4595274,2014,15.84457775 543 | 1705,316011,4595274,2018,20.499266 544 | 1706,326011,4595274,2015,5.26859572 545 | 1707,336011,4595274,2018,13.6742914 546 | 1708,346011,4595274,2021,17.01329211 547 | 1709,356011,4595274,2020,17.79728124 548 | 1710,366011,4595274,2022,4.812328527 549 | 1711,376011,4595274,2015,10.49225277 550 | 1712,386011,4595274,2021,11.46442407 551 | 1713,396011,4595274,2022,20.3014764 552 | 1714,406011,4595274,2016,19.45559721 553 | 1715,416011,4595274,2022,11.81925418 554 | --------------------------------------------------------------------------------