diff --git a/backend/requirements.txt b/backend/requirements.txt index 59e081f..144abd5 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -2,5 +2,5 @@ earthengine-api python-dotenv pandas plotly -fastapi +fastapi[standard] uvicorn \ No newline at end of file diff --git a/backend/src/cache/msavi_cache.py b/backend/src/cache/msavi_cache.py new file mode 100644 index 0000000..ce310f7 --- /dev/null +++ b/backend/src/cache/msavi_cache.py @@ -0,0 +1,544 @@ +msavi_daily_cache = [ + # Years marked above their section of the data. + + # 2017 + {"timestamp": 1493251200, "value": -4.431000367381644}, + {"timestamp": 1494720000, "value": -5.316995492647151}, + {"timestamp": 1494979200, "value": -7.973086798137311}, + {"timestamp": 1495843200, "value": -7.838449712221872}, + {"timestamp": 1496707200, "value": -1.0467874619591593}, + {"timestamp": 1497571200, "value": -1.9261113425778595}, + {"timestamp": 1498435200, "value": -3.8974326181714947}, + {"timestamp": 1499040000, "value": -4.671271421390005}, + {"timestamp": 1499299200, "value": -3.896947131496574}, + {"timestamp": 1499904000, "value": -1.226862398302503}, + {"timestamp": 1501632000, "value": -1.4971486375794503}, + {"timestamp": 1502755200, "value": -4.060044994911495}, + {"timestamp": 1504483200, "value": -4.268351695704429}, + {"timestamp": 1505088000, "value": -2.786659391004287}, + {"timestamp": 1505952000, "value": -3.4285132064595922}, + {"timestamp": 1509667200, "value": -1.154809081866889}, + {"timestamp": 1511136000, "value": -2.0201818125667486}, + {"timestamp": 1512000000, "value": -2.603414079645819}, + {"timestamp": 1512259200, "value": -1.9874363157053203}, + {"timestamp": 1512864000, "value": -2.041643161438374}, + {"timestamp": 1513123200, "value": -2.849906471581364}, + {"timestamp": 1513555200, "value": -2.158430721914352}, + {"timestamp": 1513728000, "value": -0.22437034596828198}, + {"timestamp": 1513987200, "value": 0.10179884543444519}, + + # 2018 + {"timestamp": 1515888000, "value": -0.0336505695941014}, + {"timestamp": 1516579200, "value": -1.5120684118787913}, + {"timestamp": 1517443200, "value": -2.436240487275879}, + {"timestamp": 1517875200, "value": -2.282794481825409}, + {"timestamp": 1518048000, "value": -2.0870996217045747}, + {"timestamp": 1518480000, "value": -2.2792613736811647}, + {"timestamp": 1518739200, "value": -1.8867529212186391}, + {"timestamp": 1519344000, "value": -1.376529856348907}, + {"timestamp": 1519776000, "value": -0.8174190534865039}, + {"timestamp": 1520035200, "value": -1.2701368134791358}, + {"timestamp": 1521331200, "value": -1.0911406015821747}, + {"timestamp": 1521936000, "value": -0.9154164183275707}, + {"timestamp": 1522627200, "value": -1.227328794032709}, + {"timestamp": 1523059200, "value": -1.661448703694562}, + {"timestamp": 1523232000, "value": -1.6737467481348518}, + {"timestamp": 1523923200, "value": -4.443809296928713}, + {"timestamp": 1524096000, "value": -4.069191039392678}, + {"timestamp": 1524355200, "value": -6.098625321790513}, + {"timestamp": 1524787200, "value": -8.42086121964117}, + {"timestamp": 1524960000, "value": -7.942683528928118}, + {"timestamp": 1525392000, "value": -8.444319825720948}, + {"timestamp": 1525651200, "value": -8.292322086687244}, + {"timestamp": 1525824000, "value": -6.976735281494538}, + {"timestamp": 1526083200, "value": -1.3303846208110004}, + {"timestamp": 1526256000, "value": -7.396700536259861}, + {"timestamp": 1526515200, "value": -0.804238136253652}, + {"timestamp": 1526947200, "value": -5.607996404028049}, + {"timestamp": 1527120000, "value": -4.205717017352484}, + {"timestamp": 1527552000, "value": -4.045806254536262}, + {"timestamp": 1527811200, "value": -3.276410199372174}, + {"timestamp": 1528243200, "value": -4.2712307445347495}, + {"timestamp": 1528416000, "value": -3.585817148450216}, + {"timestamp": 1528675200, "value": -2.885959202345276}, + {"timestamp": 1529107200, "value": -1.8324573025074755}, + {"timestamp": 1529280000, "value": -1.1187289135778689}, + {"timestamp": 1530403200, "value": -2.3347299498431493}, + {"timestamp": 1530576000, "value": -2.03852361485892}, + {"timestamp": 1531008000, "value": -0.8596722673661569}, + {"timestamp": 1531699200, "value": -1.71208585247463}, + {"timestamp": 1531872000, "value": -1.4713061011881976}, + {"timestamp": 1532304000, "value": -2.1801603072734657}, + {"timestamp": 1532563200, "value": -2.275877145002999}, + {"timestamp": 1532736000, "value": -1.9519204082587296}, + {"timestamp": 1532995200, "value": -1.926362792291209}, + {"timestamp": 1533427200, "value": -1.2940295467081178}, + {"timestamp": 1533600000, "value": -1.532616327451566}, + {"timestamp": 1534032000, "value": -1.3274613542145024}, + {"timestamp": 1534896000, "value": -1.103505320481981}, + {"timestamp": 1535155200, "value": -0.8039729082975013}, + {"timestamp": 1535760000, "value": -0.6252516532865182}, + {"timestamp": 1536019200, "value": -0.9021924217913372}, + {"timestamp": 1536192000, "value": -0.8517555348681844}, + {"timestamp": 1536451200, "value": -0.8649430759409419}, + {"timestamp": 1537315200, "value": -0.7074185453523396}, + {"timestamp": 1537747200, "value": -0.10895310410324782}, + {"timestamp": 1538179200, "value": -0.7831497283185018}, + {"timestamp": 1538352000, "value": -0.5634550211236486}, + {"timestamp": 1539216000, "value": -1.0617360823388704}, + {"timestamp": 1539475200, "value": -1.0916332995115228}, + {"timestamp": 1539648000, "value": -0.9795810922759659}, + {"timestamp": 1539907200, "value": -0.9426179492663695}, + {"timestamp": 1540080000, "value": -1.17354596819198}, + {"timestamp": 1540339200, "value": -1.112156161453324}, + {"timestamp": 1540944000, "value": -1.543467480665902}, + {"timestamp": 1541203200, "value": -1.1813709055184496}, + {"timestamp": 1541635200, "value": -0.17950063040098013}, + {"timestamp": 1542499200, "value": -0.42407589913108956}, + {"timestamp": 1543363200, "value": -1.9398449743636732}, + {"timestamp": 1543968000, "value": -1.988094825857856}, + {"timestamp": 1544227200, "value": -0.1434255040739643}, + {"timestamp": 1544400000, "value": -0.6293353122544786}, + {"timestamp": 1545696000, "value": -0.02655733316570224}, + {"timestamp": 1546128000, "value": -0.1690434047924854}, + + # 2019 + {"timestamp": 1546387200, "value": -0.4955474262574897}, + {"timestamp": 1546819200, "value": -0.00858885200538956}, + {"timestamp": 1547424000, "value": -1.8607985135819298}, + {"timestamp": 1548115200, "value": -1.8145645492840798}, + {"timestamp": 1548547200, "value": -0.39740226588526184}, + {"timestamp": 1549584000, "value": -1.7543786047362009}, + {"timestamp": 1549843200, "value": -0.4010353050413589}, + {"timestamp": 1550275200, "value": -2.025613850231601}, + {"timestamp": 1550448000, "value": -1.7696539940370857}, + {"timestamp": 1550707200, "value": -0.6543806812624658}, + {"timestamp": 1551139200, "value": -0.17218658321496605}, + {"timestamp": 1551744000, "value": -0.5444357126730207}, + {"timestamp": 1552435200, "value": -0.9428151830061051}, + {"timestamp": 1552867200, "value": 0.012969494026119709}, + {"timestamp": 1553040000, "value": -1.8665411726202898}, + {"timestamp": 1553299200, "value": -2.945112454358676}, + {"timestamp": 1554163200, "value": -3.6183623961576385}, + {"timestamp": 1554595200, "value": -4.263263121723981}, + {"timestamp": 1554768000, "value": -4.893130625048139}, + {"timestamp": 1555459200, "value": -4.680143129557381}, + {"timestamp": 1555632000, "value": -4.946448300389154}, + {"timestamp": 1555891200, "value": -6.174684742734432}, + {"timestamp": 1556064000, "value": -4.800505980194974}, + {"timestamp": 1557187200, "value": -6.032481750795664}, + {"timestamp": 1557619200, "value": -7.031911033351048}, + {"timestamp": 1558051200, "value": -3.3478156364738694}, + {"timestamp": 1558224000, "value": -5.76397222420251}, + {"timestamp": 1559088000, "value": -5.050031618648295}, + {"timestamp": 1559347200, "value": -5.651993835980702}, + {"timestamp": 1559520000, "value": -5.959873395104083}, + {"timestamp": 1559779200, "value": -5.400815844541562}, + {"timestamp": 1560211200, "value": -4.5017047294797985}, + {"timestamp": 1560384000, "value": -6.585962612258267}, + {"timestamp": 1560816000, "value": -5.884307038399208}, + {"timestamp": 1561075200, "value": -3.9877445482883296}, + {"timestamp": 1561248000, "value": -4.245248500163577}, + {"timestamp": 1561680000, "value": -3.1542461936641364}, + {"timestamp": 1561939200, "value": -1.06466023687381}, + {"timestamp": 1562371200, "value": -0.4053900018141121}, + {"timestamp": 1562803200, "value": -2.5567611930726333}, + {"timestamp": 1562976000, "value": -2.13446089275382}, + {"timestamp": 1563408000, "value": -1.8390466793833884}, + {"timestamp": 1563667200, "value": -1.8217675318689013}, + {"timestamp": 1563840000, "value": -2.4086606895667457}, + {"timestamp": 1564099200, "value": -2.3686107273007964}, + {"timestamp": 1564272000, "value": -2.021466374532932}, + {"timestamp": 1564704000, "value": -0.9367292149389402}, + {"timestamp": 1565395200, "value": -1.4173572500265312}, + {"timestamp": 1565568000, "value": -1.3535227818267588}, + {"timestamp": 1566432000, "value": -2.8182679314990593}, + {"timestamp": 1566691200, "value": -2.781088334966877}, + {"timestamp": 1566864000, "value": -2.7256679100018033}, + {"timestamp": 1567123200, "value": -0.5679139721083297}, + {"timestamp": 1567296000, "value": -1.7220514779979421}, + {"timestamp": 1567555200, "value": -2.13017092493178}, + {"timestamp": 1567728000, "value": -1.7575370766165523}, + {"timestamp": 1568160000, "value": -2.182925898105278}, + {"timestamp": 1568419200, "value": -1.9749729330871977}, + {"timestamp": 1568851200, "value": -2.5585358951393347}, + {"timestamp": 1569024000, "value": -2.2794470184235753}, + {"timestamp": 1569456000, "value": -1.2692098602837003}, + {"timestamp": 1569715200, "value": -0.02457411804370076}, + {"timestamp": 1571011200, "value": -2.845251090546086}, + {"timestamp": 1572307200, "value": -2.7533947572859665}, + {"timestamp": 1572912000, "value": -1.5827990843770674}, + {"timestamp": 1573344000, "value": -4.859530915739711}, + {"timestamp": 1574467200, "value": -1.2617351234374539}, + {"timestamp": 1574899200, "value": -0.44175984767040555}, + {"timestamp": 1575072000, "value": -2.016585892304853}, + {"timestamp": 1575331200, "value": -3.834390865271061}, + {"timestamp": 1575504000, "value": -3.3687185881156245}, + {"timestamp": 1575936000, "value": -3.929150650857071}, + {"timestamp": 1576195200, "value": -3.0320014895357628}, + {"timestamp": 1576368000, "value": -0.5086401792903669}, + {"timestamp": 1576800000, "value": -3.6104612015771678}, + + # 2020 + {"timestamp": 1577923200, "value": -3.737563920629822}, + {"timestamp": 1578355200, "value": -0.005805242125822093}, + {"timestamp": 1578960000, "value": -0.08364172837673736}, + {"timestamp": 1579219200, "value": -2.7852556898975975}, + {"timestamp": 1579392000, "value": -0.29462760063781757}, + {"timestamp": 1579824000, "value": -2.727188408990642}, + {"timestamp": 1580688000, "value": -0.5177023016700376}, + {"timestamp": 1581120000, "value": -2.585271588108896}, + {"timestamp": 1581984000, "value": -1.6812159748399784}, + {"timestamp": 1582243200, "value": -2.328565985486157}, + {"timestamp": 1583107200, "value": -0.4035426022516577}, + {"timestamp": 1583280000, "value": -1.019818398501554}, + {"timestamp": 1583712000, "value": -0.057995498066105544}, + {"timestamp": 1583971200, "value": -2.9039626735576087}, + {"timestamp": 1584144000, "value": -4.0993429139450726}, + {"timestamp": 1584403200, "value": -0.8201701838149547}, + {"timestamp": 1584576000, "value": -1.3515839151004507}, + {"timestamp": 1584835200, "value": -4.503992377268322}, + {"timestamp": 1585008000, "value": -4.050985704258896}, + {"timestamp": 1585267200, "value": -3.401386769327183}, + {"timestamp": 1585699200, "value": -3.88946201342351}, + {"timestamp": 1585872000, "value": -2.599303522753552}, + {"timestamp": 1586131200, "value": -4.061538626802209}, + {"timestamp": 1586304000, "value": -3.4800982529372653}, + {"timestamp": 1586736000, "value": -4.517019347049165}, + {"timestamp": 1586995200, "value": -3.5593581596489003}, + {"timestamp": 1587168000, "value": -4.345296558413699}, + {"timestamp": 1587427200, "value": -4.425319380010606}, + {"timestamp": 1587600000, "value": -4.106659248881034}, + {"timestamp": 1587859200, "value": -3.982874356192161}, + {"timestamp": 1588032000, "value": -3.582948420021519}, + {"timestamp": 1588291200, "value": -4.366621025238526}, + {"timestamp": 1588464000, "value": -1.1206908930180859}, + {"timestamp": 1588723200, "value": -6.4258499510748}, + {"timestamp": 1588896000, "value": -6.459585023122755}, + {"timestamp": 1589328000, "value": -4.16953175698427}, + {"timestamp": 1589587200, "value": -5.240016051472298}, + {"timestamp": 1590192000, "value": -0.12148526515462142}, + {"timestamp": 1590451200, "value": -6.122190070536504}, + {"timestamp": 1590624000, "value": -0.9065447274255518}, + {"timestamp": 1590883200, "value": -5.28926599258797}, + {"timestamp": 1591056000, "value": -4.36288285388885}, + {"timestamp": 1591315200, "value": -2.602488981911402}, + {"timestamp": 1591488000, "value": -1.2077887073754923}, + {"timestamp": 1592179200, "value": -3.6947581575072506}, + {"timestamp": 1592352000, "value": -0.46058505117390525}, + {"timestamp": 1592784000, "value": -3.3853119275818337}, + {"timestamp": 1593043200, "value": -2.0437679998052465}, + {"timestamp": 1593216000, "value": -2.497317971991625}, + {"timestamp": 1594080000, "value": -1.3736509095172529}, + {"timestamp": 1594339200, "value": -0.8396519991635049}, + {"timestamp": 1594512000, "value": -2.263988712031865}, + {"timestamp": 1595203200, "value": -0.7910221105615786}, + {"timestamp": 1596067200, "value": -2.1693851868273404}, + {"timestamp": 1596240000, "value": -1.8475883106196378}, + {"timestamp": 1596499200, "value": -1.4033897173709404}, + {"timestamp": 1596672000, "value": -1.6096636046379615}, + {"timestamp": 1596931200, "value": -1.1288870728545155}, + {"timestamp": 1597104000, "value": -1.3394883303984715}, + {"timestamp": 1597536000, "value": -1.1001062815252551}, + {"timestamp": 1597795200, "value": -0.9341116889843454}, + {"timestamp": 1597968000, "value": -1.0173800101435138}, + {"timestamp": 1598832000, "value": -1.1686268951307206}, + {"timestamp": 1599091200, "value": -1.7605591153673996}, + {"timestamp": 1599264000, "value": -0.07328260928310512}, + {"timestamp": 1599696000, "value": -1.8507526160671577}, + {"timestamp": 1599955200, "value": -2.701098837314166}, + {"timestamp": 1600128000, "value": -3.114659516560067}, + {"timestamp": 1600387200, "value": -3.603330531099459}, + {"timestamp": 1600560000, "value": -3.03120431396832}, + {"timestamp": 1600819200, "value": -2.8100148816153174}, + {"timestamp": 1601251200, "value": -4.266785159003384}, + {"timestamp": 1601424000, "value": -3.9710159407149237}, + {"timestamp": 1601683200, "value": -3.973298389783528}, + {"timestamp": 1602115200, "value": -1.8028489390557498}, + {"timestamp": 1602979200, "value": -0.011541224445218852}, + {"timestamp": 1603584000, "value": -5.767157195924353}, + {"timestamp": 1604448000, "value": -4.134120352990356}, + {"timestamp": 1604707200, "value": -6.151821580539443}, + {"timestamp": 1605139200, "value": -0.24593161180791834}, + {"timestamp": 1605744000, "value": -0.05032974556723841}, + {"timestamp": 1606435200, "value": -1.8668775443509775}, + {"timestamp": 1606608000, "value": -0.6960514239765465}, + {"timestamp": 1606867200, "value": -0.7346994181141604}, + {"timestamp": 1607040000, "value": -0.15411824129496812}, + {"timestamp": 1607299200, "value": -0.005744926622709519}, + {"timestamp": 1607731200, "value": -1.650046096828843}, + {"timestamp": 1608336000, "value": -3.627132506195544}, + {"timestamp": 1608595200, "value": 0.05780431091913084}, + {"timestamp": 1608768000, "value": -0.01295436963111137}, + {"timestamp": 1609200000, "value": -1.628868119675763}, + + # 2021 + {"timestamp": 1609459200, "value": -0.029371912188186414}, + {"timestamp": 1609891200, "value": -1.5927793918024307}, + {"timestamp": 1610323200, "value": -0.1444405529771034}, + {"timestamp": 1611187200, "value": -2.5722410130545685}, + {"timestamp": 1612051200, "value": -0.11870319747996139}, + {"timestamp": 1612915200, "value": -0.0306763259599792}, + {"timestamp": 1613088000, "value": -0.036100496973145706}, + {"timestamp": 1613952000, "value": -1.949104294815591}, + {"timestamp": 1614211200, "value": -1.4042442370222061}, + {"timestamp": 1614643200, "value": -1.992486846839551}, + {"timestamp": 1615248000, "value": -2.0121660431920882}, + {"timestamp": 1615507200, "value": -0.6858985062529075}, + {"timestamp": 1616371200, "value": -2.418532960616541}, + {"timestamp": 1617235200, "value": -0.02583538844696913}, + {"timestamp": 1617408000, "value": -1.2224655508959779}, + {"timestamp": 1617667200, "value": -1.1228865783309585}, + {"timestamp": 1617840000, "value": 0.0012179941656120087}, + {"timestamp": 1618099200, "value": -0.4659063127273096}, + {"timestamp": 1618272000, "value": -0.726307140772426}, + {"timestamp": 1618963200, "value": -4.980860761646362}, + {"timestamp": 1619136000, "value": -3.5989753447964374}, + {"timestamp": 1619568000, "value": -4.856025417132305}, + {"timestamp": 1620000000, "value": -0.03821754336936337}, + {"timestamp": 1620259200, "value": -0.03271299881713246}, + {"timestamp": 1620432000, "value": -4.04198819221969}, + {"timestamp": 1620691200, "value": -6.2085950082003585}, + {"timestamp": 1621123200, "value": -1.4702576218562626}, + {"timestamp": 1621296000, "value": -1.3812850759403357}, + {"timestamp": 1621555200, "value": -5.237347989474782}, + {"timestamp": 1621728000, "value": -4.644603768220883}, + {"timestamp": 1621987200, "value": 0.010969772780466956}, + {"timestamp": 1622419200, "value": -10.550768537059938}, + {"timestamp": 1622592000, "value": -7.904901886618402}, + {"timestamp": 1622851200, "value": -7.0187358243741915}, + {"timestamp": 1623024000, "value": -6.257105372278771}, + {"timestamp": 1623283200, "value": -4.304440700169409}, + {"timestamp": 1623888000, "value": -3.443541233206872}, + {"timestamp": 1624147200, "value": -2.3375278746200805}, + {"timestamp": 1624320000, "value": -0.042969010078800475}, + {"timestamp": 1624579200, "value": -1.8941718610682767}, + {"timestamp": 1624752000, "value": -2.2594890628221043}, + {"timestamp": 1625184000, "value": -1.3874223928738496}, + {"timestamp": 1625443200, "value": -0.060480678460943633}, + {"timestamp": 1625875200, "value": -2.000922879539773}, + {"timestamp": 1626048000, "value": -1.7193515207906735}, + {"timestamp": 1626307200, "value": -2.3044224444762373}, + {"timestamp": 1626480000, "value": -0.45988788421575666}, + {"timestamp": 1626912000, "value": -1.610808717829014}, + {"timestamp": 1627171200, "value": -3.5369894259325854}, + {"timestamp": 1627344000, "value": -3.719390816447317}, + {"timestamp": 1627603200, "value": -2.293601337856621}, + {"timestamp": 1628035200, "value": -4.342401621731726}, + {"timestamp": 1628208000, "value": -3.530646379242294}, + {"timestamp": 1628467200, "value": -4.132581644639337}, + {"timestamp": 1628640000, "value": -2.7759202993197514}, + {"timestamp": 1628899200, "value": -3.3048832544647753}, + {"timestamp": 1629072000, "value": -2.126312674063426}, + {"timestamp": 1629504000, "value": -0.273632740996652}, + {"timestamp": 1629763200, "value": -2.9811844589913696}, + {"timestamp": 1629936000, "value": -2.5354872781498248}, + {"timestamp": 1630195200, "value": -1.0831782798712686}, + {"timestamp": 1630627200, "value": -3.1811683702528297}, + {"timestamp": 1630800000, "value": -1.728952075752586}, + {"timestamp": 1631059200, "value": -3.368706619684609}, + {"timestamp": 1631232000, "value": -3.039886860329177}, + {"timestamp": 1631923200, "value": 0.03415349838777033}, + {"timestamp": 1632096000, "value": -0.006004807411014806}, + {"timestamp": 1632787200, "value": -0.056872452513403185}, + {"timestamp": 1633651200, "value": -5.655762636137656}, + {"timestamp": 1633824000, "value": -5.873610312326366}, + {"timestamp": 1634083200, "value": -4.717324818961415}, + {"timestamp": 1634256000, "value": -0.12303633525621183}, + {"timestamp": 1634947200, "value": -4.038822380161949}, + {"timestamp": 1635120000, "value": -4.149126757949408}, + {"timestamp": 1635379200, "value": -4.757505789998262}, + {"timestamp": 1635552000, "value": -4.186928132688978}, + {"timestamp": 1636243200, "value": -3.0870124192113098}, + {"timestamp": 1637539200, "value": -3.2114665741220922}, + {"timestamp": 1637712000, "value": 0.04337437278697441}, + {"timestamp": 1638144000, "value": 0.014738639934081891}, + {"timestamp": 1638403200, "value": -2.560165354558654}, + {"timestamp": 1638576000, "value": 0.19871610157669053}, + {"timestamp": 1639008000, "value": -0.047256662209476656}, + {"timestamp": 1639267200, "value": -0.050580919294464025}, + {"timestamp": 1639699200, "value": 0.02741377563099516}, + {"timestamp": 1639872000, "value": -0.02470897431490369}, + + # 2022 + {"timestamp": 1641427200, "value": -2.253906560457503}, + {"timestamp": 1641600000, "value": -1.776316065902636}, + {"timestamp": 1642464000, "value": -0.8651157607601431}, + {"timestamp": 1642723200, "value": -0.2038300404410527}, + {"timestamp": 1643328000, "value": -1.2691632873801888}, + {"timestamp": 1643760000, "value": -0.8438458860034452}, + {"timestamp": 1644624000, "value": -1.874021157281405}, + {"timestamp": 1645920000, "value": -0.539159488697499}, + {"timestamp": 1646611200, "value": -1.9039810336070995}, + {"timestamp": 1646784000, "value": -1.7949245326857695}, + {"timestamp": 1647043200, "value": -1.8535684066859253}, + {"timestamp": 1647216000, "value": -1.6675128322975703}, + {"timestamp": 1647648000, "value": -1.5856342663534242}, + {"timestamp": 1647907200, "value": -1.8351703445005467}, + {"timestamp": 1648080000, "value": -1.9443863747741417}, + {"timestamp": 1648339200, "value": -1.8525570064923393}, + {"timestamp": 1648512000, "value": -1.9568586684347689}, + {"timestamp": 1648944000, "value": -1.3721883559710328}, + {"timestamp": 1649635200, "value": -3.257982587049245}, + {"timestamp": 1650067200, "value": -4.048371988020582}, + {"timestamp": 1650240000, "value": -3.9170812117952996}, + {"timestamp": 1650672000, "value": -4.334710308248457}, + {"timestamp": 1650931200, "value": -5.342751714534661}, + {"timestamp": 1651104000, "value": -4.156388985871771}, + {"timestamp": 1651536000, "value": -4.697970837878134}, + {"timestamp": 1651795200, "value": -4.860583606565607}, + {"timestamp": 1651968000, "value": -5.479010375107818}, + {"timestamp": 1652227200, "value": -5.190937377016227}, + {"timestamp": 1652659200, "value": -4.5354788545860325}, + {"timestamp": 1652832000, "value": -5.085450830026249}, + {"timestamp": 1653091200, "value": -3.402619456534638}, + {"timestamp": 1654128000, "value": -5.413656117159736}, + {"timestamp": 1654819200, "value": -3.662764476468461}, + {"timestamp": 1655856000, "value": -2.419774847563414}, + {"timestamp": 1656288000, "value": -1.7368393873596444}, + {"timestamp": 1656720000, "value": -1.5810772686641867}, + {"timestamp": 1656979200, "value": -1.4104082997058258}, + {"timestamp": 1657584000, "value": -1.6854574909736348}, + {"timestamp": 1658016000, "value": -0.7731394915081767}, + {"timestamp": 1658275200, "value": -1.4195104241380918}, + {"timestamp": 1658707200, "value": -1.2655137439698116}, + {"timestamp": 1659571200, "value": -1.1123387618730571}, + {"timestamp": 1659744000, "value": -0.8756535287218514}, + {"timestamp": 1660003200, "value": -0.946651237965853}, + {"timestamp": 1660608000, "value": -0.6432297169112653}, + {"timestamp": 1661299200, "value": -0.5682542976959148}, + {"timestamp": 1661472000, "value": -0.778184881354194}, + {"timestamp": 1662163200, "value": -2.5887382572490525}, + {"timestamp": 1662336000, "value": -3.4051926714742446}, + {"timestamp": 1662768000, "value": -2.790792623026091}, + {"timestamp": 1663459200, "value": -6.860088135583952}, + {"timestamp": 1663632000, "value": -5.192032082406783}, + {"timestamp": 1664064000, "value": -4.165194374985292}, + {"timestamp": 1664496000, "value": -8.909257504857658}, + {"timestamp": 1664755200, "value": -4.136331345998694}, + {"timestamp": 1664928000, "value": -3.249443743821671}, + {"timestamp": 1666483200, "value": -4.6152951292713595}, + {"timestamp": 1666915200, "value": -6.279955471862619}, + {"timestamp": 1667088000, "value": -6.491172170177108}, + {"timestamp": 1667347200, "value": -7.1021373769749}, + {"timestamp": 1668211200, "value": -4.77223593106354}, + {"timestamp": 1668384000, "value": -6.220500157201978}, + {"timestamp": 1669507200, "value": -4.578367402836864}, + {"timestamp": 1671235200, "value": -0.08254701747632309}, + {"timestamp": 1672099200, "value": -1.6004963515406716}, + + # 2023 + {"timestamp": 1672704000, "value": -2.1632787010956784}, + {"timestamp": 1673827200, "value": -2.983477385722785}, + {"timestamp": 1677715200, "value": -1.4165868819117267}, + {"timestamp": 1678579200, "value": -0.9878430407659657}, + {"timestamp": 1679875200, "value": -3.466021499980709}, + {"timestamp": 1680048000, "value": -3.825582990682917}, + {"timestamp": 1680739200, "value": -4.934185080617706}, + {"timestamp": 1682035200, "value": -6.206812129458073}, + {"timestamp": 1683072000, "value": -9.275958556444875}, + {"timestamp": 1683504000, "value": -10.716968297484195}, + {"timestamp": 1683763200, "value": -5.408653793808816}, + {"timestamp": 1683936000, "value": -9.442098368130585}, + {"timestamp": 1684627200, "value": -7.838196738726171}, + {"timestamp": 1685059200, "value": -8.47984955920828}, + {"timestamp": 1685232000, "value": -6.972524075112018}, + {"timestamp": 1685491200, "value": -4.406292021716042}, + {"timestamp": 1685664000, "value": -5.386065534732394}, + {"timestamp": 1685923200, "value": -4.737845392070691}, + {"timestamp": 1686096000, "value": -3.93362223378809}, + {"timestamp": 1687392000, "value": -1.0281388531428395}, + {"timestamp": 1687651200, "value": -2.0277776210674223}, + {"timestamp": 1687824000, "value": -3.1058855995157315}, + {"timestamp": 1688256000, "value": -2.4884785118679584}, + {"timestamp": 1688688000, "value": -3.901995452478836}, + {"timestamp": 1688947200, "value": -2.479749261102836}, + {"timestamp": 1689120000, "value": -2.4953105755389484}, + {"timestamp": 1689379200, "value": -3.083998430925238}, + {"timestamp": 1689552000, "value": -1.5730837328758267}, + {"timestamp": 1689984000, "value": -1.1685138491804112}, + {"timestamp": 1690675200, "value": -2.8959462871988597}, + {"timestamp": 1691539200, "value": -3.9143522850427193}, + {"timestamp": 1691712000, "value": -2.0059515442340894}, + {"timestamp": 1691971200, "value": -4.046766311094322}, + {"timestamp": 1692144000, "value": -5.143252851579925}, + {"timestamp": 1692403200, "value": -3.977662184389684}, + {"timestamp": 1692576000, "value": -2.687668487446206}, + {"timestamp": 1692835200, "value": -4.739718202618833}, + {"timestamp": 1693008000, "value": -3.0932427383310914}, + {"timestamp": 1693440000, "value": -2.3917470541502226}, + {"timestamp": 1693699200, "value": -1.1291876520985438}, + {"timestamp": 1693872000, "value": -4.429118853478426}, + {"timestamp": 1694131200, "value": -3.4127348615565856}, + {"timestamp": 1694304000, "value": -3.2137746258372686}, + {"timestamp": 1694736000, "value": -3.184173382194691}, + {"timestamp": 1695168000, "value": -2.5106827835319963}, + {"timestamp": 1695600000, "value": -0.892445521634119}, + {"timestamp": 1696032000, "value": -1.9478173710739408}, + {"timestamp": 1696723200, "value": -3.338840285518137}, + {"timestamp": 1698192000, "value": -1.4373857288874872}, + {"timestamp": 1699056000, "value": -4.17210574924378}, + {"timestamp": 1699315200, "value": -3.178491343902305}, + {"timestamp": 1700352000, "value": -1.095198175191808}, + {"timestamp": 1700784000, "value": -0.6755551648481929}, + {"timestamp": 1703203200, "value": -2.4320769776043853}, + + # 2024 (until end of September) + {"timestamp": 1704672000, "value": -1.8240732303170757}, + {"timestamp": 1706400000, "value": -2.0359234164407853}, + {"timestamp": 1708128000, "value": -1.1651982353912895}, + {"timestamp": 1708819200, "value": -2.89160848955476}, + {"timestamp": 1709424000, "value": -2.7623708644791067}, + {"timestamp": 1709683200, "value": -2.2604734758474487}, + {"timestamp": 1709856000, "value": -3.0320684120611157}, + {"timestamp": 1710288000, "value": -3.044875564804314}, + {"timestamp": 1710720000, "value": -3.0506387455946324}, + {"timestamp": 1710979200, "value": -3.7108505237985505}, + {"timestamp": 1711411200, "value": -5.132474503968261}, + {"timestamp": 1711843200, "value": -1.6555395941313846}, + {"timestamp": 1712016000, "value": -4.944721667911011}, + {"timestamp": 1713312000, "value": -2.1936541594672163}, + {"timestamp": 1713571200, "value": -4.525621060522565}, + {"timestamp": 1714003200, "value": -5.350975315548725}, + {"timestamp": 1714176000, "value": -2.492177205644747}, + {"timestamp": 1714435200, "value": -10.070425581250989}, + {"timestamp": 1714608000, "value": -8.369960411513166}, + {"timestamp": 1715299200, "value": -8.075975281913461}, + {"timestamp": 1715731200, "value": -7.018195682847631}, + {"timestamp": 1715904000, "value": -5.879113894732184}, + {"timestamp": 1716163200, "value": -1.9426634232290758}, + {"timestamp": 1716595200, "value": -3.4476091230327244}, + {"timestamp": 1717200000, "value": -5.020790501798809}, + {"timestamp": 1717632000, "value": -4.461632656650202}, + {"timestamp": 1717891200, "value": -6.0027612893101185}, + {"timestamp": 1718496000, "value": -3.8380004234155543}, + {"timestamp": 1719187200, "value": -4.311023987624012}, + {"timestamp": 1719360000, "value": -3.5604621195661896}, + {"timestamp": 1719619200, "value": -4.000622873261243}, + {"timestamp": 1720224000, "value": -3.4157165497870023}, + {"timestamp": 1720483200, "value": -2.5417807734102142}, + {"timestamp": 1720656000, "value": -3.2446460227853224}, + {"timestamp": 1720915200, "value": -2.7383587783702614}, + {"timestamp": 1721347200, "value": -3.0862410722785776}, + {"timestamp": 1721520000, "value": -2.914618710471714}, + {"timestamp": 1721779200, "value": -2.0432678736381003}, + {"timestamp": 1722211200, "value": -2.9802316526965424}, + {"timestamp": 1722643200, "value": -3.3619899839479377}, + {"timestamp": 1722816000, "value": -4.030666091594237}, + {"timestamp": 1723075200, "value": -1.1686716051703105}, + {"timestamp": 1723248000, "value": -2.1501821212207193}, + {"timestamp": 1723507200, "value": -4.103490431194776}, + {"timestamp": 1724112000, "value": -2.9218929302120005}, + {"timestamp": 1724371200, "value": -2.74349596609107}, + {"timestamp": 1724803200, "value": -2.2656526045163625}, + {"timestamp": 1724976000, "value": -1.864104950442559}, + {"timestamp": 1725235200, "value": -1.6140829866530924}, + {"timestamp": 1725408000, "value": -1.6863172044651933}, + {"timestamp": 1725667200, "value": -1.6646539826070033}, + {"timestamp": 1726099200, "value": -1.4450195576891183}, + {"timestamp": 1726531200, "value": -1.6790029768346082}, + {"timestamp": 1726704000, "value": -1.7045123289727604}, + {"timestamp": 1726963200, "value": -1.7011517778472642}, + {"timestamp": 1727395200, "value": -1.9010479736367178}, + {"timestamp": 1727568000, "value": -2.048906875218465}, +] diff --git a/backend/src/gee/ndvi_cache.py b/backend/src/cache/ndvi_cache.py similarity index 100% rename from backend/src/gee/ndvi_cache.py rename to backend/src/cache/ndvi_cache.py diff --git a/backend/src/constants.py b/backend/src/constants.py index 989b361..15bc264 100644 --- a/backend/src/constants.py +++ b/backend/src/constants.py @@ -29,6 +29,7 @@ class Unit(str, Enum): MM = "mm" PERCENT = "%" M3 = "m³/m³" + DIMENSIONLESS = "Dimensionless" class LocationPolygon(Enum): diff --git a/backend/src/analysis/__init__.py b/backend/src/controller/__init__.py similarity index 100% rename from backend/src/analysis/__init__.py rename to backend/src/controller/__init__.py diff --git a/backend/src/routes/ndvi_router.py b/backend/src/controller/sat_index_controller.py similarity index 86% rename from backend/src/routes/ndvi_router.py rename to backend/src/controller/sat_index_controller.py index 0e60ffc..5f51c08 100644 --- a/backend/src/routes/ndvi_router.py +++ b/backend/src/controller/sat_index_controller.py @@ -1,35 +1,36 @@ from datetime import datetime, timezone -from fastapi import APIRouter, Query +from fastapi import Query from fastapi.responses import JSONResponse from src.constants import ( + IndexType, AggregationMethod, LocationName, TemporalResolution, Unit, ) -from src.service import ndvi_service +from src.service import sat_index_service from src.utils.temporal import get_optimistic_rounding -from src.validation.models import NDVIResponse from src.validation.utils import ( validate_timestamp_in_range_of_S2_imagery, validate_timestamp_start_date_before_end_date, ) -ndvi_router = APIRouter() - -@ndvi_router.get("/ndvi", response_model=NDVIResponse) -async def get_temperature_data( +async def sat_index_controller( + sat_index_type: IndexType, startDate: int = Query(..., + description="Start date as UNIX timestamp in seconds"), endDate: int = Query(..., + description="End date as UNIX timestamp in seconds"), location: LocationName = Query(..., description="Location name"), temporalResolution: TemporalResolution = Query( ..., description="Temporal resolution" ), aggregation: AggregationMethod = Query(..., + description="Aggregation method"), ): @@ -42,7 +43,7 @@ async def get_temperature_data( start_date_dt, end_date_dt, temporalResolution ) - data = ndvi_service( + data = sat_index_service( location=location, temporal_resolution=temporalResolution, aggregation_method=aggregation, @@ -62,4 +63,4 @@ async def get_temperature_data( "data": data, } - return JSONResponse(content=response) + return JSONResponse(content=response) \ No newline at end of file diff --git a/backend/src/gee/caching_script.py b/backend/src/gee/caching_script.py new file mode 100644 index 0000000..913b4f7 --- /dev/null +++ b/backend/src/gee/caching_script.py @@ -0,0 +1,30 @@ +import json + +content = [ + {"timestamp": 1493251200, "value": 0.6265267304234295}, + {"timestamp": 1494720000, "value": 0.68603163673333}, + {"timestamp": 1494979200, "value": 0.755257128311451}, +] +var_name = "msavi_daily_cache" +file_name = "../cache/temp_cache.py" +INDENT = " " + +def combine_chache_with_update(): + # update existing cache + return + +def write_year(file, year, results): + file.write(f"{INDENT}# Year {year}\n") + +def write_results_to_cache(results: list, var_name: str, file_name: str): + with open(file_name, "w") as file: + file.write(f"{var_name} = [\n") + for day in results: + file.write(f"{INDENT}{json.dumps(day)},\n") + file.write("]\n") + file.close() + + return + + +# write_results_to_cache(content, var_name, file_name) diff --git a/backend/src/gee/index.py b/backend/src/gee/image_preprocessing.py similarity index 100% rename from backend/src/gee/index.py rename to backend/src/gee/image_preprocessing.py diff --git a/backend/src/gee/ndvi.py b/backend/src/gee/sat_index_info.py similarity index 50% rename from backend/src/gee/ndvi.py rename to backend/src/gee/sat_index_info.py index a571b56..6a28e24 100644 --- a/backend/src/gee/ndvi.py +++ b/backend/src/gee/sat_index_info.py @@ -1,19 +1,38 @@ import ee +from ..constants import IndexType INDEX_FEATURE_LABEL = "indexing_value" TIMESTAMP_FEATURE_LABEL = "start_of_day_timestamp" INDEX_NULL_STRING = "NULL" -def calculate_mean_ndvi_GEE_SERVER(image: ee.Image, aoi: ee.Geometry.Polygon): - ndvi = image.normalizedDifference(["B8", "B4"]).rename("NDVI") - mean_ndvi = ndvi.reduceRegion( +def get_index_image_by_index_type(index_type: IndexType, image: ee.Image): + match index_type: + case IndexType.NDVI: + return image.normalizedDifference(["B8", "B4"]).rename("NDVI") + case IndexType.MSAVI: + return image.expression( + expression="((2 * NIR + 1) - ((2 * NIR + 1)**2 - 8 * (NIR - RED))**0.5) / 2", + opt_map={ + "NIR": image.select("B4"), + "RED": image.select("B8"), + }, + ).rename("MSAVI") + case _: + return None + + +def calculate_mean_index_GEE_SERVER(image: ee.Image, aoi: ee.Geometry.Polygon, index_type: IndexType): + index_image = get_index_image_by_index_type(index_type, image) + mean_index = index_image.reduceRegion( reducer=ee.Reducer.mean(), geometry=aoi, scale=10, maxPixels=1e8 - ).get("NDVI") - mean_ndvi = ee.Algorithms.If( - ee.Algorithms.IsEqual(mean_ndvi, None), INDEX_NULL_STRING, mean_ndvi + ).get(index_type.value) + + mean_index = ee.Algorithms.If( + ee.Algorithms.IsEqual(mean_index, None), INDEX_NULL_STRING, mean_index ) - return image.set(INDEX_FEATURE_LABEL, mean_ndvi) + return image.set(INDEX_FEATURE_LABEL, mean_index) + def calculate_start_of_day_timestamp_GEE_SERVER(image: ee.Image): @@ -27,28 +46,28 @@ def calculate_start_of_day_timestamp_GEE_SERVER(image: ee.Image): return image.set(TIMESTAMP_FEATURE_LABEL, start_of_day) -def get_ndvi_info( - image_collection: ee.ImageCollection, coordinates: ee.Geometry.Polygon +def get_sat_index_info( + image_collection: ee.ImageCollection, coordinates: ee.Geometry.Polygon, index_type: IndexType ): aoi = ee.Geometry.Polygon(coordinates) # Setting indexing values Server side - image_collection_with_ndvi = image_collection.map( - lambda img: calculate_mean_ndvi_GEE_SERVER(img, aoi) + image_collection_with_index = image_collection.map( + lambda img: calculate_mean_index_GEE_SERVER(img, aoi, index_type) ) # Setting timestamps Server side - image_collection_with_timestamp_and_ndvi = image_collection_with_ndvi.map( + image_collection_with_timestamp_and_index = image_collection_with_index.map( lambda img: calculate_start_of_day_timestamp_GEE_SERVER(img) ) # Getting indexing values to the client side - index_value_list = image_collection_with_timestamp_and_ndvi.aggregate_array( + index_value_list = image_collection_with_timestamp_and_index.aggregate_array( INDEX_FEATURE_LABEL ).getInfo() # Getting timestamps to the client - timestamp_list = image_collection_with_timestamp_and_ndvi.aggregate_array( + timestamp_list = image_collection_with_timestamp_and_index.aggregate_array( TIMESTAMP_FEATURE_LABEL ).getInfo() diff --git a/backend/src/main.py b/backend/src/main.py index f4aebcb..938be82 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -1,7 +1,7 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from src.routes.ndvi_router import ndvi_router +from src.routes.sat_index_router import sat_index_router from .weather.router import router as weather_router @@ -17,10 +17,11 @@ allow_headers=["*"], ) + @app.get("/") def read_root(): return {"Hello": "World"} app.include_router(weather_router, prefix="/weather", tags=["Weather Data"]) -app.include_router(ndvi_router, prefix="/index", tags=["NDVI Data"]) +app.include_router(sat_index_router, prefix="/index", tags=["Vegetation Indices"]) diff --git a/backend/src/gee/utils.py b/backend/src/routes/__init__.py similarity index 100% rename from backend/src/gee/utils.py rename to backend/src/routes/__init__.py diff --git a/backend/src/routes/sat_index_router.py b/backend/src/routes/sat_index_router.py new file mode 100644 index 0000000..7873695 --- /dev/null +++ b/backend/src/routes/sat_index_router.py @@ -0,0 +1,126 @@ +from datetime import datetime, timezone + +from fastapi import APIRouter, Query +from fastapi.responses import JSONResponse +from src.constants import ( + AggregationMethod, + LocationName, + TemporalResolution, + Unit, + IndexType, +) +from src.service import sat_index_service +from src.utils.temporal import get_optimistic_rounding +from src.validation.models import NDVIResponse, MSAVIResponse +from src.validation.utils import ( + validate_timestamp_in_range_of_S2_imagery, + validate_timestamp_start_date_before_end_date, +) + +sat_index_router = APIRouter() + + +@sat_index_router.get("/ndvi", response_model=NDVIResponse) +async def get_ndvi_data( + startDate: int = Query( + ..., + description="First date of requested date range in UNIX timestamp as seconds", + ), + endDate: int = Query( + ..., + description="Last date of requested date range in UNIX timestamp as seconds", + ), + location: LocationName = Query(..., description="Name of the requested location"), + temporalResolution: TemporalResolution = Query( + ..., + description="Time interval that a single data point should represent e.g. one month", + ), + aggregation: AggregationMethod = Query( + ..., + description="Method of aggregating available data into a single datapoint to represent the selected time interval e.g. mean average", + ), +): + validate_timestamp_start_date_before_end_date(startDate, endDate) + validate_timestamp_in_range_of_S2_imagery(startDate, endDate) + start_date_dt = datetime.fromtimestamp(startDate, tz=timezone.utc) + end_date_dt = datetime.fromtimestamp(endDate, tz=timezone.utc) + + rounded_start_date, rounded_end_date = get_optimistic_rounding( + start_date_dt, end_date_dt, temporalResolution + ) + + data = sat_index_service( + location=location, + temporal_resolution=temporalResolution, + aggregation_method=aggregation, + start_date=rounded_start_date, + end_date=rounded_end_date, + index_type=IndexType.NDVI, + ) + + response = { + "meta": { + "location": LocationName[location].value, + "startDate": int(rounded_start_date.timestamp()), + "endDate": int(rounded_end_date.timestamp()), + "temporalResolution": TemporalResolution[temporalResolution].value, + "aggregation": AggregationMethod[aggregation].value, + "unit": Unit.NORMALIZED_DIFFERENCE.value, + }, + "data": data, + } + + return JSONResponse(content=response) + + +@sat_index_router.get("/msavi", response_model=MSAVIResponse) +async def get_msavi_data( + startDate: int = Query( + ..., + description="First date of requested date range in UNIX timestamp as seconds", + ), + endDate: int = Query( + ..., + description="Last date of requested date range in UNIX timestamp as seconds", + ), + location: LocationName = Query(..., description="Name of the requested location"), + temporalResolution: TemporalResolution = Query( + ..., + description="Time interval that a single data point should represent e.g. one month", + ), + aggregation: AggregationMethod = Query( + ..., + description="Method of aggregating available data into a single datapoint to represent the selected time interval e.g. mean average", + ), +): + validate_timestamp_start_date_before_end_date(startDate, endDate) + validate_timestamp_in_range_of_S2_imagery(startDate, endDate) + start_date_dt = datetime.fromtimestamp(startDate, tz=timezone.utc) + end_date_dt = datetime.fromtimestamp(endDate, tz=timezone.utc) + + rounded_start_date, rounded_end_date = get_optimistic_rounding( + start_date_dt, end_date_dt, temporalResolution + ) + + data = sat_index_service( + location=location, + temporal_resolution=temporalResolution, + aggregation_method=aggregation, + start_date=rounded_start_date, + end_date=rounded_end_date, + index_type=IndexType.MSAVI, + ) + + response = { + "meta": { + "location": LocationName[location].value, + "startDate": int(rounded_start_date.timestamp()), + "endDate": int(rounded_end_date.timestamp()), + "temporalResolution": TemporalResolution[temporalResolution].value, + "aggregation": AggregationMethod[aggregation].value, + "unit": Unit.DIMENSIONLESS.value, + }, + "data": data, + } + + return JSONResponse(content=response) diff --git a/backend/src/service.py b/backend/src/service.py index 21e4ee1..074c508 100644 --- a/backend/src/service.py +++ b/backend/src/service.py @@ -2,18 +2,25 @@ import pandas as pd -from src.constants import AggregationMethod, LocationPolygon, TemporalResolution -from src.gee.index import get_preprocessed_imagery -from src.gee.ndvi import get_ndvi_info -from src.gee.ndvi_cache import ndvi_daily_cache +from src.constants import ( + AggregationMethod, + LocationPolygon, + TemporalResolution, + IndexType, +) +from src.gee.image_preprocessing import get_preprocessed_imagery +from src.gee.sat_index_info import get_sat_index_info +from src.cache.ndvi_cache import ndvi_daily_cache +from src.cache.msavi_cache import msavi_daily_cache from typing import List, Dict, Union +from fastapi import HTTPException import math def initialize_time_series( time_series: List[Dict[str, Union[int, float]]], temporal_resolution: TemporalResolution, - aggregation_method: AggregationMethod + aggregation_method: AggregationMethod, ) -> pd.DataFrame: """ Initializes a pandas DataFrame from a time series and applies temporal resolution and aggregation. @@ -31,28 +38,30 @@ def initialize_time_series( # Return an empty DataFrame with a datetime index and 'value' column in UTC if temporal_resolution == TemporalResolution.MONTHLY: empty_index = pd.date_range( - start="1970-01-01", periods=0, freq='MS', tz='UTC') + start="1970-01-01", periods=0, freq="MS", tz="UTC" + ) else: empty_index = pd.date_range( - start="1970-01-01", periods=0, freq='D', tz='UTC') + start="1970-01-01", periods=0, freq="D", tz="UTC" + ) - return pd.DataFrame(index=empty_index, columns=['value']) + return pd.DataFrame(index=empty_index, columns=["value"]) # Convert timestamps to datetime in UTC and create DataFrame df = pd.DataFrame(time_series) - df['timestamp'] = pd.to_datetime(df['timestamp'], unit='s', utc=True) - df.set_index('timestamp', inplace=True) + df["timestamp"] = pd.to_datetime(df["timestamp"], unit="s", utc=True) + df.set_index("timestamp", inplace=True) # Resample based on temporal resolution and apply aggregation if needed if temporal_resolution == TemporalResolution.MONTHLY: if aggregation_method == AggregationMethod.MEAN: - df = df.resample('MS').mean() + df = df.resample("MS").mean() elif aggregation_method == AggregationMethod.MEDIAN: - df = df.resample('MS').median() + df = df.resample("MS").median() elif aggregation_method == AggregationMethod.MAX: - df = df.resample('MS').max() + df = df.resample("MS").max() elif aggregation_method == AggregationMethod.MIN: - df = df.resample('MS').min() + df = df.resample("MS").min() # If DAILY, do nothing as time series is already in daily format return df @@ -61,7 +70,7 @@ def fill_missing_dates( df: pd.DataFrame, start: datetime, end: datetime, - temporal_resolution: TemporalResolution + temporal_resolution: TemporalResolution, ) -> pd.DataFrame: """ Fills missing entries in the time series, adding NaN for missing days or months. @@ -88,81 +97,121 @@ def fill_missing_dates( # Generate the complete date range based on the temporal resolution if temporal_resolution == TemporalResolution.DAILY: - date_range = pd.date_range(start=start, end=end, freq='D', tz='UTC') + date_range = pd.date_range(start=start, end=end, freq="D", tz="UTC") elif temporal_resolution == TemporalResolution.MONTHLY: - date_range = pd.date_range(start=start, end=end, freq='MS', tz='UTC') + date_range = pd.date_range(start=start, end=end, freq="MS", tz="UTC") # If the input DataFrame is empty, create a new one with NaNs for all dates in the range if df.empty: - df = pd.DataFrame(index=date_range, columns=['value']) - df['value'] = None + df = pd.DataFrame(index=date_range, columns=["value"]) + df["value"] = None else: # Reindex to the complete date range, filling missing dates with NaN df = df.reindex(date_range) - df.columns = ['value'] + df.columns = ["value"] return df -def ndvi_service( +def sat_index_service( location: LocationPolygon, temporal_resolution: TemporalResolution, aggregation_method: AggregationMethod, start_date: datetime, end_date: datetime, + index_type: IndexType, ): # Temporary implementation of GEE Caching strategy current_cache_end_date = datetime( - 2024, 9, 29, tzinfo=timezone.utc) - if start_date < current_cache_end_date and end_date < current_cache_end_date: # current end of cache + 2024, 9, 29, 23, 59, 59, tzinfo=timezone.utc + ) # current end of cache + + # Entire range is within the cache, + # get entire range from cache, process nothing. + if start_date < current_cache_end_date and end_date <= current_cache_end_date: cache_start_date = start_date cache_end_date = end_date processing_start_date = None processing_end_date = None - elif start_date < current_cache_end_date and end_date > current_cache_end_date: + # Partial overlap with the cache, + # get cached part from cache, process the rest until end of range. + elif start_date <= current_cache_end_date and end_date > current_cache_end_date: cache_start_date = start_date cache_end_date = current_cache_end_date - processing_start_date = current_cache_end_date + timedelta(days=1) + processing_start_date = ( + current_cache_end_date + timedelta(seconds=1) + ) processing_end_date = end_date + # Entire range is outside the cache, + # get nothing from cache, process entire range. elif start_date > current_cache_end_date: cache_start_date = None cache_end_date = None processing_start_date = start_date processing_end_date = end_date + else: + raise HTTPException( + status_code=422, + detail="Unprocessable input: Input value is not supported." + ) + # Get and process uncached range if processing_start_date: - + print( + f"Getting {processing_start_date.date()} to {processing_end_date.date()} from GEE." + ) masked_images = get_preprocessed_imagery( LocationPolygon[location.value].value, processing_start_date, processing_end_date, ) - NDVI_time_series = get_ndvi_info( - masked_images, LocationPolygon[location.value].value + sat_index_time_series = get_sat_index_info( + masked_images, LocationPolygon[location.value].value, index_type ) + # Get cached range if cache_start_date: - cached_data_subset = get_cache_subset(cache_start_date, cache_end_date) + print( + f"Getting {cache_start_date.date()} to {cache_end_date.date()} from cache." + ) + cached_data_subset = get_cache_subset( + cache_start_date, cache_end_date, index_type + ) if processing_start_date and cache_start_date: - ndvi_data = cached_data_subset + NDVI_time_series + index_data = cached_data_subset + sat_index_time_series else: - ndvi_data = cached_data_subset if cache_start_date else NDVI_time_series + index_data = cached_data_subset if cache_start_date else sat_index_time_series index_df = initialize_time_series( - ndvi_data, temporal_resolution, aggregation_method) + index_data, temporal_resolution, aggregation_method + ) - filled_df = fill_missing_dates( - index_df, start_date, end_date, temporal_resolution) + filled_df = fill_missing_dates(index_df, start_date, end_date, temporal_resolution) return convert_df_to_list(filled_df) -def get_cache_subset(start_date: datetime, end_date: datetime): +def get_cache_subset(start_date: datetime, end_date: datetime, index_type: IndexType): + match index_type: + case IndexType.NDVI: + cache = ndvi_daily_cache + case IndexType.MSAVI: + cache = msavi_daily_cache + case _: + cache = None + + if cache is None: + raise HTTPException( + status_code=404, detail="Cache not found for requested index type." + ) + subset: list[dict] = [] - for entry in ndvi_daily_cache: - if entry["timestamp"] >= int(start_date.timestamp()) and entry["timestamp"] <= int(end_date.timestamp()): + for entry in cache: + if entry["timestamp"] >= int(start_date.timestamp()) and entry[ + "timestamp" + ] <= int(end_date.timestamp()): subset.append(entry) return subset @@ -179,17 +228,17 @@ def convert_df_to_list(df: pd.DataFrame) -> List[Dict[str, Union[int, float, Non """ # Convert the DataFrame index to epoch timestamps and reset index df_reset = df.reset_index() - df_reset['timestamp'] = df_reset['index'].astype(int) // 10**9 - df_reset = df_reset.rename(columns={'value': 'value'}) + df_reset["timestamp"] = df_reset["index"].astype(int) // 10**9 + df_reset = df_reset.rename(columns={"value": "value"}) # Convert to list of dictionaries - result = df_reset[['timestamp', 'value']].to_dict(orient='records') + result = df_reset[["timestamp", "value"]].to_dict(orient="records") # Convert NaN to None (needs to handle empty df as well) for entry in result: - if entry['value'] is None: - entry['value'] = None - elif math.isnan(entry['value']): - entry['value'] = None + if entry["value"] is None: + entry["value"] = None + elif math.isnan(entry["value"]): + entry["value"] = None return result diff --git a/backend/src/routes/__inti__.py b/backend/src/utils/__init__.py similarity index 100% rename from backend/src/routes/__inti__.py rename to backend/src/utils/__init__.py diff --git a/backend/src/routes/ndvi.py b/backend/src/validation/__init__.py similarity index 100% rename from backend/src/routes/ndvi.py rename to backend/src/validation/__init__.py diff --git a/backend/src/validation/models.py b/backend/src/validation/models.py index feb9bae..c136ecc 100644 --- a/backend/src/validation/models.py +++ b/backend/src/validation/models.py @@ -2,7 +2,7 @@ from pydantic import BaseModel -from src.constants import AggregationMethod, LocationName, TemporalResolution, Unit +from src.constants import AggregationMethod, LocationName, TemporalResolution, Unit, IndexType class DataPoint(BaseModel): @@ -28,9 +28,18 @@ class TemperatureResponse(BaseModel): class NDVIMetaResponse(BaseMeta): + indexType: IndexType = IndexType.NDVI unit: Unit = Unit.NORMALIZED_DIFFERENCE class NDVIResponse(BaseMeta): meta: NDVIMetaResponse data: List[DataPoint] + +class MSAVIMetaResponse(BaseMeta): + indexType: IndexType = IndexType.MSAVI + unit: Unit = Unit.DIMENSIONLESS + +class MSAVIResponse(BaseMeta): + meta: MSAVIMetaResponse + data: List[DataPoint] \ No newline at end of file diff --git a/backend/src/weather/__init__.py b/backend/src/weather/__init__.py new file mode 100644 index 0000000..e69de29